@genspectrum/dashboard-components 0.16.2 → 0.16.4
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.
- package/custom-elements.json +72 -7
- package/dist/assets/mutationOverTimeWorker-CPfQDLe6.js.map +1 -0
- package/dist/components.d.ts +60 -21
- package/dist/components.js +804 -608
- package/dist/components.js.map +1 -1
- package/dist/style.css +21 -0
- package/dist/util.d.ts +69 -25
- package/package.json +5 -2
- package/src/preact/MutationAnnotationsContext.spec.tsx +58 -0
- package/src/preact/MutationAnnotationsContext.tsx +72 -0
- package/src/preact/components/annotated-mutation.stories.tsx +164 -0
- package/src/preact/components/annotated-mutation.tsx +84 -0
- package/src/preact/components/error-display.tsx +9 -9
- package/src/preact/components/info.tsx +6 -13
- package/src/preact/components/modal.stories.tsx +7 -19
- package/src/preact/components/modal.tsx +35 -4
- package/src/preact/mutationComparison/mutation-comparison-table.tsx +14 -1
- package/src/preact/mutationComparison/mutation-comparison-venn.tsx +39 -8
- package/src/preact/mutationComparison/mutation-comparison.stories.tsx +36 -12
- package/src/preact/mutationComparison/mutation-comparison.tsx +2 -0
- package/src/preact/mutations/mutations-table.tsx +14 -2
- package/src/preact/mutations/mutations.stories.tsx +33 -1
- package/src/preact/mutations/mutations.tsx +1 -0
- package/src/preact/mutationsOverTime/mutations-over-time-grid.tsx +19 -8
- package/src/preact/mutationsOverTime/mutations-over-time.stories.tsx +29 -5
- package/src/preact/mutationsOverTime/mutations-over-time.tsx +13 -1
- package/src/preact/sequencesByLocation/sequences-by-location-map.tsx +28 -30
- package/src/preact/shared/stories/expectMutationAnnotation.ts +13 -0
- package/src/preact/wastewater/mutationsOverTime/wastewater-mutations-over-time.tsx +6 -1
- package/src/utilEntrypoint.ts +2 -0
- package/src/web-components/gs-app.spec-d.ts +10 -0
- package/src/web-components/gs-app.stories.ts +24 -6
- package/src/web-components/gs-app.ts +17 -0
- package/src/web-components/mutation-annotations-context.ts +16 -0
- package/src/web-components/visualization/gs-mutation-comparison.stories.ts +18 -1
- package/src/web-components/visualization/gs-mutation-comparison.tsx +19 -8
- package/src/web-components/visualization/gs-mutations-over-time.stories.ts +19 -1
- package/src/web-components/visualization/gs-mutations-over-time.tsx +22 -11
- package/src/web-components/visualization/gs-mutations.stories.ts +19 -1
- package/src/web-components/visualization/gs-mutations.tsx +20 -9
- package/src/web-components/wastewaterVisualization/gs-wastewater-mutations-over-time.stories.ts +11 -1
- package/src/web-components/wastewaterVisualization/gs-wastewater-mutations-over-time.tsx +18 -7
- package/standalone-bundle/assets/mutationOverTimeWorker-CERZSdcA.js.map +1 -0
- package/standalone-bundle/dashboard-components.js +12555 -11853
- package/standalone-bundle/dashboard-components.js.map +1 -1
- package/standalone-bundle/style.css +1 -1
- package/dist/assets/mutationOverTimeWorker-BL50C-yi.js.map +0 -1
- package/standalone-bundle/assets/mutationOverTimeWorker-CFB5-Mdk.js.map +0 -1
package/dist/style.css
CHANGED
|
@@ -1073,6 +1073,12 @@ html {
|
|
|
1073
1073
|
--tw-bg-opacity: 1;
|
|
1074
1074
|
background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity)));
|
|
1075
1075
|
}
|
|
1076
|
+
|
|
1077
|
+
.table-zebra tr.hover:hover,
|
|
1078
|
+
.table-zebra tr.hover:nth-child(even):hover {
|
|
1079
|
+
--tw-bg-opacity: 1;
|
|
1080
|
+
background-color: var(--fallback-b3,oklch(var(--b3)/var(--tw-bg-opacity)));
|
|
1081
|
+
}
|
|
1076
1082
|
}
|
|
1077
1083
|
.btn {
|
|
1078
1084
|
display: inline-flex;
|
|
@@ -3187,6 +3193,9 @@ input.tab:checked + .tab-content,
|
|
|
3187
3193
|
.mt-4 {
|
|
3188
3194
|
margin-top: 1rem;
|
|
3189
3195
|
}
|
|
3196
|
+
.block {
|
|
3197
|
+
display: block;
|
|
3198
|
+
}
|
|
3190
3199
|
.inline {
|
|
3191
3200
|
display: inline;
|
|
3192
3201
|
}
|
|
@@ -3283,6 +3292,11 @@ input.tab:checked + .tab-content,
|
|
|
3283
3292
|
.cursor-pointer {
|
|
3284
3293
|
cursor: pointer;
|
|
3285
3294
|
}
|
|
3295
|
+
.select-text {
|
|
3296
|
+
-webkit-user-select: text;
|
|
3297
|
+
-moz-user-select: text;
|
|
3298
|
+
user-select: text;
|
|
3299
|
+
}
|
|
3286
3300
|
.list-inside {
|
|
3287
3301
|
list-style-position: inside;
|
|
3288
3302
|
}
|
|
@@ -3492,6 +3506,9 @@ input.tab:checked + .tab-content,
|
|
|
3492
3506
|
.font-medium {
|
|
3493
3507
|
font-weight: 500;
|
|
3494
3508
|
}
|
|
3509
|
+
.font-normal {
|
|
3510
|
+
font-weight: 400;
|
|
3511
|
+
}
|
|
3495
3512
|
.font-semibold {
|
|
3496
3513
|
font-weight: 600;
|
|
3497
3514
|
}
|
|
@@ -3522,6 +3539,10 @@ input.tab:checked + .tab-content,
|
|
|
3522
3539
|
--tw-text-opacity: 1;
|
|
3523
3540
|
color: rgb(115 115 115 / var(--tw-text-opacity, 1));
|
|
3524
3541
|
}
|
|
3542
|
+
.text-red-600 {
|
|
3543
|
+
--tw-text-opacity: 1;
|
|
3544
|
+
color: rgb(220 38 38 / var(--tw-text-opacity, 1));
|
|
3545
|
+
}
|
|
3525
3546
|
.text-red-700 {
|
|
3526
3547
|
--tw-text-opacity: 1;
|
|
3527
3548
|
color: rgb(185 28 28 / var(--tw-text-opacity, 1));
|
package/dist/util.d.ts
CHANGED
|
@@ -202,6 +202,50 @@ declare const mapSourceSchema: default_2.ZodObject<{
|
|
|
202
202
|
topologyObjectsKey: string;
|
|
203
203
|
}>;
|
|
204
204
|
|
|
205
|
+
export declare type MutationAnnotation = default_2.infer<typeof mutationAnnotationSchema>;
|
|
206
|
+
|
|
207
|
+
export declare type MutationAnnotations = default_2.infer<typeof mutationAnnotationsSchema>;
|
|
208
|
+
|
|
209
|
+
declare const mutationAnnotationSchema: default_2.ZodObject<{
|
|
210
|
+
name: default_2.ZodString;
|
|
211
|
+
description: default_2.ZodString;
|
|
212
|
+
symbol: default_2.ZodString;
|
|
213
|
+
nucleotideMutations: default_2.ZodArray<default_2.ZodString, "many">;
|
|
214
|
+
aminoAcidMutations: default_2.ZodArray<default_2.ZodString, "many">;
|
|
215
|
+
}, "strip", default_2.ZodTypeAny, {
|
|
216
|
+
symbol: string;
|
|
217
|
+
name: string;
|
|
218
|
+
description: string;
|
|
219
|
+
nucleotideMutations: string[];
|
|
220
|
+
aminoAcidMutations: string[];
|
|
221
|
+
}, {
|
|
222
|
+
symbol: string;
|
|
223
|
+
name: string;
|
|
224
|
+
description: string;
|
|
225
|
+
nucleotideMutations: string[];
|
|
226
|
+
aminoAcidMutations: string[];
|
|
227
|
+
}>;
|
|
228
|
+
|
|
229
|
+
declare const mutationAnnotationsSchema: default_2.ZodArray<default_2.ZodObject<{
|
|
230
|
+
name: default_2.ZodString;
|
|
231
|
+
description: default_2.ZodString;
|
|
232
|
+
symbol: default_2.ZodString;
|
|
233
|
+
nucleotideMutations: default_2.ZodArray<default_2.ZodString, "many">;
|
|
234
|
+
aminoAcidMutations: default_2.ZodArray<default_2.ZodString, "many">;
|
|
235
|
+
}, "strip", default_2.ZodTypeAny, {
|
|
236
|
+
symbol: string;
|
|
237
|
+
name: string;
|
|
238
|
+
description: string;
|
|
239
|
+
nucleotideMutations: string[];
|
|
240
|
+
aminoAcidMutations: string[];
|
|
241
|
+
}, {
|
|
242
|
+
symbol: string;
|
|
243
|
+
name: string;
|
|
244
|
+
description: string;
|
|
245
|
+
nucleotideMutations: string[];
|
|
246
|
+
aminoAcidMutations: string[];
|
|
247
|
+
}>, "many">;
|
|
248
|
+
|
|
205
249
|
export declare type MutationComparisonProps = default_2.infer<typeof mutationComparisonPropsSchema>;
|
|
206
250
|
|
|
207
251
|
declare const mutationComparisonPropsSchema: default_2.ZodObject<{
|
|
@@ -247,6 +291,7 @@ declare const mutationComparisonPropsSchema: default_2.ZodObject<{
|
|
|
247
291
|
pageSize: default_2.ZodUnion<[default_2.ZodBoolean, default_2.ZodNumber]>;
|
|
248
292
|
}, "strip", default_2.ZodTypeAny, {
|
|
249
293
|
width: string;
|
|
294
|
+
sequenceType: "nucleotide" | "amino acid";
|
|
250
295
|
pageSize: number | boolean;
|
|
251
296
|
lapisFilters: {
|
|
252
297
|
lapisFilter: Record<string, string | number | boolean | string[] | null | undefined> & {
|
|
@@ -257,11 +302,11 @@ declare const mutationComparisonPropsSchema: default_2.ZodObject<{
|
|
|
257
302
|
};
|
|
258
303
|
displayName: string;
|
|
259
304
|
}[];
|
|
260
|
-
sequenceType: "nucleotide" | "amino acid";
|
|
261
305
|
views: ("table" | "venn")[];
|
|
262
306
|
height?: string | undefined;
|
|
263
307
|
}, {
|
|
264
308
|
width: string;
|
|
309
|
+
sequenceType: "nucleotide" | "amino acid";
|
|
265
310
|
pageSize: number | boolean;
|
|
266
311
|
lapisFilters: {
|
|
267
312
|
lapisFilter: Record<string, string | number | boolean | string[] | null | undefined> & {
|
|
@@ -272,7 +317,6 @@ declare const mutationComparisonPropsSchema: default_2.ZodObject<{
|
|
|
272
317
|
};
|
|
273
318
|
displayName: string;
|
|
274
319
|
}[];
|
|
275
|
-
sequenceType: "nucleotide" | "amino acid";
|
|
276
320
|
views: ("table" | "venn")[];
|
|
277
321
|
height?: string | undefined;
|
|
278
322
|
}>;
|
|
@@ -348,8 +392,8 @@ declare const mutationsPropsSchema: default_2.ZodObject<{
|
|
|
348
392
|
aminoAcidInsertions?: string[] | undefined;
|
|
349
393
|
};
|
|
350
394
|
width: string;
|
|
351
|
-
pageSize: number | boolean;
|
|
352
395
|
sequenceType: "nucleotide" | "amino acid";
|
|
396
|
+
pageSize: number | boolean;
|
|
353
397
|
views: ("table" | "grid" | "insertions")[];
|
|
354
398
|
height?: string | undefined;
|
|
355
399
|
baselineLapisFilter?: (Record<string, string | number | boolean | string[] | null | undefined> & {
|
|
@@ -366,8 +410,8 @@ declare const mutationsPropsSchema: default_2.ZodObject<{
|
|
|
366
410
|
aminoAcidInsertions?: string[] | undefined;
|
|
367
411
|
};
|
|
368
412
|
width: string;
|
|
369
|
-
pageSize: number | boolean;
|
|
370
413
|
sequenceType: "nucleotide" | "amino acid";
|
|
414
|
+
pageSize: number | boolean;
|
|
371
415
|
views: ("table" | "grid" | "insertions")[];
|
|
372
416
|
height?: string | undefined;
|
|
373
417
|
baselineLapisFilter?: (Record<string, string | number | boolean | string[] | null | undefined> & {
|
|
@@ -912,7 +956,7 @@ declare global {
|
|
|
912
956
|
|
|
913
957
|
declare global {
|
|
914
958
|
interface HTMLElementTagNameMap {
|
|
915
|
-
'gs-
|
|
959
|
+
'gs-aggregate': AggregateComponent;
|
|
916
960
|
}
|
|
917
961
|
}
|
|
918
962
|
|
|
@@ -920,7 +964,7 @@ declare global {
|
|
|
920
964
|
declare global {
|
|
921
965
|
namespace JSX {
|
|
922
966
|
interface IntrinsicElements {
|
|
923
|
-
'gs-
|
|
967
|
+
'gs-aggregate': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
924
968
|
}
|
|
925
969
|
}
|
|
926
970
|
}
|
|
@@ -928,7 +972,7 @@ declare global {
|
|
|
928
972
|
|
|
929
973
|
declare global {
|
|
930
974
|
interface HTMLElementTagNameMap {
|
|
931
|
-
'gs-
|
|
975
|
+
'gs-number-sequences-over-time': NumberSequencesOverTimeComponent;
|
|
932
976
|
}
|
|
933
977
|
}
|
|
934
978
|
|
|
@@ -936,7 +980,7 @@ declare global {
|
|
|
936
980
|
declare global {
|
|
937
981
|
namespace JSX {
|
|
938
982
|
interface IntrinsicElements {
|
|
939
|
-
'gs-
|
|
983
|
+
'gs-number-sequences-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
940
984
|
}
|
|
941
985
|
}
|
|
942
986
|
}
|
|
@@ -992,10 +1036,11 @@ declare global {
|
|
|
992
1036
|
|
|
993
1037
|
declare global {
|
|
994
1038
|
interface HTMLElementTagNameMap {
|
|
995
|
-
'gs-
|
|
1039
|
+
'gs-date-range-filter': DateRangeFilterComponent;
|
|
996
1040
|
}
|
|
997
1041
|
interface HTMLElementEventMap {
|
|
998
|
-
'gs-
|
|
1042
|
+
'gs-date-range-filter-changed': CustomEvent<Record<string, string>>;
|
|
1043
|
+
'gs-date-range-option-changed': DateRangeOptionChangedEvent;
|
|
999
1044
|
}
|
|
1000
1045
|
}
|
|
1001
1046
|
|
|
@@ -1003,7 +1048,7 @@ declare global {
|
|
|
1003
1048
|
declare global {
|
|
1004
1049
|
namespace JSX {
|
|
1005
1050
|
interface IntrinsicElements {
|
|
1006
|
-
'gs-
|
|
1051
|
+
'gs-date-range-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
1007
1052
|
}
|
|
1008
1053
|
}
|
|
1009
1054
|
}
|
|
@@ -1011,11 +1056,7 @@ declare global {
|
|
|
1011
1056
|
|
|
1012
1057
|
declare global {
|
|
1013
1058
|
interface HTMLElementTagNameMap {
|
|
1014
|
-
'gs-
|
|
1015
|
-
}
|
|
1016
|
-
interface HTMLElementEventMap {
|
|
1017
|
-
'gs-date-range-filter-changed': CustomEvent<Record<string, string>>;
|
|
1018
|
-
'gs-date-range-option-changed': DateRangeOptionChangedEvent;
|
|
1059
|
+
'gs-wastewater-mutations-over-time': WastewaterMutationsOverTimeComponent;
|
|
1019
1060
|
}
|
|
1020
1061
|
}
|
|
1021
1062
|
|
|
@@ -1023,7 +1064,7 @@ declare global {
|
|
|
1023
1064
|
declare global {
|
|
1024
1065
|
namespace JSX {
|
|
1025
1066
|
interface IntrinsicElements {
|
|
1026
|
-
'gs-
|
|
1067
|
+
'gs-wastewater-mutations-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
1027
1068
|
}
|
|
1028
1069
|
}
|
|
1029
1070
|
}
|
|
@@ -1031,10 +1072,10 @@ declare global {
|
|
|
1031
1072
|
|
|
1032
1073
|
declare global {
|
|
1033
1074
|
interface HTMLElementTagNameMap {
|
|
1034
|
-
'gs-
|
|
1075
|
+
'gs-location-filter': LocationFilterComponent;
|
|
1035
1076
|
}
|
|
1036
1077
|
interface HTMLElementEventMap {
|
|
1037
|
-
'gs-
|
|
1078
|
+
'gs-location-changed': LocationChangedEvent;
|
|
1038
1079
|
}
|
|
1039
1080
|
}
|
|
1040
1081
|
|
|
@@ -1042,7 +1083,7 @@ declare global {
|
|
|
1042
1083
|
declare global {
|
|
1043
1084
|
namespace JSX {
|
|
1044
1085
|
interface IntrinsicElements {
|
|
1045
|
-
'gs-
|
|
1086
|
+
'gs-location-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
1046
1087
|
}
|
|
1047
1088
|
}
|
|
1048
1089
|
}
|
|
@@ -1050,10 +1091,10 @@ declare global {
|
|
|
1050
1091
|
|
|
1051
1092
|
declare global {
|
|
1052
1093
|
interface HTMLElementTagNameMap {
|
|
1053
|
-
'gs-
|
|
1094
|
+
'gs-text-filter': TextFilterComponent;
|
|
1054
1095
|
}
|
|
1055
1096
|
interface HTMLElementEventMap {
|
|
1056
|
-
'gs-
|
|
1097
|
+
'gs-text-filter-changed': TextFilterChangedEvent;
|
|
1057
1098
|
}
|
|
1058
1099
|
}
|
|
1059
1100
|
|
|
@@ -1061,7 +1102,7 @@ declare global {
|
|
|
1061
1102
|
declare global {
|
|
1062
1103
|
namespace JSX {
|
|
1063
1104
|
interface IntrinsicElements {
|
|
1064
|
-
'gs-
|
|
1105
|
+
'gs-text-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
1065
1106
|
}
|
|
1066
1107
|
}
|
|
1067
1108
|
}
|
|
@@ -1088,7 +1129,10 @@ declare global {
|
|
|
1088
1129
|
|
|
1089
1130
|
declare global {
|
|
1090
1131
|
interface HTMLElementTagNameMap {
|
|
1091
|
-
'gs-
|
|
1132
|
+
'gs-mutation-filter': MutationFilterComponent;
|
|
1133
|
+
}
|
|
1134
|
+
interface HTMLElementEventMap {
|
|
1135
|
+
'gs-mutation-filter-changed': CustomEvent<MutationsFilter>;
|
|
1092
1136
|
}
|
|
1093
1137
|
}
|
|
1094
1138
|
|
|
@@ -1096,7 +1140,7 @@ declare global {
|
|
|
1096
1140
|
declare global {
|
|
1097
1141
|
namespace JSX {
|
|
1098
1142
|
interface IntrinsicElements {
|
|
1099
|
-
'gs-
|
|
1143
|
+
'gs-mutation-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
1100
1144
|
}
|
|
1101
1145
|
}
|
|
1102
1146
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@genspectrum/dashboard-components",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.4",
|
|
4
4
|
"description": "GenSpectrum web components for building dashboards",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "AGPL-3.0-only",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"scripts": {
|
|
52
52
|
"build": "vite --config vite.release.config.ts build && npm run generate-manifest && vite --config vite.release-standalone.config.ts build",
|
|
53
53
|
"build-and-pack": "npm run build && npm pack | xargs -I {} cp {} genspectrum-dashboard-components.tgz",
|
|
54
|
-
"test": "vitest",
|
|
54
|
+
"test": "vitest --typecheck",
|
|
55
55
|
"lint": "npm run lint:lit-analyzer && npm run lint:eslint",
|
|
56
56
|
"lint:eslint": "eslint 'src/**/*.{ts,tsx}'",
|
|
57
57
|
"lint:lit-analyzer": "lit-analyzer",
|
|
@@ -87,6 +87,7 @@
|
|
|
87
87
|
"chartjs-chart-error-bars": "^4.4.0",
|
|
88
88
|
"chartjs-chart-venn": "^4.3.0",
|
|
89
89
|
"dayjs": "^1.11.10",
|
|
90
|
+
"dompurify": "^3.2.4",
|
|
90
91
|
"downshift": "^9.0.8",
|
|
91
92
|
"flatpickr": "^4.6.13",
|
|
92
93
|
"gridjs": "^6.2.0",
|
|
@@ -115,6 +116,7 @@
|
|
|
115
116
|
"@storybook/web-components": "^8.0.9",
|
|
116
117
|
"@storybook/web-components-vite": "^8.0.9",
|
|
117
118
|
"@tailwindcss/container-queries": "^0.1.1",
|
|
119
|
+
"@testing-library/preact": "^3.2.4",
|
|
118
120
|
"@types/geojson": "^7946.0.15",
|
|
119
121
|
"@types/leaflet": "^1.9.15",
|
|
120
122
|
"@types/node": "^22.0.0",
|
|
@@ -130,6 +132,7 @@
|
|
|
130
132
|
"eslint-plugin-import": "^2.29.1",
|
|
131
133
|
"eslint-plugin-jest": "^28.2.0",
|
|
132
134
|
"eslint-plugin-storybook": "^0.11.0",
|
|
135
|
+
"happy-dom": "^17.1.1",
|
|
133
136
|
"http-server": "^14.1.1",
|
|
134
137
|
"lit-analyzer": "^2.0.3",
|
|
135
138
|
"msw": "^2.2.14",
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { renderHook } from '@testing-library/preact';
|
|
2
|
+
import { type FunctionalComponent } from 'preact';
|
|
3
|
+
import { describe, expect, it } from 'vitest';
|
|
4
|
+
|
|
5
|
+
import { MutationAnnotationsContextProvider, useMutationAnnotationsProvider } from './MutationAnnotationsContext';
|
|
6
|
+
import { type MutationAnnotations } from '../web-components/mutation-annotations-context';
|
|
7
|
+
|
|
8
|
+
describe('useMutationAnnotation', () => {
|
|
9
|
+
const mockAnnotations: MutationAnnotations = [
|
|
10
|
+
{
|
|
11
|
+
name: 'Annotation 1',
|
|
12
|
+
description: 'Description 1',
|
|
13
|
+
symbol: 'A1',
|
|
14
|
+
nucleotideMutations: ['A123', 'A456'],
|
|
15
|
+
aminoAcidMutations: ['B123'],
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
name: 'Annotation 2',
|
|
19
|
+
description: 'Description 2',
|
|
20
|
+
symbol: 'A2',
|
|
21
|
+
nucleotideMutations: ['A456', 'A789'],
|
|
22
|
+
aminoAcidMutations: ['B456', 'B789'],
|
|
23
|
+
},
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
const wrapper: FunctionalComponent = ({ children }) => (
|
|
27
|
+
<MutationAnnotationsContextProvider value={mockAnnotations}>{children}</MutationAnnotationsContextProvider>
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
function renderAnnotationsHook() {
|
|
31
|
+
const { result } = renderHook(() => useMutationAnnotationsProvider(), { wrapper });
|
|
32
|
+
return result.current;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
it('should return the correct annotation for a given nucleotide mutation', () => {
|
|
36
|
+
const result = renderAnnotationsHook()('A123', 'nucleotide');
|
|
37
|
+
|
|
38
|
+
expect(result).toEqual([mockAnnotations[0]]);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('should return the correct annotations if multiple contain a mutation', () => {
|
|
42
|
+
const result = renderAnnotationsHook()('A456', 'nucleotide');
|
|
43
|
+
|
|
44
|
+
expect(result).toEqual([mockAnnotations[0], mockAnnotations[1]]);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('should return undefined for a non-existent mutation code', () => {
|
|
48
|
+
const result = renderAnnotationsHook()('NON_EXISTENT', 'nucleotide');
|
|
49
|
+
|
|
50
|
+
expect(result).toBeUndefined();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('should return the correct mutation annotation for amino acid mutations', () => {
|
|
54
|
+
const result = renderAnnotationsHook()('B456', 'amino acid');
|
|
55
|
+
|
|
56
|
+
expect(result).toEqual([mockAnnotations[1]]);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { type ComponentProps, createContext, type FunctionalComponent } from 'preact';
|
|
2
|
+
import { useContext, useMemo } from 'preact/hooks';
|
|
3
|
+
|
|
4
|
+
import { type SequenceType } from '../types';
|
|
5
|
+
import {
|
|
6
|
+
type MutationAnnotation,
|
|
7
|
+
type MutationAnnotations,
|
|
8
|
+
mutationAnnotationsSchema,
|
|
9
|
+
} from '../web-components/mutation-annotations-context';
|
|
10
|
+
import { ErrorDisplay } from './components/error-display';
|
|
11
|
+
import { ResizeContainer } from './components/resize-container';
|
|
12
|
+
|
|
13
|
+
const MutationAnnotationsContext = createContext<Record<SequenceType, Map<string, MutationAnnotations>>>({
|
|
14
|
+
nucleotide: new Map(),
|
|
15
|
+
'amino acid': new Map(),
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
export const MutationAnnotationsContextProvider: FunctionalComponent<
|
|
19
|
+
Omit<ComponentProps<typeof MutationAnnotationsContext.Provider>, 'value'> & { value: MutationAnnotations }
|
|
20
|
+
> = ({ value, children }) => {
|
|
21
|
+
const parseResult = useMemo(() => {
|
|
22
|
+
const parseResult = mutationAnnotationsSchema.safeParse(value);
|
|
23
|
+
|
|
24
|
+
if (!parseResult.success) {
|
|
25
|
+
return parseResult;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const nucleotideMap = new Map<string, MutationAnnotations>();
|
|
29
|
+
const aminoAcidMap = new Map<string, MutationAnnotations>();
|
|
30
|
+
|
|
31
|
+
value.forEach((annotation) => {
|
|
32
|
+
new Set(annotation.nucleotideMutations).forEach((code) => {
|
|
33
|
+
addAnnotationToMap(nucleotideMap, code, annotation);
|
|
34
|
+
});
|
|
35
|
+
new Set(annotation.aminoAcidMutations).forEach((code) => {
|
|
36
|
+
addAnnotationToMap(aminoAcidMap, code, annotation);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
success: true as const,
|
|
42
|
+
value: {
|
|
43
|
+
nucleotide: nucleotideMap,
|
|
44
|
+
'amino acid': aminoAcidMap,
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
}, [value]);
|
|
48
|
+
|
|
49
|
+
if (!parseResult.success) {
|
|
50
|
+
return (
|
|
51
|
+
<ResizeContainer size={{ width: '100%' }}>
|
|
52
|
+
<ErrorDisplay error={parseResult.error} layout='vertical' />
|
|
53
|
+
</ResizeContainer>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<MutationAnnotationsContext.Provider value={parseResult.value}>{children}</MutationAnnotationsContext.Provider>
|
|
59
|
+
);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
function addAnnotationToMap(map: Map<string, MutationAnnotations>, code: string, annotation: MutationAnnotation) {
|
|
63
|
+
const oldAnnotations = map.get(code.toUpperCase()) ?? [];
|
|
64
|
+
map.set(code.toUpperCase(), [...oldAnnotations, annotation]);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function useMutationAnnotationsProvider() {
|
|
68
|
+
const mutationAnnotations = useContext(MutationAnnotationsContext);
|
|
69
|
+
|
|
70
|
+
return (mutationCode: string, sequenceType: SequenceType) =>
|
|
71
|
+
mutationAnnotations[sequenceType].get(mutationCode.toUpperCase());
|
|
72
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { type Meta, type StoryObj } from '@storybook/preact';
|
|
2
|
+
import { expect, userEvent, waitFor, within } from '@storybook/test';
|
|
3
|
+
|
|
4
|
+
import { AnnotatedMutation, type AnnotatedMutationProps } from './annotated-mutation';
|
|
5
|
+
import { type MutationAnnotations } from '../../web-components/mutation-annotations-context';
|
|
6
|
+
import { MutationAnnotationsContextProvider } from '../MutationAnnotationsContext';
|
|
7
|
+
|
|
8
|
+
const meta: Meta<AnnotatedMutationProps & { annotations: MutationAnnotations }> = {
|
|
9
|
+
title: 'Component/Annotated Mutation',
|
|
10
|
+
component: AnnotatedMutation,
|
|
11
|
+
parameters: { fetchMock: {} },
|
|
12
|
+
argTypes: {
|
|
13
|
+
annotations: { control: { type: 'object' } },
|
|
14
|
+
mutation: { control: { type: 'object' } },
|
|
15
|
+
sequenceType: {
|
|
16
|
+
options: ['nucleotide', 'amino acid'],
|
|
17
|
+
control: { type: 'radio' },
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export default meta;
|
|
23
|
+
|
|
24
|
+
export const MutationWithoutAnnotationEntry: StoryObj<AnnotatedMutationProps & { annotations: MutationAnnotations }> = {
|
|
25
|
+
render: (args) => {
|
|
26
|
+
const { annotations, ...annotatedMutationsArgs } = args;
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<MutationAnnotationsContextProvider value={annotations}>
|
|
30
|
+
<AnnotatedMutation {...annotatedMutationsArgs} />
|
|
31
|
+
</MutationAnnotationsContextProvider>
|
|
32
|
+
);
|
|
33
|
+
},
|
|
34
|
+
args: {
|
|
35
|
+
mutation: {
|
|
36
|
+
type: 'substitution',
|
|
37
|
+
code: 'A23403G',
|
|
38
|
+
position: 23403,
|
|
39
|
+
valueAtReference: 'A',
|
|
40
|
+
substitutionValue: 'G',
|
|
41
|
+
},
|
|
42
|
+
sequenceType: 'nucleotide',
|
|
43
|
+
annotations: [
|
|
44
|
+
{
|
|
45
|
+
name: 'Test annotation',
|
|
46
|
+
description: 'This is a test annotation',
|
|
47
|
+
symbol: '*',
|
|
48
|
+
nucleotideMutations: ['123T'],
|
|
49
|
+
aminoAcidMutations: [],
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
},
|
|
53
|
+
play: async ({ canvasElement }) => {
|
|
54
|
+
const canvas = within(canvasElement);
|
|
55
|
+
|
|
56
|
+
await waitFor(() => expect(canvas.getByText('A23403G')).toBeVisible());
|
|
57
|
+
await expect(getAnnotationIndicator(canvas)).not.toBeInTheDocument();
|
|
58
|
+
await expect(getAnnotationName(canvas)).not.toBeInTheDocument();
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export const MutationWithAnnotationEntry: StoryObj<AnnotatedMutationProps & { annotations: MutationAnnotations }> = {
|
|
63
|
+
...MutationWithoutAnnotationEntry,
|
|
64
|
+
args: {
|
|
65
|
+
...MutationWithoutAnnotationEntry.args,
|
|
66
|
+
annotations: [
|
|
67
|
+
{
|
|
68
|
+
name: 'Test annotation',
|
|
69
|
+
description: 'This is a test annotation <a class="link" href="/">with a link.</a>',
|
|
70
|
+
symbol: '*',
|
|
71
|
+
nucleotideMutations: ['A23403G'],
|
|
72
|
+
aminoAcidMutations: [],
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
},
|
|
76
|
+
play: async ({ canvasElement }) => {
|
|
77
|
+
const canvas = within(canvasElement);
|
|
78
|
+
|
|
79
|
+
await waitFor(() => expect(canvas.getByText('A23403G')).toBeVisible());
|
|
80
|
+
await expect(getAnnotationIndicator(canvas)).toBeVisible();
|
|
81
|
+
|
|
82
|
+
await userEvent.click(canvas.getByText('A23403G'));
|
|
83
|
+
await waitFor(() => expect(getAnnotationName(canvas)).toBeVisible());
|
|
84
|
+
await expect(canvas.getByRole('link', { name: 'with a link.' })).toBeVisible();
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export const MutationWithMultipleAnnotationEntries: StoryObj<
|
|
89
|
+
AnnotatedMutationProps & { annotations: MutationAnnotations }
|
|
90
|
+
> = {
|
|
91
|
+
...MutationWithoutAnnotationEntry,
|
|
92
|
+
args: {
|
|
93
|
+
...MutationWithoutAnnotationEntry.args,
|
|
94
|
+
annotations: [
|
|
95
|
+
{
|
|
96
|
+
name: 'Test annotation',
|
|
97
|
+
description: 'This is a test annotation',
|
|
98
|
+
symbol: '*',
|
|
99
|
+
nucleotideMutations: ['A23403G'],
|
|
100
|
+
aminoAcidMutations: [],
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
name: 'Another test annotation',
|
|
104
|
+
description: 'This is a test annotation',
|
|
105
|
+
symbol: '+',
|
|
106
|
+
nucleotideMutations: ['A23403G'],
|
|
107
|
+
aminoAcidMutations: [],
|
|
108
|
+
},
|
|
109
|
+
],
|
|
110
|
+
},
|
|
111
|
+
play: async ({ canvasElement }) => {
|
|
112
|
+
const canvas = within(canvasElement);
|
|
113
|
+
|
|
114
|
+
await waitFor(() => expect(canvas.getByText('A23403G')).toBeVisible());
|
|
115
|
+
await expect(getAnnotationIndicator(canvas)).toBeVisible();
|
|
116
|
+
await expect(canvas.queryByText('+')).toBeVisible();
|
|
117
|
+
|
|
118
|
+
await userEvent.click(canvas.getByText('A23403G'));
|
|
119
|
+
await waitFor(() => expect(getAnnotationName(canvas)).toBeVisible());
|
|
120
|
+
await expect(canvas.queryByText('Another test annotation')).toBeVisible();
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
export const AminoAcidMutationWithAnnotationEntry: StoryObj<
|
|
125
|
+
AnnotatedMutationProps & { annotations: MutationAnnotations }
|
|
126
|
+
> = {
|
|
127
|
+
...MutationWithoutAnnotationEntry,
|
|
128
|
+
args: {
|
|
129
|
+
mutation: {
|
|
130
|
+
type: 'substitution',
|
|
131
|
+
code: 'S:A501G',
|
|
132
|
+
position: 501,
|
|
133
|
+
valueAtReference: 'A',
|
|
134
|
+
substitutionValue: 'G',
|
|
135
|
+
},
|
|
136
|
+
sequenceType: 'amino acid',
|
|
137
|
+
annotations: [
|
|
138
|
+
{
|
|
139
|
+
name: 'Test annotation',
|
|
140
|
+
description: 'This is a test annotation',
|
|
141
|
+
symbol: '*',
|
|
142
|
+
nucleotideMutations: [],
|
|
143
|
+
aminoAcidMutations: ['S:A501G'],
|
|
144
|
+
},
|
|
145
|
+
],
|
|
146
|
+
},
|
|
147
|
+
play: async ({ canvasElement }) => {
|
|
148
|
+
const canvas = within(canvasElement);
|
|
149
|
+
|
|
150
|
+
await waitFor(() => expect(canvas.getByText('S:A501G')).toBeVisible());
|
|
151
|
+
await expect(getAnnotationIndicator(canvas)).toBeVisible();
|
|
152
|
+
|
|
153
|
+
await userEvent.click(canvas.getByText('S:A501G'));
|
|
154
|
+
await waitFor(() => expect(getAnnotationName(canvas)).toBeVisible());
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
function getAnnotationIndicator(canvas: ReturnType<typeof within>) {
|
|
159
|
+
return canvas.queryByText('*');
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function getAnnotationName(canvas: ReturnType<typeof within>) {
|
|
163
|
+
return canvas.queryByText('Test annotation');
|
|
164
|
+
}
|