Skip to content

Commit

Permalink
e2e tests and fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
AaronPlave committed Dec 30, 2024
1 parent 0fe4636 commit 3cd3e20
Show file tree
Hide file tree
Showing 8 changed files with 136 additions and 55 deletions.
121 changes: 105 additions & 16 deletions e2e-tests/tests/timeline-view-editing.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ test.describe.serial('Timeline View Editing', () => {
test('Add an activity to the parent plan', async () => {
await plan.showPanel(PanelNames.TIMELINE_ITEMS);
await plan.addActivity('PickBanana');
await plan.addActivity('PeelBanana');
});

test('Change the start time of the activity', async () => {
Expand Down Expand Up @@ -105,26 +106,116 @@ test.describe.serial('Timeline View Editing', () => {
// Look for back button indicating that the row editor is active
expect(page.locator('.section-back-button ').first()).toBeDefined();

const existingLayerCount = await page.locator('.timeline-layer').count();

// Give the row a name
await page.locator('input[name="name"]').first().fill(rowName);
await page.locator('input[name="name"]').first().blur();
});

test('Add an activity layer', async () => {
const activityLayerEditor = page.getByLabel('Activity Layer-editor');
const existingLayerCount = await activityLayerEditor.locator('.timeline-layer-editor').count();

// Add a layer
await page.getByRole('button', { name: 'New Layer' }).click();
const newLayerCount = await page.locator('.timeline-layer').count();
// Add an activity layer
await activityLayerEditor.getByRole('button', { name: 'New Activity Layer' }).click();
const newLayerCount = await activityLayerEditor.locator('.timeline-layer-editor').count();
expect(newLayerCount - existingLayerCount).toEqual(1);

// Expect an activity layer to be created by default
expect(await page.locator('select[name="chartType"]').last().inputValue()).toBe('activity');
// Expect the activity layer to include all activities
expect(await activityLayerEditor.locator('.timeline-layer-editor').first()).toHaveText('All Activities');
});

// Expect the filter list to open
await page.getByPlaceholder('Search').last().click();
await expect(page.locator('.menu-slot > .header')).toBeDefined();
test('Edit an activity layer', async () => {
const activityLayerEditor = page.getByLabel('Activity Layer-editor');

// Open the activity filter builder
await activityLayerEditor
.locator('.timeline-layer-editor')
.first()
.getByLabel('activity-filter-builder-trigger')
.click();

// Expect that the modal is present
const modal = activityLayerEditor.getByLabel('activity-filter-builder');
expect(modal).toBeDefined();

// Expect that layer name is showing in the name input
expect(modal.locator('input[name="layer-name"]')).toHaveValue('All Activities');

// Expect that the resulting types list is not empty
const resultingTypesList = modal.locator('.resulting-types-list');
const allActivityTypesCount = await resultingTypesList.locator('.activity-type-result').count();
expect(allActivityTypesCount).toBeGreaterThan(0);

// Expect that manually selecting types cause the types to appear in the resulting types list
await modal.locator("input[name='manual-types-filter-input']").click();
expect(await modal.locator('.manual-types-menu').first()).toBeDefined();
await modal.getByRole('menuitem', { name: 'ChangeProducer' }).click();
await modal.getByRole('menuitem', { name: 'ControllableDurationActivity' }).click();
await page.keyboard.press('Escape');

expect(await resultingTypesList.getByText('ChangeProducer')).toBeDefined();
expect(await resultingTypesList.getByText('ControllableDurationActivity')).toBeDefined();

// Expect that dynamic types can be added
await modal.getByLabel('dynamic-types').getByRole('button', { name: 'Add Filter' }).click();
expect(await modal.getByLabel('dynamic-types').getByRole('listitem').count()).toBe(1);
await modal.getByLabel('dynamic-types').getByRole('listitem').locator("input[name='filter-value']").fill('banana');
expect(await resultingTypesList.locator('.activity-type-result').count()).toEqual(11);

// Expect that global filters can be added
await modal.getByLabel('global-filters').getByRole('button', { name: 'Add Filter' }).click();
expect(await modal.getByLabel('global-filters').getByRole('listitem').count()).toBe(1);
// Select parameter field
await modal.getByLabel('global-filters').locator("select[aria-label='field']").selectOption('Parameter');
// Select specific parameter
await modal.getByLabel('global-filters').getByText('Select Parameter').click();
await modal.getByLabel('global-filters').getByText('quantity (int)').click();
// Select operator
await modal.getByLabel('global-filters').locator("select[aria-label='operator']").selectOption('equals');
// Fill filter value input
await modal.getByLabel('global-filters').getByRole('listitem').locator("input[name='filter-value']").fill('10');
// Ensure that only one instance (PickBanana) is listed
expect(await modal.getByText('1 instance')).toBeDefined();

// Expect that type subfilters can be added
const activityResult = resultingTypesList.getByRole('listitem', { name: 'activity-type-result-PickBanana' });
await activityResult.getByRole('button', { name: 'Add Filter' }).click();
expect(await activityResult.getByRole('listitem').count()).toBe(1);
// Select name field
await activityResult.locator("select[aria-label='field']").selectOption('Name');
// Select operator
await activityResult.locator("select[aria-label='operator']").selectOption('includes');
// Fill filter value input
await activityResult.getByRole('listitem').locator("input[name='filter-value']").fill('foo');
// Ensure that only one instance (PickBanana) is listed
expect(await modal.getByText('0 instances')).toBeDefined();

// Expect that type subfilters can be removed
await activityResult.getByRole('button', { name: 'Remove filter' }).click();
expect(await modal.getByText('1 instance')).toBeDefined();

// Expect that global filters can be removed
await modal.getByLabel('global-filters').getByRole('button', { name: 'Remove filter' }).click();
expect(await modal.getByText('2 instances')).toBeDefined();

// Expect that dynamic types can be removed
await modal.getByLabel('dynamic-types').getByRole('button', { name: 'Remove filter' }).click();
expect(await resultingTypesList.locator('.activity-type-result').count()).toEqual(2);

// Expect that manual types can be cleared
await modal.locator("input[name='manual-types-filter-input']").click();
await modal.getByRole('menuitem', { name: 'ChangeProducer' }).click();
await page.keyboard.press('Escape');
await modal.getByRole('button', { name: 'Remove Types' }).click();
expect(await resultingTypesList.locator('.activity-type-result').count()).toEqual(allActivityTypesCount);

// Close the modal
await page.pause();
await modal.getByRole('button', { name: 'close' }).click();
});

// Add all activities
await page.locator('button', { hasText: /Select [0-9]* activit/ }).click();
test('Change activity layer settings', async () => {
const activityLayerEditor = await page.getByLabel('Activity Layer-editor');

// Expect to not see an activity tree group in this row
expect(await page.locator('.timeline-row-wrapper', { hasText: rowName }).locator('.activity-tree').count()).toBe(0);
Expand All @@ -141,9 +232,7 @@ test.describe.serial('Timeline View Editing', () => {
).toBe(1);

// Delete an activity layer
await page.getByRole('button', { name: 'Layer Settings' }).last().click();
await page.getByText('Delete Layer').click();
const finalLayerCount = await page.locator('.timeline-layer').count();
expect(finalLayerCount - newLayerCount).toEqual(-1);
await activityLayerEditor.locator('.timeline-layer-editor').first().getByRole('button', { name: 'Delete' }).click();
expect(await activityLayerEditor.locator('.timeline-layer-editor').count()).toBe(0);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import type { ValueSchemaVariant } from '../../../../types/schema';
import type { ActivityLayerFilter, ActivityLayerFilterSubfieldSchema } from '../../../../types/timeline';
import { compare, getTarget, lowercase } from '../../../../utilities/generic';
import { pluralize } from '../../../../utilities/text';
import {
applyActivityLayerFilter,
getMatchingTypesForActivityLayerFilter,
Expand Down Expand Up @@ -341,8 +342,8 @@
<div bind:this={rootRef} class="w-100" style:display="grid">
<slot name="trigger" />
{#if shown}
<!-- TODO maybe pass in dimensions? -->
<Draggable
ariaLabel="activity-filter-builder"
className="st-menu activity-filter-builder"
initialWidth={filterWidth}
initialHeight={filterHeight}
Expand All @@ -356,19 +357,20 @@
autocomplete="off"
class="st-input cancel-drag"
name="layer-name"
aria-label="layer-name"
placeholder="Enter a name for this filter..."
style="width: 220px"
on:input={onLayerNameChange}
/>
<button on:click|stopPropagation={hide} class="st-button icon">
<button on:click|stopPropagation={hide} class="st-button icon" aria-label="close">
<CloseIcon />
</button>
</MenuHeader>
</div>
<div class="body">
<CssGrid columns="0.7fr 3px 0.3fr" columnMinSizes={{ 0: 500, 1: 3, 2: 300 }} class="activity-filter-grid">
<div class="filters">
<div class="filter-section">
<div class="filter-section" aria-label="manual-types">
<div class="filter-section-header st-typography-medium">
Manually Select Types
{#if dirtyFilter.static_types?.length}
Expand All @@ -387,6 +389,7 @@
<div class="search-icon" slot="left"><SearchIcon /></div>
<input
bind:this={manualInputRef}
name="manual-types-filter-input"
class="st-input w-100 manual-types-filter-input"
placeholder="Select types"
bind:value={manualInputValue}
Expand Down Expand Up @@ -441,7 +444,7 @@
{/if}
</div>
</div>
<div class="filter-section">
<div class="filter-section" aria-label="dynamic-types">
<div class="filter-section-header st-typography-medium">
<div class="filter-section-title">
Dynamically Select Types
Expand All @@ -450,14 +453,15 @@
<button
class="st-button icon"
on:click={() => onAddDynamicFilter('dynamic_type_filters')}
aria-label="Add Filter"
use:tooltip={{ content: 'Add Filter' }}
>
<FilterWithPlusIcon />
</button>
</div>
{#if dirtyFilter.dynamic_type_filters?.length}
<div class="filter-section-content">
<div class="dynamic-filter-content">
<div class="dynamic-filter-content" role="list">
{#each dirtyFilter.dynamic_type_filters as filter, i (filter.id)}
<DynamicFilter
{filter}
Expand All @@ -482,14 +486,15 @@
</div>
{/if}
</div>
<div class="filter-section">
<div class="filter-section" aria-label="global-filters">
<div class="filter-section-header st-typography-medium">
<div class="filter-section-title">
Global Filters
<div class="hint st-typography-body">Tags, parameter, scheduling goal, etc...</div>
</div>
<button
class="st-button icon"
aria-label="Add Filter"
on:click={() => onAddDynamicFilter('global_filters')}
use:tooltip={{ content: 'Add Filter' }}
>
Expand All @@ -498,7 +503,7 @@
</div>
{#if dirtyFilter.global_filters?.length}
<div class="filter-section-content">
<div class="dynamic-filter-content">
<div class="dynamic-filter-content" role="list">
{#each dirtyFilter.global_filters as filter, i (filter.id)}
<DynamicFilter
{filter}
Expand Down Expand Up @@ -539,7 +544,7 @@
Resulting Types
<div class="resulting-types-info-container">
<div class="resulting-types-info"><DirectiveIcon /> {matchingTypes.length} types</div>
<div class="resulting-types-info"><SpanIcon /> {instanceCount} instances</div>
<div class="resulting-types-info"><SpanIcon /> {instanceCount} instance{pluralize(instanceCount)}</div>
</div>
</div>
<Input>
Expand All @@ -553,6 +558,7 @@
slot="right"
on:click={() => onAddTypeSubfilter(type.name)}
class="st-button icon"
aria-label="Add Filter"
use:tooltip={{ content: 'Add Filter' }}
>
<FilterWithPlusIcon />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
}>();
</script>

<div class="activity-type-result">
<div class="activity-type-result" role="listitem" aria-label="activity-type-result-{name}">
<div class="top-row">
<div class="title st-typography-medium">
<DirectiveIcon />
Expand Down
2 changes: 2 additions & 0 deletions src/components/timeline/form/TimelineEditor/Draggable.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import { draggable, type DragOptions } from '@neodrag/svelte';
import Resizable from './Resizable.svelte';
export let ariaLabel: string = '';
export let dragOptions: DragOptions = {};
export let className: string = '';
export let initialWidth: number = 500;
Expand All @@ -26,6 +27,7 @@
</script>

<div
aria-label={ariaLabel}
bind:this={rootRef}
use:draggable={{
bounds: 'body',
Expand Down
13 changes: 8 additions & 5 deletions src/components/timeline/form/TimelineEditor/DynamicFilter.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@
$: currentValueAsStringOrNumber = currentValue as string | number;
</script>

<div class="dynamic-filter">
<div class="dynamic-filter" role="listitem">
{#if verb}
<div class="st-typography-body verb">{verb}</div>
{/if}
Expand Down Expand Up @@ -204,12 +204,13 @@
</select>
<div class="dynamic-filter-value">
{#if currentType === 'string'}
<input class="st-input w-100" bind:value={currentValue} />
<input name="filter-value" aria-label="value" class="st-input w-100" bind:value={currentValue} />
{:else if currentOperator === 'is_within' || currentOperator === 'is_not_within'}
{#if Array.isArray(currentValue)}
<div class="range-input">
<Input class="dynamic-filter-input">
<input
name="filter-value-min"
aria-label="value-min"
class="st-input w-100"
type="number"
Expand All @@ -222,6 +223,7 @@
<div class="st-typography-label">To</div>
<Input class="dynamic-filter-input">
<input
name="filter-value-max"
aria-label="value-max"
class="st-input w-100"
type="number"
Expand All @@ -235,20 +237,20 @@
{/if}
{:else if currentType === 'int' || currentType === 'real'}
<Input class="dynamic-filter-input">
<input aria-label="value" bind:value={currentValue} class="st-input w-100" type="number" />
<input name="filter-value" aria-label="value" bind:value={currentValue} class="st-input w-100" type="number" />
<div class="parameter-right" slot="right">
<ParameterUnits unit={currentUnit} />
</div>
</Input>
{:else if currentType === 'duration'}
<Input class="dynamic-filter-input">
<input aria-label="value" bind:value={currentValue} class="st-input w-100" type="number" />
<input name="filter-value" aria-label="value" bind:value={currentValue} class="st-input w-100" type="number" />
<div class="parameter-right" slot="right">
<ParameterUnits unit="ms" />
</div>
</Input>
{:else if currentType === 'boolean'}
<select aria-label="value" class="st-select w-100" bind:value={currentValue}>
<select name="filter-value" aria-label="value" class="st-select w-100" bind:value={currentValue}>
<option value={true}>True</option>
<option value={false}>False</option>
</select>
Expand All @@ -270,6 +272,7 @@
)}
<div style:width="100%">
<TagsInput
name="filter-value"
options={currentValuePossibilities}
selected={currentValueTags}
on:change={onTagsInputChange}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@
}>();
</script>

<fieldset class={classNames('editor-section', { 'editor-section-draggable': isDragContainer })}>
<fieldset
aria-label="{item}-editor"
class={classNames('editor-section', { 'editor-section-draggable': isDragContainer })}
>
<div class="editor-section-header">
<div class="st-typography-medium">{pluralizedItem}</div>
{#if creatable}
Expand All @@ -36,6 +39,7 @@
</button>
{/if}
<button
aria-label={`New ${item}`}
on:click|stopPropagation={() => dispatch('create')}
use:tooltip={{ content: `New ${item}`, placement: 'top' }}
class="st-button icon"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@
bind:this={filterMenu}
>
<button
aria-label="activity-filter-builder-trigger"
slot="trigger"
on:click|stopPropagation={toggleFilterMenu}
class="st-button icon w-100"
Expand Down
Loading

0 comments on commit 3cd3e20

Please sign in to comment.