@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,348 @@
1
+ import { describe, it, expect, afterEach } from 'vitest'
2
+ import { mount, VueWrapper } from '@vue/test-utils'
3
+ import DayLarge from '@/components/DayLarge.vue'
4
+ import type { Day, DayRegions, WarningsMap, Theme, Language } from '@/types'
5
+
6
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
7
+ type ComponentInstance = any
8
+
9
+ const mockDay: Day = {
10
+ weekdayName: 'thursday',
11
+ day: 31,
12
+ month: 10,
13
+ year: 2025,
14
+ severity: 3,
15
+ updatedDate: '31.10.2025',
16
+ updatedTime: '14:00',
17
+ }
18
+
19
+ const mockRegions: DayRegions = {
20
+ land: [],
21
+ sea: [],
22
+ }
23
+
24
+ describe('DayLarge.vue', () => {
25
+ let wrapper: VueWrapper | null = null
26
+
27
+ afterEach(() => {
28
+ if (wrapper) {
29
+ wrapper.unmount()
30
+ wrapper = null
31
+ }
32
+ })
33
+
34
+ describe('Component mounting', () => {
35
+ it('should mount with required props', () => {
36
+ wrapper = mount(DayLarge, {
37
+ props: {
38
+ index: 0,
39
+ input: mockDay,
40
+ visibleWarnings: [],
41
+ warnings: null,
42
+ regions: mockRegions,
43
+ geometryId: 2021,
44
+ theme: 'light-theme' as Theme,
45
+ language: 'fi' as Language,
46
+ },
47
+ })
48
+
49
+ expect(wrapper.exists()).toBe(true)
50
+ })
51
+
52
+ it('should mount with minimal props', () => {
53
+ wrapper = mount(DayLarge, {
54
+ props: {
55
+ index: 0,
56
+ },
57
+ })
58
+
59
+ expect(wrapper.exists()).toBe(true)
60
+ })
61
+ })
62
+
63
+ describe('Computed properties', () => {
64
+ it('should compute warningsTitle from translations', () => {
65
+ wrapper = mount(DayLarge, {
66
+ props: {
67
+ index: 0,
68
+ input: mockDay,
69
+ language: 'fi' as Language,
70
+ },
71
+ })
72
+
73
+ expect(typeof (wrapper.vm as ComponentInstance).warningsTitle).toBe(
74
+ 'string'
75
+ )
76
+ })
77
+
78
+ it('should compute updatedTitle from translations', () => {
79
+ wrapper = mount(DayLarge, {
80
+ props: {
81
+ index: 0,
82
+ input: mockDay,
83
+ language: 'fi' as Language,
84
+ },
85
+ })
86
+
87
+ expect(typeof (wrapper.vm as ComponentInstance).updatedTitle).toBe(
88
+ 'string'
89
+ )
90
+ })
91
+
92
+ it('should compute atTime from translations', () => {
93
+ wrapper = mount(DayLarge, {
94
+ props: {
95
+ index: 0,
96
+ input: mockDay,
97
+ language: 'fi' as Language,
98
+ },
99
+ })
100
+
101
+ expect(typeof (wrapper.vm as ComponentInstance).atTime).toBe('string')
102
+ })
103
+
104
+ it('should compute warningsDate from input', () => {
105
+ wrapper = mount(DayLarge, {
106
+ props: {
107
+ index: 0,
108
+ input: mockDay,
109
+ staticDays: true,
110
+ language: 'fi' as Language,
111
+ },
112
+ })
113
+
114
+ expect((wrapper.vm as ComponentInstance).warningsDate).toBe('31.10.2025')
115
+ })
116
+
117
+ it('should compute warningsDate with time range when not static', () => {
118
+ wrapper = mount(DayLarge, {
119
+ props: {
120
+ index: 0,
121
+ input: mockDay,
122
+ staticDays: false,
123
+ timeOffset: 0,
124
+ language: 'fi' as Language,
125
+ },
126
+ })
127
+
128
+ expect((wrapper.vm as ComponentInstance).warningsDate).toContain(
129
+ '31.10.2025'
130
+ )
131
+ })
132
+
133
+ it('should return empty warningsDate when input is missing date parts', () => {
134
+ wrapper = mount(DayLarge, {
135
+ props: {
136
+ index: 0,
137
+ input: {} as Day,
138
+ language: 'fi' as Language,
139
+ },
140
+ })
141
+
142
+ expect((wrapper.vm as ComponentInstance).warningsDate).toBe('')
143
+ })
144
+
145
+ it('should compute updatedDate from input', () => {
146
+ wrapper = mount(DayLarge, {
147
+ props: {
148
+ index: 0,
149
+ input: mockDay,
150
+ language: 'fi' as Language,
151
+ },
152
+ })
153
+
154
+ expect((wrapper.vm as ComponentInstance).updatedDate).toBe('31.10.2025')
155
+ })
156
+
157
+ it('should compute updatedTime from input', () => {
158
+ wrapper = mount(DayLarge, {
159
+ props: {
160
+ index: 0,
161
+ input: mockDay,
162
+ language: 'fi' as Language,
163
+ },
164
+ })
165
+
166
+ expect((wrapper.vm as ComponentInstance).updatedTime).toBe('14:00')
167
+ })
168
+
169
+ it('should compute dataProviderFirst from translations', () => {
170
+ wrapper = mount(DayLarge, {
171
+ props: {
172
+ index: 0,
173
+ input: mockDay,
174
+ language: 'fi' as Language,
175
+ },
176
+ })
177
+
178
+ expect(typeof (wrapper.vm as ComponentInstance).dataProviderFirst).toBe(
179
+ 'string'
180
+ )
181
+ })
182
+
183
+ it('should compute dataProviderSecond from translations', () => {
184
+ wrapper = mount(DayLarge, {
185
+ props: {
186
+ index: 0,
187
+ input: mockDay,
188
+ language: 'fi' as Language,
189
+ },
190
+ })
191
+
192
+ expect(typeof (wrapper.vm as ComponentInstance).dataProviderSecond).toBe(
193
+ 'string'
194
+ )
195
+ })
196
+ })
197
+
198
+ describe('Props handling', () => {
199
+ it('should accept visibleWarnings prop', () => {
200
+ wrapper = mount(DayLarge, {
201
+ props: {
202
+ index: 0,
203
+ visibleWarnings: ['wind', 'rain'],
204
+ },
205
+ })
206
+
207
+ expect(wrapper.exists()).toBe(true)
208
+ })
209
+
210
+ it('should accept warnings prop', () => {
211
+ const mockWarnings: WarningsMap = {}
212
+
213
+ wrapper = mount(DayLarge, {
214
+ props: {
215
+ index: 0,
216
+ warnings: mockWarnings,
217
+ },
218
+ })
219
+
220
+ expect(wrapper.exists()).toBe(true)
221
+ })
222
+
223
+ it('should accept loading prop', () => {
224
+ wrapper = mount(DayLarge, {
225
+ props: {
226
+ index: 0,
227
+ loading: false,
228
+ },
229
+ })
230
+
231
+ expect(wrapper.exists()).toBe(true)
232
+ })
233
+
234
+ it('should accept spinnerEnabled prop', () => {
235
+ wrapper = mount(DayLarge, {
236
+ props: {
237
+ index: 0,
238
+ spinnerEnabled: false,
239
+ },
240
+ })
241
+
242
+ expect((wrapper.vm as ComponentInstance).spinnerEnabled).toBe(false)
243
+ })
244
+ })
245
+
246
+ describe('Events', () => {
247
+ it('should have onLoaded method', () => {
248
+ wrapper = mount(DayLarge, {
249
+ props: {
250
+ index: 0,
251
+ },
252
+ })
253
+
254
+ expect(typeof (wrapper.vm as ComponentInstance).onLoaded).toBe('function')
255
+ })
256
+
257
+ it('should emit loaded event when onLoaded is called with true', () => {
258
+ wrapper = mount(DayLarge, {
259
+ props: {
260
+ index: 0,
261
+ },
262
+ })
263
+ ;(wrapper.vm as ComponentInstance).onLoaded(true)
264
+
265
+ expect(wrapper.emitted('loaded')).toBeTruthy()
266
+ expect(wrapper.emitted('loaded')![0]).toEqual([true])
267
+ })
268
+
269
+ it('should not emit loaded event when onLoaded is called with false', () => {
270
+ wrapper = mount(DayLarge, {
271
+ props: {
272
+ index: 0,
273
+ },
274
+ })
275
+ ;(wrapper.vm as ComponentInstance).onLoaded(false)
276
+
277
+ expect(wrapper.emitted('loaded')).toBeFalsy()
278
+ })
279
+ })
280
+
281
+ describe('Theme support', () => {
282
+ it('should accept theme prop', () => {
283
+ wrapper = mount(DayLarge, {
284
+ props: {
285
+ index: 0,
286
+ theme: 'dark-theme' as Theme,
287
+ },
288
+ })
289
+
290
+ expect(wrapper.exists()).toBe(true)
291
+ })
292
+
293
+ it('should support all theme variants', () => {
294
+ const themes: Theme[] = [
295
+ 'light-theme',
296
+ 'dark-theme',
297
+ 'light-gray-theme',
298
+ 'dark-gray-theme',
299
+ ]
300
+
301
+ themes.forEach((theme) => {
302
+ wrapper = mount(DayLarge, {
303
+ props: {
304
+ index: 0,
305
+ theme,
306
+ },
307
+ })
308
+
309
+ expect(wrapper.exists()).toBe(true)
310
+ })
311
+ })
312
+ })
313
+
314
+ describe('Language support', () => {
315
+ it('should support Finnish language', () => {
316
+ wrapper = mount(DayLarge, {
317
+ props: {
318
+ index: 0,
319
+ language: 'fi' as Language,
320
+ },
321
+ })
322
+
323
+ expect(wrapper.exists()).toBe(true)
324
+ })
325
+
326
+ it('should support Swedish language', () => {
327
+ wrapper = mount(DayLarge, {
328
+ props: {
329
+ index: 0,
330
+ language: 'sv' as Language,
331
+ },
332
+ })
333
+
334
+ expect(wrapper.exists()).toBe(true)
335
+ })
336
+
337
+ it('should support English language', () => {
338
+ wrapper = mount(DayLarge, {
339
+ props: {
340
+ index: 0,
341
+ language: 'en' as Language,
342
+ },
343
+ })
344
+
345
+ expect(wrapper.exists()).toBe(true)
346
+ })
347
+ })
348
+ })
@@ -0,0 +1,352 @@
1
+ import { describe, it, expect, afterEach } from 'vitest'
2
+ import { mount, VueWrapper } from '@vue/test-utils'
3
+ import DaySmall from '@/components/DaySmall.vue'
4
+ import type { Day, DayRegions, WarningsMap, Theme, Language } from '@/types'
5
+
6
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
7
+ type ComponentInstance = any
8
+
9
+ const mockDay: Day = {
10
+ weekdayName: 'thursday',
11
+ day: 31,
12
+ month: 10,
13
+ year: 2025,
14
+ severity: 3,
15
+ updatedDate: '31.10.2025',
16
+ updatedTime: '14:00',
17
+ }
18
+
19
+ const mockRegions: DayRegions = {
20
+ land: [],
21
+ sea: [],
22
+ }
23
+
24
+ describe('DaySmall.vue', () => {
25
+ let wrapper: VueWrapper | null = null
26
+
27
+ afterEach(() => {
28
+ if (wrapper) {
29
+ wrapper.unmount()
30
+ wrapper = null
31
+ }
32
+ })
33
+
34
+ describe('Component mounting', () => {
35
+ it('should mount with required props', () => {
36
+ wrapper = mount(DaySmall, {
37
+ props: {
38
+ index: 0,
39
+ input: mockDay,
40
+ visibleWarnings: [],
41
+ warnings: null,
42
+ regions: mockRegions,
43
+ geometryId: 2021,
44
+ theme: 'light-theme' as Theme,
45
+ language: 'fi' as Language,
46
+ },
47
+ })
48
+
49
+ expect(wrapper.exists()).toBe(true)
50
+ })
51
+
52
+ it('should mount with minimal props', () => {
53
+ wrapper = mount(DaySmall, {
54
+ props: {
55
+ index: 0,
56
+ },
57
+ })
58
+
59
+ expect(wrapper.exists()).toBe(true)
60
+ })
61
+ })
62
+
63
+ describe('Computed properties', () => {
64
+ it('should compute weekday from translations', () => {
65
+ wrapper = mount(DaySmall, {
66
+ props: {
67
+ index: 0,
68
+ input: mockDay,
69
+ language: 'fi' as Language,
70
+ },
71
+ })
72
+
73
+ expect(typeof (wrapper.vm as ComponentInstance).weekday).toBe('string')
74
+ })
75
+
76
+ it('should compute severity from input', () => {
77
+ wrapper = mount(DaySmall, {
78
+ props: {
79
+ index: 0,
80
+ input: mockDay,
81
+ },
82
+ })
83
+
84
+ expect((wrapper.vm as ComponentInstance).severity).toBe(3)
85
+ })
86
+
87
+ it('should return 0 severity when input is empty', () => {
88
+ wrapper = mount(DaySmall, {
89
+ props: {
90
+ index: 0,
91
+ input: {} as Day,
92
+ },
93
+ })
94
+
95
+ expect((wrapper.vm as ComponentInstance).severity).toBe(0)
96
+ })
97
+
98
+ it('should compute date with static days', () => {
99
+ wrapper = mount(DaySmall, {
100
+ props: {
101
+ index: 0,
102
+ input: mockDay,
103
+ staticDays: true,
104
+ },
105
+ })
106
+
107
+ expect((wrapper.vm as ComponentInstance).date).toBe('31.10.')
108
+ })
109
+
110
+ it('should compute date with time ranges when not static', () => {
111
+ wrapper = mount(DaySmall, {
112
+ props: {
113
+ index: 0,
114
+ staticDays: false,
115
+ },
116
+ })
117
+
118
+ expect((wrapper.vm as ComponentInstance).date).toBe('0...24 h')
119
+ })
120
+
121
+ it('should compute different time ranges for different indices', () => {
122
+ const timeRanges = [
123
+ '0...24 h',
124
+ '24...48 h',
125
+ '48...72 h',
126
+ '72...96 h',
127
+ '96...120 h',
128
+ ]
129
+
130
+ timeRanges.forEach((expected, index) => {
131
+ wrapper = mount(DaySmall, {
132
+ props: {
133
+ index,
134
+ staticDays: false,
135
+ },
136
+ })
137
+
138
+ expect((wrapper.vm as ComponentInstance).date).toBe(expected)
139
+ })
140
+ })
141
+
142
+ it('should compute ariaLabel', () => {
143
+ wrapper = mount(DaySmall, {
144
+ props: {
145
+ index: 0,
146
+ input: mockDay,
147
+ regions: mockRegions,
148
+ language: 'fi' as Language,
149
+ },
150
+ })
151
+
152
+ expect(typeof (wrapper.vm as ComponentInstance).ariaLabel).toBe('string')
153
+ })
154
+
155
+ it('should return empty date when input is missing date parts', () => {
156
+ wrapper = mount(DaySmall, {
157
+ props: {
158
+ index: 0,
159
+ input: {} as Day,
160
+ staticDays: true,
161
+ },
162
+ })
163
+
164
+ expect((wrapper.vm as ComponentInstance).date).toBe('')
165
+ })
166
+ })
167
+
168
+ describe('Props handling', () => {
169
+ it('should accept active prop', () => {
170
+ wrapper = mount(DaySmall, {
171
+ props: {
172
+ index: 0,
173
+ active: true,
174
+ },
175
+ })
176
+
177
+ expect(wrapper.find('.date-selector-cell').classes()).toContain('active')
178
+ })
179
+
180
+ it('should not have active class when not active', () => {
181
+ wrapper = mount(DaySmall, {
182
+ props: {
183
+ index: 0,
184
+ active: false,
185
+ },
186
+ })
187
+
188
+ expect(wrapper.find('.date-selector-cell').classes()).not.toContain(
189
+ 'active'
190
+ )
191
+ })
192
+
193
+ it('should accept staticDays prop', () => {
194
+ wrapper = mount(DaySmall, {
195
+ props: {
196
+ index: 0,
197
+ staticDays: false,
198
+ },
199
+ })
200
+
201
+ expect(wrapper.exists()).toBe(true)
202
+ })
203
+
204
+ it('should accept loading prop', () => {
205
+ wrapper = mount(DaySmall, {
206
+ props: {
207
+ index: 0,
208
+ loading: false,
209
+ },
210
+ })
211
+
212
+ expect(wrapper.exists()).toBe(true)
213
+ })
214
+
215
+ it('should accept warnings prop', () => {
216
+ const mockWarnings: WarningsMap = {}
217
+
218
+ wrapper = mount(DaySmall, {
219
+ props: {
220
+ index: 0,
221
+ warnings: mockWarnings,
222
+ },
223
+ })
224
+
225
+ expect(wrapper.exists()).toBe(true)
226
+ })
227
+
228
+ it('should accept visibleWarnings prop', () => {
229
+ wrapper = mount(DaySmall, {
230
+ props: {
231
+ index: 0,
232
+ visibleWarnings: ['wind', 'rain'],
233
+ },
234
+ })
235
+
236
+ expect(wrapper.exists()).toBe(true)
237
+ })
238
+ })
239
+
240
+ describe('Theme support', () => {
241
+ it('should apply theme class', () => {
242
+ wrapper = mount(DaySmall, {
243
+ props: {
244
+ index: 0,
245
+ theme: 'dark-theme' as Theme,
246
+ },
247
+ })
248
+
249
+ expect(wrapper.find('.date-selector-cell').classes()).toContain(
250
+ 'dark-theme'
251
+ )
252
+ })
253
+
254
+ it('should support all theme variants', () => {
255
+ const themes: Theme[] = [
256
+ 'light-theme',
257
+ 'dark-theme',
258
+ 'light-gray-theme',
259
+ 'dark-gray-theme',
260
+ ]
261
+
262
+ themes.forEach((theme) => {
263
+ wrapper = mount(DaySmall, {
264
+ props: {
265
+ index: 0,
266
+ theme,
267
+ },
268
+ })
269
+
270
+ expect(wrapper.find('.date-selector-cell').classes()).toContain(theme)
271
+ })
272
+ })
273
+ })
274
+
275
+ describe('CSS classes', () => {
276
+ it('should apply severity class to footer', () => {
277
+ wrapper = mount(DaySmall, {
278
+ props: {
279
+ index: 0,
280
+ input: mockDay,
281
+ },
282
+ })
283
+
284
+ const footer = wrapper.find('.date-selector-cell-footer')
285
+ expect(footer.classes()).toContain('dark-level-3')
286
+ })
287
+
288
+ it('should apply mobile severity class to text', () => {
289
+ wrapper = mount(DaySmall, {
290
+ props: {
291
+ index: 0,
292
+ input: mockDay,
293
+ },
294
+ })
295
+
296
+ const text = wrapper.find('.date-selector-text')
297
+ expect(text.classes()).toContain('mobile-level-3')
298
+ })
299
+ })
300
+
301
+ describe('Language support', () => {
302
+ it('should support Finnish language', () => {
303
+ wrapper = mount(DaySmall, {
304
+ props: {
305
+ index: 0,
306
+ language: 'fi' as Language,
307
+ },
308
+ })
309
+
310
+ expect(wrapper.exists()).toBe(true)
311
+ })
312
+
313
+ it('should support Swedish language', () => {
314
+ wrapper = mount(DaySmall, {
315
+ props: {
316
+ index: 0,
317
+ language: 'sv' as Language,
318
+ },
319
+ })
320
+
321
+ expect(wrapper.exists()).toBe(true)
322
+ })
323
+
324
+ it('should support English language', () => {
325
+ wrapper = mount(DaySmall, {
326
+ props: {
327
+ index: 0,
328
+ language: 'en' as Language,
329
+ },
330
+ })
331
+
332
+ expect(wrapper.exists()).toBe(true)
333
+ })
334
+ })
335
+
336
+ describe('Accessibility', () => {
337
+ it('should have aria-label on main element', () => {
338
+ wrapper = mount(DaySmall, {
339
+ props: {
340
+ index: 0,
341
+ input: mockDay,
342
+ regions: mockRegions,
343
+ language: 'fi' as Language,
344
+ },
345
+ })
346
+
347
+ expect(
348
+ wrapper.find('.date-selector-cell').attributes('aria-label')
349
+ ).toBeDefined()
350
+ })
351
+ })
352
+ })