@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,281 @@
1
+ import { describe, it, expect, afterEach } from 'vitest'
2
+ import { mount } from '@vue/test-utils'
3
+ import DayLarge from '@/components/DayLarge.vue'
4
+
5
+ const mockInput = {
6
+ weekdayName: 'thursday',
7
+ day: 31,
8
+ month: 10,
9
+ year: 2025,
10
+ severity: 3,
11
+ updatedDate: '31.10.2025',
12
+ updatedTime: '14:00',
13
+ }
14
+
15
+ const mockRegions = {
16
+ land: [],
17
+ sea: [],
18
+ }
19
+
20
+ describe('DayLarge.vue', () => {
21
+ let wrapper
22
+
23
+ afterEach(() => {
24
+ if (wrapper) {
25
+ wrapper.unmount()
26
+ }
27
+ })
28
+
29
+ describe('Component mounting', () => {
30
+ it('should mount with required props', () => {
31
+ wrapper = mount(DayLarge, {
32
+ props: {
33
+ index: 0,
34
+ input: mockInput,
35
+ regions: mockRegions,
36
+ geometryId: 2021,
37
+ language: 'fi',
38
+ },
39
+ })
40
+
41
+ expect(wrapper.exists()).toBe(true)
42
+ })
43
+ })
44
+
45
+ describe('Computed properties', () => {
46
+ it('should compute warningsTitle', () => {
47
+ wrapper = mount(DayLarge, {
48
+ props: {
49
+ index: 0,
50
+ input: mockInput,
51
+ regions: mockRegions,
52
+ geometryId: 2021,
53
+ language: 'fi',
54
+ },
55
+ })
56
+
57
+ expect(wrapper.vm.warningsTitle).toBeTruthy()
58
+ expect(typeof wrapper.vm.warningsTitle).toBe('string')
59
+ })
60
+
61
+ it('should compute updatedTitle', () => {
62
+ wrapper = mount(DayLarge, {
63
+ props: {
64
+ index: 0,
65
+ input: mockInput,
66
+ regions: mockRegions,
67
+ geometryId: 2021,
68
+ language: 'fi',
69
+ },
70
+ })
71
+
72
+ expect(wrapper.vm.updatedTitle).toBeTruthy()
73
+ })
74
+
75
+ it('should compute atTime', () => {
76
+ wrapper = mount(DayLarge, {
77
+ props: {
78
+ index: 0,
79
+ input: mockInput,
80
+ regions: mockRegions,
81
+ geometryId: 2021,
82
+ language: 'fi',
83
+ },
84
+ })
85
+
86
+ expect(wrapper.vm.atTime).toBeTruthy()
87
+ })
88
+
89
+ it('should format static warnings date', () => {
90
+ wrapper = mount(DayLarge, {
91
+ props: {
92
+ index: 0,
93
+ input: mockInput,
94
+ regions: mockRegions,
95
+ geometryId: 2021,
96
+ staticDays: true,
97
+ language: 'fi',
98
+ },
99
+ })
100
+
101
+ expect(wrapper.vm.warningsDate).toBe('31.10.2025')
102
+ })
103
+
104
+ it('should format dynamic warnings date with time range', () => {
105
+ wrapper = mount(DayLarge, {
106
+ props: {
107
+ index: 0,
108
+ input: mockInput,
109
+ regions: mockRegions,
110
+ geometryId: 2021,
111
+ staticDays: false,
112
+ timeOffset: 0,
113
+ language: 'fi',
114
+ },
115
+ })
116
+
117
+ const warningsDate = wrapper.vm.warningsDate
118
+
119
+ expect(warningsDate).toContain('31.10.2025')
120
+ expect(warningsDate).toContain('1.11.2025')
121
+ })
122
+
123
+ it('should return empty string when date data is missing', () => {
124
+ const incompleteInput = {
125
+ weekdayName: 'thursday',
126
+ severity: 2,
127
+ }
128
+
129
+ wrapper = mount(DayLarge, {
130
+ props: {
131
+ index: 0,
132
+ input: incompleteInput,
133
+ regions: mockRegions,
134
+ geometryId: 2021,
135
+ language: 'fi',
136
+ },
137
+ })
138
+
139
+ expect(wrapper.vm.warningsDate).toBe('')
140
+ })
141
+
142
+ it('should display updatedDate from input', () => {
143
+ wrapper = mount(DayLarge, {
144
+ props: {
145
+ index: 0,
146
+ input: mockInput,
147
+ regions: mockRegions,
148
+ geometryId: 2021,
149
+ language: 'fi',
150
+ },
151
+ })
152
+
153
+ expect(wrapper.vm.updatedDate).toBe('31.10.2025')
154
+ })
155
+
156
+ it('should display updatedTime from input', () => {
157
+ wrapper = mount(DayLarge, {
158
+ props: {
159
+ index: 0,
160
+ input: mockInput,
161
+ regions: mockRegions,
162
+ geometryId: 2021,
163
+ language: 'fi',
164
+ },
165
+ })
166
+
167
+ expect(wrapper.vm.updatedTime).toBe('14:00')
168
+ })
169
+ })
170
+
171
+ describe('Event handling', () => {
172
+ it('should emit loaded event when child component loads', () => {
173
+ wrapper = mount(DayLarge, {
174
+ props: {
175
+ index: 0,
176
+ input: mockInput,
177
+ regions: mockRegions,
178
+ geometryId: 2021,
179
+ language: 'fi',
180
+ },
181
+ })
182
+
183
+ wrapper.vm.onLoaded(true)
184
+
185
+ expect(wrapper.emitted('loaded')).toBeTruthy()
186
+ expect(wrapper.emitted('loaded')[0]).toEqual([true])
187
+ })
188
+
189
+ it('should not emit loaded event when loaded is false', () => {
190
+ wrapper = mount(DayLarge, {
191
+ props: {
192
+ index: 0,
193
+ input: mockInput,
194
+ regions: mockRegions,
195
+ geometryId: 2021,
196
+ language: 'fi',
197
+ },
198
+ })
199
+
200
+ wrapper.vm.onLoaded(false)
201
+
202
+ expect(wrapper.emitted('loaded')).toBeFalsy()
203
+ })
204
+ })
205
+
206
+ describe('Content rendering', () => {
207
+ it('should render warnings title', () => {
208
+ wrapper = mount(DayLarge, {
209
+ props: {
210
+ index: 0,
211
+ input: mockInput,
212
+ regions: mockRegions,
213
+ geometryId: 2021,
214
+ language: 'fi',
215
+ },
216
+ })
217
+
218
+ const text = wrapper.text()
219
+ expect(text).toBeTruthy()
220
+ })
221
+
222
+ it('should render updated date and time', () => {
223
+ wrapper = mount(DayLarge, {
224
+ props: {
225
+ index: 0,
226
+ input: mockInput,
227
+ regions: mockRegions,
228
+ geometryId: 2021,
229
+ language: 'fi',
230
+ },
231
+ })
232
+
233
+ expect(wrapper.text()).toContain('31.10.2025')
234
+ expect(wrapper.text()).toContain('14:00')
235
+ })
236
+ })
237
+
238
+ describe('Props validation', () => {
239
+ it('should use default empty object for input', () => {
240
+ wrapper = mount(DayLarge, {
241
+ props: {
242
+ index: 0,
243
+ regions: mockRegions,
244
+ geometryId: 2021,
245
+ language: 'fi',
246
+ },
247
+ })
248
+
249
+ expect(wrapper.vm.input).toEqual({})
250
+ })
251
+
252
+ it('should accept spinnerEnabled prop', () => {
253
+ wrapper = mount(DayLarge, {
254
+ props: {
255
+ index: 0,
256
+ input: mockInput,
257
+ regions: mockRegions,
258
+ geometryId: 2021,
259
+ spinnerEnabled: false,
260
+ language: 'fi',
261
+ },
262
+ })
263
+
264
+ expect(wrapper.vm.spinnerEnabled).toBe(false)
265
+ })
266
+
267
+ it('should use default spinnerEnabled true', () => {
268
+ wrapper = mount(DayLarge, {
269
+ props: {
270
+ index: 0,
271
+ input: mockInput,
272
+ regions: mockRegions,
273
+ geometryId: 2021,
274
+ language: 'fi',
275
+ },
276
+ })
277
+
278
+ expect(wrapper.vm.spinnerEnabled).toBe(true)
279
+ })
280
+ })
281
+ })
@@ -0,0 +1,278 @@
1
+ import { describe, it, expect, afterEach } from 'vitest'
2
+ import { mount } from '@vue/test-utils'
3
+ import DaySmall from '@/components/DaySmall.vue'
4
+
5
+ const mockInput = {
6
+ weekdayName: 'thursday',
7
+ day: 31,
8
+ month: 10,
9
+ year: 2025,
10
+ severity: 3,
11
+ updatedDate: '31.10.2025',
12
+ updatedTime: '14:00',
13
+ }
14
+
15
+ const mockRegions = {
16
+ land: [
17
+ {
18
+ key: 'county.1',
19
+ name: 'Uusimaa',
20
+ warnings: [],
21
+ },
22
+ ],
23
+ sea: [
24
+ {
25
+ key: 'sea_region.B1N',
26
+ name: 'Perämeri',
27
+ warnings: [],
28
+ },
29
+ ],
30
+ }
31
+
32
+ describe('DaySmall.vue', () => {
33
+ let wrapper
34
+
35
+ afterEach(() => {
36
+ if (wrapper) {
37
+ wrapper.unmount()
38
+ }
39
+ })
40
+
41
+ describe('Component mounting', () => {
42
+ it('should mount with required props', () => {
43
+ wrapper = mount(DaySmall, {
44
+ props: {
45
+ index: 0,
46
+ input: mockInput,
47
+ regions: mockRegions,
48
+ geometryId: 2021,
49
+ language: 'fi',
50
+ },
51
+ })
52
+
53
+ expect(wrapper.exists()).toBe(true)
54
+ })
55
+ })
56
+
57
+ describe('Computed properties', () => {
58
+ it('should compute weekday from input', () => {
59
+ wrapper = mount(DaySmall, {
60
+ props: {
61
+ index: 0,
62
+ input: mockInput,
63
+ regions: mockRegions,
64
+ geometryId: 2021,
65
+ language: 'fi',
66
+ },
67
+ })
68
+
69
+ // Weekday is computed via t() which might return empty for unknown keys
70
+ expect(typeof wrapper.vm.weekday).toBe('string')
71
+ })
72
+
73
+ it('should compute severity from input', () => {
74
+ wrapper = mount(DaySmall, {
75
+ props: {
76
+ index: 0,
77
+ input: mockInput,
78
+ regions: mockRegions,
79
+ geometryId: 2021,
80
+ language: 'fi',
81
+ },
82
+ })
83
+
84
+ expect(wrapper.vm.severity).toBe(3)
85
+ })
86
+
87
+ it('should format static date correctly', () => {
88
+ wrapper = mount(DaySmall, {
89
+ props: {
90
+ index: 0,
91
+ input: mockInput,
92
+ regions: mockRegions,
93
+ geometryId: 2021,
94
+ staticDays: true,
95
+ language: 'fi',
96
+ },
97
+ })
98
+
99
+ expect(wrapper.vm.date).toBe('31.10.')
100
+ })
101
+
102
+ it('should format dynamic date correctly', () => {
103
+ wrapper = mount(DaySmall, {
104
+ props: {
105
+ index: 0,
106
+ input: mockInput,
107
+ regions: mockRegions,
108
+ geometryId: 2021,
109
+ staticDays: false,
110
+ language: 'fi',
111
+ },
112
+ })
113
+
114
+ expect(wrapper.vm.date).toBe('0...24 h')
115
+ })
116
+
117
+ it('should show correct time ranges for dynamic days', () => {
118
+ const timeRanges = [
119
+ '0...24 h',
120
+ '24...48 h',
121
+ '48...72 h',
122
+ '72...96 h',
123
+ '96...120 h',
124
+ ]
125
+
126
+ timeRanges.forEach((expected, index) => {
127
+ wrapper = mount(DaySmall, {
128
+ props: {
129
+ index,
130
+ input: mockInput,
131
+ regions: mockRegions,
132
+ geometryId: 2021,
133
+ staticDays: false,
134
+ language: 'fi',
135
+ },
136
+ })
137
+
138
+ expect(wrapper.vm.date).toBe(expected)
139
+ })
140
+ })
141
+
142
+ it('should generate aria label with region counts', () => {
143
+ wrapper = mount(DaySmall, {
144
+ props: {
145
+ index: 0,
146
+ input: mockInput,
147
+ regions: mockRegions,
148
+ geometryId: 2021,
149
+ language: 'fi',
150
+ },
151
+ })
152
+
153
+ const ariaLabel = wrapper.vm.ariaLabel
154
+
155
+ expect(ariaLabel).toContain('31')
156
+ expect(ariaLabel).toContain('10')
157
+ expect(ariaLabel).toContain('1') // land region count
158
+ expect(ariaLabel).toContain('1') // sea region count
159
+ })
160
+ })
161
+
162
+ describe('Theme support', () => {
163
+ it('should apply active class when active prop is true', () => {
164
+ wrapper = mount(DaySmall, {
165
+ props: {
166
+ index: 0,
167
+ input: mockInput,
168
+ regions: mockRegions,
169
+ geometryId: 2021,
170
+ active: true,
171
+ language: 'fi',
172
+ },
173
+ })
174
+
175
+ expect(wrapper.find('.date-selector-cell').classes()).toContain('active')
176
+ })
177
+
178
+ it('should not apply active class when active prop is false', () => {
179
+ wrapper = mount(DaySmall, {
180
+ props: {
181
+ index: 0,
182
+ input: mockInput,
183
+ regions: mockRegions,
184
+ geometryId: 2021,
185
+ active: false,
186
+ language: 'fi',
187
+ },
188
+ })
189
+
190
+ expect(wrapper.find('.date-selector-cell').classes()).not.toContain(
191
+ 'active'
192
+ )
193
+ })
194
+
195
+ it('should apply theme class', () => {
196
+ wrapper = mount(DaySmall, {
197
+ props: {
198
+ index: 0,
199
+ input: mockInput,
200
+ regions: mockRegions,
201
+ geometryId: 2021,
202
+ theme: 'dark-theme',
203
+ language: 'fi',
204
+ },
205
+ })
206
+
207
+ expect(wrapper.find('.date-selector-cell').classes()).toContain(
208
+ 'dark-theme'
209
+ )
210
+ })
211
+
212
+ it('should apply severity class to footer', () => {
213
+ wrapper = mount(DaySmall, {
214
+ props: {
215
+ index: 0,
216
+ input: mockInput,
217
+ regions: mockRegions,
218
+ geometryId: 2021,
219
+ language: 'fi',
220
+ },
221
+ })
222
+
223
+ const footer = wrapper.find('.date-selector-cell-footer')
224
+ expect(footer.classes()).toContain('dark-level-3')
225
+ })
226
+ })
227
+
228
+ describe('Content rendering', () => {
229
+ it('should render weekday when staticDays is true', () => {
230
+ wrapper = mount(DaySmall, {
231
+ props: {
232
+ index: 0,
233
+ input: mockInput,
234
+ regions: mockRegions,
235
+ geometryId: 2021,
236
+ staticDays: true,
237
+ language: 'fi',
238
+ },
239
+ })
240
+
241
+ const text = wrapper.find('.date-selector-text').text()
242
+ expect(text).toBeTruthy()
243
+ })
244
+
245
+ it('should render date', () => {
246
+ wrapper = mount(DaySmall, {
247
+ props: {
248
+ index: 0,
249
+ input: mockInput,
250
+ regions: mockRegions,
251
+ geometryId: 2021,
252
+ language: 'fi',
253
+ },
254
+ })
255
+
256
+ expect(wrapper.text()).toContain('31.10.')
257
+ })
258
+
259
+ it('should handle missing date data gracefully', () => {
260
+ const incompleteInput = {
261
+ weekdayName: 'thursday',
262
+ severity: 2,
263
+ }
264
+
265
+ wrapper = mount(DaySmall, {
266
+ props: {
267
+ index: 0,
268
+ input: incompleteInput,
269
+ regions: mockRegions,
270
+ geometryId: 2021,
271
+ language: 'fi',
272
+ },
273
+ })
274
+
275
+ expect(wrapper.vm.date).toBe('')
276
+ })
277
+ })
278
+ })