@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.
Files changed (105) hide show
  1. package/.eslintignore +2 -14
  2. package/.github/workflows/test.yaml +26 -0
  3. package/.nvmrc +1 -0
  4. package/dist/index.html +5 -0
  5. package/dist/index.js +105 -135
  6. package/dist/index.mjs +112 -135
  7. package/dist/locale-en-DCEKDw5G.js +8 -0
  8. package/dist/locale-fi-DPiOM1rB.js +8 -0
  9. package/dist/locale-sv-B0FlbgEF.js +8 -0
  10. package/dist/vendor-Cfkkvdz7.js +21 -0
  11. package/dist/vue/index.mjs +15245 -0
  12. package/dist/vue/style.css +1 -0
  13. package/dist/xml-parser-BiNO9kc-.js +13 -0
  14. package/package.json +60 -24
  15. package/src/AlertClientVue.vue +170 -0
  16. package/src/App.vue +55 -205
  17. package/src/assets/img/ui/arrow-down.svg +4 -11
  18. package/src/assets/img/ui/arrow-up.svg +4 -11
  19. package/src/assets/img/ui/clear.svg +7 -21
  20. package/src/assets/img/ui/close.svg +4 -15
  21. package/src/assets/img/ui/toggle-selected.svg +5 -6
  22. package/src/assets/img/ui/toggle-unselected.svg +5 -6
  23. package/src/assets/img/warning/cold-weather.svg +3 -6
  24. package/src/assets/img/warning/flood-level-3.svg +4 -7
  25. package/src/assets/img/warning/forest-fire-weather.svg +2 -6
  26. package/src/assets/img/warning/grass-fire-weather.svg +2 -6
  27. package/src/assets/img/warning/hot-weather.svg +3 -6
  28. package/src/assets/img/warning/pedestrian-safety.svg +3 -7
  29. package/src/assets/img/warning/rain.svg +2 -7
  30. package/src/assets/img/warning/sea-icing.svg +2 -6
  31. package/src/assets/img/warning/sea-thunder-storm.svg +2 -5
  32. package/src/assets/img/warning/sea-water-height-high-water.svg +3 -8
  33. package/src/assets/img/warning/sea-water-height-shallow-water.svg +3 -7
  34. package/src/assets/img/warning/sea-wave-height.svg +4 -7
  35. package/src/assets/img/warning/sea-wind-legend.svg +2 -5
  36. package/src/assets/img/warning/sea-wind.svg +2 -5
  37. package/src/assets/img/warning/several.svg +2 -5
  38. package/src/assets/img/warning/thunder-storm.svg +2 -5
  39. package/src/assets/img/warning/traffic-weather.svg +2 -6
  40. package/src/assets/img/warning/uv-note.svg +2 -6
  41. package/src/assets/img/warning/wind.svg +2 -5
  42. package/src/components/AlertClient.vue +41 -19
  43. package/src/components/CollapsiblePanel.vue +284 -0
  44. package/src/components/DayLarge.vue +12 -7
  45. package/src/components/DaySmall.vue +16 -6
  46. package/src/components/Days.vue +76 -51
  47. package/src/components/DescriptionWarning.vue +15 -8
  48. package/src/components/GrayScaleToggle.vue +11 -6
  49. package/src/components/Legend.vue +36 -248
  50. package/src/components/MapLarge.vue +41 -42
  51. package/src/components/MapSmall.vue +44 -28
  52. package/src/components/PopupRow.vue +6 -3
  53. package/src/components/Region.vue +30 -15
  54. package/src/components/RegionWarning.vue +6 -5
  55. package/src/components/Regions.vue +50 -19
  56. package/src/components/Warning.vue +18 -10
  57. package/src/components/Warnings.vue +36 -21
  58. package/src/main.js +1 -0
  59. package/src/mixins/alertClientCore.js +210 -0
  60. package/src/mixins/config.js +262 -256
  61. package/src/mixins/utils.js +40 -26
  62. package/src/plugins/index.js +1 -1
  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/vue.js +41 -0
  67. package/svgo.config.js +45 -0
  68. package/tests/README.md +430 -0
  69. package/tests/fixtures/mockWarningData.js +135 -0
  70. package/tests/integration/warning-flow.spec.js +452 -0
  71. package/tests/setup.js +41 -0
  72. package/tests/unit/components/AlertClient.spec.js +734 -0
  73. package/tests/unit/components/DayLarge.spec.js +281 -0
  74. package/tests/unit/components/DaySmall.spec.js +278 -0
  75. package/tests/unit/components/Days.spec.js +565 -0
  76. package/tests/unit/components/DescriptionWarning.spec.js +432 -0
  77. package/tests/unit/components/GrayScaleToggle.spec.js +311 -0
  78. package/tests/unit/components/Legend.spec.js +223 -0
  79. package/tests/unit/components/MapLarge.spec.js +276 -0
  80. package/tests/unit/components/MapSmall.spec.js +226 -0
  81. package/tests/unit/components/PopupRow.spec.js +261 -0
  82. package/tests/unit/components/Region.spec.js +430 -0
  83. package/tests/unit/components/RegionWarning.snapshot.spec.js +73 -0
  84. package/tests/unit/components/RegionWarning.spec.js +408 -0
  85. package/tests/unit/components/Regions.spec.js +335 -0
  86. package/tests/unit/components/Warning.snapshot.spec.js +107 -0
  87. package/tests/unit/components/Warning.spec.js +472 -0
  88. package/tests/unit/components/Warnings.spec.js +329 -0
  89. package/tests/unit/components/__snapshots__/RegionWarning.snapshot.spec.js.snap +21 -0
  90. package/tests/unit/components/__snapshots__/Warning.snapshot.spec.js.snap +199 -0
  91. package/tests/unit/mixins/config.spec.js +269 -0
  92. package/tests/unit/mixins/i18n.spec.js +115 -0
  93. package/tests/unit/mixins/keycodes.spec.js +37 -0
  94. package/tests/unit/mixins/utils.spec.js +624 -0
  95. package/vite.config.js +96 -26
  96. package/vitest.config.js +40 -0
  97. package/dist/index.mjs.map +0 -1
  98. package/dist/index.relative.html +0 -19
  99. package/dist/index.start.html +0 -20
  100. package/playwright.config.ts +0 -18
  101. package/public/index.relative.html +0 -19
  102. package/public/index.start.html +0 -20
  103. package/src/mixins/panzoom.js +0 -900
  104. package/test/snapshot.test.ts +0 -126
  105. package/vitest.config.ts +0 -6
@@ -0,0 +1,261 @@
1
+ import { describe, it, expect, afterEach } from 'vitest'
2
+ import { mount } from '@vue/test-utils'
3
+ import PopupRow from '@/components/PopupRow.vue'
4
+
5
+ const mockWarning = {
6
+ type: 'wind',
7
+ severity: 3,
8
+ direction: 270,
9
+ text: '15',
10
+ interval: '14:00 – 18:00',
11
+ }
12
+
13
+ describe('PopupRow.vue', () => {
14
+ let wrapper
15
+
16
+ afterEach(() => {
17
+ if (wrapper) {
18
+ wrapper.unmount()
19
+ }
20
+ })
21
+
22
+ describe('Component mounting', () => {
23
+ it('should mount with required props', () => {
24
+ wrapper = mount(PopupRow, {
25
+ props: {
26
+ input: mockWarning,
27
+ },
28
+ })
29
+
30
+ expect(wrapper.exists()).toBe(true)
31
+ })
32
+
33
+ it('should use default empty object for input prop', () => {
34
+ wrapper = mount(PopupRow, {
35
+ props: {
36
+ input: {
37
+ type: 'rain',
38
+ severity: 2,
39
+ },
40
+ },
41
+ })
42
+
43
+ expect(wrapper.exists()).toBe(true)
44
+ expect(wrapper.vm.input).toBeDefined()
45
+ })
46
+ })
47
+
48
+ describe('Fields mixin integration', () => {
49
+ it('should compute typeClass from warning type', () => {
50
+ wrapper = mount(PopupRow, {
51
+ props: {
52
+ input: mockWarning,
53
+ },
54
+ })
55
+
56
+ expect(wrapper.vm.typeClass).toBe('wind')
57
+ })
58
+
59
+ it('should compute typeClass for thunderStorm', () => {
60
+ wrapper = mount(PopupRow, {
61
+ props: {
62
+ input: {
63
+ type: 'thunderStorm',
64
+ severity: 4,
65
+ },
66
+ },
67
+ })
68
+
69
+ expect(wrapper.vm.typeClass).toBe('thunder-storm')
70
+ })
71
+
72
+ it('should compute rotation from direction', () => {
73
+ wrapper = mount(PopupRow, {
74
+ props: {
75
+ input: mockWarning,
76
+ },
77
+ })
78
+
79
+ expect(wrapper.vm.rotation).toBe(270)
80
+ })
81
+
82
+ it('should compute invertedRotation', () => {
83
+ wrapper = mount(PopupRow, {
84
+ props: {
85
+ input: mockWarning,
86
+ },
87
+ })
88
+
89
+ expect(wrapper.vm.invertedRotation).toBe(90)
90
+ })
91
+
92
+ it('should compute severity', () => {
93
+ wrapper = mount(PopupRow, {
94
+ props: {
95
+ input: mockWarning,
96
+ },
97
+ })
98
+
99
+ expect(wrapper.vm.severity).toBe(3)
100
+ })
101
+
102
+ it('should return 0 for invalid severity', () => {
103
+ wrapper = mount(PopupRow, {
104
+ props: {
105
+ input: {
106
+ type: 'wind',
107
+ severity: 1,
108
+ },
109
+ },
110
+ })
111
+
112
+ expect(wrapper.vm.severity).toBe(0)
113
+ })
114
+ })
115
+
116
+ describe('Content rendering', () => {
117
+ it('should render interval text', () => {
118
+ wrapper = mount(PopupRow, {
119
+ props: {
120
+ input: mockWarning,
121
+ },
122
+ })
123
+
124
+ expect(wrapper.text()).toContain('14:00 – 18:00')
125
+ })
126
+
127
+ it('should render warning text in symbol', () => {
128
+ wrapper = mount(PopupRow, {
129
+ props: {
130
+ input: mockWarning,
131
+ },
132
+ })
133
+
134
+ expect(wrapper.find('.symbol-text').text()).toBe('15')
135
+ })
136
+
137
+ it('should render empty text when not provided', () => {
138
+ const warningNoText = {
139
+ type: 'rain',
140
+ severity: 2,
141
+ interval: '10:00 – 12:00',
142
+ }
143
+
144
+ wrapper = mount(PopupRow, {
145
+ props: {
146
+ input: warningNoText,
147
+ },
148
+ })
149
+
150
+ expect(wrapper.find('.symbol-text').text()).toBe('')
151
+ })
152
+ })
153
+
154
+ describe('CSS classes', () => {
155
+ it('should apply severity level class to symbol', () => {
156
+ wrapper = mount(PopupRow, {
157
+ props: {
158
+ input: mockWarning,
159
+ },
160
+ })
161
+
162
+ const symbol = wrapper.find('.popup-table-symbol-cell')
163
+ expect(symbol.classes()).toContain('level-3')
164
+ })
165
+
166
+ it('should apply type class to symbol', () => {
167
+ wrapper = mount(PopupRow, {
168
+ props: {
169
+ input: mockWarning,
170
+ },
171
+ })
172
+
173
+ const symbol = wrapper.find('.popup-table-symbol-cell')
174
+ expect(symbol.classes()).toContain('wind')
175
+ })
176
+
177
+ it('should apply rotation class', () => {
178
+ wrapper = mount(PopupRow, {
179
+ props: {
180
+ input: mockWarning,
181
+ },
182
+ })
183
+
184
+ const symbol = wrapper.find('.popup-table-symbol-cell')
185
+ expect(symbol.classes()).toContain('transform-rotate-270')
186
+ })
187
+
188
+ it('should apply inverted rotation to text', () => {
189
+ wrapper = mount(PopupRow, {
190
+ props: {
191
+ input: mockWarning,
192
+ },
193
+ })
194
+
195
+ const text = wrapper.find('.symbol-text')
196
+ expect(text.classes()).toContain('transform-rotate-90')
197
+ })
198
+
199
+ it('should apply text level class to interval cell', () => {
200
+ wrapper = mount(PopupRow, {
201
+ props: {
202
+ input: mockWarning,
203
+ },
204
+ })
205
+
206
+ const textCell = wrapper.find('.popup-table-text-cell')
207
+ expect(textCell.classes()).toContain('text-level-3')
208
+ })
209
+
210
+ it('should hide level-0 warnings', () => {
211
+ const warningLevel0 = {
212
+ type: 'wind',
213
+ severity: 0,
214
+ }
215
+
216
+ wrapper = mount(PopupRow, {
217
+ props: {
218
+ input: warningLevel0,
219
+ },
220
+ })
221
+
222
+ // Level-0 warning images should have display: none via CSS
223
+ const symbol = wrapper.find('.popup-table-symbol-cell')
224
+ expect(symbol.classes()).toContain('level-0')
225
+ })
226
+ })
227
+
228
+ describe('Table structure', () => {
229
+ it('should render as table row', () => {
230
+ wrapper = mount(PopupRow, {
231
+ props: {
232
+ input: mockWarning,
233
+ },
234
+ })
235
+
236
+ expect(wrapper.find('.popup-table-row').exists()).toBe(true)
237
+ })
238
+
239
+ it('should have two table cells', () => {
240
+ wrapper = mount(PopupRow, {
241
+ props: {
242
+ input: mockWarning,
243
+ },
244
+ })
245
+
246
+ const cells = wrapper.findAll('.popup-table-cell')
247
+ expect(cells).toHaveLength(2)
248
+ })
249
+
250
+ it('should have symbol cell and text cell', () => {
251
+ wrapper = mount(PopupRow, {
252
+ props: {
253
+ input: mockWarning,
254
+ },
255
+ })
256
+
257
+ expect(wrapper.find('.popup-table-symbol-cell').exists()).toBe(true)
258
+ expect(wrapper.find('.popup-table-text-cell').exists()).toBe(true)
259
+ })
260
+ })
261
+ })
@@ -0,0 +1,430 @@
1
+ import { describe, it, expect, afterEach } from 'vitest'
2
+ import { mount } from '@vue/test-utils'
3
+ import Region from '@/components/Region.vue'
4
+
5
+ const mockWarningInput = [
6
+ {
7
+ type: 'wind',
8
+ identifiers: ['warning-1', 'warning-2'],
9
+ coverage: 100,
10
+ },
11
+ {
12
+ type: 'rain',
13
+ identifiers: ['warning-3'],
14
+ coverage: 50,
15
+ },
16
+ ]
17
+
18
+ const mockWarnings = {
19
+ 'warning-1': {
20
+ id: 'warning-1',
21
+ type: 'wind',
22
+ severity: 3,
23
+ info: {
24
+ fi: 'Tuulivaroitus',
25
+ sv: 'Vindvarning',
26
+ en: 'Wind warning',
27
+ },
28
+ },
29
+ 'warning-2': {
30
+ id: 'warning-2',
31
+ type: 'wind',
32
+ severity: 4,
33
+ info: {
34
+ fi: 'Toinen tuulivaroitus',
35
+ sv: 'Andra vindvarning',
36
+ en: 'Second wind warning',
37
+ },
38
+ },
39
+ 'warning-3': {
40
+ id: 'warning-3',
41
+ type: 'rain',
42
+ severity: 2,
43
+ info: {
44
+ fi: 'Sadevaroitus',
45
+ sv: 'Regnvarning',
46
+ en: 'Rain warning',
47
+ },
48
+ },
49
+ }
50
+
51
+ describe('Region.vue', () => {
52
+ let wrapper
53
+
54
+ afterEach(() => {
55
+ if (wrapper) {
56
+ wrapper.unmount()
57
+ }
58
+ })
59
+
60
+ describe('Component mounting', () => {
61
+ it('should mount with required props', () => {
62
+ wrapper = mount(Region, {
63
+ props: {
64
+ type: 'land',
65
+ code: 'county.1',
66
+ name: 'Uusimaa',
67
+ input: mockWarningInput,
68
+ warnings: mockWarnings,
69
+ theme: 'light-theme',
70
+ language: 'fi',
71
+ },
72
+ })
73
+
74
+ expect(wrapper.exists()).toBe(true)
75
+ })
76
+
77
+ it('should initialize with open false', () => {
78
+ wrapper = mount(Region, {
79
+ props: {
80
+ type: 'land',
81
+ code: 'county.1',
82
+ name: 'Uusimaa',
83
+ input: mockWarningInput,
84
+ warnings: mockWarnings,
85
+ theme: 'light-theme',
86
+ language: 'fi',
87
+ },
88
+ })
89
+
90
+ expect(wrapper.vm.open).toBe(false)
91
+ })
92
+ })
93
+
94
+ describe('Computed properties', () => {
95
+ it('should compute identifier from code', () => {
96
+ wrapper = mount(Region, {
97
+ props: {
98
+ type: 'land',
99
+ code: 'county.1',
100
+ name: 'Uusimaa',
101
+ input: mockWarningInput,
102
+ warnings: mockWarnings,
103
+ theme: 'light-theme',
104
+ language: 'fi',
105
+ },
106
+ })
107
+
108
+ expect(wrapper.vm.identifier).toBe('accordion-item-county.1')
109
+ })
110
+
111
+ it('should compute regionName from translation', () => {
112
+ wrapper = mount(Region, {
113
+ props: {
114
+ type: 'land',
115
+ code: 'county.1',
116
+ name: 'Uusimaa',
117
+ input: mockWarningInput,
118
+ warnings: mockWarnings,
119
+ theme: 'light-theme',
120
+ language: 'fi',
121
+ },
122
+ })
123
+
124
+ expect(typeof wrapper.vm.regionName).toBe('string')
125
+ })
126
+
127
+ it('should compute warningsSummary from input', () => {
128
+ wrapper = mount(Region, {
129
+ props: {
130
+ type: 'land',
131
+ code: 'county.1',
132
+ name: 'Uusimaa',
133
+ input: mockWarningInput,
134
+ warnings: mockWarnings,
135
+ theme: 'light-theme',
136
+ language: 'fi',
137
+ },
138
+ })
139
+
140
+ expect(Array.isArray(wrapper.vm.warningsSummary)).toBe(true)
141
+ expect(wrapper.vm.warningsSummary.length).toBeGreaterThan(0)
142
+ })
143
+
144
+ it('should filter warnings by coverage criterion', () => {
145
+ wrapper = mount(Region, {
146
+ props: {
147
+ type: 'land',
148
+ code: 'county.1',
149
+ name: 'Uusimaa',
150
+ input: mockWarningInput,
151
+ warnings: mockWarnings,
152
+ theme: 'light-theme',
153
+ language: 'fi',
154
+ },
155
+ })
156
+
157
+ // Coverage >= 20% should be included
158
+ const summary = wrapper.vm.warningsSummary
159
+ expect(summary.some((w) => w.type === 'wind')).toBe(true)
160
+ })
161
+
162
+ it('should compute reducedWarnings with all warning details', () => {
163
+ wrapper = mount(Region, {
164
+ props: {
165
+ type: 'land',
166
+ code: 'county.1',
167
+ name: 'Uusimaa',
168
+ input: mockWarningInput,
169
+ warnings: mockWarnings,
170
+ theme: 'light-theme',
171
+ language: 'fi',
172
+ },
173
+ })
174
+
175
+ const reduced = wrapper.vm.reducedWarnings
176
+ expect(Array.isArray(reduced)).toBe(true)
177
+ expect(reduced.length).toBeGreaterThanOrEqual(
178
+ wrapper.vm.warningsSummary.length
179
+ )
180
+ })
181
+
182
+ it('should compute ariaButton based on open state', () => {
183
+ wrapper = mount(Region, {
184
+ props: {
185
+ type: 'land',
186
+ code: 'county.1',
187
+ name: 'Uusimaa',
188
+ input: mockWarningInput,
189
+ warnings: mockWarnings,
190
+ theme: 'light-theme',
191
+ language: 'fi',
192
+ },
193
+ })
194
+
195
+ const closedLabel = wrapper.vm.ariaButton
196
+ wrapper.vm.open = true
197
+ const openLabel = wrapper.vm.ariaButton
198
+
199
+ expect(closedLabel).not.toBe(openLabel)
200
+ expect(typeof closedLabel).toBe('string')
201
+ expect(typeof openLabel).toBe('string')
202
+ })
203
+
204
+ it('should compute ariaInfo with warning details', () => {
205
+ wrapper = mount(Region, {
206
+ props: {
207
+ type: 'land',
208
+ code: 'county.1',
209
+ name: 'Uusimaa',
210
+ input: mockWarningInput,
211
+ warnings: mockWarnings,
212
+ theme: 'light-theme',
213
+ language: 'fi',
214
+ },
215
+ })
216
+
217
+ const ariaInfo = wrapper.vm.ariaInfo
218
+ expect(Array.isArray(ariaInfo)).toBe(true)
219
+ expect(ariaInfo.length).toBe(wrapper.vm.reducedWarnings.length)
220
+ })
221
+ })
222
+
223
+ describe('Toggle functionality', () => {
224
+ it('should toggle open state on region toggle', () => {
225
+ wrapper = mount(Region, {
226
+ props: {
227
+ type: 'land',
228
+ code: 'county.1',
229
+ name: 'Uusimaa',
230
+ input: mockWarningInput,
231
+ warnings: mockWarnings,
232
+ theme: 'light-theme',
233
+ language: 'fi',
234
+ },
235
+ })
236
+
237
+ const initialOpen = wrapper.vm.open
238
+ wrapper.vm.onRegionToggle()
239
+ expect(wrapper.vm.open).toBe(!initialOpen)
240
+ })
241
+
242
+ it('should apply correct aria-expanded attribute', async () => {
243
+ wrapper = mount(Region, {
244
+ props: {
245
+ type: 'land',
246
+ code: 'county.1',
247
+ name: 'Uusimaa',
248
+ input: mockWarningInput,
249
+ warnings: mockWarnings,
250
+ theme: 'light-theme',
251
+ language: 'fi',
252
+ },
253
+ })
254
+
255
+ const trigger = wrapper.find('.accordion-trigger')
256
+ expect(trigger.attributes('aria-expanded')).toBe('false')
257
+
258
+ wrapper.vm.open = true
259
+ await wrapper.vm.$nextTick()
260
+
261
+ expect(trigger.attributes('aria-expanded')).toBe('true')
262
+ })
263
+
264
+ it('should apply collapsed class when closed', () => {
265
+ wrapper = mount(Region, {
266
+ props: {
267
+ type: 'land',
268
+ code: 'county.1',
269
+ name: 'Uusimaa',
270
+ input: mockWarningInput,
271
+ warnings: mockWarnings,
272
+ theme: 'light-theme',
273
+ language: 'fi',
274
+ },
275
+ })
276
+
277
+ const trigger = wrapper.find('.accordion-trigger')
278
+ expect(trigger.classes()).toContain('collapsed')
279
+ })
280
+
281
+ it('should not apply collapsed class when open', async () => {
282
+ wrapper = mount(Region, {
283
+ props: {
284
+ type: 'land',
285
+ code: 'county.1',
286
+ name: 'Uusimaa',
287
+ input: mockWarningInput,
288
+ warnings: mockWarnings,
289
+ theme: 'light-theme',
290
+ language: 'fi',
291
+ },
292
+ })
293
+
294
+ wrapper.vm.open = true
295
+ await wrapper.vm.$nextTick()
296
+
297
+ const trigger = wrapper.find('.accordion-trigger')
298
+ expect(trigger.classes()).not.toContain('collapsed')
299
+ })
300
+ })
301
+
302
+ describe('Content rendering', () => {
303
+ it('should render region name', () => {
304
+ wrapper = mount(Region, {
305
+ props: {
306
+ type: 'land',
307
+ code: 'county.1',
308
+ name: 'Uusimaa',
309
+ input: mockWarningInput,
310
+ warnings: mockWarnings,
311
+ theme: 'light-theme',
312
+ language: 'fi',
313
+ },
314
+ })
315
+
316
+ expect(wrapper.text()).toContain(wrapper.vm.regionName)
317
+ })
318
+
319
+ it('should render RegionWarning components', () => {
320
+ wrapper = mount(Region, {
321
+ props: {
322
+ type: 'land',
323
+ code: 'county.1',
324
+ name: 'Uusimaa',
325
+ input: mockWarningInput,
326
+ warnings: mockWarnings,
327
+ theme: 'light-theme',
328
+ language: 'fi',
329
+ },
330
+ })
331
+
332
+ const regionWarnings = wrapper.findAllComponents({
333
+ name: 'RegionWarning',
334
+ })
335
+ expect(regionWarnings.length).toBe(wrapper.vm.warningsSummary.length)
336
+ })
337
+
338
+ it('should hide accordion panel when closed', () => {
339
+ wrapper = mount(Region, {
340
+ props: {
341
+ type: 'land',
342
+ code: 'county.1',
343
+ name: 'Uusimaa',
344
+ input: mockWarningInput,
345
+ warnings: mockWarnings,
346
+ theme: 'light-theme',
347
+ language: 'fi',
348
+ },
349
+ })
350
+
351
+ const panel = wrapper.find('.accordion-panel')
352
+ expect(panel.attributes('hidden')).toBe('')
353
+ })
354
+
355
+ it('should show accordion panel when open', async () => {
356
+ wrapper = mount(Region, {
357
+ props: {
358
+ type: 'land',
359
+ code: 'county.1',
360
+ name: 'Uusimaa',
361
+ input: mockWarningInput,
362
+ warnings: mockWarnings,
363
+ theme: 'light-theme',
364
+ language: 'fi',
365
+ },
366
+ })
367
+
368
+ wrapper.vm.open = true
369
+ await wrapper.vm.$nextTick()
370
+
371
+ const panel = wrapper.find('.accordion-panel')
372
+ expect(panel.attributes('hidden')).toBeUndefined()
373
+ })
374
+ })
375
+
376
+ describe('Accessibility', () => {
377
+ it('should have correct aria-controls attribute', () => {
378
+ wrapper = mount(Region, {
379
+ props: {
380
+ type: 'land',
381
+ code: 'county.1',
382
+ name: 'Uusimaa',
383
+ input: mockWarningInput,
384
+ warnings: mockWarnings,
385
+ theme: 'light-theme',
386
+ language: 'fi',
387
+ },
388
+ })
389
+
390
+ const trigger = wrapper.find('.accordion-trigger')
391
+ expect(trigger.attributes('aria-controls')).toBe(
392
+ 'accordion-section-county.1'
393
+ )
394
+ })
395
+
396
+ it('should have matching id on panel', () => {
397
+ wrapper = mount(Region, {
398
+ props: {
399
+ type: 'land',
400
+ code: 'county.1',
401
+ name: 'Uusimaa',
402
+ input: mockWarningInput,
403
+ warnings: mockWarnings,
404
+ theme: 'light-theme',
405
+ language: 'fi',
406
+ },
407
+ })
408
+
409
+ const panel = wrapper.find('.accordion-panel')
410
+ expect(panel.attributes('id')).toBe('accordion-section-county.1')
411
+ })
412
+
413
+ it('should have role region on panel', () => {
414
+ wrapper = mount(Region, {
415
+ props: {
416
+ type: 'land',
417
+ code: 'county.1',
418
+ name: 'Uusimaa',
419
+ input: mockWarningInput,
420
+ warnings: mockWarnings,
421
+ theme: 'light-theme',
422
+ language: 'fi',
423
+ },
424
+ })
425
+
426
+ const panel = wrapper.find('.accordion-panel')
427
+ expect(panel.attributes('role')).toBe('region')
428
+ })
429
+ })
430
+ })