@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,295 @@
1
+ import { describe, it, expect, afterEach } from 'vitest'
2
+ import { mount, VueWrapper } from '@vue/test-utils'
3
+ import Legend from '@/components/Legend.vue'
4
+ import type { LegendItem, Theme, Language } from '@/types'
5
+
6
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
7
+ type ComponentInstance = any
8
+
9
+ const mockLegendItems: LegendItem[] = [
10
+ { type: 'wind', severity: 3, visible: true },
11
+ { type: 'rain', severity: 2, visible: true },
12
+ { type: 'thunderStorm', severity: 4, visible: false },
13
+ ]
14
+
15
+ describe('Legend.vue', () => {
16
+ let wrapper: VueWrapper | null = null
17
+
18
+ afterEach(() => {
19
+ if (wrapper) {
20
+ wrapper.unmount()
21
+ wrapper = null
22
+ }
23
+ })
24
+
25
+ describe('Component mounting', () => {
26
+ it('should mount with default props', () => {
27
+ wrapper = mount(Legend, {
28
+ props: {},
29
+ })
30
+
31
+ expect(wrapper.exists()).toBe(true)
32
+ })
33
+
34
+ it('should mount with all props', () => {
35
+ wrapper = mount(Legend, {
36
+ props: {
37
+ input: mockLegendItems,
38
+ language: 'fi' as Language,
39
+ grayScaleSelector: true,
40
+ theme: 'light-theme' as Theme,
41
+ visibleWarnings: ['wind', 'rain'],
42
+ },
43
+ })
44
+
45
+ expect(wrapper.exists()).toBe(true)
46
+ })
47
+ })
48
+
49
+ describe('Computed properties', () => {
50
+ it('should compute warnings from input', () => {
51
+ wrapper = mount(Legend, {
52
+ props: {
53
+ input: mockLegendItems,
54
+ },
55
+ })
56
+
57
+ expect((wrapper.vm as ComponentInstance).warnings).toEqual(
58
+ mockLegendItems
59
+ )
60
+ })
61
+
62
+ it('should compute warningSymbolsText from translations', () => {
63
+ wrapper = mount(Legend, {
64
+ props: {
65
+ input: mockLegendItems,
66
+ language: 'fi' as Language,
67
+ },
68
+ })
69
+
70
+ expect(typeof (wrapper.vm as ComponentInstance).warningSymbolsText).toBe(
71
+ 'string'
72
+ )
73
+ })
74
+
75
+ it('should compute toggleLegendsText based on visibility state', () => {
76
+ wrapper = mount(Legend, {
77
+ props: {
78
+ input: mockLegendItems,
79
+ language: 'fi' as Language,
80
+ },
81
+ })
82
+
83
+ expect(typeof (wrapper.vm as ComponentInstance).toggleLegendsText).toBe(
84
+ 'string'
85
+ )
86
+ })
87
+ })
88
+
89
+ describe('Event handling', () => {
90
+ it('should emit warningsToggled when onWarningsToggled is called', () => {
91
+ wrapper = mount(Legend, {
92
+ props: {
93
+ input: mockLegendItems,
94
+ visibleWarnings: ['wind'],
95
+ },
96
+ })
97
+ ;(wrapper.vm as ComponentInstance).onWarningsToggled(['wind', 'rain'])
98
+
99
+ expect(wrapper.emitted('warningsToggled')).toBeTruthy()
100
+ expect(wrapper.emitted('warningsToggled')![0]).toEqual([['wind', 'rain']])
101
+ })
102
+
103
+ it('should emit themeChanged when onThemeChanged is called with different theme', () => {
104
+ wrapper = mount(Legend, {
105
+ props: {
106
+ input: mockLegendItems,
107
+ theme: 'light-theme' as Theme,
108
+ },
109
+ })
110
+ ;(wrapper.vm as ComponentInstance).onThemeChanged('dark-theme')
111
+
112
+ expect(wrapper.emitted('themeChanged')).toBeTruthy()
113
+ expect(wrapper.emitted('themeChanged')![0]).toEqual(['dark-theme'])
114
+ })
115
+
116
+ it('should not emit themeChanged when theme is the same', () => {
117
+ wrapper = mount(Legend, {
118
+ props: {
119
+ input: mockLegendItems,
120
+ theme: 'light-theme' as Theme,
121
+ },
122
+ })
123
+ ;(wrapper.vm as ComponentInstance).onThemeChanged('light-theme')
124
+
125
+ expect(wrapper.emitted('themeChanged')).toBeFalsy()
126
+ })
127
+
128
+ it('should emit all visible warnings on showAllWarnings', () => {
129
+ wrapper = mount(Legend, {
130
+ props: {
131
+ input: mockLegendItems,
132
+ visibleWarnings: ['wind'],
133
+ },
134
+ })
135
+ ;(wrapper.vm as ComponentInstance).onShowAllWarnings()
136
+
137
+ expect(wrapper.emitted('warningsToggled')).toBeTruthy()
138
+ expect(wrapper.emitted('warningsToggled')![0]![0]).toContain('wind')
139
+ expect(wrapper.emitted('warningsToggled')![0]![0]).toContain('rain')
140
+ expect(wrapper.emitted('warningsToggled')![0]![0]).toContain(
141
+ 'thunderStorm'
142
+ )
143
+ })
144
+
145
+ it('should toggle legend visibility on onLegendToggle', () => {
146
+ wrapper = mount(Legend, {
147
+ props: {
148
+ input: mockLegendItems,
149
+ },
150
+ })
151
+
152
+ expect((wrapper.vm as ComponentInstance).visible).toBe(false)
153
+ ;(wrapper.vm as ComponentInstance).onLegendToggle()
154
+
155
+ expect((wrapper.vm as ComponentInstance).visible).toBe(true)
156
+ ;(wrapper.vm as ComponentInstance).onLegendToggle()
157
+
158
+ expect((wrapper.vm as ComponentInstance).visible).toBe(false)
159
+ })
160
+ })
161
+
162
+ describe('Props handling', () => {
163
+ it('should accept grayScaleSelector prop', () => {
164
+ wrapper = mount(Legend, {
165
+ props: {
166
+ grayScaleSelector: true,
167
+ },
168
+ })
169
+
170
+ expect(wrapper.exists()).toBe(true)
171
+ })
172
+
173
+ it('should accept visibleWarnings prop', () => {
174
+ wrapper = mount(Legend, {
175
+ props: {
176
+ visibleWarnings: ['wind', 'rain'],
177
+ },
178
+ })
179
+
180
+ expect(wrapper.exists()).toBe(true)
181
+ })
182
+
183
+ it('should default language to fi', () => {
184
+ wrapper = mount(Legend, {
185
+ props: {},
186
+ })
187
+
188
+ expect(wrapper.exists()).toBe(true)
189
+ })
190
+ })
191
+
192
+ describe('Theme support', () => {
193
+ it('should apply theme class to container', () => {
194
+ wrapper = mount(Legend, {
195
+ props: {
196
+ input: mockLegendItems,
197
+ theme: 'dark-theme' as Theme,
198
+ },
199
+ })
200
+
201
+ expect(wrapper.find('.sticky-top').classes()).toContain('dark-theme')
202
+ })
203
+
204
+ it('should support all theme variants', () => {
205
+ const themes: Theme[] = [
206
+ 'light-theme',
207
+ 'dark-theme',
208
+ 'light-gray-theme',
209
+ 'dark-gray-theme',
210
+ ]
211
+
212
+ themes.forEach((theme) => {
213
+ wrapper = mount(Legend, {
214
+ props: {
215
+ input: mockLegendItems,
216
+ theme,
217
+ },
218
+ })
219
+
220
+ expect(wrapper.find('.sticky-top').classes()).toContain(theme)
221
+ })
222
+ })
223
+ })
224
+
225
+ describe('Language support', () => {
226
+ it('should support Finnish language', () => {
227
+ wrapper = mount(Legend, {
228
+ props: {
229
+ language: 'fi' as Language,
230
+ },
231
+ })
232
+
233
+ expect(wrapper.exists()).toBe(true)
234
+ })
235
+
236
+ it('should support Swedish language', () => {
237
+ wrapper = mount(Legend, {
238
+ props: {
239
+ language: 'sv' as Language,
240
+ },
241
+ })
242
+
243
+ expect(wrapper.exists()).toBe(true)
244
+ })
245
+
246
+ it('should support English language', () => {
247
+ wrapper = mount(Legend, {
248
+ props: {
249
+ language: 'en' as Language,
250
+ },
251
+ })
252
+
253
+ expect(wrapper.exists()).toBe(true)
254
+ })
255
+ })
256
+
257
+ describe('Structure', () => {
258
+ it('should render symbol list header', () => {
259
+ wrapper = mount(Legend, {
260
+ props: {
261
+ input: mockLegendItems,
262
+ },
263
+ })
264
+
265
+ expect(wrapper.find('.symbol-list-header').exists()).toBe(true)
266
+ })
267
+
268
+ it('should render collapsible panel', () => {
269
+ wrapper = mount(Legend, {
270
+ props: {
271
+ input: mockLegendItems,
272
+ },
273
+ })
274
+
275
+ // CollapsiblePanel component should be rendered
276
+ expect(wrapper.findComponent({ name: 'CollapsiblePanel' }).exists()).toBe(
277
+ true
278
+ )
279
+ })
280
+
281
+ it('should render GrayScaleToggle components', () => {
282
+ wrapper = mount(Legend, {
283
+ props: {
284
+ input: mockLegendItems,
285
+ grayScaleSelector: true,
286
+ },
287
+ })
288
+
289
+ // Should render GrayScaleToggle components
290
+ expect(
291
+ wrapper.findAllComponents({ name: 'GrayScaleToggle' }).length
292
+ ).toBeGreaterThan(0)
293
+ })
294
+ })
295
+ })