@cdc/map 4.26.2 → 4.26.4
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.
- package/CONFIG.md +235 -0
- package/README.md +70 -24
- package/dist/cdcmap-CY9IcPSi.es.js +6 -0
- package/dist/cdcmap-DlpiY3fQ.es.js +4 -0
- package/dist/cdcmap.js +31260 -27946
- package/examples/{testing-layer-2.json → __data__/testing-layer-2.json} +1 -1
- package/examples/{testing-layer.json → __data__/testing-layer.json} +1 -1
- package/examples/county-hsa-toggle.json +51993 -0
- package/examples/custom-map-layers.json +2 -2
- package/examples/default-county.json +3 -3
- package/examples/minimal-example.json +69 -0
- package/examples/private/annotation-bug.json +642 -0
- package/examples/private/css-issue.json +314 -0
- package/examples/private/region-breaking.json +1639 -0
- package/examples/private/test1.json +27247 -0
- package/package.json +4 -4
- package/src/CdcMap.tsx +3 -14
- package/src/CdcMapComponent.tsx +302 -164
- package/src/_stories/CdcMap.Defaults.smoke.stories.tsx +76 -0
- package/src/_stories/CdcMap.Editor.ColumnsSectionTests.stories.tsx +601 -0
- package/src/_stories/CdcMap.Editor.DataTableSectionTests.stories.tsx +404 -0
- package/src/_stories/CdcMap.Editor.FiltersSectionTests.stories.tsx +229 -0
- package/src/_stories/CdcMap.Editor.GeneralSectionTests.stories.tsx +262 -0
- package/src/_stories/CdcMap.Editor.LegendSectionTests.stories.tsx +541 -0
- package/src/_stories/CdcMap.Editor.MultiCountryWorldMapTests.stories.tsx +359 -0
- package/src/_stories/CdcMap.Editor.PatternSettingsSectionTests.stories.tsx +516 -0
- package/src/_stories/CdcMap.Editor.SmallMultiplesSectionTests.stories.tsx +165 -0
- package/src/_stories/CdcMap.Editor.TextAnnotationsSectionTests.stories.tsx +145 -0
- package/src/_stories/CdcMap.Editor.TypeSectionTests.stories.tsx +312 -0
- package/src/_stories/CdcMap.Editor.VisualSectionTests.stories.tsx +359 -0
- package/src/_stories/CdcMap.Editor.ZoomControlsTests.stories.tsx +88 -0
- package/src/_stories/{CdcMap.stories.tsx → CdcMap.smoke.stories.tsx} +23 -1
- package/src/_stories/Map.HTMLInDataTable.stories.tsx +385 -0
- package/src/_stories/_mock/legends/legend-tests.json +3 -3
- package/src/_stories/_mock/multi-state-show-unselected.json +82 -0
- package/src/cdcMapComponent.styles.css +2 -2
- package/src/components/Annotation/Annotation.Draggable.styles.css +4 -4
- package/src/components/Annotation/AnnotationDropdown.styles.css +1 -1
- package/src/components/Annotation/AnnotationList.styles.css +13 -13
- package/src/components/Annotation/AnnotationList.tsx +1 -1
- package/src/components/EditorPanel/components/EditorPanel.tsx +905 -416
- package/src/components/EditorPanel/components/HexShapeSettings.tsx +1 -1
- package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +112 -117
- package/src/components/EditorPanel/components/Panels/Panel.PatternSettings-style.css +1 -1
- package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +31 -15
- package/src/components/EditorPanel/components/editorPanel.styles.css +55 -25
- package/src/components/Legend/components/Legend.tsx +12 -7
- package/src/components/Legend/components/LegendGroup/legend.group.css +5 -5
- package/src/components/Legend/components/LegendItem.Hex.tsx +4 -2
- package/src/components/Legend/components/index.scss +2 -3
- package/src/components/NavigationMenu.tsx +2 -1
- package/src/components/SmallMultiples/SmallMultiples.css +5 -5
- package/src/components/SmallMultiples/SynchronizedTooltip.tsx +1 -1
- package/src/components/UsaMap/components/SingleState/SingleState.StateOutput.tsx +32 -17
- package/src/components/UsaMap/components/TerritoriesSection.tsx +3 -2
- package/src/components/UsaMap/components/Territory/Territory.Rectangle.tsx +13 -8
- package/src/components/UsaMap/components/UsaMap.County.tsx +629 -231
- package/src/components/UsaMap/components/UsaMap.Region.styles.css +1 -1
- package/src/components/UsaMap/components/UsaMap.SingleState.styles.css +2 -2
- package/src/components/UsaMap/components/UsaMap.State.tsx +14 -9
- package/src/components/UsaMap/data/cb_2019_us_county_20m.json +75817 -1
- package/src/components/UsaMap/data/hsa_fips_mapping.json +3144 -0
- package/src/components/WorldMap/WorldMap.tsx +10 -13
- package/src/components/WorldMap/data/world-topo-updated.json +1 -0
- package/src/components/WorldMap/data/world-topo.json +1 -1
- package/src/components/WorldMap/worldMap.styles.css +1 -1
- package/src/components/ZoomControls.tsx +49 -18
- package/src/components/zoomControls.styles.css +27 -11
- package/src/data/initial-state.js +15 -5
- package/src/data/legacy-defaults.ts +8 -0
- package/src/data/supported-counties.json +1 -1
- package/src/data/supported-geos.js +19 -0
- package/src/helpers/colors.ts +2 -1
- package/src/helpers/countyTerritories.ts +38 -0
- package/src/helpers/dataTableHelpers.ts +85 -0
- package/src/helpers/displayGeoName.ts +19 -11
- package/src/helpers/getMapContainerClasses.ts +8 -2
- package/src/helpers/getMatchingPatternForRow.ts +67 -0
- package/src/helpers/getPatternForRow.ts +11 -18
- package/src/helpers/tests/countyTerritories.test.ts +87 -0
- package/src/helpers/tests/dataTableHelpers.test.ts +78 -0
- package/src/helpers/tests/displayGeoName.test.ts +17 -0
- package/src/helpers/tests/getMatchingPatternForRow.test.ts +150 -0
- package/src/helpers/tests/getPatternForRow.test.ts +140 -2
- package/src/helpers/urlDataHelpers.ts +7 -1
- package/src/hooks/useApplyTooltipsToGeo.tsx +7 -4
- package/src/hooks/useMapLayers.tsx +1 -1
- package/src/hooks/useResizeObserver.ts +36 -22
- package/src/hooks/useTooltip.test.tsx +64 -0
- package/src/hooks/useTooltip.ts +46 -15
- package/src/scss/editor-panel.scss +1 -1
- package/src/scss/main.scss +140 -6
- package/src/scss/map.scss +9 -4
- package/src/store/map.actions.ts +5 -0
- package/src/store/map.reducer.ts +13 -0
- package/src/test/CdcMap.test.jsx +26 -2
- package/src/types/MapConfig.ts +28 -4
- package/src/types/MapContext.ts +5 -1
- package/topojson-updater/README.txt +1 -1
- package/dist/cdcmap-Cf9_fbQf.es.js +0 -6
- package/examples/__data__/city-state-data.json +0 -668
- package/examples/city-state.json +0 -434
- package/examples/default-world-data.json +0 -1450
- package/examples/new-cities.json +0 -656
- package/src/_stories/CdcMap.Editor.stories.tsx +0 -3475
- package/src/helpers/componentHelpers.ts +0 -8
- package/topojson-updater/package-lock.json +0 -223
- /package/src/_stories/{CdcMap.ColumnWrap.stories.tsx → CdcMap.ColumnWrap.smoke.stories.tsx} +0 -0
- /package/src/_stories/{CdcMap.DistrictOfColumbia.stories.tsx → CdcMap.DistrictOfColumbia.smoke.stories.tsx} +0 -0
- /package/src/_stories/{CdcMap.Filters.stories.tsx → CdcMap.Filters.smoke.stories.tsx} +0 -0
- /package/src/_stories/{CdcMap.Legend.Gradient.stories.tsx → CdcMap.Legend.Gradient.smoke.stories.tsx} +0 -0
- /package/src/_stories/{CdcMap.Legend.stories.tsx → CdcMap.Legend.smoke.stories.tsx} +0 -0
- /package/src/_stories/{CdcMap.Patterns.stories.tsx → CdcMap.Patterns.smoke.stories.tsx} +0 -0
- /package/src/_stories/{CdcMap.SmallMultiples.stories.tsx → CdcMap.SmallMultiples.smoke.stories.tsx} +0 -0
- /package/src/_stories/{CdcMap.Table.stories.tsx → CdcMap.Table.smoke.stories.tsx} +0 -0
- /package/src/_stories/{CdcMap.ZeroColor.stories.tsx → CdcMap.ZeroColor.smoke.stories.tsx} +0 -0
- /package/src/_stories/{GoogleMap.stories.tsx → GoogleMap.smoke.stories.tsx} +0 -0
- /package/src/_stories/{UsaMap.NoData.stories.tsx → UsaMap.NoData.smoke.stories.tsx} +0 -0
|
@@ -0,0 +1,541 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
|
+
import { within, userEvent, expect } from 'storybook/test'
|
|
3
|
+
import CdcMap from '../CdcMap'
|
|
4
|
+
import usaStateGradientConfig from './_mock/usa-state-gradient.json'
|
|
5
|
+
import multiCountryConfig from './_mock/multi-country.json'
|
|
6
|
+
import wastewaterMapSmallMultiplesConfig from './_mock/small_multiples/wastewater-map-small-multiples.json'
|
|
7
|
+
import { performAndAssert, waitForEditor, waitForPresence, openAccordion } from '@cdc/core/helpers/testing'
|
|
8
|
+
|
|
9
|
+
type Story = StoryObj<typeof CdcMap>
|
|
10
|
+
|
|
11
|
+
const mapMeta: Meta<typeof CdcMap> = {
|
|
12
|
+
title: 'Components/Templates/Map/Editor Tests',
|
|
13
|
+
component: CdcMap,
|
|
14
|
+
parameters: {
|
|
15
|
+
layout: 'fullscreen'
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export default mapMeta
|
|
20
|
+
|
|
21
|
+
const DEFAULT_ARGS = {
|
|
22
|
+
isEditor: true,
|
|
23
|
+
config: usaStateGradientConfig
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const LegendSectionTests: Story = {
|
|
27
|
+
args: {
|
|
28
|
+
...DEFAULT_ARGS
|
|
29
|
+
},
|
|
30
|
+
play: async ({ canvasElement }) => {
|
|
31
|
+
const canvas = within(canvasElement)
|
|
32
|
+
|
|
33
|
+
await waitForEditor(canvas)
|
|
34
|
+
await waitForPresence('.map-container', canvasElement)
|
|
35
|
+
|
|
36
|
+
await openAccordion(canvas, 'Legend')
|
|
37
|
+
|
|
38
|
+
// ==========================================================================
|
|
39
|
+
// TEST: Legend Type
|
|
40
|
+
// ==========================================================================
|
|
41
|
+
const legendTypeSelect = Array.from(canvasElement.querySelectorAll('select') || []).find(select => {
|
|
42
|
+
const label = select.closest('label')
|
|
43
|
+
const labelSpan = label?.querySelector('.edit-label')
|
|
44
|
+
return labelSpan?.textContent?.includes('Legend Type')
|
|
45
|
+
}) as HTMLSelectElement
|
|
46
|
+
|
|
47
|
+
const getLegendType = () => {
|
|
48
|
+
const legendContainer = canvasElement.querySelector('.legend-container')
|
|
49
|
+
// For gradient legend, get labels from SVG text elements
|
|
50
|
+
const textElements = Array.from(legendContainer?.querySelectorAll('text tspan') || [])
|
|
51
|
+
const labels = textElements.map(el => el.textContent?.trim() || '')
|
|
52
|
+
return {
|
|
53
|
+
legendHTML: legendContainer?.innerHTML || '',
|
|
54
|
+
labels
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
await performAndAssert(
|
|
59
|
+
'Legend Type → Equal Interval',
|
|
60
|
+
getLegendType,
|
|
61
|
+
async () => {
|
|
62
|
+
await userEvent.selectOptions(legendTypeSelect, 'equalinterval')
|
|
63
|
+
},
|
|
64
|
+
(before, after) => {
|
|
65
|
+
// Equal interval creates evenly spaced ranges (e.g., "0 - < 43.33", "43.33 - < 86.67")
|
|
66
|
+
return after.labels.length > 0 && after.labels.join(',') !== before.labels.join(',')
|
|
67
|
+
}
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
await performAndAssert(
|
|
71
|
+
'Legend Type → Equal Number',
|
|
72
|
+
getLegendType,
|
|
73
|
+
async () => {
|
|
74
|
+
await userEvent.selectOptions(legendTypeSelect, 'equalnumber')
|
|
75
|
+
},
|
|
76
|
+
(before, after) => {
|
|
77
|
+
// Equal number (quantiles) creates ranges with equal counts (e.g., "0 - 40", "40 - 57")
|
|
78
|
+
return after.labels.length > 0 && after.labels.join(',') !== before.labels.join(',')
|
|
79
|
+
}
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
// ==========================================================================
|
|
83
|
+
// TEST: Show Legend checkbox
|
|
84
|
+
// ==========================================================================
|
|
85
|
+
const showLegendCheckbox = canvas.getByLabelText('Show Legend')
|
|
86
|
+
|
|
87
|
+
const getLegendVisibility = () => {
|
|
88
|
+
const legendContainer = canvasElement.querySelector('.legend-container')
|
|
89
|
+
return {
|
|
90
|
+
legendExists: Boolean(legendContainer),
|
|
91
|
+
isVisible: legendContainer ? !legendContainer.classList.contains('hidden') : false
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
await performAndAssert(
|
|
96
|
+
'Show Legend → Uncheck',
|
|
97
|
+
getLegendVisibility,
|
|
98
|
+
async () => {
|
|
99
|
+
await userEvent.click(showLegendCheckbox)
|
|
100
|
+
},
|
|
101
|
+
(before, after) => {
|
|
102
|
+
return before.isVisible && !after.isVisible
|
|
103
|
+
}
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
await performAndAssert(
|
|
107
|
+
'Show Legend → Check',
|
|
108
|
+
getLegendVisibility,
|
|
109
|
+
async () => {
|
|
110
|
+
await userEvent.click(showLegendCheckbox)
|
|
111
|
+
},
|
|
112
|
+
(before, after) => {
|
|
113
|
+
return !before.isVisible && after.isVisible
|
|
114
|
+
}
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
// ==========================================================================
|
|
118
|
+
// TEST: Legend Position
|
|
119
|
+
// ==========================================================================
|
|
120
|
+
const legendPositionSelect = Array.from(canvasElement.querySelectorAll('select') || []).find(select => {
|
|
121
|
+
const label = select.closest('label')
|
|
122
|
+
const labelSpan = label?.querySelector('.edit-label')
|
|
123
|
+
return labelSpan?.textContent?.includes('Legend Position')
|
|
124
|
+
}) as HTMLSelectElement
|
|
125
|
+
|
|
126
|
+
const getLegendPosition = () => {
|
|
127
|
+
const legendAside = canvasElement.querySelector('aside[aria-label="Legend"]') as HTMLElement
|
|
128
|
+
return {
|
|
129
|
+
classes: legendAside ? Array.from(legendAside.classList) : [],
|
|
130
|
+
isBottom: legendAside?.classList.contains('bottom') ?? false,
|
|
131
|
+
isSide: legendAside?.classList.contains('side') ?? false,
|
|
132
|
+
isTop: legendAside?.classList.contains('top') ?? false
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
await performAndAssert(
|
|
137
|
+
'Legend Position → Side',
|
|
138
|
+
getLegendPosition,
|
|
139
|
+
async () => {
|
|
140
|
+
await userEvent.selectOptions(legendPositionSelect, 'side')
|
|
141
|
+
},
|
|
142
|
+
(before, after) => {
|
|
143
|
+
return !before.isSide && after.isSide
|
|
144
|
+
}
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
await performAndAssert(
|
|
148
|
+
'Legend Position → Top',
|
|
149
|
+
getLegendPosition,
|
|
150
|
+
async () => {
|
|
151
|
+
await userEvent.selectOptions(legendPositionSelect, 'top')
|
|
152
|
+
},
|
|
153
|
+
(before, after) => {
|
|
154
|
+
return !before.isTop && after.isTop
|
|
155
|
+
}
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
await performAndAssert(
|
|
159
|
+
'Legend Position → Bottom',
|
|
160
|
+
getLegendPosition,
|
|
161
|
+
async () => {
|
|
162
|
+
await userEvent.selectOptions(legendPositionSelect, 'bottom')
|
|
163
|
+
},
|
|
164
|
+
(before, after) => {
|
|
165
|
+
return !before.isBottom && after.isBottom
|
|
166
|
+
}
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
// ==========================================================================
|
|
170
|
+
// TEST: Legend Style
|
|
171
|
+
// ==========================================================================
|
|
172
|
+
const legendStyleSelect = Array.from(canvasElement.querySelectorAll('select') || []).find(select => {
|
|
173
|
+
const label = select.closest('label')
|
|
174
|
+
const labelSpan = label?.querySelector('.edit-label')
|
|
175
|
+
return labelSpan?.textContent?.includes('Legend Style')
|
|
176
|
+
}) as HTMLSelectElement
|
|
177
|
+
|
|
178
|
+
const getLegendStyle = () => {
|
|
179
|
+
const legendContainer = canvasElement.querySelector('.legend-container')
|
|
180
|
+
const legendItems = legendContainer?.querySelectorAll('.legend-container__li')
|
|
181
|
+
const linearGradient = legendContainer?.querySelector('linearGradient')
|
|
182
|
+
const legendHTML = legendContainer?.innerHTML || ''
|
|
183
|
+
|
|
184
|
+
return {
|
|
185
|
+
hasLegendItems: (legendItems?.length ?? 0) > 0,
|
|
186
|
+
hasGradient: Boolean(linearGradient),
|
|
187
|
+
legendHTML
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
await performAndAssert(
|
|
192
|
+
'Legend Style → Circles',
|
|
193
|
+
getLegendStyle,
|
|
194
|
+
async () => {
|
|
195
|
+
await userEvent.selectOptions(legendStyleSelect, 'circles')
|
|
196
|
+
},
|
|
197
|
+
(before, after) => {
|
|
198
|
+
return after.hasLegendItems && !after.hasGradient && after.legendHTML !== before.legendHTML
|
|
199
|
+
}
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
await performAndAssert(
|
|
203
|
+
'Legend Style → Boxes',
|
|
204
|
+
getLegendStyle,
|
|
205
|
+
async () => {
|
|
206
|
+
await userEvent.selectOptions(legendStyleSelect, 'boxes')
|
|
207
|
+
},
|
|
208
|
+
(before, after) => {
|
|
209
|
+
return after.hasLegendItems && !after.hasGradient && after.legendHTML !== before.legendHTML
|
|
210
|
+
}
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
await performAndAssert(
|
|
214
|
+
'Legend Style → Gradient',
|
|
215
|
+
getLegendStyle,
|
|
216
|
+
async () => {
|
|
217
|
+
await userEvent.selectOptions(legendStyleSelect, 'gradient')
|
|
218
|
+
},
|
|
219
|
+
(before, after) => {
|
|
220
|
+
return !before.hasGradient && after.hasGradient
|
|
221
|
+
}
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
// ==========================================================================
|
|
225
|
+
// TEST: Gradient Style (only visible when Legend Style is gradient)
|
|
226
|
+
// ==========================================================================
|
|
227
|
+
const gradientStyleSelect = Array.from(canvasElement.querySelectorAll('select') || []).find(select => {
|
|
228
|
+
const label = select.closest('label')
|
|
229
|
+
return label?.textContent?.includes('Gradient Style')
|
|
230
|
+
}) as HTMLSelectElement
|
|
231
|
+
|
|
232
|
+
const getGradientStyle = () => {
|
|
233
|
+
const legendContainer = canvasElement.querySelector('.legend-container')
|
|
234
|
+
const svgHTML = legendContainer?.querySelector('svg')?.outerHTML || ''
|
|
235
|
+
|
|
236
|
+
return {
|
|
237
|
+
svgHTML,
|
|
238
|
+
hasLinearGradient: svgHTML.includes('linearGradient')
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
await performAndAssert(
|
|
243
|
+
'Gradient Style → Linear blocks',
|
|
244
|
+
getGradientStyle,
|
|
245
|
+
async () => {
|
|
246
|
+
await userEvent.selectOptions(gradientStyleSelect, 'linear blocks')
|
|
247
|
+
},
|
|
248
|
+
(before, after) => {
|
|
249
|
+
return after.hasLinearGradient && after.svgHTML !== before.svgHTML
|
|
250
|
+
}
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
await performAndAssert(
|
|
254
|
+
'Gradient Style → Smooth',
|
|
255
|
+
getGradientStyle,
|
|
256
|
+
async () => {
|
|
257
|
+
await userEvent.selectOptions(gradientStyleSelect, 'smooth')
|
|
258
|
+
},
|
|
259
|
+
(before, after) => {
|
|
260
|
+
return after.hasLinearGradient && after.svgHTML !== before.svgHTML
|
|
261
|
+
}
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
// ==========================================================================
|
|
265
|
+
// TEST: Tick Rotation (only visible when Legend Style is gradient)
|
|
266
|
+
// ==========================================================================
|
|
267
|
+
const tickRotationInput = Array.from(canvasElement.querySelectorAll('input[type="number"]') || []).find(input => {
|
|
268
|
+
const label = input.closest('label')
|
|
269
|
+
const labelSpan = label?.querySelector('.edit-label')
|
|
270
|
+
return labelSpan?.textContent?.includes('Tick Rotation')
|
|
271
|
+
}) as HTMLInputElement
|
|
272
|
+
|
|
273
|
+
const getTickRotation = () => {
|
|
274
|
+
const legendContainer = canvasElement.querySelector('.legend-container')
|
|
275
|
+
const svgHTML = legendContainer?.querySelector('svg')?.outerHTML || ''
|
|
276
|
+
const textElements = Array.from(legendContainer?.querySelectorAll('text') || [])
|
|
277
|
+
const transforms = textElements.map(el => el.getAttribute('transform') || '')
|
|
278
|
+
return {
|
|
279
|
+
svgHTML,
|
|
280
|
+
transforms,
|
|
281
|
+
hasRotation: transforms.some(t => t.includes('rotate'))
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
await performAndAssert(
|
|
286
|
+
'Tick Rotation → Set to 45 degrees',
|
|
287
|
+
getTickRotation,
|
|
288
|
+
async () => {
|
|
289
|
+
await userEvent.clear(tickRotationInput)
|
|
290
|
+
await userEvent.type(tickRotationInput, '45')
|
|
291
|
+
},
|
|
292
|
+
(before, after) => {
|
|
293
|
+
// Check that rotation transform is applied to text elements
|
|
294
|
+
return after.hasRotation && after.svgHTML !== before.svgHTML
|
|
295
|
+
}
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
// ==========================================================================
|
|
299
|
+
// TEST: Hide Legend Box
|
|
300
|
+
// ==========================================================================
|
|
301
|
+
const hideLegendBoxCheckbox = Array.from(canvasElement.querySelectorAll('input[type="checkbox"]') || []).find(
|
|
302
|
+
input => {
|
|
303
|
+
const label = input.closest('label')
|
|
304
|
+
const labelSpan = label?.querySelector('.edit-label')
|
|
305
|
+
return labelSpan?.textContent?.includes('Hide Legend Box')
|
|
306
|
+
}
|
|
307
|
+
) as HTMLInputElement
|
|
308
|
+
|
|
309
|
+
const getLegendBorder = () => {
|
|
310
|
+
const legendAside = canvasElement.querySelector('aside[aria-label="Legend"]') as HTMLElement
|
|
311
|
+
return {
|
|
312
|
+
hasNoBorder: legendAside?.classList.contains('no-border') ?? false
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
await performAndAssert(
|
|
317
|
+
'Hide Legend Box → Uncheck',
|
|
318
|
+
getLegendBorder,
|
|
319
|
+
async () => {
|
|
320
|
+
await userEvent.click(hideLegendBoxCheckbox)
|
|
321
|
+
},
|
|
322
|
+
(before, after) => {
|
|
323
|
+
return before.hasNoBorder && !after.hasNoBorder
|
|
324
|
+
}
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
await performAndAssert(
|
|
328
|
+
'Hide Legend Box → Check',
|
|
329
|
+
getLegendBorder,
|
|
330
|
+
async () => {
|
|
331
|
+
await userEvent.click(hideLegendBoxCheckbox)
|
|
332
|
+
},
|
|
333
|
+
(before, after) => {
|
|
334
|
+
return !before.hasNoBorder && after.hasNoBorder
|
|
335
|
+
}
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
// ==========================================================================
|
|
339
|
+
// TEST: Vertical sorted legend
|
|
340
|
+
// ==========================================================================
|
|
341
|
+
// First switch to boxes style and side position to enable vertical sorting
|
|
342
|
+
await userEvent.selectOptions(legendStyleSelect, 'boxes')
|
|
343
|
+
await userEvent.selectOptions(legendPositionSelect, 'side')
|
|
344
|
+
|
|
345
|
+
const verticalSortedCheckbox = Array.from(canvasElement.querySelectorAll('input[type="checkbox"]') || []).find(
|
|
346
|
+
input => {
|
|
347
|
+
const label = input.closest('label')
|
|
348
|
+
const labelSpan = label?.querySelector('.edit-label')
|
|
349
|
+
return labelSpan?.textContent?.includes('Vertical sorted legend')
|
|
350
|
+
}
|
|
351
|
+
) as HTMLInputElement
|
|
352
|
+
|
|
353
|
+
const getVerticalSorted = () => {
|
|
354
|
+
const legendUl = canvasElement.querySelector('.legend-container__ul')
|
|
355
|
+
return {
|
|
356
|
+
hasVerticalSorted: legendUl?.classList.contains('vertical-sorted') ?? false
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
await performAndAssert(
|
|
361
|
+
'Vertical sorted legend → Check',
|
|
362
|
+
getVerticalSorted,
|
|
363
|
+
async () => {
|
|
364
|
+
await userEvent.click(verticalSortedCheckbox)
|
|
365
|
+
},
|
|
366
|
+
(before, after) => {
|
|
367
|
+
return !before.hasVerticalSorted && after.hasVerticalSorted
|
|
368
|
+
}
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
await performAndAssert(
|
|
372
|
+
'Vertical sorted legend → Uncheck',
|
|
373
|
+
getVerticalSorted,
|
|
374
|
+
async () => {
|
|
375
|
+
await userEvent.click(verticalSortedCheckbox)
|
|
376
|
+
},
|
|
377
|
+
(before, after) => {
|
|
378
|
+
return before.hasVerticalSorted && !after.hasVerticalSorted
|
|
379
|
+
}
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
// NOTE: Show Special Classes Last is skipped because it doesn't produce
|
|
383
|
+
// visible changes with the current test data (no special classes present)
|
|
384
|
+
|
|
385
|
+
// ==========================================================================
|
|
386
|
+
// TEST: Separate Zero
|
|
387
|
+
// ==========================================================================
|
|
388
|
+
const separateZeroCheckbox = Array.from(canvasElement.querySelectorAll('input[type="checkbox"]') || []).find(
|
|
389
|
+
input => {
|
|
390
|
+
const label = input.closest('label')
|
|
391
|
+
const labelSpan = label?.querySelector('.edit-label')
|
|
392
|
+
return labelSpan?.textContent?.includes('Separate Zero')
|
|
393
|
+
}
|
|
394
|
+
) as HTMLInputElement
|
|
395
|
+
|
|
396
|
+
const getSeparateZero = () => {
|
|
397
|
+
const legendContainer = canvasElement.querySelector('.legend-container')
|
|
398
|
+
const legendItems = Array.from(legendContainer?.querySelectorAll('.legend-container__li') || [])
|
|
399
|
+
const itemLabels = legendItems.map(item => item.textContent?.trim() || '')
|
|
400
|
+
const hasZeroItem = itemLabels.some(label => label.includes('0') && !label.match(/[1-9]/))
|
|
401
|
+
return {
|
|
402
|
+
itemCount: legendItems.length,
|
|
403
|
+
itemLabels,
|
|
404
|
+
hasZeroItem
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
await performAndAssert(
|
|
409
|
+
'Separate Zero → Check',
|
|
410
|
+
getSeparateZero,
|
|
411
|
+
async () => {
|
|
412
|
+
await userEvent.click(separateZeroCheckbox)
|
|
413
|
+
},
|
|
414
|
+
(before, after) => {
|
|
415
|
+
// Should separate zero into its own legend item (e.g., "0" instead of "0 - 40")
|
|
416
|
+
return after.hasZeroItem && after.itemLabels.join(',') !== before.itemLabels.join(',')
|
|
417
|
+
}
|
|
418
|
+
)
|
|
419
|
+
|
|
420
|
+
await performAndAssert(
|
|
421
|
+
'Separate Zero → Uncheck',
|
|
422
|
+
getSeparateZero,
|
|
423
|
+
async () => {
|
|
424
|
+
await userEvent.click(separateZeroCheckbox)
|
|
425
|
+
},
|
|
426
|
+
(before, after) => {
|
|
427
|
+
// Should merge zero back into a range
|
|
428
|
+
return !after.hasZeroItem && after.itemLabels.join(',') !== before.itemLabels.join(',')
|
|
429
|
+
}
|
|
430
|
+
)
|
|
431
|
+
|
|
432
|
+
// ==========================================================================
|
|
433
|
+
// TEST: Number of Items
|
|
434
|
+
// ==========================================================================
|
|
435
|
+
const numberOfItemsSelect = Array.from(canvasElement.querySelectorAll('select') || []).find(select => {
|
|
436
|
+
const label = select.closest('label')
|
|
437
|
+
const labelSpan = label?.querySelector('.edit-label')
|
|
438
|
+
return labelSpan?.textContent?.includes('Number of Items')
|
|
439
|
+
}) as HTMLSelectElement
|
|
440
|
+
|
|
441
|
+
const getNumberOfItems = () => {
|
|
442
|
+
const legendContainer = canvasElement.querySelector('.legend-container')
|
|
443
|
+
const legendItems = legendContainer?.querySelectorAll('.legend-container__li')
|
|
444
|
+
return {
|
|
445
|
+
itemCount: legendItems?.length ?? 0,
|
|
446
|
+
legendHTML: legendContainer?.innerHTML || ''
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
await performAndAssert(
|
|
451
|
+
'Number of Items → Set to 5',
|
|
452
|
+
getNumberOfItems,
|
|
453
|
+
async () => {
|
|
454
|
+
await userEvent.selectOptions(numberOfItemsSelect, '5')
|
|
455
|
+
},
|
|
456
|
+
(before, after) => {
|
|
457
|
+
// Should have 5 legend items
|
|
458
|
+
return after.itemCount === 5 && after.itemCount !== before.itemCount
|
|
459
|
+
}
|
|
460
|
+
)
|
|
461
|
+
|
|
462
|
+
await performAndAssert(
|
|
463
|
+
'Number of Items → Set to 3',
|
|
464
|
+
getNumberOfItems,
|
|
465
|
+
async () => {
|
|
466
|
+
await userEvent.selectOptions(numberOfItemsSelect, '3')
|
|
467
|
+
},
|
|
468
|
+
(before, after) => {
|
|
469
|
+
// Should have 3 legend items
|
|
470
|
+
return after.itemCount === 3 && after.itemCount !== before.itemCount
|
|
471
|
+
}
|
|
472
|
+
)
|
|
473
|
+
|
|
474
|
+
// Switch back to gradient for the legend title test
|
|
475
|
+
await userEvent.selectOptions(legendPositionSelect, 'bottom')
|
|
476
|
+
await userEvent.selectOptions(legendStyleSelect, 'gradient')
|
|
477
|
+
|
|
478
|
+
// ==========================================================================
|
|
479
|
+
// TEST: Legend Title
|
|
480
|
+
// ==========================================================================
|
|
481
|
+
const legendTitleInput = Array.from(canvasElement.querySelectorAll('input[type="text"]') || []).find(input => {
|
|
482
|
+
const label = input.closest('label')
|
|
483
|
+
return label?.textContent?.includes('Legend Title')
|
|
484
|
+
}) as HTMLInputElement
|
|
485
|
+
|
|
486
|
+
const getLegendTitle = () => {
|
|
487
|
+
const legendContainer = canvasElement.querySelector('.legend-container')
|
|
488
|
+
const titleElement = legendContainer?.querySelector('.legend-container__title, .legend-title')
|
|
489
|
+
return {
|
|
490
|
+
titleText: titleElement?.textContent?.trim() || '',
|
|
491
|
+
titleExists: Boolean(titleElement),
|
|
492
|
+
legendHTML: legendContainer?.innerHTML || ''
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
await performAndAssert(
|
|
497
|
+
'Legend Title → Set custom title',
|
|
498
|
+
getLegendTitle,
|
|
499
|
+
async () => {
|
|
500
|
+
await userEvent.clear(legendTitleInput)
|
|
501
|
+
await userEvent.type(legendTitleInput, 'Custom Legend Title')
|
|
502
|
+
},
|
|
503
|
+
(before, after) => {
|
|
504
|
+
return after.titleText.includes('Custom Legend Title') && after.legendHTML !== before.legendHTML
|
|
505
|
+
}
|
|
506
|
+
)
|
|
507
|
+
|
|
508
|
+
// ==========================================================================
|
|
509
|
+
// TEST: Legend Description
|
|
510
|
+
// ==========================================================================
|
|
511
|
+
const legendDescriptionTextarea = Array.from(canvasElement.querySelectorAll('textarea') || []).find(textarea => {
|
|
512
|
+
const label = textarea.closest('label')
|
|
513
|
+
return label?.textContent?.includes('Legend Description')
|
|
514
|
+
}) as HTMLTextAreaElement
|
|
515
|
+
|
|
516
|
+
const getLegendDescription = () => {
|
|
517
|
+
const legendContainer = canvasElement.querySelector('.legend-container')
|
|
518
|
+
const descriptionElement = legendContainer?.querySelector('.legend-container__description, .legend-description')
|
|
519
|
+
return {
|
|
520
|
+
descriptionText: descriptionElement?.textContent?.trim() || '',
|
|
521
|
+
descriptionExists: Boolean(descriptionElement),
|
|
522
|
+
legendHTML: legendContainer?.innerHTML || ''
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
await performAndAssert(
|
|
527
|
+
'Legend Description → Set custom description',
|
|
528
|
+
getLegendDescription,
|
|
529
|
+
async () => {
|
|
530
|
+
await userEvent.clear(legendDescriptionTextarea)
|
|
531
|
+
await userEvent.type(legendDescriptionTextarea, 'This is a custom legend description for testing.')
|
|
532
|
+
},
|
|
533
|
+
(before, after) => {
|
|
534
|
+
return (
|
|
535
|
+
after.descriptionText.includes('This is a custom legend description') &&
|
|
536
|
+
after.legendHTML !== before.legendHTML
|
|
537
|
+
)
|
|
538
|
+
}
|
|
539
|
+
)
|
|
540
|
+
}
|
|
541
|
+
}
|