@genspectrum/dashboard-components 0.16.3 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/custom-elements.json +86 -61
  2. package/dist/{LineageFilterChangedEvent-COWV-Y0k.js → LineageFilterChangedEvent-DkvWdq_G.js} +2 -2
  3. package/dist/LineageFilterChangedEvent-DkvWdq_G.js.map +1 -0
  4. package/dist/assets/{mutationOverTimeWorker-DJcZmEH9.js.map → mutationOverTimeWorker-CPfQDLe6.js.map} +1 -1
  5. package/dist/components.d.ts +64 -51
  6. package/dist/components.js +1134 -937
  7. package/dist/components.js.map +1 -1
  8. package/dist/style.css +81 -9
  9. package/dist/util.d.ts +76 -34
  10. package/dist/util.js +1 -1
  11. package/package.json +2 -1
  12. package/src/preact/components/annotated-mutation.stories.tsx +2 -1
  13. package/src/preact/components/annotated-mutation.tsx +6 -2
  14. package/src/preact/components/clearable-select.stories.tsx +75 -0
  15. package/src/preact/components/clearable-select.tsx +76 -0
  16. package/src/preact/components/downshift-combobox.tsx +9 -7
  17. package/src/preact/dateRangeFilter/computeInitialValues.spec.ts +31 -33
  18. package/src/preact/dateRangeFilter/computeInitialValues.ts +2 -15
  19. package/src/preact/dateRangeFilter/date-picker.tsx +66 -0
  20. package/src/preact/dateRangeFilter/date-range-filter.stories.tsx +69 -31
  21. package/src/preact/dateRangeFilter/date-range-filter.tsx +136 -139
  22. package/src/preact/dateRangeFilter/dateRangeOption.ts +11 -11
  23. package/src/preact/mutationComparison/mutation-comparison-table.tsx +14 -1
  24. package/src/preact/mutationComparison/mutation-comparison-venn.tsx +39 -8
  25. package/src/preact/mutationComparison/mutation-comparison.stories.tsx +36 -12
  26. package/src/preact/mutationComparison/mutation-comparison.tsx +2 -0
  27. package/src/preact/mutations/mutations.stories.tsx +3 -9
  28. package/src/preact/mutationsOverTime/mutations-over-time.stories.tsx +3 -8
  29. package/src/preact/shared/WithClassName/WithClassName.ts +1 -0
  30. package/src/preact/shared/icons/DeleteIcon.tsx +3 -0
  31. package/src/preact/shared/stories/expectMutationAnnotation.ts +13 -0
  32. package/src/preact/shared/stories/expectOptionSelected.tsx +7 -0
  33. package/src/utilEntrypoint.ts +3 -1
  34. package/src/web-components/MutationAnnotations.mdx +33 -0
  35. package/src/web-components/ResizeContainer.mdx +1 -1
  36. package/src/web-components/errorHandling.mdx +1 -1
  37. package/src/web-components/gs-app.ts +2 -2
  38. package/src/web-components/input/gs-date-range-filter.stories.ts +38 -32
  39. package/src/web-components/input/gs-date-range-filter.tsx +8 -2
  40. package/src/web-components/input/gs-lineage-filter.tsx +1 -1
  41. package/src/web-components/input/gs-location-filter.tsx +1 -1
  42. package/src/web-components/input/gs-mutation-filter.tsx +1 -1
  43. package/src/web-components/input/gs-text-filter.tsx +1 -1
  44. package/src/web-components/visualization/gs-aggregate.tsx +2 -2
  45. package/src/web-components/visualization/gs-mutation-comparison.stories.ts +18 -1
  46. package/src/web-components/visualization/gs-mutation-comparison.tsx +24 -10
  47. package/src/web-components/visualization/gs-mutations-over-time.stories.ts +2 -1
  48. package/src/web-components/visualization/gs-mutations-over-time.tsx +5 -2
  49. package/src/web-components/visualization/gs-mutations.stories.ts +2 -1
  50. package/src/web-components/visualization/gs-mutations.tsx +5 -2
  51. package/src/web-components/visualization/gs-number-sequences-over-time.tsx +2 -2
  52. package/src/web-components/visualization/gs-prevalence-over-time.tsx +2 -2
  53. package/src/web-components/visualization/gs-relative-growth-advantage.tsx +2 -2
  54. package/src/web-components/visualization/gs-sequences-by-location.tsx +2 -2
  55. package/src/web-components/visualization/gs-statistics.tsx +2 -2
  56. package/src/web-components/wastewaterVisualization/gs-wastewater-mutations-over-time.tsx +2 -2
  57. package/standalone-bundle/assets/mutationOverTimeWorker-CERZSdcA.js.map +1 -1
  58. package/standalone-bundle/dashboard-components.js +13293 -12635
  59. package/standalone-bundle/dashboard-components.js.map +1 -1
  60. package/standalone-bundle/style.css +1 -1
  61. package/dist/LineageFilterChangedEvent-COWV-Y0k.js.map +0 -1
@@ -2,6 +2,8 @@ import { useCombobox } from 'downshift/preact';
2
2
  import { type ComponentChild } from 'preact';
3
3
  import { useMemo, useRef, useState } from 'preact/hooks';
4
4
 
5
+ import { DeleteIcon } from '../shared/icons/DeleteIcon';
6
+
5
7
  export function DownshiftCombobox<Item>({
6
8
  allItems,
7
9
  value,
@@ -10,18 +12,18 @@ export function DownshiftCombobox<Item>({
10
12
  itemToString,
11
13
  placeholderText,
12
14
  formatItemInList,
15
+ inputClassName = '',
13
16
  }: {
14
17
  allItems: Item[];
15
- value?: Item;
18
+ value?: Item | null;
16
19
  filterItemsByInputValue: (item: Item, value: string) => boolean;
17
20
  createEvent: (item: Item | null) => CustomEvent;
18
21
  itemToString: (item: Item | undefined | null) => string;
19
22
  placeholderText?: string;
20
23
  formatItemInList: (item: Item) => ComponentChild;
24
+ inputClassName?: string;
21
25
  }) {
22
- const initialSelectedItem = value ?? null;
23
-
24
- const [itemsFilter, setItemsFilter] = useState(itemToString(initialSelectedItem));
26
+ const [itemsFilter, setItemsFilter] = useState(itemToString(value));
25
27
  const items = useMemo(
26
28
  () => allItems.filter((item) => filterItemsByInputValue(item, itemsFilter)),
27
29
  [allItems, filterItemsByInputValue, itemsFilter],
@@ -65,7 +67,7 @@ export function DownshiftCombobox<Item>({
65
67
  itemToString(item) {
66
68
  return itemToString(item);
67
69
  },
68
- initialSelectedItem,
70
+ selectedItem: value,
69
71
  environment,
70
72
  });
71
73
 
@@ -89,7 +91,7 @@ export function DownshiftCombobox<Item>({
89
91
  <div ref={divRef} className={'relative w-full'}>
90
92
  <div className='w-full flex flex-col gap-1'>
91
93
  <div
92
- className='flex gap-0.5 input input-bordered min-w-32'
94
+ className={`flex gap-0.5 input input-bordered min-w-32 ${inputClassName}`}
93
95
  onBlur={(event) => {
94
96
  if (event.relatedTarget != buttonRef.current) {
95
97
  closeMenu();
@@ -109,7 +111,7 @@ export function DownshiftCombobox<Item>({
109
111
  onClick={clearInput}
110
112
  tabIndex={-1}
111
113
  >
112
- ×
114
+ <DeleteIcon />
113
115
  </button>
114
116
  <button
115
117
  aria-label='toggle menu'
@@ -18,36 +18,34 @@ const dateRangeOptions = [
18
18
  ];
19
19
 
20
20
  describe('computeInitialValues', () => {
21
+ it('should return undefined for unedfined value', () => {
22
+ const result = computeInitialValues(undefined, earliestDate, dateRangeOptions);
23
+
24
+ expect(result).toBeUndefined();
25
+ });
26
+
21
27
  it('should compute initial value if value is dateRangeOption label', () => {
22
28
  const result = computeInitialValues(fromToOption, earliestDate, dateRangeOptions);
23
29
 
24
- expect(result.initialSelectedDateRange).toEqual(fromToOption);
25
- expectDateMatches(result.initialSelectedDateFrom, new Date(dateFromOptionValue));
26
- expectDateMatches(result.initialSelectedDateTo, new Date(dateToOptionValue));
30
+ expect(result?.initialSelectedDateRange).toEqual(fromToOption);
31
+ expectDateMatches(result?.initialSelectedDateFrom, new Date(dateFromOptionValue));
32
+ expectDateMatches(result?.initialSelectedDateTo, new Date(dateToOptionValue));
27
33
  });
28
34
 
29
35
  it('should use today as "dateTo" if it is unset in selected option', () => {
30
36
  const result = computeInitialValues(fromOption, earliestDate, dateRangeOptions);
31
37
 
32
- expect(result.initialSelectedDateRange).toEqual(fromOption);
33
- expectDateMatches(result.initialSelectedDateFrom, new Date(dateFromOptionValue));
34
- expectDateMatches(result.initialSelectedDateTo, today);
38
+ expect(result?.initialSelectedDateRange).toEqual(fromOption);
39
+ expectDateMatches(result?.initialSelectedDateFrom, new Date(dateFromOptionValue));
40
+ expectDateMatches(result?.initialSelectedDateTo, today);
35
41
  });
36
42
 
37
43
  it('should use earliest date as "dateFrom" if it is unset in selected option', () => {
38
44
  const result = computeInitialValues(toOption, earliestDate, dateRangeOptions);
39
45
 
40
- expect(result.initialSelectedDateRange).toEqual(toOption);
41
- expectDateMatches(result.initialSelectedDateFrom, new Date(earliestDate));
42
- expectDateMatches(result.initialSelectedDateTo, new Date(dateToOptionValue));
43
- });
44
-
45
- it('should fall back to full range if initial value is not set', () => {
46
- const result = computeInitialValues(undefined, earliestDate, dateRangeOptions);
47
-
48
- expect(result.initialSelectedDateRange).toBeUndefined();
49
- expectDateMatches(result.initialSelectedDateFrom, new Date(earliestDate));
50
- expectDateMatches(result.initialSelectedDateTo, today);
46
+ expect(result?.initialSelectedDateRange).toEqual(toOption);
47
+ expectDateMatches(result?.initialSelectedDateFrom, new Date(earliestDate));
48
+ expectDateMatches(result?.initialSelectedDateTo, new Date(dateToOptionValue));
51
49
  });
52
50
 
53
51
  it('should throw when initial value is unknown', () => {
@@ -66,18 +64,18 @@ describe('computeInitialValues', () => {
66
64
  const initialDateFrom = '2020-01-01';
67
65
  const result = computeInitialValues({ dateFrom: initialDateFrom }, earliestDate, dateRangeOptions);
68
66
 
69
- expect(result.initialSelectedDateRange).toBeUndefined();
70
- expectDateMatches(result.initialSelectedDateFrom, new Date(initialDateFrom));
71
- expectDateMatches(result.initialSelectedDateTo, today);
67
+ expect(result?.initialSelectedDateRange).toBeUndefined();
68
+ expectDateMatches(result?.initialSelectedDateFrom, new Date(initialDateFrom));
69
+ expectDateMatches(result?.initialSelectedDateTo, today);
72
70
  });
73
71
 
74
72
  it('should select from earliest date until date if only dateTo is given', () => {
75
73
  const initialDateTo = '2020-01-01';
76
74
  const result = computeInitialValues({ dateTo: initialDateTo }, earliestDate, dateRangeOptions);
77
75
 
78
- expect(result.initialSelectedDateRange).toBeUndefined();
79
- expectDateMatches(result.initialSelectedDateFrom, new Date(earliestDate));
80
- expectDateMatches(result.initialSelectedDateTo, new Date(initialDateTo));
76
+ expect(result?.initialSelectedDateRange).toBeUndefined();
77
+ expectDateMatches(result?.initialSelectedDateFrom, new Date(earliestDate));
78
+ expectDateMatches(result?.initialSelectedDateTo, new Date(initialDateTo));
81
79
  });
82
80
 
83
81
  it('should select date range is dateFrom and dateTo are given', () => {
@@ -92,9 +90,9 @@ describe('computeInitialValues', () => {
92
90
  dateRangeOptions,
93
91
  );
94
92
 
95
- expect(result.initialSelectedDateRange).toBeUndefined();
96
- expectDateMatches(result.initialSelectedDateFrom, new Date(initialDateFrom));
97
- expectDateMatches(result.initialSelectedDateTo, new Date(initialDateTo));
93
+ expect(result?.initialSelectedDateRange).toBeUndefined();
94
+ expectDateMatches(result?.initialSelectedDateFrom, new Date(initialDateFrom));
95
+ expectDateMatches(result?.initialSelectedDateTo, new Date(initialDateTo));
98
96
  });
99
97
 
100
98
  it('should set initial "to" to "from" if "from" is after "to"', () => {
@@ -109,9 +107,9 @@ describe('computeInitialValues', () => {
109
107
  dateRangeOptions,
110
108
  );
111
109
 
112
- expect(result.initialSelectedDateRange).toBeUndefined();
113
- expectDateMatches(result.initialSelectedDateFrom, new Date(initialDateFrom));
114
- expectDateMatches(result.initialSelectedDateTo, new Date(initialDateFrom));
110
+ expect(result?.initialSelectedDateRange).toBeUndefined();
111
+ expectDateMatches(result?.initialSelectedDateFrom, new Date(initialDateFrom));
112
+ expectDateMatches(result?.initialSelectedDateTo, new Date(initialDateFrom));
115
113
  });
116
114
 
117
115
  it('should throw if initial "from" is not a valid date', () => {
@@ -126,9 +124,9 @@ describe('computeInitialValues', () => {
126
124
  );
127
125
  });
128
126
 
129
- function expectDateMatches(actual: Date, expected: Date) {
130
- expect(actual.getFullYear()).toEqual(expected.getFullYear());
131
- expect(actual.getMonth()).toEqual(expected.getMonth());
132
- expect(actual.getDate()).toEqual(expected.getDate());
127
+ function expectDateMatches(actual: Date | undefined, expected: Date | undefined) {
128
+ expect(actual?.getFullYear()).toEqual(expected?.getFullYear());
129
+ expect(actual?.getMonth()).toEqual(expected?.getMonth());
130
+ expect(actual?.getDate()).toEqual(expected?.getDate());
133
131
  }
134
132
  });
@@ -2,22 +2,9 @@ import { type DateRangeOption, type DateRangeValue } from './dateRangeOption';
2
2
  import { getDatesForSelectorValue, getSelectableOptions } from './selectableOptions';
3
3
  import { UserFacingError } from '../components/error-display';
4
4
 
5
- export function computeInitialValues(
6
- value: DateRangeValue | undefined,
7
- earliestDate: string,
8
- dateRangeOptions: DateRangeOption[],
9
- ): {
10
- initialSelectedDateRange: string | undefined;
11
- initialSelectedDateFrom: Date;
12
- initialSelectedDateTo: Date;
13
- } {
5
+ export function computeInitialValues(value: DateRangeValue, earliestDate: string, dateRangeOptions: DateRangeOption[]) {
14
6
  if (value === undefined) {
15
- const { dateFrom, dateTo } = getDatesForSelectorValue(undefined, dateRangeOptions, earliestDate);
16
- return {
17
- initialSelectedDateRange: undefined,
18
- initialSelectedDateFrom: dateFrom,
19
- initialSelectedDateTo: dateTo,
20
- };
7
+ return undefined;
21
8
  }
22
9
 
23
10
  if (typeof value === 'string') {
@@ -0,0 +1,66 @@
1
+ import 'flatpickr/dist/flatpickr.min.css';
2
+ import flatpickr from 'flatpickr';
3
+ import { useEffect, useRef, useState } from 'preact/hooks';
4
+
5
+ import { type WithClassName } from '../shared/WithClassName/WithClassName';
6
+
7
+ export function DatePicker({
8
+ onChange,
9
+ value,
10
+ minDate,
11
+ maxDate,
12
+ placeholderText,
13
+ className,
14
+ }: WithClassName<{
15
+ onChange?: (date: Date | undefined) => void;
16
+ value?: Date;
17
+ minDate?: Date;
18
+ maxDate?: Date;
19
+ placeholderText?: string;
20
+ }>) {
21
+ const inputRef = useRef<HTMLInputElement>(null);
22
+
23
+ const [datePicker, setDatePicker] = useState<flatpickr.Instance | null>(null);
24
+
25
+ useEffect(() => {
26
+ if (!inputRef.current) {
27
+ return;
28
+ }
29
+
30
+ const instance = flatpickr(inputRef.current, {
31
+ allowInput: true,
32
+ dateFormat: 'Y-m-d',
33
+ defaultDate: value,
34
+ minDate,
35
+ maxDate,
36
+ });
37
+
38
+ setDatePicker(instance);
39
+
40
+ return () => {
41
+ instance.destroy();
42
+ };
43
+ }, [maxDate, minDate, onChange, value]);
44
+
45
+ if (value === undefined && inputRef.current) {
46
+ inputRef.current.value = '';
47
+ }
48
+
49
+ const handleChange = () => {
50
+ const newValue = datePicker?.selectedDates[0];
51
+ if (onChange) {
52
+ onChange(newValue);
53
+ }
54
+ };
55
+
56
+ return (
57
+ <input
58
+ className={`input input-bordered w-full ${className}`}
59
+ type='text'
60
+ placeholder={placeholderText}
61
+ ref={inputRef}
62
+ onChange={handleChange}
63
+ onBlur={handleChange}
64
+ />
65
+ );
66
+ }
@@ -8,8 +8,9 @@ import { DateRangeFilter, type DateRangeFilterProps } from './date-range-filter'
8
8
  import { previewHandles } from '../../../.storybook/preview';
9
9
  import { LAPIS_URL } from '../../constants';
10
10
  import { LapisUrlContextProvider } from '../LapisUrlContext';
11
- import { dateRangeOptionPresets } from './dateRangeOption';
11
+ import { dateRangeOptionPresets, type DateRangeValue } from './dateRangeOption';
12
12
  import { expectInvalidAttributesErrorMessage } from '../shared/stories/expectErrorMessage';
13
+ import { expectOptionSelected } from '../shared/stories/expectOptionSelected';
13
14
 
14
15
  const earliestDate = '1970-01-01';
15
16
 
@@ -19,6 +20,8 @@ const customDateRange = {
19
20
  dateTo: '2021-12-31',
20
21
  };
21
22
 
23
+ const placeholder = 'Date range';
24
+
22
25
  const meta: Meta<DateRangeFilterProps> = {
23
26
  title: 'Input/DateRangeFilter',
24
27
  component: DateRangeFilter,
@@ -56,6 +59,7 @@ const meta: Meta<DateRangeFilterProps> = {
56
59
  value: undefined,
57
60
  lapisDateField: 'aDateColumn',
58
61
  width: '100%',
62
+ placeholder,
59
63
  },
60
64
  };
61
65
 
@@ -69,6 +73,22 @@ const Primary: StoryObj<DateRangeFilterProps> = {
69
73
  ),
70
74
  };
71
75
 
76
+ export const WithUndefinedValue: StoryObj<DateRangeFilterProps> = {
77
+ ...Primary,
78
+ args: {
79
+ ...Primary.args,
80
+ },
81
+ play: async ({ canvasElement }) => {
82
+ const canvas = within(canvasElement);
83
+
84
+ await waitFor(async () => {
85
+ await expectOptionSelected(canvasElement, placeholder);
86
+ await expect(dateFromPicker(canvas)).toHaveValue('');
87
+ await expect(dateToPicker(canvas)).toHaveValue('');
88
+ });
89
+ },
90
+ };
91
+
72
92
  export const SetCorrectInitialValues: StoryObj<DateRangeFilterProps> = {
73
93
  ...Primary,
74
94
  args: {
@@ -79,6 +99,7 @@ export const SetCorrectInitialValues: StoryObj<DateRangeFilterProps> = {
79
99
  const canvas = within(canvasElement);
80
100
 
81
101
  await waitFor(async () => {
102
+ await expectOptionSelected(canvasElement, 'CustomDateRange');
82
103
  await expect(selectField(canvas)).toHaveValue('CustomDateRange');
83
104
  await expect(dateFromPicker(canvas)).toHaveValue('2021-01-01');
84
105
  await expect(dateToPicker(canvas)).toHaveValue('2021-12-31');
@@ -98,7 +119,7 @@ export const SetCorrectInitialDateFrom: StoryObj<DateRangeFilterProps> = {
98
119
  const canvas = within(canvasElement);
99
120
 
100
121
  await waitFor(async () => {
101
- await expect(selectField(canvas)).toHaveValue('Custom');
122
+ await expectOptionSelected(canvasElement, 'Custom');
102
123
  await expect(dateFromPicker(canvas)).toHaveValue(initialDateFrom);
103
124
  await expect(dateToPicker(canvas)).toHaveValue(dayjs().format('YYYY-MM-DD'));
104
125
  });
@@ -117,14 +138,14 @@ export const SetCorrectInitialDateTo: StoryObj<DateRangeFilterProps> = {
117
138
  const canvas = within(canvasElement);
118
139
 
119
140
  await waitFor(async () => {
120
- await expect(selectField(canvas)).toHaveValue('Custom');
141
+ await expectOptionSelected(canvasElement, 'Custom');
121
142
  await expect(dateFromPicker(canvas)).toHaveValue(earliestDate);
122
143
  await expect(dateToPicker(canvas)).toHaveValue(initialDateTo);
123
144
  });
124
145
  },
125
146
  };
126
147
 
127
- export const ChangingDateSetsOptionToCustom: StoryObj<DateRangeFilterProps> = {
148
+ export const SetsValueOnBlur: StoryObj<DateRangeFilterProps> = {
128
149
  ...Primary,
129
150
  args: {
130
151
  ...Primary.args,
@@ -143,26 +164,28 @@ export const ChangingDateSetsOptionToCustom: StoryObj<DateRangeFilterProps> = {
143
164
  await userEvent.click(dateToPicker(canvas));
144
165
 
145
166
  await waitFor(async () => {
146
- await expect(selectField(canvas)).toHaveValue('Custom');
167
+ await expectOptionSelected(canvasElement, 'Custom');
147
168
  });
148
169
 
149
- await expect(filterChangedListenerMock).toHaveBeenCalledWith(
150
- expect.objectContaining({
151
- detail: {
152
- aDateColumnFrom: '2000-01-01',
153
- aDateColumnTo: dayjs().format('YYYY-MM-DD'),
154
- },
155
- }),
156
- );
157
-
158
- await expect(optionChangedListenerMock).toHaveBeenCalledWith(
159
- expect.objectContaining({
160
- detail: {
161
- dateFrom: '2000-01-01',
162
- dateTo: dayjs().format('YYYY-MM-DD'),
163
- },
164
- }),
165
- );
170
+ await waitFor(async () => {
171
+ await expect(filterChangedListenerMock).toHaveBeenCalledWith(
172
+ expect.objectContaining({
173
+ detail: {
174
+ aDateColumnFrom: '2000-01-01',
175
+ aDateColumnTo: dayjs().format('YYYY-MM-DD'),
176
+ },
177
+ }),
178
+ );
179
+
180
+ await expect(optionChangedListenerMock).toHaveBeenCalledWith(
181
+ expect.objectContaining({
182
+ detail: {
183
+ dateFrom: '2000-01-01',
184
+ dateTo: dayjs().format('YYYY-MM-DD'),
185
+ },
186
+ }),
187
+ );
188
+ });
166
189
  });
167
190
  },
168
191
  };
@@ -171,12 +194,13 @@ export const ChangingTheValueProgrammatically: StoryObj<DateRangeFilterProps> =
171
194
  ...Primary,
172
195
  render: (args) => {
173
196
  const StatefulWrapper = () => {
174
- const [value, setValue] = useState('Last month');
197
+ const [value, setValue] = useState<DateRangeValue | undefined>('Last month');
175
198
  const ref = useRef<HTMLDivElement>(null);
176
199
 
177
200
  useEffect(() => {
178
201
  ref.current?.addEventListener('gs-date-range-option-changed', (event) => {
179
- setValue((event as CustomEvent).detail);
202
+ const newValue = (event as CustomEvent).detail;
203
+ setValue(newValue ?? undefined);
180
204
  });
181
205
  }, []);
182
206
 
@@ -207,12 +231,13 @@ export const ChangingTheValueProgrammatically: StoryObj<DateRangeFilterProps> =
207
231
  await step('Change the value of the component programmatically', async () => {
208
232
  await userEvent.click(canvas.getByRole('button', { name: 'Set to Custom' }));
209
233
  await waitFor(async () => {
234
+ await expectOptionSelected(canvasElement, customDateRange.label);
210
235
  await expect(selectField(canvas)).toHaveValue(customDateRange.label);
211
236
  });
212
237
 
213
238
  await userEvent.click(canvas.getByRole('button', { name: 'Set to Last month' }));
214
239
  await waitFor(async () => {
215
- await expect(selectField(canvas)).toHaveValue('Last month');
240
+ await expectOptionSelected(canvasElement, 'Last month');
216
241
  });
217
242
 
218
243
  await expect(filterChangedListenerMock).toHaveBeenCalledTimes(0);
@@ -220,12 +245,25 @@ export const ChangingTheValueProgrammatically: StoryObj<DateRangeFilterProps> =
220
245
  });
221
246
 
222
247
  await step('Changing the value from within the component is still possible', async () => {
223
- await userEvent.selectOptions(selectField(canvas), 'All times');
224
248
  await waitFor(async () => {
225
- await expect(selectField(canvas)).toHaveValue('All times');
249
+ await userEvent.selectOptions(selectField(canvas), 'All times');
250
+ await expectOptionSelected(canvasElement, 'All times');
251
+ });
252
+ await waitFor(async () => {
253
+ await expect(filterChangedListenerMock).toHaveBeenCalledTimes(1);
254
+ await expect(optionChangedListenerMock).toHaveBeenCalledTimes(1);
255
+ });
256
+ });
257
+
258
+ await step('Clearing the value from within the component is still possible', async () => {
259
+ await waitFor(async () => {
260
+ await userEvent.click(canvas.getByRole('button', { name: '×' }));
261
+ await expectOptionSelected(canvasElement, placeholder);
262
+ });
263
+ await waitFor(async () => {
264
+ await expect(filterChangedListenerMock).toHaveBeenCalledTimes(2);
265
+ await expect(optionChangedListenerMock).toHaveBeenCalledTimes(2);
226
266
  });
227
- await expect(filterChangedListenerMock).toHaveBeenCalledTimes(1);
228
- await expect(optionChangedListenerMock).toHaveBeenCalledTimes(1);
229
267
  });
230
268
  },
231
269
  };
@@ -236,13 +274,13 @@ export const ChangingDateOption: StoryObj<DateRangeFilterProps> = {
236
274
  const { canvas, filterChangedListenerMock, optionChangedListenerMock } = await prepare(canvasElement, step);
237
275
 
238
276
  await waitFor(async () => {
239
- await expect(selectField(canvas)).toHaveValue('Custom');
277
+ await expectOptionSelected(canvasElement, placeholder);
240
278
  });
241
279
 
242
280
  await step('Change date to custom', async () => {
243
281
  await waitFor(async () => {
244
282
  await userEvent.selectOptions(selectField(canvas), 'CustomDateRange');
245
- await expect(selectField(canvas)).toHaveValue('CustomDateRange');
283
+ await expectOptionSelected(canvasElement, 'CustomDateRange');
246
284
  });
247
285
 
248
286
  await expect(filterChangedListenerMock).toHaveBeenCalledWith(