@fmidev/smartmet-alert-client 4.4.19 → 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.
- package/.eslintignore +2 -14
- package/.github/workflows/test.yaml +26 -0
- package/.nvmrc +1 -0
- package/dist/index.html +5 -0
- package/dist/index.js +105 -135
- package/dist/index.mjs +112 -135
- package/dist/locale-en-DCEKDw5G.js +8 -0
- package/dist/locale-fi-DPiOM1rB.js +8 -0
- package/dist/locale-sv-B0FlbgEF.js +8 -0
- package/dist/vendor-Cfkkvdz7.js +21 -0
- package/dist/vue/index.mjs +15245 -0
- package/dist/vue/style.css +1 -0
- package/dist/xml-parser-BiNO9kc-.js +13 -0
- package/package.json +60 -24
- package/src/AlertClientVue.vue +170 -0
- package/src/App.vue +55 -205
- package/src/assets/img/ui/arrow-down.svg +4 -11
- package/src/assets/img/ui/arrow-up.svg +4 -11
- package/src/assets/img/ui/clear.svg +7 -21
- package/src/assets/img/ui/close.svg +4 -15
- package/src/assets/img/ui/toggle-selected.svg +5 -6
- package/src/assets/img/ui/toggle-unselected.svg +5 -6
- package/src/assets/img/warning/cold-weather.svg +3 -6
- package/src/assets/img/warning/flood-level-3.svg +4 -7
- package/src/assets/img/warning/forest-fire-weather.svg +2 -6
- package/src/assets/img/warning/grass-fire-weather.svg +2 -6
- package/src/assets/img/warning/hot-weather.svg +3 -6
- package/src/assets/img/warning/pedestrian-safety.svg +3 -7
- package/src/assets/img/warning/rain.svg +2 -7
- package/src/assets/img/warning/sea-icing.svg +2 -6
- package/src/assets/img/warning/sea-thunder-storm.svg +2 -5
- package/src/assets/img/warning/sea-water-height-high-water.svg +3 -8
- package/src/assets/img/warning/sea-water-height-shallow-water.svg +3 -7
- package/src/assets/img/warning/sea-wave-height.svg +4 -7
- package/src/assets/img/warning/sea-wind-legend.svg +2 -5
- package/src/assets/img/warning/sea-wind.svg +2 -5
- package/src/assets/img/warning/several.svg +2 -5
- package/src/assets/img/warning/thunder-storm.svg +2 -5
- package/src/assets/img/warning/traffic-weather.svg +2 -6
- package/src/assets/img/warning/uv-note.svg +2 -6
- package/src/assets/img/warning/wind.svg +2 -5
- package/src/components/AlertClient.vue +41 -19
- package/src/components/CollapsiblePanel.vue +284 -0
- package/src/components/DayLarge.vue +12 -7
- package/src/components/DaySmall.vue +16 -6
- package/src/components/Days.vue +76 -51
- package/src/components/DescriptionWarning.vue +15 -8
- package/src/components/GrayScaleToggle.vue +11 -6
- package/src/components/Legend.vue +36 -248
- package/src/components/MapLarge.vue +41 -42
- package/src/components/MapSmall.vue +44 -28
- package/src/components/PopupRow.vue +6 -3
- package/src/components/Region.vue +30 -15
- package/src/components/RegionWarning.vue +6 -5
- package/src/components/Regions.vue +50 -19
- package/src/components/Warning.vue +18 -10
- package/src/components/Warnings.vue +36 -21
- package/src/main.js +1 -0
- package/src/mixins/alertClientCore.js +210 -0
- package/src/mixins/config.js +262 -256
- package/src/mixins/utils.js +40 -26
- package/src/plugins/index.js +1 -1
- package/src/scss/_utilities.scss +193 -0
- package/src/scss/constants.scss +2 -1
- package/src/scss/warningImages.scss +8 -3
- package/src/vue.js +41 -0
- package/svgo.config.js +45 -0
- package/tests/README.md +430 -0
- package/tests/fixtures/mockWarningData.js +135 -0
- package/tests/integration/warning-flow.spec.js +452 -0
- package/tests/setup.js +41 -0
- package/tests/unit/components/AlertClient.spec.js +734 -0
- package/tests/unit/components/DayLarge.spec.js +281 -0
- package/tests/unit/components/DaySmall.spec.js +278 -0
- package/tests/unit/components/Days.spec.js +565 -0
- package/tests/unit/components/DescriptionWarning.spec.js +432 -0
- package/tests/unit/components/GrayScaleToggle.spec.js +311 -0
- package/tests/unit/components/Legend.spec.js +223 -0
- package/tests/unit/components/MapLarge.spec.js +276 -0
- package/tests/unit/components/MapSmall.spec.js +226 -0
- package/tests/unit/components/PopupRow.spec.js +261 -0
- package/tests/unit/components/Region.spec.js +430 -0
- package/tests/unit/components/RegionWarning.snapshot.spec.js +73 -0
- package/tests/unit/components/RegionWarning.spec.js +408 -0
- package/tests/unit/components/Regions.spec.js +335 -0
- package/tests/unit/components/Warning.snapshot.spec.js +107 -0
- package/tests/unit/components/Warning.spec.js +472 -0
- package/tests/unit/components/Warnings.spec.js +329 -0
- package/tests/unit/components/__snapshots__/RegionWarning.snapshot.spec.js.snap +21 -0
- package/tests/unit/components/__snapshots__/Warning.snapshot.spec.js.snap +199 -0
- package/tests/unit/mixins/config.spec.js +269 -0
- package/tests/unit/mixins/i18n.spec.js +115 -0
- package/tests/unit/mixins/keycodes.spec.js +37 -0
- package/tests/unit/mixins/utils.spec.js +624 -0
- package/vite.config.js +96 -26
- package/vitest.config.js +40 -0
- package/dist/index.mjs.map +0 -1
- package/dist/index.relative.html +0 -19
- package/dist/index.start.html +0 -20
- package/playwright.config.ts +0 -18
- package/public/index.relative.html +0 -19
- package/public/index.start.html +0 -20
- package/src/mixins/panzoom.js +0 -900
- package/test/snapshot.test.ts +0 -126
- package/vitest.config.ts +0 -6
|
@@ -0,0 +1,734 @@
|
|
|
1
|
+
import { describe, it, expect, afterEach, beforeEach, vi } from 'vitest'
|
|
2
|
+
import { mount, flushPromises } from '@vue/test-utils'
|
|
3
|
+
import AlertClient from '@/components/AlertClient.vue'
|
|
4
|
+
import Days from '@/components/Days.vue'
|
|
5
|
+
import Regions from '@/components/Regions.vue'
|
|
6
|
+
import Legend from '@/components/Legend.vue'
|
|
7
|
+
import { mockWarningsData } from '../../fixtures/mockWarningData'
|
|
8
|
+
|
|
9
|
+
describe('AlertClient.vue', () => {
|
|
10
|
+
let wrapper
|
|
11
|
+
|
|
12
|
+
afterEach(() => {
|
|
13
|
+
if (wrapper) {
|
|
14
|
+
wrapper.unmount()
|
|
15
|
+
}
|
|
16
|
+
vi.restoreAllMocks()
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
describe('Component mounting', () => {
|
|
20
|
+
it('should mount with default props and render child components', () => {
|
|
21
|
+
wrapper = mount(AlertClient, {
|
|
22
|
+
props: {
|
|
23
|
+
language: 'fi',
|
|
24
|
+
},
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
// Verify component exists
|
|
28
|
+
expect(wrapper.exists()).toBe(true)
|
|
29
|
+
|
|
30
|
+
// Verify child components are rendered
|
|
31
|
+
expect(wrapper.findComponent(Days).exists()).toBe(true)
|
|
32
|
+
expect(wrapper.findComponent(Regions).exists()).toBe(true)
|
|
33
|
+
expect(wrapper.findComponent(Legend).exists()).toBe(true)
|
|
34
|
+
|
|
35
|
+
// Verify default state
|
|
36
|
+
expect(wrapper.vm.selectedDay).toBe(0)
|
|
37
|
+
expect(wrapper.vm.theme).toBe('light-theme')
|
|
38
|
+
expect(wrapper.vm.geometryId).toBe(2021)
|
|
39
|
+
expect(wrapper.vm.visibleWarnings).toEqual([])
|
|
40
|
+
expect(wrapper.vm.warnings).toBeNull()
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('should use provided default day prop', () => {
|
|
44
|
+
wrapper = mount(AlertClient, {
|
|
45
|
+
props: {
|
|
46
|
+
defaultDay: 2,
|
|
47
|
+
language: 'fi',
|
|
48
|
+
},
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
expect(wrapper.vm.selectedDay).toBe(2)
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it('should initialize with correct data', () => {
|
|
55
|
+
wrapper = mount(AlertClient, {
|
|
56
|
+
props: {
|
|
57
|
+
language: 'fi',
|
|
58
|
+
},
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
expect(wrapper.vm.selectedDay).toBeDefined()
|
|
62
|
+
expect(wrapper.vm.visibleWarnings).toEqual([])
|
|
63
|
+
expect(wrapper.vm.warnings).toBeNull()
|
|
64
|
+
expect(wrapper.vm.days).toEqual([])
|
|
65
|
+
})
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
describe('Props validation', () => {
|
|
69
|
+
it('should accept refresh interval prop', () => {
|
|
70
|
+
wrapper = mount(AlertClient, {
|
|
71
|
+
props: {
|
|
72
|
+
refreshInterval: 60000,
|
|
73
|
+
language: 'fi',
|
|
74
|
+
},
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
expect(wrapper.vm.refreshInterval).toBe(60000)
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('should use default refresh interval', () => {
|
|
81
|
+
wrapper = mount(AlertClient, {
|
|
82
|
+
props: {
|
|
83
|
+
language: 'fi',
|
|
84
|
+
},
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
expect(wrapper.vm.refreshInterval).toBe(900000) // 15 minutes
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
it('should accept warnings data prop', () => {
|
|
91
|
+
wrapper = mount(AlertClient, {
|
|
92
|
+
props: {
|
|
93
|
+
warningsData: mockWarningsData,
|
|
94
|
+
language: 'fi',
|
|
95
|
+
},
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
expect(wrapper.vm.warningsData).toBeDefined()
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
it('should accept theme prop', () => {
|
|
102
|
+
wrapper = mount(AlertClient, {
|
|
103
|
+
props: {
|
|
104
|
+
theme: 'dark-theme',
|
|
105
|
+
language: 'fi',
|
|
106
|
+
},
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
expect(wrapper.vm.theme).toBe('dark-theme')
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
it('should accept geometry ID prop', () => {
|
|
113
|
+
wrapper = mount(AlertClient, {
|
|
114
|
+
props: {
|
|
115
|
+
geometryId: 2021,
|
|
116
|
+
language: 'fi',
|
|
117
|
+
},
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
expect(wrapper.vm.geometryId).toBe(2021)
|
|
121
|
+
})
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
describe('Timer functionality', () => {
|
|
125
|
+
it('should initialize timer with correct interval', () => {
|
|
126
|
+
const setIntervalSpy = vi.spyOn(global, 'setInterval')
|
|
127
|
+
|
|
128
|
+
wrapper = mount(AlertClient, {
|
|
129
|
+
props: {
|
|
130
|
+
refreshInterval: 60000,
|
|
131
|
+
language: 'fi',
|
|
132
|
+
},
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
expect(wrapper.vm.timer).toBeDefined()
|
|
136
|
+
expect(setIntervalSpy).toHaveBeenCalledWith(expect.any(Function), 60000)
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
it('should not initialize timer when refresh interval is 0', () => {
|
|
140
|
+
wrapper = mount(AlertClient, {
|
|
141
|
+
props: {
|
|
142
|
+
refreshInterval: 0,
|
|
143
|
+
language: 'fi',
|
|
144
|
+
},
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
// Timer should not be initialized when interval is 0
|
|
148
|
+
expect(wrapper.vm.timer).toBeNull()
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
it('should have cancelTimer method', () => {
|
|
152
|
+
wrapper = mount(AlertClient, {
|
|
153
|
+
props: {
|
|
154
|
+
refreshInterval: 60000,
|
|
155
|
+
language: 'fi',
|
|
156
|
+
},
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
expect(wrapper.vm.timer).toBeDefined()
|
|
160
|
+
expect(typeof wrapper.vm.cancelTimer).toBe('function')
|
|
161
|
+
|
|
162
|
+
// cancelTimer should be callable without errors
|
|
163
|
+
expect(() => wrapper.vm.cancelTimer()).not.toThrow()
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
it('should have initTimer method', () => {
|
|
167
|
+
wrapper = mount(AlertClient, {
|
|
168
|
+
props: {
|
|
169
|
+
refreshInterval: 60000,
|
|
170
|
+
language: 'fi',
|
|
171
|
+
},
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
expect(typeof wrapper.vm.initTimer).toBe('function')
|
|
175
|
+
expect(wrapper.vm.timer).toBeDefined()
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
it('should handle multiple timer cancellations safely', () => {
|
|
179
|
+
wrapper = mount(AlertClient, {
|
|
180
|
+
props: {
|
|
181
|
+
refreshInterval: 60000,
|
|
182
|
+
language: 'fi',
|
|
183
|
+
},
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
// Multiple cancellations should not throw errors
|
|
187
|
+
expect(() => {
|
|
188
|
+
wrapper.vm.cancelTimer()
|
|
189
|
+
wrapper.vm.cancelTimer()
|
|
190
|
+
wrapper.vm.cancelTimer()
|
|
191
|
+
}).not.toThrow()
|
|
192
|
+
})
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
describe('Event emissions', () => {
|
|
196
|
+
it('should emit loaded event on data loaded', async () => {
|
|
197
|
+
wrapper = mount(AlertClient, {
|
|
198
|
+
props: {
|
|
199
|
+
warningsData: mockWarningsData,
|
|
200
|
+
language: 'fi',
|
|
201
|
+
},
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
await flushPromises()
|
|
205
|
+
|
|
206
|
+
wrapper.vm.onLoaded(true)
|
|
207
|
+
|
|
208
|
+
expect(wrapper.emitted('loaded')).toBeTruthy()
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
it('should emit themeChanged event', () => {
|
|
212
|
+
wrapper = mount(AlertClient, {
|
|
213
|
+
props: {
|
|
214
|
+
language: 'fi',
|
|
215
|
+
},
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
wrapper.vm.onThemeChanged('dark-theme')
|
|
219
|
+
|
|
220
|
+
expect(wrapper.emitted('themeChanged')).toBeTruthy()
|
|
221
|
+
expect(wrapper.emitted('themeChanged')[0]).toEqual(['dark-theme'])
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
it('should emit update-warnings event on update', () => {
|
|
225
|
+
wrapper = mount(AlertClient, {
|
|
226
|
+
props: {
|
|
227
|
+
refreshInterval: 60000,
|
|
228
|
+
language: 'fi',
|
|
229
|
+
},
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
wrapper.vm.update()
|
|
233
|
+
|
|
234
|
+
expect(wrapper.emitted('update-warnings')).toBeTruthy()
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
it('should not emit themeChanged if theme is same', () => {
|
|
238
|
+
wrapper = mount(AlertClient, {
|
|
239
|
+
props: {
|
|
240
|
+
theme: 'light-theme',
|
|
241
|
+
language: 'fi',
|
|
242
|
+
},
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
wrapper.vm.onThemeChanged('light-theme')
|
|
246
|
+
|
|
247
|
+
expect(wrapper.emitted('themeChanged')).toBeFalsy()
|
|
248
|
+
})
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
describe('Data processing', () => {
|
|
252
|
+
it('should process warnings data when provided', async () => {
|
|
253
|
+
wrapper = mount(AlertClient, {
|
|
254
|
+
props: {
|
|
255
|
+
warningsData: mockWarningsData,
|
|
256
|
+
language: 'fi',
|
|
257
|
+
},
|
|
258
|
+
})
|
|
259
|
+
|
|
260
|
+
await flushPromises()
|
|
261
|
+
|
|
262
|
+
expect(wrapper.vm.warnings).toBeDefined()
|
|
263
|
+
expect(wrapper.vm.days).toBeDefined()
|
|
264
|
+
expect(wrapper.vm.days.length).toBe(5)
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
it('should create visible warnings from legend', async () => {
|
|
268
|
+
wrapper = mount(AlertClient, {
|
|
269
|
+
props: {
|
|
270
|
+
warningsData: mockWarningsData,
|
|
271
|
+
language: 'fi',
|
|
272
|
+
},
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
await flushPromises()
|
|
276
|
+
|
|
277
|
+
expect(Array.isArray(wrapper.vm.visibleWarnings)).toBe(true)
|
|
278
|
+
})
|
|
279
|
+
|
|
280
|
+
it('should update regions data', async () => {
|
|
281
|
+
wrapper = mount(AlertClient, {
|
|
282
|
+
props: {
|
|
283
|
+
warningsData: mockWarningsData,
|
|
284
|
+
language: 'fi',
|
|
285
|
+
},
|
|
286
|
+
})
|
|
287
|
+
|
|
288
|
+
await flushPromises()
|
|
289
|
+
|
|
290
|
+
expect(wrapper.vm.regions).toBeDefined()
|
|
291
|
+
expect(Array.isArray(wrapper.vm.regions)).toBe(true)
|
|
292
|
+
})
|
|
293
|
+
})
|
|
294
|
+
|
|
295
|
+
describe('Day selection', () => {
|
|
296
|
+
it('should update selected day on daySelected event', () => {
|
|
297
|
+
wrapper = mount(AlertClient, {
|
|
298
|
+
props: {
|
|
299
|
+
language: 'fi',
|
|
300
|
+
},
|
|
301
|
+
})
|
|
302
|
+
|
|
303
|
+
wrapper.vm.onDaySelected(2)
|
|
304
|
+
|
|
305
|
+
expect(wrapper.vm.selectedDay).toBe(2)
|
|
306
|
+
})
|
|
307
|
+
|
|
308
|
+
it('should start with default day', () => {
|
|
309
|
+
wrapper = mount(AlertClient, {
|
|
310
|
+
props: {
|
|
311
|
+
defaultDay: 3,
|
|
312
|
+
language: 'fi',
|
|
313
|
+
},
|
|
314
|
+
})
|
|
315
|
+
|
|
316
|
+
expect(wrapper.vm.selectedDay).toBe(3)
|
|
317
|
+
})
|
|
318
|
+
})
|
|
319
|
+
|
|
320
|
+
describe('Warnings toggle', () => {
|
|
321
|
+
it('should update visible warnings on toggle', async () => {
|
|
322
|
+
wrapper = mount(AlertClient, {
|
|
323
|
+
props: {
|
|
324
|
+
warningsData: mockWarningsData,
|
|
325
|
+
language: 'fi',
|
|
326
|
+
},
|
|
327
|
+
})
|
|
328
|
+
|
|
329
|
+
await flushPromises()
|
|
330
|
+
|
|
331
|
+
const newVisibleWarnings = ['wind', 'rain']
|
|
332
|
+
wrapper.vm.onWarningsToggled(newVisibleWarnings)
|
|
333
|
+
|
|
334
|
+
expect(wrapper.vm.visibleWarnings).toEqual(newVisibleWarnings)
|
|
335
|
+
})
|
|
336
|
+
|
|
337
|
+
it('should update legend visibility', async () => {
|
|
338
|
+
wrapper = mount(AlertClient, {
|
|
339
|
+
props: {
|
|
340
|
+
warningsData: mockWarningsData,
|
|
341
|
+
language: 'fi',
|
|
342
|
+
},
|
|
343
|
+
})
|
|
344
|
+
|
|
345
|
+
await flushPromises()
|
|
346
|
+
|
|
347
|
+
if (wrapper.vm.legend.length > 0) {
|
|
348
|
+
const firstWarningType = wrapper.vm.legend[0].type
|
|
349
|
+
wrapper.vm.onWarningsToggled([firstWarningType])
|
|
350
|
+
|
|
351
|
+
const legendItem = wrapper.vm.legend.find(
|
|
352
|
+
(item) => item.type === firstWarningType
|
|
353
|
+
)
|
|
354
|
+
expect(legendItem.visible).toBe(true)
|
|
355
|
+
}
|
|
356
|
+
})
|
|
357
|
+
})
|
|
358
|
+
|
|
359
|
+
describe('Error handling', () => {
|
|
360
|
+
it('should handle errors in error array', () => {
|
|
361
|
+
wrapper = mount(AlertClient, {
|
|
362
|
+
props: {
|
|
363
|
+
language: 'fi',
|
|
364
|
+
},
|
|
365
|
+
})
|
|
366
|
+
|
|
367
|
+
const testError = 'test_error'
|
|
368
|
+
wrapper.vm.handleError(testError)
|
|
369
|
+
|
|
370
|
+
expect(wrapper.vm.errors).toContain(testError)
|
|
371
|
+
})
|
|
372
|
+
|
|
373
|
+
it('should not duplicate errors', () => {
|
|
374
|
+
wrapper = mount(AlertClient, {
|
|
375
|
+
props: {
|
|
376
|
+
language: 'fi',
|
|
377
|
+
},
|
|
378
|
+
})
|
|
379
|
+
|
|
380
|
+
const testError = 'test_error'
|
|
381
|
+
wrapper.vm.handleError(testError)
|
|
382
|
+
wrapper.vm.handleError(testError)
|
|
383
|
+
|
|
384
|
+
expect(wrapper.vm.errors.filter((e) => e === testError).length).toBe(1)
|
|
385
|
+
})
|
|
386
|
+
|
|
387
|
+
it('should handle null warnings data gracefully', async () => {
|
|
388
|
+
wrapper = mount(AlertClient, {
|
|
389
|
+
props: {
|
|
390
|
+
warningsData: null,
|
|
391
|
+
language: 'fi',
|
|
392
|
+
},
|
|
393
|
+
})
|
|
394
|
+
|
|
395
|
+
await flushPromises()
|
|
396
|
+
|
|
397
|
+
expect(wrapper.vm.warnings).toBeNull()
|
|
398
|
+
expect(wrapper.vm.days).toEqual([])
|
|
399
|
+
expect(wrapper.exists()).toBe(true)
|
|
400
|
+
})
|
|
401
|
+
|
|
402
|
+
it('should handle undefined warnings data gracefully', async () => {
|
|
403
|
+
wrapper = mount(AlertClient, {
|
|
404
|
+
props: {
|
|
405
|
+
warningsData: undefined,
|
|
406
|
+
language: 'fi',
|
|
407
|
+
},
|
|
408
|
+
})
|
|
409
|
+
|
|
410
|
+
await flushPromises()
|
|
411
|
+
|
|
412
|
+
expect(wrapper.vm.warnings).toBeNull()
|
|
413
|
+
expect(wrapper.exists()).toBe(true)
|
|
414
|
+
})
|
|
415
|
+
|
|
416
|
+
it('should handle malformed warnings data', async () => {
|
|
417
|
+
const malformedData = {
|
|
418
|
+
invalid: 'data',
|
|
419
|
+
structure: 'wrong',
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
wrapper = mount(AlertClient, {
|
|
423
|
+
props: {
|
|
424
|
+
warningsData: malformedData,
|
|
425
|
+
language: 'fi',
|
|
426
|
+
},
|
|
427
|
+
})
|
|
428
|
+
|
|
429
|
+
await flushPromises()
|
|
430
|
+
|
|
431
|
+
// Component should still exist and not crash
|
|
432
|
+
expect(wrapper.exists()).toBe(true)
|
|
433
|
+
})
|
|
434
|
+
|
|
435
|
+
it('should handle empty warnings data', async () => {
|
|
436
|
+
const emptyData = {
|
|
437
|
+
weather_update_time: '2025-10-31T12:00:00Z',
|
|
438
|
+
flood_update_time: '2025-10-31T12:00:00Z',
|
|
439
|
+
weather_finland_active_all: { features: [] },
|
|
440
|
+
flood_finland_active_all: { features: [] },
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
wrapper = mount(AlertClient, {
|
|
444
|
+
props: {
|
|
445
|
+
warningsData: emptyData,
|
|
446
|
+
language: 'fi',
|
|
447
|
+
},
|
|
448
|
+
})
|
|
449
|
+
|
|
450
|
+
await flushPromises()
|
|
451
|
+
|
|
452
|
+
expect(wrapper.vm.warnings).toBeDefined()
|
|
453
|
+
expect(wrapper.vm.days).toHaveLength(5)
|
|
454
|
+
expect(wrapper.exists()).toBe(true)
|
|
455
|
+
})
|
|
456
|
+
})
|
|
457
|
+
|
|
458
|
+
describe('Computed properties', () => {
|
|
459
|
+
it('should compute toContentText correctly', async () => {
|
|
460
|
+
wrapper = mount(AlertClient, {
|
|
461
|
+
props: {
|
|
462
|
+
warningsData: mockWarningsData,
|
|
463
|
+
language: 'fi',
|
|
464
|
+
regionListEnabled: true,
|
|
465
|
+
},
|
|
466
|
+
})
|
|
467
|
+
|
|
468
|
+
await flushPromises()
|
|
469
|
+
|
|
470
|
+
expect(wrapper.vm.toContentText).toBeDefined()
|
|
471
|
+
expect(typeof wrapper.vm.toContentText).toBe('string')
|
|
472
|
+
})
|
|
473
|
+
|
|
474
|
+
it('should compute validData correctly with warnings', async () => {
|
|
475
|
+
wrapper = mount(AlertClient, {
|
|
476
|
+
props: {
|
|
477
|
+
warningsData: mockWarningsData,
|
|
478
|
+
language: 'fi',
|
|
479
|
+
},
|
|
480
|
+
})
|
|
481
|
+
|
|
482
|
+
await flushPromises()
|
|
483
|
+
|
|
484
|
+
expect(typeof wrapper.vm.validData).toBe('boolean')
|
|
485
|
+
})
|
|
486
|
+
|
|
487
|
+
it('should compute numWarnings correctly', async () => {
|
|
488
|
+
wrapper = mount(AlertClient, {
|
|
489
|
+
props: {
|
|
490
|
+
warningsData: mockWarningsData,
|
|
491
|
+
language: 'fi',
|
|
492
|
+
},
|
|
493
|
+
})
|
|
494
|
+
|
|
495
|
+
await flushPromises()
|
|
496
|
+
|
|
497
|
+
expect(typeof wrapper.vm.numWarnings).toBe('number')
|
|
498
|
+
expect(wrapper.vm.numWarnings).toBeGreaterThanOrEqual(0)
|
|
499
|
+
})
|
|
500
|
+
})
|
|
501
|
+
|
|
502
|
+
describe('Visibility change handling', () => {
|
|
503
|
+
it('should handle visibility change when document becomes hidden', () => {
|
|
504
|
+
const cancelTimerSpy = vi.spyOn(AlertClient.methods, 'cancelTimer')
|
|
505
|
+
|
|
506
|
+
wrapper = mount(AlertClient, {
|
|
507
|
+
props: {
|
|
508
|
+
refreshInterval: 60000,
|
|
509
|
+
sleep: true,
|
|
510
|
+
language: 'fi',
|
|
511
|
+
},
|
|
512
|
+
})
|
|
513
|
+
|
|
514
|
+
// Simulate document becoming hidden
|
|
515
|
+
Object.defineProperty(document, 'hidden', {
|
|
516
|
+
writable: true,
|
|
517
|
+
value: true,
|
|
518
|
+
})
|
|
519
|
+
|
|
520
|
+
wrapper.vm.visibilityChange()
|
|
521
|
+
|
|
522
|
+
expect(cancelTimerSpy).toHaveBeenCalled()
|
|
523
|
+
})
|
|
524
|
+
|
|
525
|
+
it('should restart timer when document becomes visible again', () => {
|
|
526
|
+
const initTimerSpy = vi.spyOn(AlertClient.methods, 'initTimer')
|
|
527
|
+
|
|
528
|
+
wrapper = mount(AlertClient, {
|
|
529
|
+
props: {
|
|
530
|
+
refreshInterval: 60000,
|
|
531
|
+
sleep: true,
|
|
532
|
+
language: 'fi',
|
|
533
|
+
},
|
|
534
|
+
})
|
|
535
|
+
|
|
536
|
+
// Hide document (cancels timer)
|
|
537
|
+
Object.defineProperty(document, 'hidden', {
|
|
538
|
+
writable: true,
|
|
539
|
+
value: true,
|
|
540
|
+
})
|
|
541
|
+
wrapper.vm.visibilityChange()
|
|
542
|
+
|
|
543
|
+
// Show document again (should try to restart timer)
|
|
544
|
+
Object.defineProperty(document, 'hidden', {
|
|
545
|
+
writable: true,
|
|
546
|
+
value: false,
|
|
547
|
+
})
|
|
548
|
+
wrapper.vm.visibilityChange()
|
|
549
|
+
|
|
550
|
+
expect(initTimerSpy).toHaveBeenCalled()
|
|
551
|
+
})
|
|
552
|
+
})
|
|
553
|
+
|
|
554
|
+
describe('Accessibility', () => {
|
|
555
|
+
it('should have proper ARIA labels on main container', async () => {
|
|
556
|
+
wrapper = mount(AlertClient, {
|
|
557
|
+
props: {
|
|
558
|
+
warningsData: mockWarningsData,
|
|
559
|
+
language: 'fi',
|
|
560
|
+
},
|
|
561
|
+
})
|
|
562
|
+
|
|
563
|
+
await flushPromises()
|
|
564
|
+
|
|
565
|
+
const container = wrapper.find('#fmi-warnings-view')
|
|
566
|
+
expect(container.exists()).toBe(true)
|
|
567
|
+
})
|
|
568
|
+
|
|
569
|
+
it('should provide accessible navigation structure', async () => {
|
|
570
|
+
wrapper = mount(AlertClient, {
|
|
571
|
+
props: {
|
|
572
|
+
warningsData: mockWarningsData,
|
|
573
|
+
language: 'fi',
|
|
574
|
+
},
|
|
575
|
+
})
|
|
576
|
+
|
|
577
|
+
await flushPromises()
|
|
578
|
+
|
|
579
|
+
// Check that Days component can be navigated
|
|
580
|
+
const days = wrapper.findComponent(Days)
|
|
581
|
+
expect(days.exists()).toBe(true)
|
|
582
|
+
})
|
|
583
|
+
|
|
584
|
+
it('should support keyboard navigation', async () => {
|
|
585
|
+
wrapper = mount(AlertClient, {
|
|
586
|
+
props: {
|
|
587
|
+
warningsData: mockWarningsData,
|
|
588
|
+
language: 'fi',
|
|
589
|
+
},
|
|
590
|
+
})
|
|
591
|
+
|
|
592
|
+
await flushPromises()
|
|
593
|
+
|
|
594
|
+
// Verify focus-ring class is used for keyboard navigation
|
|
595
|
+
const focusableElements = wrapper.findAll('.focus-ring')
|
|
596
|
+
expect(focusableElements.length).toBeGreaterThan(0)
|
|
597
|
+
})
|
|
598
|
+
|
|
599
|
+
it('should have proper language attribute', () => {
|
|
600
|
+
wrapper = mount(AlertClient, {
|
|
601
|
+
props: {
|
|
602
|
+
language: 'fi',
|
|
603
|
+
},
|
|
604
|
+
})
|
|
605
|
+
|
|
606
|
+
expect(wrapper.vm.language).toBe('fi')
|
|
607
|
+
})
|
|
608
|
+
})
|
|
609
|
+
|
|
610
|
+
describe('Edge cases', () => {
|
|
611
|
+
it('should handle invalid geometry ID', () => {
|
|
612
|
+
wrapper = mount(AlertClient, {
|
|
613
|
+
props: {
|
|
614
|
+
geometryId: -1,
|
|
615
|
+
language: 'fi',
|
|
616
|
+
},
|
|
617
|
+
})
|
|
618
|
+
|
|
619
|
+
expect(wrapper.exists()).toBe(true)
|
|
620
|
+
expect(wrapper.vm.geometryId).toBe(-1)
|
|
621
|
+
})
|
|
622
|
+
|
|
623
|
+
it('should handle very large refresh interval', () => {
|
|
624
|
+
wrapper = mount(AlertClient, {
|
|
625
|
+
props: {
|
|
626
|
+
refreshInterval: 2147483647, // Max 32-bit signed integer
|
|
627
|
+
language: 'fi',
|
|
628
|
+
},
|
|
629
|
+
})
|
|
630
|
+
|
|
631
|
+
expect(wrapper.vm.timer).toBeDefined()
|
|
632
|
+
})
|
|
633
|
+
|
|
634
|
+
it('should handle invalid theme gracefully', () => {
|
|
635
|
+
wrapper = mount(AlertClient, {
|
|
636
|
+
props: {
|
|
637
|
+
theme: 'invalid-theme',
|
|
638
|
+
language: 'fi',
|
|
639
|
+
},
|
|
640
|
+
})
|
|
641
|
+
|
|
642
|
+
expect(wrapper.exists()).toBe(true)
|
|
643
|
+
expect(wrapper.vm.theme).toBe('invalid-theme')
|
|
644
|
+
})
|
|
645
|
+
|
|
646
|
+
it('should accept valid default day range', () => {
|
|
647
|
+
wrapper = mount(AlertClient, {
|
|
648
|
+
props: {
|
|
649
|
+
defaultDay: 4,
|
|
650
|
+
language: 'fi',
|
|
651
|
+
},
|
|
652
|
+
})
|
|
653
|
+
|
|
654
|
+
// Component should mount with valid day value
|
|
655
|
+
expect(wrapper.exists()).toBe(true)
|
|
656
|
+
expect(wrapper.vm.selectedDay).toBe(4)
|
|
657
|
+
})
|
|
658
|
+
|
|
659
|
+
it('should handle concurrent day selections', async () => {
|
|
660
|
+
wrapper = mount(AlertClient, {
|
|
661
|
+
props: {
|
|
662
|
+
warningsData: mockWarningsData,
|
|
663
|
+
language: 'fi',
|
|
664
|
+
},
|
|
665
|
+
})
|
|
666
|
+
|
|
667
|
+
await flushPromises()
|
|
668
|
+
|
|
669
|
+
// Rapidly change days
|
|
670
|
+
wrapper.vm.onDaySelected(1)
|
|
671
|
+
wrapper.vm.onDaySelected(2)
|
|
672
|
+
wrapper.vm.onDaySelected(3)
|
|
673
|
+
wrapper.vm.onDaySelected(4)
|
|
674
|
+
|
|
675
|
+
// Should end up with last selected day
|
|
676
|
+
expect(wrapper.vm.selectedDay).toBe(4)
|
|
677
|
+
})
|
|
678
|
+
})
|
|
679
|
+
|
|
680
|
+
describe('Performance', () => {
|
|
681
|
+
it('should handle multiple mount and unmount cycles', () => {
|
|
682
|
+
// Create and destroy multiple instances
|
|
683
|
+
for (let i = 0; i < 3; i++) {
|
|
684
|
+
const w = mount(AlertClient, {
|
|
685
|
+
props: {
|
|
686
|
+
refreshInterval: 60000,
|
|
687
|
+
language: 'fi',
|
|
688
|
+
},
|
|
689
|
+
})
|
|
690
|
+
expect(w.exists()).toBe(true)
|
|
691
|
+
w.unmount()
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
// Test completed without errors
|
|
695
|
+
expect(true).toBe(true)
|
|
696
|
+
})
|
|
697
|
+
|
|
698
|
+
it('should handle large warning datasets efficiently', async () => {
|
|
699
|
+
const largeDataset = {
|
|
700
|
+
...mockWarningsData,
|
|
701
|
+
weather_finland_active_all: {
|
|
702
|
+
features: Array(100)
|
|
703
|
+
.fill(null)
|
|
704
|
+
.map((_, i) => ({
|
|
705
|
+
...mockWarningsData.weather_finland_active_all.features[0],
|
|
706
|
+
properties: {
|
|
707
|
+
...mockWarningsData.weather_finland_active_all.features[0]
|
|
708
|
+
.properties,
|
|
709
|
+
identifier: `warning-${i}`,
|
|
710
|
+
},
|
|
711
|
+
})),
|
|
712
|
+
},
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
const startTime = performance.now()
|
|
716
|
+
|
|
717
|
+
wrapper = mount(AlertClient, {
|
|
718
|
+
props: {
|
|
719
|
+
warningsData: largeDataset,
|
|
720
|
+
language: 'fi',
|
|
721
|
+
},
|
|
722
|
+
})
|
|
723
|
+
|
|
724
|
+
await flushPromises()
|
|
725
|
+
|
|
726
|
+
const endTime = performance.now()
|
|
727
|
+
const duration = endTime - startTime
|
|
728
|
+
|
|
729
|
+
// Processing should complete in reasonable time (< 1000ms)
|
|
730
|
+
expect(duration).toBeLessThan(1000)
|
|
731
|
+
expect(wrapper.exists()).toBe(true)
|
|
732
|
+
})
|
|
733
|
+
})
|
|
734
|
+
})
|