@fmidev/smartmet-alert-client 4.2.7 → 4.7.0-alpha.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 (127) hide show
  1. package/.eslintignore +2 -14
  2. package/.github/workflows/test.yaml +26 -0
  3. package/.nvmrc +1 -0
  4. package/dist/index.dark.html +1 -1
  5. package/dist/index.en.html +1 -1
  6. package/dist/index.fi.html +1 -1
  7. package/dist/index.html +5 -0
  8. package/dist/index.js +105 -135
  9. package/dist/index.mjs +112 -135
  10. package/dist/index.sv.html +1 -1
  11. package/dist/locale-en-DCEKDw5G.js +8 -0
  12. package/dist/locale-fi-DPiOM1rB.js +8 -0
  13. package/dist/locale-sv-B0FlbgEF.js +8 -0
  14. package/dist/vendor-Cfkkvdz7.js +21 -0
  15. package/dist/vue/index.mjs +15245 -0
  16. package/dist/vue/style.css +1 -0
  17. package/dist/xml-parser-BiNO9kc-.js +13 -0
  18. package/package.json +61 -25
  19. package/public/index.dark.html +1 -1
  20. package/public/index.en.html +1 -1
  21. package/public/index.fi.html +1 -1
  22. package/public/index.sv.html +1 -1
  23. package/src/AlertClientVue.vue +170 -0
  24. package/src/App.vue +58 -176
  25. package/src/assets/img/ui/arrow-down.svg +4 -14
  26. package/src/assets/img/ui/arrow-up.svg +4 -14
  27. package/src/assets/img/ui/clear.svg +7 -21
  28. package/src/assets/img/ui/close.svg +4 -15
  29. package/src/assets/img/ui/toggle-selected.svg +12 -0
  30. package/src/assets/img/ui/toggle-unselected.svg +12 -0
  31. package/src/assets/img/warning/cold-weather.svg +3 -6
  32. package/src/assets/img/warning/flood-level-3.svg +4 -7
  33. package/src/assets/img/warning/forest-fire-weather.svg +2 -6
  34. package/src/assets/img/warning/grass-fire-weather.svg +2 -6
  35. package/src/assets/img/warning/hot-weather.svg +3 -6
  36. package/src/assets/img/warning/pedestrian-safety.svg +3 -7
  37. package/src/assets/img/warning/rain.svg +2 -7
  38. package/src/assets/img/warning/sea-icing.svg +2 -6
  39. package/src/assets/img/warning/sea-thunder-storm.svg +2 -5
  40. package/src/assets/img/warning/sea-water-height-high-water.svg +3 -8
  41. package/src/assets/img/warning/sea-water-height-shallow-water.svg +3 -7
  42. package/src/assets/img/warning/sea-wave-height.svg +4 -7
  43. package/src/assets/img/warning/sea-wind-legend.svg +2 -5
  44. package/src/assets/img/warning/sea-wind.svg +2 -5
  45. package/src/assets/img/warning/several.svg +2 -5
  46. package/src/assets/img/warning/thunder-storm.svg +2 -5
  47. package/src/assets/img/warning/traffic-weather.svg +2 -6
  48. package/src/assets/img/warning/uv-note.svg +2 -6
  49. package/src/assets/img/warning/wind.svg +2 -5
  50. package/src/components/AlertClient.vue +42 -20
  51. package/src/components/CollapsiblePanel.vue +284 -0
  52. package/src/components/DayLarge.vue +40 -32
  53. package/src/components/DaySmall.vue +17 -7
  54. package/src/components/Days.vue +77 -52
  55. package/src/components/DescriptionWarning.vue +26 -8
  56. package/src/components/GrayScaleToggle.vue +42 -35
  57. package/src/components/Legend.vue +36 -240
  58. package/src/components/MapLarge.vue +55 -50
  59. package/src/components/MapSmall.vue +48 -29
  60. package/src/components/PopupRow.vue +6 -3
  61. package/src/components/Region.vue +162 -53
  62. package/src/components/RegionWarning.vue +33 -11
  63. package/src/components/Regions.vue +59 -29
  64. package/src/components/Warning.vue +53 -49
  65. package/src/components/Warnings.vue +54 -16
  66. package/src/locales/en.json +21 -4
  67. package/src/locales/fi.json +23 -6
  68. package/src/locales/sv.json +20 -3
  69. package/src/main.js +1 -0
  70. package/src/mixins/alertClientCore.js +210 -0
  71. package/src/mixins/config.js +263 -257
  72. package/src/mixins/utils.js +46 -15
  73. package/src/plugins/index.js +1 -1
  74. package/src/scss/_utilities.scss +193 -0
  75. package/src/scss/backgrounds.scss +2 -0
  76. package/src/scss/colors.scss +5 -3
  77. package/src/scss/constants.scss +3 -2
  78. package/src/scss/themes/dark-gray.scss +3 -3
  79. package/src/scss/themes/dark.scss +1 -1
  80. package/src/scss/themes/light-gray.scss +5 -5
  81. package/src/scss/themes/light.scss +3 -3
  82. package/src/scss/warningImages.scss +9 -5
  83. package/src/vue.js +41 -0
  84. package/svgo.config.js +45 -0
  85. package/tests/README.md +430 -0
  86. package/tests/fixtures/mockWarningData.js +135 -0
  87. package/tests/integration/warning-flow.spec.js +452 -0
  88. package/tests/setup.js +41 -0
  89. package/tests/unit/components/AlertClient.spec.js +734 -0
  90. package/tests/unit/components/DayLarge.spec.js +281 -0
  91. package/tests/unit/components/DaySmall.spec.js +278 -0
  92. package/tests/unit/components/Days.spec.js +565 -0
  93. package/tests/unit/components/DescriptionWarning.spec.js +432 -0
  94. package/tests/unit/components/GrayScaleToggle.spec.js +311 -0
  95. package/tests/unit/components/Legend.spec.js +223 -0
  96. package/tests/unit/components/MapLarge.spec.js +276 -0
  97. package/tests/unit/components/MapSmall.spec.js +226 -0
  98. package/tests/unit/components/PopupRow.spec.js +261 -0
  99. package/tests/unit/components/Region.spec.js +430 -0
  100. package/tests/unit/components/RegionWarning.snapshot.spec.js +73 -0
  101. package/tests/unit/components/RegionWarning.spec.js +408 -0
  102. package/tests/unit/components/Regions.spec.js +335 -0
  103. package/tests/unit/components/Warning.snapshot.spec.js +107 -0
  104. package/tests/unit/components/Warning.spec.js +472 -0
  105. package/tests/unit/components/Warnings.spec.js +329 -0
  106. package/tests/unit/components/__snapshots__/RegionWarning.snapshot.spec.js.snap +21 -0
  107. package/tests/unit/components/__snapshots__/Warning.snapshot.spec.js.snap +199 -0
  108. package/tests/unit/mixins/config.spec.js +269 -0
  109. package/tests/unit/mixins/i18n.spec.js +115 -0
  110. package/tests/unit/mixins/keycodes.spec.js +37 -0
  111. package/tests/unit/mixins/utils.spec.js +624 -0
  112. package/vite.config.js +96 -26
  113. package/vitest.config.js +40 -0
  114. package/dist/index.mjs.map +0 -1
  115. package/dist/index.relative.html +0 -19
  116. package/dist/index.start.html +0 -20
  117. package/playwright.config.ts +0 -18
  118. package/public/index.relative.html +0 -19
  119. package/public/index.start.html +0 -20
  120. package/src/assets/img/ui/toggle-selected-blue.svg +0 -4
  121. package/src/assets/img/ui/toggle-selected-dark.svg +0 -4
  122. package/src/assets/img/ui/toggle-selected-light.svg +0 -4
  123. package/src/assets/img/ui/toggle-unselected-dark.svg +0 -4
  124. package/src/assets/img/ui/toggle-unselected-light.svg +0 -4
  125. package/src/mixins/panzoom.js +0 -900
  126. package/test/snapshot.test.ts +0 -126
  127. package/vitest.config.ts +0 -6
@@ -0,0 +1,73 @@
1
+ import { describe, it, expect, afterEach } from 'vitest'
2
+ import { mount } from '@vue/test-utils'
3
+ import RegionWarning from '@/components/RegionWarning.vue'
4
+
5
+ const mockWarning = {
6
+ type: 'wind',
7
+ severity: 3,
8
+ text: '15',
9
+ direction: 180,
10
+ key: 'test-warning-1',
11
+ }
12
+
13
+ describe('RegionWarning.vue snapshots', () => {
14
+ let wrapper
15
+
16
+ afterEach(() => {
17
+ if (wrapper) {
18
+ wrapper.unmount()
19
+ }
20
+ })
21
+
22
+ it('should match snapshot for wind warning', () => {
23
+ wrapper = mount(RegionWarning, {
24
+ props: {
25
+ input: mockWarning,
26
+ language: 'fi',
27
+ },
28
+ })
29
+
30
+ expect(wrapper.html()).toMatchSnapshot()
31
+ })
32
+
33
+ it('should match snapshot for different warning types', () => {
34
+ const types = ['wind', 'rain', 'thunderstorm', 'forestFire']
35
+
36
+ types.forEach((type) => {
37
+ wrapper = mount(RegionWarning, {
38
+ props: {
39
+ input: { ...mockWarning, type },
40
+ language: 'fi',
41
+ },
42
+ })
43
+
44
+ expect(wrapper.html()).toMatchSnapshot(`type-${type}`)
45
+ wrapper.unmount()
46
+ })
47
+ })
48
+
49
+ it('should match snapshot without wind speed', () => {
50
+ wrapper = mount(RegionWarning, {
51
+ props: {
52
+ input: { ...mockWarning, text: null, direction: null },
53
+ language: 'fi',
54
+ },
55
+ })
56
+
57
+ expect(wrapper.html()).toMatchSnapshot()
58
+ })
59
+
60
+ it('should match snapshot for all severity levels', () => {
61
+ for (let severity = 1; severity <= 4; severity++) {
62
+ wrapper = mount(RegionWarning, {
63
+ props: {
64
+ input: { ...mockWarning, severity },
65
+ language: 'fi',
66
+ },
67
+ })
68
+
69
+ expect(wrapper.html()).toMatchSnapshot(`severity-${severity}`)
70
+ wrapper.unmount()
71
+ }
72
+ })
73
+ })
@@ -0,0 +1,408 @@
1
+ import { describe, it, expect, afterEach } from 'vitest'
2
+ import { mount } from '@vue/test-utils'
3
+ import RegionWarning from '@/components/RegionWarning.vue'
4
+
5
+ const mockWarning = {
6
+ type: 'wind',
7
+ severity: 3,
8
+ direction: 270,
9
+ text: '15',
10
+ }
11
+
12
+ describe('RegionWarning.vue', () => {
13
+ let wrapper
14
+
15
+ afterEach(() => {
16
+ if (wrapper) {
17
+ wrapper.unmount()
18
+ }
19
+ })
20
+
21
+ describe('Component mounting', () => {
22
+ it('should mount with required props', () => {
23
+ wrapper = mount(RegionWarning, {
24
+ props: {
25
+ input: mockWarning,
26
+ language: 'fi',
27
+ },
28
+ })
29
+
30
+ expect(wrapper.exists()).toBe(true)
31
+ })
32
+
33
+ it('should handle minimal input data', () => {
34
+ wrapper = mount(RegionWarning, {
35
+ props: {
36
+ input: {
37
+ type: 'rain',
38
+ severity: 2,
39
+ },
40
+ language: 'fi',
41
+ },
42
+ })
43
+
44
+ expect(wrapper.exists()).toBe(true)
45
+ })
46
+ })
47
+
48
+ describe('Computed properties', () => {
49
+ it('should compute warningLevel from severity', () => {
50
+ wrapper = mount(RegionWarning, {
51
+ props: {
52
+ input: mockWarning,
53
+ language: 'fi',
54
+ },
55
+ })
56
+
57
+ expect(typeof wrapper.vm.warningLevel).toBe('string')
58
+ })
59
+
60
+ it('should compute warningTypeText in lowercase', () => {
61
+ wrapper = mount(RegionWarning, {
62
+ props: {
63
+ input: mockWarning,
64
+ language: 'fi',
65
+ },
66
+ })
67
+
68
+ const typeText = wrapper.vm.warningTypeText
69
+ expect(typeof typeText).toBe('string')
70
+ expect(typeText).toBe(typeText.toLowerCase())
71
+ })
72
+
73
+ it('should compute warningDetails with text and direction', () => {
74
+ wrapper = mount(RegionWarning, {
75
+ props: {
76
+ input: mockWarning,
77
+ language: 'fi',
78
+ },
79
+ })
80
+
81
+ const details = wrapper.vm.warningDetails
82
+ expect(details).toContain('15')
83
+ expect(details).toContain('m/s')
84
+ expect(details).toContain('450') // 270 + 180
85
+ })
86
+
87
+ it('should return empty warningDetails when text is null', () => {
88
+ const warningNoText = {
89
+ ...mockWarning,
90
+ text: null,
91
+ }
92
+
93
+ wrapper = mount(RegionWarning, {
94
+ props: {
95
+ input: warningNoText,
96
+ language: 'fi',
97
+ },
98
+ })
99
+
100
+ expect(wrapper.vm.warningDetails).toBe('')
101
+ })
102
+
103
+ it('should return empty warningDetails when direction is null', () => {
104
+ const warningNoDirection = {
105
+ ...mockWarning,
106
+ direction: null,
107
+ }
108
+
109
+ wrapper = mount(RegionWarning, {
110
+ props: {
111
+ input: warningNoDirection,
112
+ language: 'fi',
113
+ },
114
+ })
115
+
116
+ expect(wrapper.vm.warningDetails).toBe('')
117
+ })
118
+
119
+ it('should return empty warningDetails when both are null', () => {
120
+ const warningNoData = {
121
+ type: 'rain',
122
+ severity: 2,
123
+ }
124
+
125
+ wrapper = mount(RegionWarning, {
126
+ props: {
127
+ input: warningNoData,
128
+ language: 'fi',
129
+ },
130
+ })
131
+
132
+ expect(wrapper.vm.warningDetails).toBe('')
133
+ })
134
+ })
135
+
136
+ describe('Fields mixin integration', () => {
137
+ it('should compute typeClass correctly', () => {
138
+ wrapper = mount(RegionWarning, {
139
+ props: {
140
+ input: mockWarning,
141
+ language: 'fi',
142
+ },
143
+ })
144
+
145
+ expect(wrapper.vm.typeClass).toBe('wind')
146
+ })
147
+
148
+ it('should compute typeClass for sea wind', () => {
149
+ wrapper = mount(RegionWarning, {
150
+ props: {
151
+ input: {
152
+ type: 'seaWind',
153
+ severity: 3,
154
+ },
155
+ language: 'fi',
156
+ },
157
+ })
158
+
159
+ expect(wrapper.vm.typeClass).toBe('sea-wind')
160
+ })
161
+
162
+ it('should compute rotation from direction', () => {
163
+ wrapper = mount(RegionWarning, {
164
+ props: {
165
+ input: mockWarning,
166
+ language: 'fi',
167
+ },
168
+ })
169
+
170
+ expect(wrapper.vm.rotation).toBe(270)
171
+ })
172
+
173
+ it('should round rotation to nearest 5 degrees', () => {
174
+ wrapper = mount(RegionWarning, {
175
+ props: {
176
+ input: {
177
+ ...mockWarning,
178
+ direction: 273,
179
+ },
180
+ language: 'fi',
181
+ },
182
+ })
183
+
184
+ expect(wrapper.vm.rotation).toBe(275)
185
+ })
186
+
187
+ it('should compute invertedRotation', () => {
188
+ wrapper = mount(RegionWarning, {
189
+ props: {
190
+ input: mockWarning,
191
+ language: 'fi',
192
+ },
193
+ })
194
+
195
+ expect(wrapper.vm.invertedRotation).toBe(90)
196
+ })
197
+
198
+ it('should compute severity', () => {
199
+ wrapper = mount(RegionWarning, {
200
+ props: {
201
+ input: mockWarning,
202
+ language: 'fi',
203
+ },
204
+ })
205
+
206
+ expect(wrapper.vm.severity).toBe(3)
207
+ })
208
+
209
+ it('should return 0 for invalid severities', () => {
210
+ const severities = [0, 1, 5, 6]
211
+
212
+ severities.forEach((severity) => {
213
+ wrapper = mount(RegionWarning, {
214
+ props: {
215
+ input: {
216
+ type: 'wind',
217
+ severity,
218
+ },
219
+ language: 'fi',
220
+ },
221
+ })
222
+
223
+ expect(wrapper.vm.severity).toBe(0)
224
+ })
225
+ })
226
+ })
227
+
228
+ describe('CSS classes', () => {
229
+ it('should apply all required classes to warning image', () => {
230
+ wrapper = mount(RegionWarning, {
231
+ props: {
232
+ input: mockWarning,
233
+ language: 'fi',
234
+ },
235
+ })
236
+
237
+ const image = wrapper.find('.warning-image')
238
+ expect(image.classes()).toContain('symbol-image')
239
+ expect(image.classes()).toContain('current-warning-image')
240
+ expect(image.classes()).toContain('level-3')
241
+ expect(image.classes()).toContain('wind')
242
+ })
243
+
244
+ it('should apply rotation classes', () => {
245
+ wrapper = mount(RegionWarning, {
246
+ props: {
247
+ input: mockWarning,
248
+ language: 'fi',
249
+ },
250
+ })
251
+
252
+ const image = wrapper.find('.warning-image')
253
+ expect(image.classes()).toContain('transform-rotate-270')
254
+ expect(image.classes()).toContain('symbol-rotate-270')
255
+ })
256
+
257
+ it('should apply inverted rotation to text span', () => {
258
+ wrapper = mount(RegionWarning, {
259
+ props: {
260
+ input: mockWarning,
261
+ language: 'fi',
262
+ },
263
+ })
264
+
265
+ const text = wrapper.find('.symbol-text')
266
+ expect(text.classes()).toContain('transform-rotate-90')
267
+ })
268
+ })
269
+
270
+ describe('Content rendering', () => {
271
+ it('should render text in symbol', () => {
272
+ wrapper = mount(RegionWarning, {
273
+ props: {
274
+ input: mockWarning,
275
+ language: 'fi',
276
+ },
277
+ })
278
+
279
+ expect(wrapper.find('.symbol-text').text()).toBe('15')
280
+ })
281
+
282
+ it('should render empty text when not provided', () => {
283
+ const warningNoText = {
284
+ type: 'rain',
285
+ severity: 2,
286
+ }
287
+
288
+ wrapper = mount(RegionWarning, {
289
+ props: {
290
+ input: warningNoText,
291
+ language: 'fi',
292
+ },
293
+ })
294
+
295
+ expect(wrapper.find('.symbol-text').text()).toBe('')
296
+ })
297
+
298
+ it('should handle undefined text gracefully', () => {
299
+ const warningUndefinedText = {
300
+ type: 'rain',
301
+ severity: 2,
302
+ text: undefined,
303
+ }
304
+
305
+ wrapper = mount(RegionWarning, {
306
+ props: {
307
+ input: warningUndefinedText,
308
+ language: 'fi',
309
+ },
310
+ })
311
+
312
+ expect(wrapper.find('.symbol-text').text()).toBe('')
313
+ })
314
+ })
315
+
316
+ describe('Accessibility', () => {
317
+ it('should have aria-label on warning image', () => {
318
+ wrapper = mount(RegionWarning, {
319
+ props: {
320
+ input: mockWarning,
321
+ language: 'fi',
322
+ },
323
+ })
324
+
325
+ const image = wrapper.find('.warning-image')
326
+ const ariaLabel = image.attributes('aria-label')
327
+
328
+ expect(ariaLabel).toBeDefined()
329
+ expect(ariaLabel.length).toBeGreaterThan(0)
330
+ })
331
+
332
+ it('should include severity, type and details in aria-label', () => {
333
+ wrapper = mount(RegionWarning, {
334
+ props: {
335
+ input: mockWarning,
336
+ language: 'fi',
337
+ },
338
+ })
339
+
340
+ const image = wrapper.find('.warning-image')
341
+ const ariaLabel = image.attributes('aria-label')
342
+
343
+ // Should contain computed values from warningLevel, warningTypeText, warningDetails
344
+ expect(typeof ariaLabel).toBe('string')
345
+ })
346
+
347
+ it('should mark symbol text as aria-hidden', () => {
348
+ wrapper = mount(RegionWarning, {
349
+ props: {
350
+ input: mockWarning,
351
+ language: 'fi',
352
+ },
353
+ })
354
+
355
+ const text = wrapper.find('.symbol-text')
356
+ expect(text.attributes('aria-hidden')).toBe('true')
357
+ })
358
+ })
359
+
360
+ describe('Different warning types', () => {
361
+ it('should render thunderStorm warning', () => {
362
+ wrapper = mount(RegionWarning, {
363
+ props: {
364
+ input: {
365
+ type: 'thunderStorm',
366
+ severity: 4,
367
+ },
368
+ language: 'fi',
369
+ },
370
+ })
371
+
372
+ expect(wrapper.vm.typeClass).toBe('thunder-storm')
373
+ expect(wrapper.find('.warning-image').classes()).toContain('level-4')
374
+ })
375
+
376
+ it('should render seaWind warning', () => {
377
+ wrapper = mount(RegionWarning, {
378
+ props: {
379
+ input: {
380
+ type: 'seaWind',
381
+ severity: 3,
382
+ direction: 180,
383
+ text: '20',
384
+ },
385
+ language: 'fi',
386
+ },
387
+ })
388
+
389
+ expect(wrapper.vm.typeClass).toBe('sea-wind')
390
+ expect(wrapper.find('.symbol-text').text()).toBe('20')
391
+ })
392
+
393
+ it('should render rain warning without direction', () => {
394
+ wrapper = mount(RegionWarning, {
395
+ props: {
396
+ input: {
397
+ type: 'rain',
398
+ severity: 2,
399
+ },
400
+ language: 'fi',
401
+ },
402
+ })
403
+
404
+ expect(wrapper.vm.typeClass).toBe('rain')
405
+ expect(wrapper.vm.rotation).toBe(0)
406
+ })
407
+ })
408
+ })