@fmidev/smartmet-alert-client 4.4.19 → 4.7.0-beta.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 (123) hide show
  1. package/.eslintignore +2 -14
  2. package/.github/workflows/test.yaml +26 -0
  3. package/.nvmrc +1 -0
  4. package/AGENTS.md +26 -0
  5. package/index.html +1 -1
  6. package/package.json +80 -22
  7. package/src/AlertClientVue.vue +160 -0
  8. package/src/App.vue +154 -296
  9. package/src/assets/img/ui/arrow-down.svg +4 -11
  10. package/src/assets/img/ui/arrow-up.svg +4 -11
  11. package/src/assets/img/ui/clear.svg +7 -21
  12. package/src/assets/img/ui/close.svg +4 -15
  13. package/src/assets/img/ui/toggle-selected.svg +5 -6
  14. package/src/assets/img/ui/toggle-unselected.svg +5 -6
  15. package/src/assets/img/warning/cold-weather.svg +3 -6
  16. package/src/assets/img/warning/flood-level-3.svg +4 -7
  17. package/src/assets/img/warning/forest-fire-weather.svg +2 -6
  18. package/src/assets/img/warning/grass-fire-weather.svg +2 -6
  19. package/src/assets/img/warning/hot-weather.svg +3 -6
  20. package/src/assets/img/warning/pedestrian-safety.svg +3 -7
  21. package/src/assets/img/warning/rain.svg +2 -7
  22. package/src/assets/img/warning/sea-icing.svg +2 -6
  23. package/src/assets/img/warning/sea-thunder-storm.svg +2 -5
  24. package/src/assets/img/warning/sea-water-height-high-water.svg +3 -8
  25. package/src/assets/img/warning/sea-water-height-shallow-water.svg +3 -7
  26. package/src/assets/img/warning/sea-wave-height.svg +4 -7
  27. package/src/assets/img/warning/sea-wind-legend.svg +2 -5
  28. package/src/assets/img/warning/sea-wind.svg +2 -5
  29. package/src/assets/img/warning/several.svg +2 -5
  30. package/src/assets/img/warning/thunder-storm.svg +2 -5
  31. package/src/assets/img/warning/traffic-weather.svg +2 -6
  32. package/src/assets/img/warning/uv-note.svg +2 -6
  33. package/src/assets/img/warning/wind.svg +2 -5
  34. package/src/components/AlertClient.vue +330 -251
  35. package/src/components/CollapsiblePanel.vue +281 -0
  36. package/src/components/DayLarge.vue +146 -110
  37. package/src/components/DaySmall.vue +97 -81
  38. package/src/components/Days.vue +229 -159
  39. package/src/components/DescriptionWarning.vue +63 -38
  40. package/src/components/GrayScaleToggle.vue +58 -54
  41. package/src/components/Legend.vue +102 -325
  42. package/src/components/MapLarge.vue +574 -351
  43. package/src/components/MapSmall.vue +137 -122
  44. package/src/components/PopupRow.vue +24 -12
  45. package/src/components/Region.vue +168 -118
  46. package/src/components/RegionWarning.vue +40 -33
  47. package/src/components/Regions.vue +189 -105
  48. package/src/components/Warning.vue +70 -45
  49. package/src/components/Warnings.vue +136 -72
  50. package/src/composables/useAlertClient.ts +360 -0
  51. package/src/composables/useConfig.ts +573 -0
  52. package/src/composables/useFields.ts +66 -0
  53. package/src/composables/useI18n.ts +62 -0
  54. package/src/composables/useKeyCodes.ts +16 -0
  55. package/src/composables/useMapPaths.ts +477 -0
  56. package/src/composables/useUtils.ts +683 -0
  57. package/src/composables/useWarningsProcessor.ts +1007 -0
  58. package/src/data/geometries.json +993 -0
  59. package/src/{main.js → main.ts} +1 -0
  60. package/src/mixins/geojsonsvg.d.ts +57 -0
  61. package/src/mixins/geojsonsvg.js +5 -3
  62. package/src/plugins/index.ts +5 -0
  63. package/src/scss/_utilities.scss +193 -0
  64. package/src/scss/constants.scss +2 -1
  65. package/src/scss/warningImages.scss +8 -3
  66. package/src/types/index.ts +509 -0
  67. package/src/vite-env.d.ts +23 -0
  68. package/src/vue.ts +41 -0
  69. package/svgo.config.js +45 -0
  70. package/tests/README.md +430 -0
  71. package/tests/fixtures/mockWarningData.ts +152 -0
  72. package/tests/integration/warning-flow.spec.ts +445 -0
  73. package/tests/setup.ts +41 -0
  74. package/tests/unit/components/AlertClient.spec.ts +701 -0
  75. package/tests/unit/components/DayLarge.spec.ts +348 -0
  76. package/tests/unit/components/DaySmall.spec.ts +352 -0
  77. package/tests/unit/components/Days.spec.ts +548 -0
  78. package/tests/unit/components/DescriptionWarning.spec.ts +385 -0
  79. package/tests/unit/components/GrayScaleToggle.spec.ts +318 -0
  80. package/tests/unit/components/Legend.spec.ts +295 -0
  81. package/tests/unit/components/MapLarge.spec.ts +448 -0
  82. package/tests/unit/components/MapSmall.spec.ts +367 -0
  83. package/tests/unit/components/PopupRow.spec.ts +270 -0
  84. package/tests/unit/components/Region.spec.ts +373 -0
  85. package/tests/unit/components/RegionWarning.snapshot.spec.ts +361 -0
  86. package/tests/unit/components/RegionWarning.spec.ts +381 -0
  87. package/tests/unit/components/Regions.spec.ts +503 -0
  88. package/tests/unit/components/Warning.snapshot.spec.ts +483 -0
  89. package/tests/unit/components/Warning.spec.ts +489 -0
  90. package/tests/unit/components/Warnings.spec.ts +343 -0
  91. package/tests/unit/components/__snapshots__/RegionWarning.snapshot.spec.ts.snap +41 -0
  92. package/tests/unit/components/__snapshots__/Warning.snapshot.spec.ts.snap +433 -0
  93. package/tests/unit/composables/useConfig.spec.ts +279 -0
  94. package/tests/unit/composables/useI18n.spec.ts +116 -0
  95. package/tests/unit/composables/useKeyCodes.spec.ts +27 -0
  96. package/tests/unit/composables/useUtils.spec.ts +213 -0
  97. package/tsconfig.json +43 -0
  98. package/tsconfig.node.json +11 -0
  99. package/vite.config.js +96 -26
  100. package/vitest.config.js +40 -0
  101. package/dist/favicon.ico +0 -0
  102. package/dist/index.dark.html +0 -20
  103. package/dist/index.en.html +0 -15
  104. package/dist/index.fi.html +0 -15
  105. package/dist/index.html +0 -15
  106. package/dist/index.js +0 -281
  107. package/dist/index.mjs +0 -281
  108. package/dist/index.mjs.map +0 -1
  109. package/dist/index.relative.html +0 -19
  110. package/dist/index.start.html +0 -20
  111. package/dist/index.sv.html +0 -15
  112. package/playwright.config.ts +0 -18
  113. package/public/index.relative.html +0 -19
  114. package/public/index.start.html +0 -20
  115. package/src/mixins/config.js +0 -1378
  116. package/src/mixins/fields.js +0 -26
  117. package/src/mixins/i18n.js +0 -25
  118. package/src/mixins/keycodes.js +0 -10
  119. package/src/mixins/panzoom.js +0 -900
  120. package/src/mixins/utils.js +0 -900
  121. package/src/plugins/index.js +0 -3
  122. package/test/snapshot.test.ts +0 -126
  123. package/vitest.config.ts +0 -6
@@ -0,0 +1,448 @@
1
+ import { describe, it, expect, afterEach, vi } from 'vitest'
2
+ import { mount, VueWrapper } from '@vue/test-utils'
3
+ import MapLarge from '@/components/MapLarge.vue'
4
+ import type { DayRegions, WarningsMap, Theme, Language } from '@/types'
5
+
6
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
7
+ type ComponentInstance = any
8
+
9
+ const mockRegions: DayRegions = {
10
+ land: [],
11
+ sea: [],
12
+ }
13
+
14
+ describe('MapLarge.vue', () => {
15
+ let wrapper: VueWrapper | null = null
16
+
17
+ afterEach(() => {
18
+ if (wrapper) {
19
+ wrapper.unmount()
20
+ wrapper = null
21
+ }
22
+ })
23
+
24
+ describe('Component mounting', () => {
25
+ it('should mount with default props', () => {
26
+ wrapper = mount(MapLarge, {
27
+ props: {
28
+ index: 0,
29
+ },
30
+ })
31
+
32
+ expect(wrapper.exists()).toBe(true)
33
+ })
34
+
35
+ it('should mount with all props', () => {
36
+ wrapper = mount(MapLarge, {
37
+ props: {
38
+ index: 0,
39
+ input: mockRegions,
40
+ visibleWarnings: [],
41
+ warnings: null,
42
+ geometryId: 2021,
43
+ loading: true,
44
+ theme: 'light-theme' as Theme,
45
+ language: 'fi' as Language,
46
+ spinnerEnabled: true,
47
+ },
48
+ })
49
+
50
+ expect(wrapper.exists()).toBe(true)
51
+ })
52
+ })
53
+
54
+ describe('Computed properties', () => {
55
+ it('should compute zoomInText from translations', () => {
56
+ wrapper = mount(MapLarge, {
57
+ props: {
58
+ index: 0,
59
+ language: 'fi' as Language,
60
+ },
61
+ })
62
+
63
+ expect(typeof (wrapper.vm as ComponentInstance).zoomInText).toBe('string')
64
+ })
65
+
66
+ it('should compute zoomOutText from translations', () => {
67
+ wrapper = mount(MapLarge, {
68
+ props: {
69
+ index: 0,
70
+ language: 'fi' as Language,
71
+ },
72
+ })
73
+
74
+ expect(typeof (wrapper.vm as ComponentInstance).zoomOutText).toBe(
75
+ 'string'
76
+ )
77
+ })
78
+
79
+ it('should compute moveText from translations', () => {
80
+ wrapper = mount(MapLarge, {
81
+ props: {
82
+ index: 0,
83
+ language: 'fi' as Language,
84
+ },
85
+ })
86
+
87
+ expect(typeof (wrapper.vm as ComponentInstance).moveText).toBe('string')
88
+ })
89
+
90
+ it('should compute tooltipStyle', () => {
91
+ wrapper = mount(MapLarge, {
92
+ props: {
93
+ index: 0,
94
+ },
95
+ })
96
+
97
+ expect((wrapper.vm as ComponentInstance).tooltipStyle).toContain('left:')
98
+ expect((wrapper.vm as ComponentInstance).tooltipStyle).toContain('top:')
99
+ })
100
+
101
+ it('should compute strokeWidth', () => {
102
+ wrapper = mount(MapLarge, {
103
+ props: {
104
+ index: 0,
105
+ },
106
+ })
107
+
108
+ expect(typeof (wrapper.vm as ComponentInstance).strokeWidth).toBe(
109
+ 'string'
110
+ )
111
+ })
112
+
113
+ it('should compute iconSize based on scale', () => {
114
+ wrapper = mount(MapLarge, {
115
+ props: {
116
+ index: 0,
117
+ },
118
+ })
119
+
120
+ expect(typeof (wrapper.vm as ComponentInstance).iconSize).toBe('number')
121
+ })
122
+
123
+ it('should compute icons as array', () => {
124
+ wrapper = mount(MapLarge, {
125
+ props: {
126
+ index: 0,
127
+ input: mockRegions,
128
+ warnings: null,
129
+ },
130
+ })
131
+
132
+ expect(Array.isArray((wrapper.vm as ComponentInstance).icons)).toBe(true)
133
+ })
134
+
135
+ it('should compute coverageIcons as array', () => {
136
+ wrapper = mount(MapLarge, {
137
+ props: {
138
+ index: 0,
139
+ },
140
+ })
141
+
142
+ expect(
143
+ Array.isArray((wrapper.vm as ComponentInstance).coverageIcons)
144
+ ).toBe(true)
145
+ })
146
+ })
147
+
148
+ describe('Props handling', () => {
149
+ it('should accept loading prop', () => {
150
+ wrapper = mount(MapLarge, {
151
+ props: {
152
+ index: 0,
153
+ loading: false,
154
+ },
155
+ })
156
+
157
+ expect((wrapper.vm as ComponentInstance).loading).toBe(false)
158
+ })
159
+
160
+ it('should accept spinnerEnabled prop', () => {
161
+ wrapper = mount(MapLarge, {
162
+ props: {
163
+ index: 0,
164
+ spinnerEnabled: false,
165
+ },
166
+ })
167
+
168
+ expect((wrapper.vm as ComponentInstance).spinnerEnabled).toBe(false)
169
+ })
170
+
171
+ it('should show spinner when loading and enabled', () => {
172
+ wrapper = mount(MapLarge, {
173
+ props: {
174
+ index: 0,
175
+ loading: true,
176
+ spinnerEnabled: true,
177
+ },
178
+ })
179
+
180
+ expect(wrapper.find('.spinner-container').exists()).toBe(true)
181
+ })
182
+
183
+ it('should hide spinner when spinnerEnabled is false', () => {
184
+ wrapper = mount(MapLarge, {
185
+ props: {
186
+ index: 0,
187
+ loading: true,
188
+ spinnerEnabled: false,
189
+ },
190
+ })
191
+
192
+ expect(wrapper.find('.spinner-container').exists()).toBe(false)
193
+ })
194
+
195
+ it('should accept warnings prop', () => {
196
+ const mockWarnings: WarningsMap = {}
197
+
198
+ wrapper = mount(MapLarge, {
199
+ props: {
200
+ index: 0,
201
+ warnings: mockWarnings,
202
+ },
203
+ })
204
+
205
+ expect(wrapper.exists()).toBe(true)
206
+ })
207
+
208
+ it('should accept visibleWarnings prop', () => {
209
+ wrapper = mount(MapLarge, {
210
+ props: {
211
+ index: 0,
212
+ visibleWarnings: ['wind', 'rain'],
213
+ },
214
+ })
215
+
216
+ expect(wrapper.exists()).toBe(true)
217
+ })
218
+
219
+ it('should accept geometryId prop', () => {
220
+ wrapper = mount(MapLarge, {
221
+ props: {
222
+ index: 0,
223
+ geometryId: 2021,
224
+ },
225
+ })
226
+
227
+ expect((wrapper.vm as ComponentInstance).geometryId).toBe(2021)
228
+ })
229
+ })
230
+
231
+ describe('Zoom functionality', () => {
232
+ it('should have zoomIn method', () => {
233
+ wrapper = mount(MapLarge, {
234
+ props: {
235
+ index: 0,
236
+ },
237
+ })
238
+
239
+ expect(typeof (wrapper.vm as ComponentInstance).zoomIn).toBe('function')
240
+ })
241
+
242
+ it('should have zoomOut method', () => {
243
+ wrapper = mount(MapLarge, {
244
+ props: {
245
+ index: 0,
246
+ },
247
+ })
248
+
249
+ expect(typeof (wrapper.vm as ComponentInstance).zoomOut).toBe('function')
250
+ })
251
+
252
+ it('should have scale property', () => {
253
+ wrapper = mount(MapLarge, {
254
+ props: {
255
+ index: 0,
256
+ },
257
+ })
258
+
259
+ expect((wrapper.vm as ComponentInstance).scale).toBe(1)
260
+ })
261
+ })
262
+
263
+ describe('Navigation methods', () => {
264
+ it('should have moveWest method', () => {
265
+ wrapper = mount(MapLarge, {
266
+ props: {
267
+ index: 0,
268
+ },
269
+ })
270
+
271
+ expect(typeof (wrapper.vm as ComponentInstance).moveWest).toBe('function')
272
+ })
273
+
274
+ it('should have moveEast method', () => {
275
+ wrapper = mount(MapLarge, {
276
+ props: {
277
+ index: 0,
278
+ },
279
+ })
280
+
281
+ expect(typeof (wrapper.vm as ComponentInstance).moveEast).toBe('function')
282
+ })
283
+
284
+ it('should have moveNorth method', () => {
285
+ wrapper = mount(MapLarge, {
286
+ props: {
287
+ index: 0,
288
+ },
289
+ })
290
+
291
+ expect(typeof (wrapper.vm as ComponentInstance).moveNorth).toBe(
292
+ 'function'
293
+ )
294
+ })
295
+
296
+ it('should have moveSouth method', () => {
297
+ wrapper = mount(MapLarge, {
298
+ props: {
299
+ index: 0,
300
+ },
301
+ })
302
+
303
+ expect(typeof (wrapper.vm as ComponentInstance).moveSouth).toBe(
304
+ 'function'
305
+ )
306
+ })
307
+ })
308
+
309
+ describe('Tooltip functionality', () => {
310
+ it('should have closeTooltip method', () => {
311
+ wrapper = mount(MapLarge, {
312
+ props: {
313
+ index: 0,
314
+ },
315
+ })
316
+
317
+ expect(typeof (wrapper.vm as ComponentInstance).closeTooltip).toBe(
318
+ 'function'
319
+ )
320
+ })
321
+
322
+ it('should close tooltip when closeTooltip is called', () => {
323
+ wrapper = mount(MapLarge, {
324
+ props: {
325
+ index: 0,
326
+ },
327
+ })
328
+ ;(wrapper.vm as ComponentInstance).showTooltip = true
329
+
330
+ const event = { preventDefault: vi.fn() }
331
+ ;(wrapper.vm as ComponentInstance).closeTooltip(event)
332
+
333
+ expect((wrapper.vm as ComponentInstance).showTooltip).toBe(false)
334
+ expect(event.preventDefault).toHaveBeenCalled()
335
+ })
336
+ })
337
+
338
+ describe('Theme support', () => {
339
+ it('should apply theme class', () => {
340
+ wrapper = mount(MapLarge, {
341
+ props: {
342
+ index: 0,
343
+ theme: 'dark-theme' as Theme,
344
+ },
345
+ })
346
+
347
+ expect(wrapper.find('.map-large').classes()).toContain('dark-theme')
348
+ })
349
+
350
+ it('should support all theme variants', () => {
351
+ const themes: Theme[] = [
352
+ 'light-theme',
353
+ 'dark-theme',
354
+ 'light-gray-theme',
355
+ 'dark-gray-theme',
356
+ ]
357
+
358
+ themes.forEach((theme) => {
359
+ wrapper = mount(MapLarge, {
360
+ props: {
361
+ index: 0,
362
+ theme,
363
+ },
364
+ })
365
+
366
+ expect(wrapper.find('.map-large').classes()).toContain(theme)
367
+ })
368
+ })
369
+ })
370
+
371
+ describe('Events', () => {
372
+ it('should emit loaded event when warnings are provided', async () => {
373
+ wrapper = mount(MapLarge, {
374
+ props: {
375
+ index: 0,
376
+ warnings: {},
377
+ },
378
+ })
379
+
380
+ // Wait for mounted hook
381
+ await wrapper.vm.$nextTick()
382
+
383
+ expect(wrapper.emitted('loaded')).toBeTruthy()
384
+ })
385
+ })
386
+
387
+ describe('SVG structure', () => {
388
+ it('should render SVG element', () => {
389
+ wrapper = mount(MapLarge, {
390
+ props: {
391
+ index: 0,
392
+ },
393
+ })
394
+
395
+ expect(wrapper.find('svg#finland-large').exists()).toBe(true)
396
+ })
397
+
398
+ it('should have correct viewBox', () => {
399
+ wrapper = mount(MapLarge, {
400
+ props: {
401
+ index: 0,
402
+ },
403
+ })
404
+
405
+ expect(wrapper.find('svg#finland-large').attributes('viewBox')).toBe(
406
+ '0 0 440 550'
407
+ )
408
+ })
409
+ })
410
+
411
+ describe('Accessibility', () => {
412
+ it('should have zoom in button with aria-label', () => {
413
+ wrapper = mount(MapLarge, {
414
+ props: {
415
+ index: 0,
416
+ language: 'fi' as Language,
417
+ },
418
+ })
419
+
420
+ const zoomInButton = wrapper.find('#fmi-warnings-zoom-in')
421
+ expect(zoomInButton.attributes('aria-label')).toBeDefined()
422
+ })
423
+
424
+ it('should have zoom out button with aria-label', () => {
425
+ wrapper = mount(MapLarge, {
426
+ props: {
427
+ index: 0,
428
+ language: 'fi' as Language,
429
+ },
430
+ })
431
+
432
+ const zoomOutButton = wrapper.find('#fmi-warnings-zoom-out')
433
+ expect(zoomOutButton.attributes('aria-label')).toBeDefined()
434
+ })
435
+
436
+ it('should have move button with aria-label', () => {
437
+ wrapper = mount(MapLarge, {
438
+ props: {
439
+ index: 0,
440
+ language: 'fi' as Language,
441
+ },
442
+ })
443
+
444
+ const moveButton = wrapper.find('#fmi-warnings-move')
445
+ expect(moveButton.attributes('aria-label')).toBeDefined()
446
+ })
447
+ })
448
+ })