@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,381 @@
1
+ import { describe, it, expect, afterEach } from 'vitest'
2
+ import { mount, VueWrapper } from '@vue/test-utils'
3
+ import RegionWarning from '@/components/RegionWarning.vue'
4
+ import type { WarningIconInput, Language } from '@/types'
5
+
6
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
7
+ type ComponentInstance = any
8
+
9
+ const mockWarning: WarningIconInput = {
10
+ type: 'wind',
11
+ severity: 3,
12
+ direction: 270,
13
+ text: '25',
14
+ }
15
+
16
+ describe('RegionWarning.vue', () => {
17
+ let wrapper: VueWrapper | null = null
18
+
19
+ afterEach(() => {
20
+ if (wrapper) {
21
+ wrapper.unmount()
22
+ wrapper = null
23
+ }
24
+ })
25
+
26
+ describe('Component mounting', () => {
27
+ it('should mount with required props', () => {
28
+ wrapper = mount(RegionWarning, {
29
+ props: {
30
+ input: mockWarning,
31
+ language: 'fi' as Language,
32
+ },
33
+ })
34
+
35
+ expect(wrapper.exists()).toBe(true)
36
+ })
37
+ })
38
+
39
+ describe('Computed properties', () => {
40
+ it('should compute warningLevel from translations', () => {
41
+ wrapper = mount(RegionWarning, {
42
+ props: {
43
+ input: mockWarning,
44
+ language: 'fi' as Language,
45
+ },
46
+ })
47
+
48
+ expect(typeof (wrapper.vm as ComponentInstance).warningLevel).toBe(
49
+ 'string'
50
+ )
51
+ })
52
+
53
+ it('should compute warningTypeText from translations', () => {
54
+ wrapper = mount(RegionWarning, {
55
+ props: {
56
+ input: mockWarning,
57
+ language: 'fi' as Language,
58
+ },
59
+ })
60
+
61
+ expect(typeof (wrapper.vm as ComponentInstance).warningTypeText).toBe(
62
+ 'string'
63
+ )
64
+ })
65
+
66
+ it('should compute warningDetails for wind with direction', () => {
67
+ wrapper = mount(RegionWarning, {
68
+ props: {
69
+ input: mockWarning,
70
+ language: 'fi' as Language,
71
+ },
72
+ })
73
+
74
+ expect((wrapper.vm as ComponentInstance).warningDetails).toContain('m/s')
75
+ expect((wrapper.vm as ComponentInstance).warningDetails).toContain('°')
76
+ })
77
+
78
+ it('should return empty warningDetails when text is null', () => {
79
+ const warningNoText: WarningIconInput = {
80
+ type: 'rain',
81
+ severity: 2,
82
+ }
83
+
84
+ wrapper = mount(RegionWarning, {
85
+ props: {
86
+ input: warningNoText,
87
+ language: 'fi' as Language,
88
+ },
89
+ })
90
+
91
+ expect((wrapper.vm as ComponentInstance).warningDetails).toBe('')
92
+ })
93
+
94
+ it('should return empty warningDetails when direction is null', () => {
95
+ const warningNoDirection: WarningIconInput = {
96
+ type: 'wind',
97
+ severity: 3,
98
+ text: '25',
99
+ }
100
+
101
+ wrapper = mount(RegionWarning, {
102
+ props: {
103
+ input: warningNoDirection,
104
+ language: 'fi' as Language,
105
+ },
106
+ })
107
+
108
+ expect((wrapper.vm as ComponentInstance).warningDetails).toBe('')
109
+ })
110
+ })
111
+
112
+ describe('Fields mixin integration', () => {
113
+ it('should compute severity', () => {
114
+ wrapper = mount(RegionWarning, {
115
+ props: {
116
+ input: mockWarning,
117
+ language: 'fi' as Language,
118
+ },
119
+ })
120
+
121
+ expect((wrapper.vm as ComponentInstance).severity).toBe(3)
122
+ })
123
+
124
+ it('should compute typeClass', () => {
125
+ wrapper = mount(RegionWarning, {
126
+ props: {
127
+ input: mockWarning,
128
+ language: 'fi' as Language,
129
+ },
130
+ })
131
+
132
+ expect((wrapper.vm as ComponentInstance).typeClass).toBe('wind')
133
+ })
134
+
135
+ it('should compute typeClass for thunderStorm', () => {
136
+ const thunderWarning: WarningIconInput = {
137
+ type: 'thunderStorm',
138
+ severity: 4,
139
+ }
140
+
141
+ wrapper = mount(RegionWarning, {
142
+ props: {
143
+ input: thunderWarning,
144
+ language: 'fi' as Language,
145
+ },
146
+ })
147
+
148
+ expect((wrapper.vm as ComponentInstance).typeClass).toBe('thunder-storm')
149
+ })
150
+
151
+ it('should compute rotation', () => {
152
+ wrapper = mount(RegionWarning, {
153
+ props: {
154
+ input: mockWarning,
155
+ language: 'fi' as Language,
156
+ },
157
+ })
158
+
159
+ expect((wrapper.vm as ComponentInstance).rotation).toBe(270)
160
+ })
161
+
162
+ it('should compute invertedRotation', () => {
163
+ wrapper = mount(RegionWarning, {
164
+ props: {
165
+ input: mockWarning,
166
+ language: 'fi' as Language,
167
+ },
168
+ })
169
+
170
+ expect((wrapper.vm as ComponentInstance).invertedRotation).toBe(90)
171
+ })
172
+
173
+ it('should return 0 rotation when direction is not set', () => {
174
+ const warningNoDirection: WarningIconInput = {
175
+ type: 'rain',
176
+ severity: 2,
177
+ }
178
+
179
+ wrapper = mount(RegionWarning, {
180
+ props: {
181
+ input: warningNoDirection,
182
+ language: 'fi' as Language,
183
+ },
184
+ })
185
+
186
+ expect((wrapper.vm as ComponentInstance).rotation).toBe(0)
187
+ })
188
+
189
+ it('should return 0 for invalid severity', () => {
190
+ const invalidSeverities = [0, 1, 5, 6]
191
+
192
+ invalidSeverities.forEach((severity) => {
193
+ const warning: WarningIconInput = {
194
+ type: 'wind',
195
+ severity: severity as 0 | 1 | 2 | 3 | 4,
196
+ }
197
+
198
+ wrapper = mount(RegionWarning, {
199
+ props: {
200
+ input: warning,
201
+ language: 'fi' as Language,
202
+ },
203
+ })
204
+
205
+ expect((wrapper.vm as ComponentInstance).severity).toBe(0)
206
+ })
207
+ })
208
+
209
+ it('should accept valid severity levels 2-4', () => {
210
+ const validSeverities = [2, 3, 4]
211
+
212
+ validSeverities.forEach((severity) => {
213
+ const warning: WarningIconInput = {
214
+ type: 'wind',
215
+ severity: severity as 2 | 3 | 4,
216
+ }
217
+
218
+ wrapper = mount(RegionWarning, {
219
+ props: {
220
+ input: warning,
221
+ language: 'fi' as Language,
222
+ },
223
+ })
224
+
225
+ expect((wrapper.vm as ComponentInstance).severity).toBe(severity)
226
+ })
227
+ })
228
+ })
229
+
230
+ describe('Content rendering', () => {
231
+ it('should render warning text in symbol', () => {
232
+ wrapper = mount(RegionWarning, {
233
+ props: {
234
+ input: mockWarning,
235
+ language: 'fi' as Language,
236
+ },
237
+ })
238
+
239
+ expect(wrapper.find('.warning-symbol-text').text()).toBe('25')
240
+ })
241
+
242
+ it('should render empty text when not provided', () => {
243
+ const warningNoText: WarningIconInput = {
244
+ type: 'rain',
245
+ severity: 2,
246
+ }
247
+
248
+ wrapper = mount(RegionWarning, {
249
+ props: {
250
+ input: warningNoText,
251
+ language: 'fi' as Language,
252
+ },
253
+ })
254
+
255
+ expect(wrapper.find('.warning-symbol-text').text()).toBe('')
256
+ })
257
+ })
258
+
259
+ describe('CSS classes', () => {
260
+ it('should apply severity level class', () => {
261
+ wrapper = mount(RegionWarning, {
262
+ props: {
263
+ input: mockWarning,
264
+ language: 'fi' as Language,
265
+ },
266
+ })
267
+
268
+ const symbol = wrapper.find('.symbol-image')
269
+ expect(symbol.classes()).toContain('level-3')
270
+ })
271
+
272
+ it('should apply type class', () => {
273
+ wrapper = mount(RegionWarning, {
274
+ props: {
275
+ input: mockWarning,
276
+ language: 'fi' as Language,
277
+ },
278
+ })
279
+
280
+ const symbol = wrapper.find('.symbol-image')
281
+ expect(symbol.classes()).toContain('wind')
282
+ })
283
+
284
+ it('should apply rotation class', () => {
285
+ wrapper = mount(RegionWarning, {
286
+ props: {
287
+ input: mockWarning,
288
+ language: 'fi' as Language,
289
+ },
290
+ })
291
+
292
+ const symbol = wrapper.find('.symbol-image')
293
+ expect(symbol.classes()).toContain('transform-rotate-270')
294
+ })
295
+
296
+ it('should apply symbol-rotate class', () => {
297
+ wrapper = mount(RegionWarning, {
298
+ props: {
299
+ input: mockWarning,
300
+ language: 'fi' as Language,
301
+ },
302
+ })
303
+
304
+ const symbol = wrapper.find('.symbol-image')
305
+ expect(symbol.classes()).toContain('symbol-rotate-270')
306
+ })
307
+
308
+ it('should apply inverted rotation to text', () => {
309
+ wrapper = mount(RegionWarning, {
310
+ props: {
311
+ input: mockWarning,
312
+ language: 'fi' as Language,
313
+ },
314
+ })
315
+
316
+ const text = wrapper.find('.warning-symbol-text')
317
+ expect(text.classes()).toContain('transform-rotate-90')
318
+ })
319
+ })
320
+
321
+ describe('Accessibility', () => {
322
+ it('should have aria-label on main element', () => {
323
+ wrapper = mount(RegionWarning, {
324
+ props: {
325
+ input: mockWarning,
326
+ language: 'fi' as Language,
327
+ },
328
+ })
329
+
330
+ const symbol = wrapper.find('.symbol-image')
331
+ expect(symbol.attributes('aria-label')).toBeDefined()
332
+ })
333
+
334
+ it('should have aria-hidden on text span', () => {
335
+ wrapper = mount(RegionWarning, {
336
+ props: {
337
+ input: mockWarning,
338
+ language: 'fi' as Language,
339
+ },
340
+ })
341
+
342
+ const text = wrapper.find('.warning-symbol-text')
343
+ expect(text.attributes('aria-hidden')).toBe('true')
344
+ })
345
+ })
346
+
347
+ describe('Language support', () => {
348
+ it('should support Finnish language', () => {
349
+ wrapper = mount(RegionWarning, {
350
+ props: {
351
+ input: mockWarning,
352
+ language: 'fi' as Language,
353
+ },
354
+ })
355
+
356
+ expect(wrapper.exists()).toBe(true)
357
+ })
358
+
359
+ it('should support Swedish language', () => {
360
+ wrapper = mount(RegionWarning, {
361
+ props: {
362
+ input: mockWarning,
363
+ language: 'sv' as Language,
364
+ },
365
+ })
366
+
367
+ expect(wrapper.exists()).toBe(true)
368
+ })
369
+
370
+ it('should support English language', () => {
371
+ wrapper = mount(RegionWarning, {
372
+ props: {
373
+ input: mockWarning,
374
+ language: 'en' as Language,
375
+ },
376
+ })
377
+
378
+ expect(wrapper.exists()).toBe(true)
379
+ })
380
+ })
381
+ })