@genspectrum/dashboard-components 0.18.5 → 0.19.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 (46) hide show
  1. package/README.md +12 -2
  2. package/custom-elements.json +3 -3
  3. package/dist/assets/{mutationOverTimeWorker--b8ZHlji.js.map → mutationOverTimeWorker-ChQTFL68.js.map} +1 -1
  4. package/dist/components.d.ts +15 -14
  5. package/dist/components.js +1602 -332
  6. package/dist/components.js.map +1 -1
  7. package/dist/util.d.ts +14 -14
  8. package/package.json +3 -4
  9. package/src/preact/MutationAnnotationsContext.tsx +34 -27
  10. package/src/preact/components/dropdown.tsx +1 -1
  11. package/src/preact/components/info.tsx +1 -2
  12. package/src/preact/components/min-max-range-slider.tsx +0 -2
  13. package/src/preact/components/mutations-over-time-text-filter.stories.tsx +57 -0
  14. package/src/preact/components/mutations-over-time-text-filter.tsx +63 -0
  15. package/src/preact/components/segment-selector.tsx +1 -1
  16. package/src/preact/components/table.tsx +0 -2
  17. package/src/preact/dateRangeFilter/date-picker.tsx +15 -10
  18. package/src/preact/mutationFilter/mutation-filter.stories.tsx +169 -50
  19. package/src/preact/mutationFilter/mutation-filter.tsx +239 -234
  20. package/src/preact/mutationFilter/parseAndValidateMutation.ts +62 -10
  21. package/src/preact/mutationFilter/parseMutation.spec.ts +62 -47
  22. package/src/preact/mutationsOverTime/getFilteredMutationsOverTime.spec.ts +128 -0
  23. package/src/preact/mutationsOverTime/getFilteredMutationsOverTimeData.ts +39 -2
  24. package/src/preact/mutationsOverTime/mutations-over-time-grid.tsx +8 -11
  25. package/src/preact/mutationsOverTime/mutations-over-time.stories.tsx +27 -0
  26. package/src/preact/mutationsOverTime/mutations-over-time.tsx +26 -5
  27. package/src/preact/shared/tanstackTable/pagination-context.tsx +30 -0
  28. package/src/preact/shared/tanstackTable/pagination.tsx +19 -6
  29. package/src/preact/shared/tanstackTable/tanstackTable.tsx +17 -3
  30. package/src/preact/wastewater/mutationsOverTime/wastewater-mutations-over-time.stories.tsx +19 -1
  31. package/src/preact/wastewater/mutationsOverTime/wastewater-mutations-over-time.tsx +6 -1
  32. package/src/styles/replaceCssProperties.stories.tsx +49 -0
  33. package/src/styles/replaceCssProperties.ts +25 -0
  34. package/src/styles/tailwind.css +1 -0
  35. package/src/web-components/PreactLitAdapter.tsx +6 -3
  36. package/src/web-components/PreactLitAdapterWithGridJsStyles.tsx +0 -2
  37. package/src/web-components/gs-app.stories.ts +6 -2
  38. package/src/web-components/gs-app.ts +4 -1
  39. package/src/web-components/input/gs-date-range-filter.tsx +6 -0
  40. package/src/web-components/input/gs-mutation-filter.stories.ts +4 -4
  41. package/src/web-components/visualization/gs-prevalence-over-time.stories.ts +1 -1
  42. package/standalone-bundle/assets/mutationOverTimeWorker-jChgWnwp.js.map +1 -1
  43. package/standalone-bundle/dashboard-components.js +10836 -11289
  44. package/standalone-bundle/dashboard-components.js.map +1 -1
  45. package/dist/style.css +0 -392
  46. package/standalone-bundle/style.css +0 -1
@@ -44,7 +44,7 @@ export const Default: StoryObj<MutationFilterProps> = {
44
44
  },
45
45
  };
46
46
 
47
- export const FiresFilterMultipleCommaSeparatedQueries: StoryObj<MutationFilterProps> = {
47
+ export const EnterSingleMutationByClick: StoryObj<MutationFilterProps> = {
48
48
  ...Default,
49
49
  play: async ({ canvasElement, step }) => {
50
50
  const { canvas, changedListenerMock } = await prepare(canvasElement, step);
@@ -65,9 +65,101 @@ export const FiresFilterMultipleCommaSeparatedQueries: StoryObj<MutationFilterPr
65
65
  ),
66
66
  );
67
67
  });
68
+ },
69
+ };
70
+
71
+ export const EnterSingleMutationByEnter: StoryObj<MutationFilterProps> = {
72
+ ...Default,
73
+ play: async ({ canvasElement, step }) => {
74
+ const { canvas, changedListenerMock } = await prepare(canvasElement, step);
75
+
76
+ await step('Enter a valid mutation', async () => {
77
+ await submitMutation(canvas, 'A123T', 'enter');
78
+
79
+ await waitFor(() =>
80
+ expect(changedListenerMock).toHaveBeenCalledWith(
81
+ expect.objectContaining({
82
+ detail: {
83
+ nucleotideMutations: ['A123T'],
84
+ aminoAcidMutations: [],
85
+ nucleotideInsertions: [],
86
+ aminoAcidInsertions: [],
87
+ },
88
+ }),
89
+ ),
90
+ );
91
+ });
92
+ },
93
+ };
94
+
95
+ export const IgnoreDuplicates: StoryObj<MutationFilterProps> = {
96
+ ...Default,
97
+ play: async ({ canvasElement, step }) => {
98
+ const { canvas } = await prepare(canvasElement, step);
99
+
100
+ await step('Enter duplicate', async () => {
101
+ await userEvent.type(inputField(canvas), 'A123T', INPUT_DELAY);
102
+ });
103
+
104
+ await step('Should not show duplicate', async () => {
105
+ const options = await canvas.findAllByRole('option');
106
+ expect(options.length === 1);
107
+
108
+ const firstOption = await canvas.findByRole('option', { name: 'A123' });
109
+ await expect(firstOption).toBeVisible();
110
+ });
111
+ },
112
+ args: {
113
+ ...Default.args,
114
+ initialValue: {
115
+ nucleotideMutations: ['A123T'],
116
+ aminoAcidMutations: [],
117
+ nucleotideInsertions: [],
118
+ aminoAcidInsertions: [],
119
+ },
120
+ },
121
+ };
122
+
123
+ export const RemoveMutationByClick: StoryObj<MutationFilterProps> = {
124
+ ...Default,
125
+ args: {
126
+ ...Default.args,
127
+ initialValue: {
128
+ nucleotideMutations: ['A234T'],
129
+ aminoAcidMutations: ['S:A123G'],
130
+ nucleotideInsertions: ['ins_123:AAA'],
131
+ aminoAcidInsertions: ['ins_S:123:AAA'],
132
+ },
133
+ },
134
+ play: async ({ canvasElement, step }) => {
135
+ const { canvas, changedListenerMock } = await prepare(canvasElement, step);
136
+
137
+ await step('Remove a mutation', async () => {
138
+ await fireEvent.click(canvas.getByRole('button', { name: 'remove mutation filter A234T' }));
139
+
140
+ await waitFor(() =>
141
+ expect(changedListenerMock).toHaveBeenCalledWith(
142
+ expect.objectContaining({
143
+ detail: {
144
+ nucleotideMutations: [],
145
+ aminoAcidMutations: ['S:A123G'],
146
+ nucleotideInsertions: ['ins_123:AAA'],
147
+ aminoAcidInsertions: ['ins_S:123:AAA'],
148
+ },
149
+ }),
150
+ ),
151
+ );
152
+ });
153
+ },
154
+ };
155
+
156
+ export const PasteCommaSeparatedList: StoryObj<MutationFilterProps> = {
157
+ ...Default,
158
+ play: async ({ canvasElement, step }) => {
159
+ const { canvas, changedListenerMock } = await prepare(canvasElement, step);
68
160
 
69
161
  await step('Enter a comma separated list of valid and invalid mutations', async () => {
70
- await pasteMutations(canvas, 'A123T, error_insX, A234T, ins_123:AA');
162
+ await pasteMutations(canvas, 'A123T, error_insX, A234T, notAMutation, ins_123:AA');
71
163
 
72
164
  await waitFor(() =>
73
165
  expect(changedListenerMock).toHaveBeenCalledWith(
@@ -83,27 +175,45 @@ export const FiresFilterMultipleCommaSeparatedQueries: StoryObj<MutationFilterPr
83
175
  );
84
176
  await expect(canvas.queryByText('A123T')).toBeVisible();
85
177
  await expect(canvas.queryByText('A234T')).toBeVisible();
86
- await expect(inputField(canvas)).toHaveValue('error_insX');
178
+ await expect(inputField(canvas)).toHaveValue('error_insX,notAMutation');
87
179
  });
180
+ },
181
+ };
88
182
 
89
- await step('Remove the first mutation', async () => {
90
- const mutationItem = within(canvas.getByText('A234T'));
91
- await fireEvent.click(mutationItem.getByRole('button', { name: '×' }));
183
+ export const IgnoresDuplicatesOnPasteCommaSeparatedList: StoryObj<MutationFilterProps> = {
184
+ ...Default,
185
+ play: async ({ canvasElement, step }) => {
186
+ const { canvas, changedListenerMock } = await prepare(canvasElement, step);
187
+
188
+ await step('Enter a comma separated list with duplicates', async () => {
189
+ await pasteMutations(canvas, 'A123T, error_insX, A234T, A234T');
92
190
 
93
191
  await waitFor(() =>
94
192
  expect(changedListenerMock).toHaveBeenCalledWith(
95
193
  expect.objectContaining({
96
194
  detail: {
97
- nucleotideMutations: ['A123T'],
195
+ nucleotideMutations: ['A123T', 'A234T'],
98
196
  aminoAcidMutations: [],
99
- nucleotideInsertions: ['ins_123:AA'],
197
+ nucleotideInsertions: [],
100
198
  aminoAcidInsertions: [],
101
199
  },
102
200
  }),
103
201
  ),
104
202
  );
203
+ await expect(canvas.queryByText('A123T')).toBeVisible();
204
+ await expect(canvas.queryByText('A234T')).toBeVisible();
205
+ await expect(inputField(canvas)).toHaveValue('error_insX');
105
206
  });
106
207
  },
208
+ args: {
209
+ ...Default.args,
210
+ initialValue: {
211
+ nucleotideMutations: ['A123T'],
212
+ aminoAcidMutations: [],
213
+ nucleotideInsertions: [],
214
+ aminoAcidInsertions: [],
215
+ },
216
+ },
107
217
  };
108
218
 
109
219
  export const FiresFilterChangedEvents: StoryObj<MutationFilterProps> = {
@@ -115,7 +225,7 @@ export const FiresFilterChangedEvents: StoryObj<MutationFilterProps> = {
115
225
  await testNoOptionsExist(canvas, 'notAMutation');
116
226
  await expect(changedListenerMock).not.toHaveBeenCalled();
117
227
 
118
- await userEvent.type(inputField(canvas), '{backspace>12/}');
228
+ await userEvent.type(inputField(canvas), '{backspace>12/}', INPUT_DELAY);
119
229
  });
120
230
 
121
231
  await step('Enter a valid mutation', async () => {
@@ -138,46 +248,51 @@ export const FiresFilterChangedEvents: StoryObj<MutationFilterProps> = {
138
248
  await step('Enter a second valid nucleotide mutation', async () => {
139
249
  await submitMutation(canvas, 'A234-');
140
250
 
141
- await expect(changedListenerMock).toHaveBeenCalledWith(
142
- expect.objectContaining({
143
- detail: {
144
- nucleotideMutations: ['A123T', 'A234-'],
145
- aminoAcidMutations: [],
146
- nucleotideInsertions: [],
147
- aminoAcidInsertions: [],
148
- },
149
- }),
251
+ await waitFor(() =>
252
+ expect(changedListenerMock).toHaveBeenCalledWith(
253
+ expect.objectContaining({
254
+ detail: {
255
+ nucleotideMutations: ['A123T', 'A234-'],
256
+ aminoAcidMutations: [],
257
+ nucleotideInsertions: [],
258
+ aminoAcidInsertions: [],
259
+ },
260
+ }),
261
+ ),
150
262
  );
151
263
  });
152
264
 
153
265
  await step('Enter another valid mutation', async () => {
154
266
  await submitMutation(canvas, 'ins_123:AA', 'enter');
155
267
 
156
- await expect(changedListenerMock).toHaveBeenCalledWith(
157
- expect.objectContaining({
158
- detail: {
159
- nucleotideMutations: ['A123T', 'A234-'],
160
- aminoAcidMutations: [],
161
- nucleotideInsertions: ['ins_123:AA'],
162
- aminoAcidInsertions: [],
163
- },
164
- }),
268
+ await waitFor(() =>
269
+ expect(changedListenerMock).toHaveBeenCalledWith(
270
+ expect.objectContaining({
271
+ detail: {
272
+ nucleotideMutations: ['A123T', 'A234-'],
273
+ aminoAcidMutations: [],
274
+ nucleotideInsertions: ['ins_123:AA'],
275
+ aminoAcidInsertions: [],
276
+ },
277
+ }),
278
+ ),
165
279
  );
166
280
  });
167
281
 
168
282
  await step('Remove the first mutation', async () => {
169
- const mutationItem = within(canvas.getByText('A234-'));
170
- await fireEvent.click(mutationItem.getByRole('button', { name: '×' }));
171
-
172
- await expect(changedListenerMock).toHaveBeenCalledWith(
173
- expect.objectContaining({
174
- detail: {
175
- nucleotideMutations: ['A123T'],
176
- aminoAcidMutations: [],
177
- nucleotideInsertions: ['ins_123:AA'],
178
- aminoAcidInsertions: [],
179
- },
180
- }),
283
+ await fireEvent.click(canvas.getByRole('button', { name: 'remove mutation filter A234-' }));
284
+
285
+ await waitFor(() =>
286
+ expect(changedListenerMock).toHaveBeenCalledWith(
287
+ expect.objectContaining({
288
+ detail: {
289
+ nucleotideMutations: ['A123T'],
290
+ aminoAcidMutations: [],
291
+ nucleotideInsertions: ['ins_123:AA'],
292
+ aminoAcidInsertions: [],
293
+ },
294
+ }),
295
+ ),
181
296
  );
182
297
  });
183
298
  },
@@ -206,15 +321,17 @@ export const WithInitialValue: StoryObj<MutationFilterProps> = {
206
321
  await step('Add input to initial value', async () => {
207
322
  await submitMutation(canvas, 'G500T');
208
323
 
209
- await expect(changedListenerMock).toHaveBeenCalledWith(
210
- expect.objectContaining({
211
- detail: {
212
- nucleotideMutations: ['A234T', 'G500T'],
213
- aminoAcidMutations: ['S:A123G'],
214
- nucleotideInsertions: ['ins_123:AAA'],
215
- aminoAcidInsertions: ['ins_S:123:AAA'],
216
- },
217
- }),
324
+ await waitFor(() =>
325
+ expect(changedListenerMock).toHaveBeenCalledWith(
326
+ expect.objectContaining({
327
+ detail: {
328
+ nucleotideMutations: ['A234T', 'G500T'],
329
+ aminoAcidMutations: ['S:A123G'],
330
+ nucleotideInsertions: ['ins_123:AAA'],
331
+ aminoAcidInsertions: ['ins_S:123:AAA'],
332
+ },
333
+ }),
334
+ ),
218
335
  );
219
336
  });
220
337
  },
@@ -254,12 +371,14 @@ async function prepare(canvasElement: HTMLElement, step: StepFunction<PreactRend
254
371
 
255
372
  export type SubmissionMethod = 'click' | 'enter';
256
373
 
374
+ const INPUT_DELAY = { delay: 50 };
375
+
257
376
  const submitMutation = async (
258
377
  canvas: ReturnType<typeof within>,
259
378
  mutation: string,
260
379
  submissionMethod: SubmissionMethod = 'click',
261
380
  ) => {
262
- await userEvent.type(inputField(canvas), mutation);
381
+ await userEvent.type(inputField(canvas), mutation, INPUT_DELAY);
263
382
  const firstOption = await canvas.findByRole('option', { name: mutation });
264
383
  if (submissionMethod === 'click') {
265
384
  await userEvent.click(firstOption);
@@ -275,7 +394,7 @@ const pasteMutations = async (canvas: ReturnType<typeof within>, mutation: strin
275
394
  };
276
395
 
277
396
  const testNoOptionsExist = async (canvas: ReturnType<typeof within>, mutation: string) => {
278
- await userEvent.type(inputField(canvas), mutation);
397
+ await userEvent.type(inputField(canvas), mutation, INPUT_DELAY);
279
398
  const options = canvas.queryAllByRole('option');
280
399
 
281
400
  await expect(options).toHaveLength(0);