@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,445 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest'
2
+ import { mockWarningsData } from '../fixtures/mockWarningData'
3
+ import {
4
+ processWarnings,
5
+ type WarningsProcessorContext,
6
+ } from '@/composables/useWarningsProcessor'
7
+ import { useConfig } from '@/composables/useConfig'
8
+ import { useI18n } from '@/composables/useI18n'
9
+ import geojsonsvg from '@/mixins/geojsonsvg'
10
+ import type { WarningsData } from '@/types'
11
+
12
+ // Bind all methods so 'this' works correctly when called standalone
13
+ const geoJSONToSVG = geojsonsvg.methods.geoJSONToSVG.bind(geojsonsvg.methods)
14
+
15
+ describe('Warning data flow integration', () => {
16
+ let config: ReturnType<typeof useConfig>
17
+ let ctx: WarningsProcessorContext
18
+
19
+ beforeEach(() => {
20
+ config = useConfig()
21
+ const { t } = useI18n('fi')
22
+
23
+ // Create a context object that processWarnings needs
24
+ ctx = {
25
+ geometryId: '2021',
26
+ geometries: config.geometries,
27
+ warningTypes: config.warningTypes,
28
+ regionIds: config.regionIds,
29
+ dailyWarningTypes: [],
30
+ currentTime: new Date('2025-10-31T12:00:00Z').getTime(),
31
+ startFrom: '',
32
+ staticDays: true,
33
+ timeZone: config.timeZone,
34
+ locale: config.dateTimeFormatLocale,
35
+ bbox: config.bbox as WarningsProcessorContext['bbox'],
36
+ geoJSONToSVG,
37
+ maxUpdateDelay:
38
+ config.maxUpdateDelay as WarningsProcessorContext['maxUpdateDelay'],
39
+ t,
40
+ handleError: () => {
41
+ // Mock error handler - does nothing in tests
42
+ },
43
+ onDataError: () => {
44
+ // Mock data error handler - does nothing in tests
45
+ },
46
+ }
47
+ })
48
+
49
+ describe('Complete warning processing', () => {
50
+ it('should process full warning dataset', () => {
51
+ const result = processWarnings(mockWarningsData, ctx)
52
+
53
+ expect(result).toHaveProperty('warnings')
54
+ expect(result).toHaveProperty('days')
55
+ expect(result).toHaveProperty('regions')
56
+ expect(result).toHaveProperty('parents')
57
+ expect(result).toHaveProperty('legend')
58
+ })
59
+
60
+ it('should create 5 days from warnings', () => {
61
+ const result = processWarnings(mockWarningsData, ctx)
62
+
63
+ expect(result.days).toHaveLength(5)
64
+ result.days.forEach((day) => {
65
+ expect(day).toHaveProperty('weekdayName')
66
+ expect(day).toHaveProperty('day')
67
+ expect(day).toHaveProperty('month')
68
+ expect(day).toHaveProperty('year')
69
+ expect(day).toHaveProperty('severity')
70
+ expect(day).toHaveProperty('updatedDate')
71
+ expect(day).toHaveProperty('updatedTime')
72
+ })
73
+ })
74
+
75
+ it('should parse weather warnings correctly', () => {
76
+ const result = processWarnings(mockWarningsData, ctx)
77
+
78
+ const windWarning = result.warnings['test-warning-wind-1']
79
+ expect(windWarning).toBeDefined()
80
+ expect(windWarning!.type).toBe('wind')
81
+ expect(windWarning!.severity).toBe(3)
82
+ })
83
+
84
+ it('should parse flood warnings correctly', () => {
85
+ const result = processWarnings(mockWarningsData, ctx)
86
+
87
+ const floodWarning = result.warnings['test-warning-flood-1']
88
+ expect(floodWarning).toBeDefined()
89
+ expect(floodWarning!.type).toBe('floodLevel')
90
+ expect(floodWarning!.severity).toBe(3)
91
+ })
92
+
93
+ it('should create legend sorted by severity', () => {
94
+ const result = processWarnings(mockWarningsData, ctx)
95
+
96
+ expect(result.legend.length).toBeGreaterThan(0)
97
+
98
+ // Check that legend is sorted by severity (descending)
99
+ for (let i = 0; i < result.legend.length - 1; i++) {
100
+ expect(result.legend[i]!.severity).toBeGreaterThanOrEqual(
101
+ result.legend[i + 1]!.severity
102
+ )
103
+ }
104
+ })
105
+
106
+ it('should create regions with warnings', () => {
107
+ const result = processWarnings(mockWarningsData, ctx)
108
+
109
+ expect(result.regions).toHaveLength(5)
110
+ result.regions.forEach((day) => {
111
+ expect(day).toHaveProperty('land')
112
+ expect(day).toHaveProperty('sea')
113
+ expect(Array.isArray(day.land)).toBe(true)
114
+ expect(Array.isArray(day.sea)).toBe(true)
115
+ })
116
+ })
117
+
118
+ it('should handle missing weather data gracefully', () => {
119
+ const incompleteData = {
120
+ weather_update_time: mockWarningsData.weather_update_time,
121
+ flood_update_time: mockWarningsData.flood_update_time,
122
+ // Missing weather and flood warnings
123
+ } as WarningsData
124
+
125
+ const result = processWarnings(incompleteData, ctx)
126
+ expect(result).toBeDefined()
127
+ })
128
+
129
+ it('should set updatedAt timestamp', () => {
130
+ const result = processWarnings(mockWarningsData, ctx)
131
+
132
+ expect(result.updatedAt).toBeDefined()
133
+ expect(typeof result.updatedAt).toBe('number')
134
+ expect(result.updatedAt).toBeGreaterThan(0)
135
+ })
136
+
137
+ it('should filter out invalid warnings', () => {
138
+ const dataWithInvalid = {
139
+ ...mockWarningsData,
140
+ weather_finland_active_all: {
141
+ type: 'FeatureCollection' as const,
142
+ features: [
143
+ ...mockWarningsData.weather_finland_active_all!.features,
144
+ {
145
+ type: 'Feature' as const,
146
+ properties: {
147
+ identifier: 'invalid-warning',
148
+ warning_context: 'wind',
149
+ severity: 'level-1', // Invalid for wind
150
+ effective_from: '2025-10-31T12:00:00Z',
151
+ effective_until: '2025-11-01T12:00:00Z',
152
+ reference: 'fi-warning#county.1',
153
+ },
154
+ geometry: null,
155
+ },
156
+ ],
157
+ },
158
+ } as WarningsData
159
+
160
+ const result = processWarnings(dataWithInvalid, ctx)
161
+
162
+ expect(result.warnings).not.toHaveProperty('invalid-warning')
163
+ })
164
+ })
165
+
166
+ describe('Region hierarchy', () => {
167
+ it('should handle parent-child relationships', () => {
168
+ const result = processWarnings(mockWarningsData, ctx)
169
+
170
+ // Check that parents object is created
171
+ expect(result.parents).toBeDefined()
172
+ expect(typeof result.parents).toBe('object')
173
+ })
174
+ })
175
+
176
+ describe('Coverage handling', () => {
177
+ it('should process coverage data when present', () => {
178
+ const result = processWarnings(mockWarningsData, ctx)
179
+
180
+ const coverageWarning = result.warnings['test-warning-coverage-1']
181
+ if (coverageWarning) {
182
+ expect(coverageWarning.covRegions).toBeInstanceOf(Map)
183
+ }
184
+ })
185
+ })
186
+
187
+ describe('Multi-language support', () => {
188
+ it('should include info in all languages for weather warnings', () => {
189
+ const result = processWarnings(mockWarningsData, ctx)
190
+
191
+ const windWarning = result.warnings['test-warning-wind-1']
192
+ expect(windWarning!.info).toHaveProperty('fi')
193
+ expect(windWarning!.info).toHaveProperty('sv')
194
+ expect(windWarning!.info).toHaveProperty('en')
195
+ })
196
+
197
+ it('should decode HTML entities in warning info', () => {
198
+ const result = processWarnings(mockWarningsData, ctx)
199
+
200
+ const windWarning = result.warnings['test-warning-wind-1']
201
+ // HTML entities should be decoded by he.decode
202
+ expect(windWarning!.info.fi).not.toContain('&amp;')
203
+ expect(windWarning!.info.fi).not.toContain('&lt;')
204
+ })
205
+ })
206
+
207
+ describe('Time calculations', () => {
208
+ it('should calculate effective days correctly', () => {
209
+ const result = processWarnings(mockWarningsData, ctx)
210
+
211
+ const windWarning = result.warnings['test-warning-wind-1']
212
+ expect(windWarning!.effectiveDays).toHaveLength(5)
213
+ expect(windWarning!.effectiveDays.some((day) => day === true)).toBe(true)
214
+ })
215
+
216
+ it('should format valid intervals correctly', () => {
217
+ const result = processWarnings(mockWarningsData, ctx)
218
+
219
+ const windWarning = result.warnings['test-warning-wind-1']
220
+ expect(windWarning!.validInterval).toContain('–')
221
+ expect(windWarning!.validInterval).toMatch(/\d{1,2}\.\d{1,2}\./)
222
+ })
223
+ })
224
+
225
+ describe('Severity calculations', () => {
226
+ it('should calculate day severities from warnings', () => {
227
+ const result = processWarnings(mockWarningsData, ctx)
228
+
229
+ const firstDay = result.days[0]
230
+ // Should have severity based on active warnings
231
+ expect(typeof firstDay!.severity).toBe('number')
232
+ expect(firstDay!.severity).toBeGreaterThanOrEqual(0)
233
+ expect(firstDay!.severity).toBeLessThanOrEqual(4)
234
+ })
235
+
236
+ it('should show highest severity per day', () => {
237
+ const result = processWarnings(mockWarningsData, ctx)
238
+
239
+ // Check that severity is the max of all warnings for that day
240
+ result.days.forEach((day, dayIndex) => {
241
+ const warningsForDay = Object.values(result.warnings).filter(
242
+ (warning) => warning.effectiveDays[dayIndex]
243
+ )
244
+
245
+ if (warningsForDay.length > 0) {
246
+ const maxSeverity = Math.max(...warningsForDay.map((w) => w.severity))
247
+ expect(day.severity).toBe(maxSeverity)
248
+ } else {
249
+ expect(day.severity).toBe(0)
250
+ }
251
+ })
252
+ })
253
+ })
254
+
255
+ describe('Edge cases and error handling', () => {
256
+ it('should handle warnings with some missing properties', () => {
257
+ const incompleteData = {
258
+ weather_update_time: '2025-10-31T12:00:00Z',
259
+ weather_finland_active_all: {
260
+ type: 'FeatureCollection' as const,
261
+ features: [
262
+ {
263
+ type: 'Feature' as const,
264
+ properties: {
265
+ identifier: 'incomplete-warning',
266
+ warning_context: 'wind',
267
+ severity: 'level-2',
268
+ effective_from: '2025-10-31T12:00:00Z',
269
+ effective_until: '2025-11-01T12:00:00Z',
270
+ reference: 'fi-warning#county.1',
271
+ // Missing descriptions, but has essential properties
272
+ },
273
+ geometry: {
274
+ type: 'Point' as const,
275
+ coordinates: [25.0, 60.0] as [number, number],
276
+ },
277
+ },
278
+ ],
279
+ },
280
+ } as unknown as WarningsData
281
+
282
+ const result = processWarnings(incompleteData, ctx)
283
+
284
+ // Should process the warning even with missing descriptions
285
+ expect(result).toBeDefined()
286
+ expect(result.warnings).toBeDefined()
287
+ })
288
+
289
+ it('should handle malformed timestamps', () => {
290
+ const badTimestampData = {
291
+ weather_update_time: 'invalid-timestamp',
292
+ flood_update_time: 'also-invalid',
293
+ weather_finland_active_all: {
294
+ type: 'FeatureCollection' as const,
295
+ features: [],
296
+ },
297
+ } as unknown as WarningsData
298
+
299
+ const result = processWarnings(badTimestampData, ctx)
300
+ expect(result).toBeDefined()
301
+ expect(result.days).toHaveLength(5)
302
+ })
303
+
304
+ it('should handle circular references in region hierarchy', () => {
305
+ const dataWithCircular = {
306
+ ...mockWarningsData,
307
+ weather_finland_active_all: {
308
+ type: 'FeatureCollection' as const,
309
+ features: mockWarningsData.weather_finland_active_all!.features.map(
310
+ (f) => ({
311
+ ...f,
312
+ properties: {
313
+ ...f.properties,
314
+ // Create potential circular reference
315
+ reference: f.properties.identifier,
316
+ },
317
+ })
318
+ ),
319
+ },
320
+ } as WarningsData
321
+
322
+ expect(() => {
323
+ processWarnings(dataWithCircular, ctx)
324
+ }).not.toThrow()
325
+ })
326
+
327
+ it('should handle very long warning descriptions', () => {
328
+ const longDescription = 'A'.repeat(10000)
329
+ const dataWithLongText = {
330
+ ...mockWarningsData,
331
+ weather_finland_active_all: {
332
+ type: 'FeatureCollection' as const,
333
+ features: [
334
+ {
335
+ ...mockWarningsData.weather_finland_active_all!.features[0],
336
+ properties: {
337
+ ...mockWarningsData.weather_finland_active_all!.features[0]!
338
+ .properties,
339
+ description_fi: longDescription,
340
+ description_sv: longDescription,
341
+ description_en: longDescription,
342
+ },
343
+ },
344
+ ],
345
+ },
346
+ } as WarningsData
347
+
348
+ const result = processWarnings(dataWithLongText, ctx)
349
+ expect(result).toBeDefined()
350
+ expect(result.warnings).toBeDefined()
351
+ })
352
+
353
+ it('should handle warnings spanning across midnight', () => {
354
+ const midnightData = {
355
+ weather_update_time: '2025-10-31T23:00:00Z',
356
+ weather_finland_active_all: {
357
+ type: 'FeatureCollection' as const,
358
+ features: [
359
+ {
360
+ type: 'Feature' as const,
361
+ properties: {
362
+ identifier: 'midnight-warning',
363
+ warning_context: 'wind',
364
+ severity: 'level-2',
365
+ effective_from: '2025-10-31T23:30:00Z',
366
+ effective_until: '2025-11-01T01:30:00Z',
367
+ reference: 'fi-warning#county.1',
368
+ description_fi: 'Kova tuuli',
369
+ description_sv: 'Hårt väder',
370
+ description_en: 'Strong wind',
371
+ },
372
+ geometry: {
373
+ type: 'Point' as const,
374
+ coordinates: [25.0, 60.0] as [number, number],
375
+ },
376
+ },
377
+ ],
378
+ },
379
+ } as unknown as WarningsData
380
+
381
+ const result = processWarnings(midnightData, ctx)
382
+ expect(result.warnings['midnight-warning']).toBeDefined()
383
+ expect(result.warnings['midnight-warning']!.effectiveDays).toBeDefined()
384
+ })
385
+
386
+ it('should handle duplicate warning identifiers', () => {
387
+ const duplicateData = {
388
+ weather_update_time: '2025-10-31T12:00:00Z',
389
+ weather_finland_active_all: {
390
+ type: 'FeatureCollection' as const,
391
+ features: [
392
+ mockWarningsData.weather_finland_active_all!.features[0],
393
+ mockWarningsData.weather_finland_active_all!.features[0], // Duplicate
394
+ ],
395
+ },
396
+ } as unknown as WarningsData
397
+
398
+ const result = processWarnings(duplicateData, ctx)
399
+
400
+ // Should handle duplicates gracefully
401
+ expect(result).toBeDefined()
402
+ expect(Object.keys(result.warnings).length).toBeGreaterThan(0)
403
+ })
404
+
405
+ it('should handle special characters in warning text', () => {
406
+ const specialCharsData = {
407
+ weather_update_time: '2025-10-31T12:00:00Z',
408
+ weather_finland_active_all: {
409
+ type: 'FeatureCollection' as const,
410
+ features: [
411
+ {
412
+ type: 'Feature' as const,
413
+ properties: {
414
+ identifier: 'special-chars-warning',
415
+ warning_context: 'wind',
416
+ severity: 'level-2',
417
+ effective_from: '2025-10-31T12:00:00Z',
418
+ effective_until: '2025-11-01T12:00:00Z',
419
+ reference: 'fi-warning#county.1',
420
+ description_fi:
421
+ 'Test &lt;script&gt;alert("xss")&lt;/script&gt; &amp; special chars',
422
+ description_sv:
423
+ 'Test &lt;script&gt;alert("xss")&lt;/script&gt; &amp; special chars',
424
+ description_en:
425
+ 'Test &lt;script&gt;alert("xss")&lt;/script&gt; &amp; special chars',
426
+ },
427
+ geometry: {
428
+ type: 'Point' as const,
429
+ coordinates: [25.0, 60.0] as [number, number],
430
+ },
431
+ },
432
+ ],
433
+ },
434
+ } as unknown as WarningsData
435
+
436
+ const result = processWarnings(specialCharsData, ctx)
437
+ const warning = result.warnings['special-chars-warning']
438
+
439
+ // HTML entities should be decoded
440
+ expect(warning!.info.fi).not.toContain('&lt;')
441
+ expect(warning!.info.fi).not.toContain('&gt;')
442
+ expect(warning!.info.fi).not.toContain('&amp;')
443
+ })
444
+ })
445
+ })
package/tests/setup.ts ADDED
@@ -0,0 +1,41 @@
1
+ import { vi } from 'vitest'
2
+ import '@testing-library/jest-dom'
3
+ import { config } from '@vue/test-utils'
4
+
5
+ // Mock environment variables
6
+ process.env.VITE_LANGUAGE = 'fi'
7
+
8
+ // Configure global stubs for components
9
+ config.global.stubs = {
10
+ GrayScaleToggle: true,
11
+ CollapsiblePanel: true,
12
+ }
13
+
14
+ // Mock window.getComputedStyle
15
+ global.window.getComputedStyle = vi.fn(() => ({
16
+ fontSize: '16px',
17
+ })) as unknown as typeof window.getComputedStyle
18
+
19
+ // Mock IntersectionObserver
20
+ global.IntersectionObserver = class IntersectionObserver {
21
+ constructor() {}
22
+ disconnect() {}
23
+ observe() {}
24
+ takeRecords(): IntersectionObserverEntry[] {
25
+ return []
26
+ }
27
+ unobserve() {}
28
+ } as unknown as typeof IntersectionObserver
29
+
30
+ // Mock ResizeObserver
31
+ global.ResizeObserver = class ResizeObserver {
32
+ constructor() {}
33
+ disconnect() {}
34
+ observe() {}
35
+ unobserve() {}
36
+ } as unknown as typeof ResizeObserver
37
+
38
+ // Mock fetch if not available
39
+ if (!global.fetch) {
40
+ global.fetch = vi.fn() as unknown as typeof fetch
41
+ }