@cdc/core 4.25.10 → 4.26.1
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/_stories/Gallery.Charts.stories.tsx +307 -0
- package/_stories/Gallery.DataBite.stories.tsx +72 -0
- package/_stories/Gallery.Maps.stories.tsx +230 -0
- package/_stories/Gallery.WaffleChart.stories.tsx +187 -0
- package/_stories/PageART.stories.tsx +192 -0
- package/_stories/PageBRFSS.stories.tsx +289 -0
- package/_stories/PageCancerRegistries.stories.tsx +199 -0
- package/_stories/PageEasternEquineEncephalitis.stories.tsx +202 -0
- package/_stories/PageExcessiveAlcoholUse.stories.tsx +196 -0
- package/_stories/PageMaternalMortality.stories.tsx +192 -0
- package/_stories/PageOralHealth.stories.tsx +196 -0
- package/_stories/PageRespiratory.stories.tsx +332 -0
- package/_stories/PageSmokingTobacco.stories.tsx +195 -0
- package/_stories/PageStateDiabetesProfiles.stories.tsx +196 -0
- package/_stories/PageWastewater.stories.tsx +463 -0
- package/_stories/StoryRenderingTests.stories.tsx +164 -0
- package/assets/icon-magnifying-glass.svg +5 -0
- package/assets/icon-warming-stripes.svg +13 -0
- package/components/AdvancedEditor/AdvancedEditor.tsx +7 -1
- package/components/AdvancedEditor/EmbedEditor.tsx +281 -0
- package/components/ComboBox/ComboBox.tsx +345 -0
- package/components/ComboBox/combobox.styles.css +185 -0
- package/components/ComboBox/index.ts +1 -0
- package/components/CustomColorsEditor/CustomColorsEditor.css +299 -0
- package/components/CustomColorsEditor/CustomColorsEditor.tsx +209 -0
- package/components/CustomColorsEditor/index.ts +1 -0
- package/components/DataTable/DataTable.tsx +132 -58
- package/components/DataTable/DataTableStandAlone.tsx +8 -3
- package/components/DataTable/components/DataTableEditorPanel.tsx +12 -2
- package/components/DataTable/data-table.css +217 -210
- package/components/DataTable/helpers/mapCellMatrix.tsx +28 -9
- package/components/DataTable/helpers/standardizeState.js +2 -2
- package/components/DataTable/helpers/tests/standardizeState.test.js +54 -0
- package/components/EditorPanel/ColumnsEditor.tsx +37 -19
- package/components/EditorPanel/DataTableEditor.tsx +54 -28
- package/components/EditorPanel/EditorPanel.styles.css +439 -0
- package/components/EditorPanel/EditorPanel.tsx +144 -0
- package/components/EditorPanel/EditorPanelDispatch.tsx +75 -0
- package/components/EditorPanel/FieldSetWrapper.tsx +66 -23
- package/components/EditorPanel/FootnotesEditor.tsx +44 -37
- package/components/EditorPanel/Inputs.tsx +44 -8
- package/components/EditorPanel/VizFilterEditor/NestedDropdownEditor.tsx +35 -62
- package/components/EditorPanel/VizFilterEditor/VizFilterEditor.tsx +246 -175
- package/components/EditorPanel/components/MarkupVariablesEditor.tsx +61 -22
- package/components/EditorPanel/sections/VisualSection.tsx +169 -0
- package/components/Filters/Filters.tsx +57 -10
- package/components/Filters/components/Dropdown.tsx +6 -1
- package/components/Filters/helpers/getNestedOptions.ts +2 -1
- package/components/Filters/helpers/handleSorting.ts +1 -1
- package/components/Footnotes/Footnotes.tsx +35 -25
- package/components/Footnotes/FootnotesStandAlone.tsx +42 -6
- package/components/HeaderThemeSelector/HeaderThemeSelector.css +43 -0
- package/components/HeaderThemeSelector/HeaderThemeSelector.stories.tsx +74 -0
- package/components/HeaderThemeSelector/HeaderThemeSelector.tsx +61 -0
- package/components/HeaderThemeSelector/index.ts +2 -0
- package/components/Layout/components/Sidebar/components/sidebar.styles.scss +82 -0
- package/components/Layout/components/Visualization/index.tsx +16 -1
- package/components/Layout/components/Visualization/visualizations.scss +7 -0
- package/components/Layout/styles/editor.scss +2 -1
- package/components/Legend/Legend.Gradient.tsx +1 -1
- package/components/Loader/Loader.tsx +1 -1
- package/components/MediaControls.tsx +63 -34
- package/components/PaletteConversionModal.tsx +7 -4
- package/components/PaletteSelector/PaletteSelector.css +49 -6
- package/components/Table/components/Cell.tsx +23 -2
- package/components/Table/components/Row.tsx +5 -3
- package/components/_stories/Filters.stories.tsx +20 -1
- package/components/_stories/Footnotes.CSV.stories.tsx +247 -0
- package/components/_stories/Footnotes.stories.tsx +768 -3
- package/components/_stories/Inputs.stories.tsx +2 -2
- package/components/_stories/styles.scss +0 -1
- package/components/ui/Accordion.jsx +1 -1
- package/components/ui/Icon.tsx +3 -1
- package/components/ui/Title/index.tsx +30 -2
- package/components/ui/Title/title.styles.css +42 -0
- package/components/ui/accordion.styles.css +57 -0
- package/data/chartColorPalettes.ts +1 -1
- package/dist/cove-main.css +75 -6
- package/dist/cove-main.css.map +1 -1
- package/generateViteConfig.js +8 -1
- package/helpers/addValuesToFilters.ts +11 -1
- package/helpers/constants.ts +37 -0
- package/helpers/cove/number.ts +33 -12
- package/helpers/coveUpdateWorker.ts +20 -11
- package/helpers/embedCodeGenerator.ts +109 -0
- package/helpers/fetchRemoteData.ts +3 -15
- package/helpers/getUniqueValues.ts +19 -0
- package/helpers/hashObj.ts +25 -0
- package/helpers/isRightAlignedTableValue.js +5 -0
- package/helpers/markupProcessor.ts +27 -12
- package/helpers/mergeCustomOrderValues.ts +37 -0
- package/helpers/metrics/helpers.ts +1 -0
- package/helpers/parseCsvWithQuotes.ts +65 -0
- package/helpers/pivotData.ts +2 -2
- package/helpers/prepareScreenshot.ts +268 -0
- package/helpers/queryStringUtils.ts +29 -0
- package/helpers/testing.ts +17 -4
- package/helpers/tests/prepareScreenshot.test.ts +414 -0
- package/helpers/tests/queryStringUtils.test.ts +381 -0
- package/helpers/tests/testStandaloneBuild.ts +23 -5
- package/helpers/useDataVizClasses.ts +0 -1
- package/helpers/ver/4.25.11.ts +13 -0
- package/helpers/ver/4.26.1.ts +80 -0
- package/helpers/viewports.ts +2 -0
- package/hooks/useDataColumns.ts +63 -0
- package/hooks/useFilterManagement.ts +94 -0
- package/hooks/useLegendSeparators.ts +26 -0
- package/hooks/useListManagement.ts +192 -0
- package/package.json +6 -4
- package/styles/_button-section.scss +0 -3
- package/styles/_common-components.css +73 -0
- package/styles/_global.scss +25 -5
- package/styles/base.scss +0 -50
- package/styles/cove-main.scss +3 -1
- package/styles/filters.scss +10 -3
- package/styles/v2/base/index.scss +0 -1
- package/styles/v2/components/editor.scss +14 -6
- package/styles/v2/utils/_breakpoints.scss +1 -1
- package/styles/v2/utils/index.scss +0 -1
- package/styles/waiting.scss +1 -1
- package/types/Axis.ts +1 -0
- package/types/ForecastingSeriesKey.ts +1 -0
- package/types/MarkupInclude.ts +5 -3
- package/types/MarkupVariable.ts +1 -1
- package/types/Series.ts +3 -0
- package/types/Table.ts +1 -0
- package/types/Visualization.ts +1 -0
- package/types/VizFilter.ts +2 -0
- package/LICENSE +0 -201
- package/styles/_mixins.scss +0 -13
- package/styles/_typography.scss +0 -0
- package/styles/v2/base/_typography.scss +0 -0
- package/styles/v2/components/guidance-block.scss +0 -74
- package/styles/v2/utils/_functions.scss +0 -0
|
@@ -13,6 +13,35 @@ export function getQueryStringFilterValue(filter) {
|
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Check if a filter should be hidden based on URL query parameters
|
|
18
|
+
* Checks for hide{setByQueryParameter}=true|1|yes in the URL
|
|
19
|
+
*
|
|
20
|
+
* @param filter - Filter object with setByQueryParameter property
|
|
21
|
+
* @returns true if filter should be hidden, false otherwise
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* // If filter has setByQueryParameter: "state"
|
|
25
|
+
* // URL: ?hidestate=true -> returns true
|
|
26
|
+
* // URL: ?hideState=true -> returns false (case sensitive!)
|
|
27
|
+
*/
|
|
28
|
+
export function isFilterHiddenByQuery(filter) {
|
|
29
|
+
if (!filter) return false
|
|
30
|
+
|
|
31
|
+
// Only check setByQueryParameter - this is the standard way filters are identified in URL params
|
|
32
|
+
const paramName = filter.setByQueryParameter
|
|
33
|
+
if (!paramName) return false
|
|
34
|
+
|
|
35
|
+
const urlParams = new URLSearchParams(window.location.search)
|
|
36
|
+
const hideParamName = `hide${paramName}`
|
|
37
|
+
const hideValue = urlParams.get(hideParamName)
|
|
38
|
+
|
|
39
|
+
if (!hideValue) return false
|
|
40
|
+
|
|
41
|
+
const lower = hideValue.toLowerCase()
|
|
42
|
+
return lower === 'true' || hideValue === '1' || lower === 'yes'
|
|
43
|
+
}
|
|
44
|
+
|
|
16
45
|
export function getQueryParams() {
|
|
17
46
|
const queryParams = {}
|
|
18
47
|
for (const [key, value] of Array.from(new URLSearchParams(window.location.search).entries())) {
|
package/helpers/testing.ts
CHANGED
|
@@ -39,7 +39,7 @@ export const MIN_ANIMATION_DELAY_MS = (() => {
|
|
|
39
39
|
return 500
|
|
40
40
|
})()
|
|
41
41
|
|
|
42
|
-
const WAIT_FOR_TIMEOUT_MS =
|
|
42
|
+
const WAIT_FOR_TIMEOUT_MS = 10000
|
|
43
43
|
|
|
44
44
|
// ============================================================================
|
|
45
45
|
// CORE POLLING UTILITIES
|
|
@@ -177,8 +177,11 @@ export const waitForTextContent = async (el: HTMLElement | null, expected: strin
|
|
|
177
177
|
*/
|
|
178
178
|
export const waitForEditor = async (canvas: any) => {
|
|
179
179
|
await waitForWithDelay(() => {
|
|
180
|
-
const
|
|
181
|
-
expect(
|
|
180
|
+
const accordionButtons = canvas.getAllByRole('button', { name: /general|data|visual/i })
|
|
181
|
+
expect(accordionButtons.length).toBeGreaterThan(0)
|
|
182
|
+
for (const button of accordionButtons) {
|
|
183
|
+
expect(button).toBeVisible()
|
|
184
|
+
}
|
|
182
185
|
})
|
|
183
186
|
}
|
|
184
187
|
|
|
@@ -189,7 +192,17 @@ export const waitForEditor = async (canvas: any) => {
|
|
|
189
192
|
* @param sectionName Name of the accordion section (case-insensitive)
|
|
190
193
|
*/
|
|
191
194
|
export const openAccordion = async (canvas: any, sectionName: string) => {
|
|
192
|
-
|
|
195
|
+
// Get all buttons with matching name and filter to only accordion buttons
|
|
196
|
+
const allButtons = canvas.getAllByRole('button', { name: new RegExp(sectionName, 'i') })
|
|
197
|
+
const accordion = allButtons.find(
|
|
198
|
+
(button: HTMLElement) =>
|
|
199
|
+
button.classList.contains('accordion__button') || button.closest('.editor-panel, .accordion')
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
if (!accordion) {
|
|
203
|
+
throw new Error(`Could not find accordion button for "${sectionName}"`)
|
|
204
|
+
}
|
|
205
|
+
|
|
193
206
|
await userEvent.click(accordion)
|
|
194
207
|
await waitForWithDelay(() => {
|
|
195
208
|
const accordionContent = accordion.closest('.accordion-item, .accordion-section, [class*="accordion"]')
|
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
import { describe, it, expect, afterEach } from 'vitest'
|
|
2
|
+
import { prepareClonedElements } from '../prepareScreenshot'
|
|
3
|
+
|
|
4
|
+
describe('prepareClonedElements', () => {
|
|
5
|
+
// Helper to create DOM structure and append to document
|
|
6
|
+
function createDOM(htmlString: string): HTMLElement {
|
|
7
|
+
const container = document.createElement('div')
|
|
8
|
+
container.innerHTML = htmlString
|
|
9
|
+
document.body.appendChild(container)
|
|
10
|
+
return container
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Clean up after each test
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
document.body.innerHTML = ''
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
describe('includeContextInDownload = false', () => {
|
|
19
|
+
it('should return viz-only clone when context not requested', () => {
|
|
20
|
+
const container = createDOM(`
|
|
21
|
+
<div class="dfe-section">
|
|
22
|
+
<h2>Title</h2>
|
|
23
|
+
<p>Text</p>
|
|
24
|
+
<div class="cdc-open-viz-module" data-download-id="viz1">Viz Content</div>
|
|
25
|
+
</div>
|
|
26
|
+
`)
|
|
27
|
+
const viz = container.querySelector('[data-download-id="viz1"]') as HTMLElement
|
|
28
|
+
|
|
29
|
+
const result = prepareClonedElements(viz, false, 'viz1')
|
|
30
|
+
|
|
31
|
+
// When context not requested, clonedTree should be same as clonedViz (just the viz)
|
|
32
|
+
expect(result.clonedTree).toBe(result.clonedViz)
|
|
33
|
+
expect(result.clonedTree.querySelector('h2')).toBeNull()
|
|
34
|
+
expect(result.clonedTree.querySelector('p')).toBeNull()
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('should return viz-only even with perfect context available when not requested', () => {
|
|
38
|
+
const container = createDOM(`
|
|
39
|
+
<div class="dfe-section">
|
|
40
|
+
<h2>Perfect Title</h2>
|
|
41
|
+
<p>Perfect description</p>
|
|
42
|
+
<div class="cdc-open-viz-module" data-download-id="viz1">Chart</div>
|
|
43
|
+
</div>
|
|
44
|
+
`)
|
|
45
|
+
const viz = container.querySelector('[data-download-id="viz1"]') as HTMLElement
|
|
46
|
+
|
|
47
|
+
const result = prepareClonedElements(viz, false, 'viz1')
|
|
48
|
+
|
|
49
|
+
// Even though context is available, should not include it
|
|
50
|
+
expect(result.clonedTree).toBe(result.clonedViz)
|
|
51
|
+
expect(result.clonedTree.querySelector('h2')).toBeNull()
|
|
52
|
+
expect(result.clonedTree.querySelector('p')).toBeNull()
|
|
53
|
+
})
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
describe('Basic heading + viz patterns', () => {
|
|
57
|
+
it('should include H2 and paragraph when before viz', () => {
|
|
58
|
+
const container = createDOM(`
|
|
59
|
+
<div class="dfe-section">
|
|
60
|
+
<h2>Emergency Department Visits</h2>
|
|
61
|
+
<p>Weekly percent of total visits.</p>
|
|
62
|
+
<div class="cdc-open-viz-module" data-download-id="viz1">Chart</div>
|
|
63
|
+
</div>
|
|
64
|
+
`)
|
|
65
|
+
const viz = container.querySelector('[data-download-id="viz1"]') as HTMLElement
|
|
66
|
+
|
|
67
|
+
const result = prepareClonedElements(viz, true, 'viz1')
|
|
68
|
+
const children = Array.from(result.clonedTree.children)
|
|
69
|
+
|
|
70
|
+
expect(children.length).toBe(3)
|
|
71
|
+
expect(children[0].tagName).toBe('H2')
|
|
72
|
+
expect(children[1].tagName).toBe('P')
|
|
73
|
+
expect(result.clonedTree.contains(result.clonedViz)).toBe(true)
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it('should include H3 and paragraph when before viz', () => {
|
|
77
|
+
const container = createDOM(`
|
|
78
|
+
<div class="dfe-section">
|
|
79
|
+
<h3>Emergency Department Visits by Age</h3>
|
|
80
|
+
<p>Weekly percent of total visits.</p>
|
|
81
|
+
<div class="cdc-open-viz-module" data-download-id="viz1">Chart</div>
|
|
82
|
+
</div>
|
|
83
|
+
`)
|
|
84
|
+
const viz = container.querySelector('[data-download-id="viz1"]') as HTMLElement
|
|
85
|
+
|
|
86
|
+
const result = prepareClonedElements(viz, true, 'viz1')
|
|
87
|
+
const children = Array.from(result.clonedTree.children)
|
|
88
|
+
|
|
89
|
+
expect(children.length).toBe(3)
|
|
90
|
+
expect(children[0].tagName).toBe('H3')
|
|
91
|
+
expect(children[1].tagName).toBe('P')
|
|
92
|
+
expect(result.clonedTree.contains(result.clonedViz)).toBe(true)
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it('should prefer H3 over H2 when both present', () => {
|
|
96
|
+
const container = createDOM(`
|
|
97
|
+
<div class="dfe-section">
|
|
98
|
+
<h2>By Age</h2>
|
|
99
|
+
<h3>Emergency Department Visits by Age</h3>
|
|
100
|
+
<p>Weekly percent of total visits.</p>
|
|
101
|
+
<div class="cdc-open-viz-module" data-download-id="viz1">Chart</div>
|
|
102
|
+
</div>
|
|
103
|
+
`)
|
|
104
|
+
const viz = container.querySelector('[data-download-id="viz1"]') as HTMLElement
|
|
105
|
+
|
|
106
|
+
const result = prepareClonedElements(viz, true, 'viz1')
|
|
107
|
+
|
|
108
|
+
// Should include H3 (nearest to viz) but NOT H2
|
|
109
|
+
expect(result.clonedTree.querySelector('h2')).toBeNull()
|
|
110
|
+
expect(result.clonedTree.querySelector('h3')).not.toBeNull()
|
|
111
|
+
expect(result.clonedTree.querySelector('h3')?.textContent).toBe('Emergency Department Visits by Age')
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
it('should include all paragraphs between heading and viz', () => {
|
|
115
|
+
const container = createDOM(`
|
|
116
|
+
<div class="dfe-section">
|
|
117
|
+
<h3>Emergency Department Visits</h3>
|
|
118
|
+
<p>First paragraph.</p>
|
|
119
|
+
<p>Second paragraph.</p>
|
|
120
|
+
<p>Third paragraph.</p>
|
|
121
|
+
<div class="cdc-open-viz-module" data-download-id="viz1">Chart</div>
|
|
122
|
+
</div>
|
|
123
|
+
`)
|
|
124
|
+
const viz = container.querySelector('[data-download-id="viz1"]') as HTMLElement
|
|
125
|
+
|
|
126
|
+
const result = prepareClonedElements(viz, true, 'viz1')
|
|
127
|
+
const paragraphs = result.clonedTree.querySelectorAll('p')
|
|
128
|
+
|
|
129
|
+
expect(paragraphs.length).toBe(3)
|
|
130
|
+
expect(paragraphs[0].textContent).toBe('First paragraph.')
|
|
131
|
+
expect(paragraphs[1].textContent).toBe('Second paragraph.')
|
|
132
|
+
expect(paragraphs[2].textContent).toBe('Third paragraph.')
|
|
133
|
+
})
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
describe('Multiple visualizations', () => {
|
|
137
|
+
it('should return viz-only when viz2 follows viz1 with no heading between', () => {
|
|
138
|
+
const container = createDOM(`
|
|
139
|
+
<div class="dfe-section">
|
|
140
|
+
<h2>Title</h2>
|
|
141
|
+
<div class="cdc-open-viz-module" data-download-id="viz1">Chart 1</div>
|
|
142
|
+
<div class="cdc-open-viz-module" data-download-id="viz2">Chart 2</div>
|
|
143
|
+
</div>
|
|
144
|
+
`)
|
|
145
|
+
const viz2 = container.querySelector('[data-download-id="viz2"]') as HTMLElement
|
|
146
|
+
|
|
147
|
+
const result = prepareClonedElements(viz2, true, 'viz2')
|
|
148
|
+
|
|
149
|
+
// findNearestHeadingIndex stops at viz1 (returns -1), so we get viz-only
|
|
150
|
+
expect(result.clonedTree).toBe(result.clonedViz)
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
it('should not include heading from previous viz when taking screenshot of second viz', () => {
|
|
154
|
+
const container = createDOM(`
|
|
155
|
+
<div class="dfe-section">
|
|
156
|
+
<h2>First Chart</h2>
|
|
157
|
+
<div class="cdc-open-viz-module" data-download-id="viz1">Chart 1</div>
|
|
158
|
+
<h2>Second Chart</h2>
|
|
159
|
+
<div class="cdc-open-viz-module" data-download-id="viz2">Chart 2</div>
|
|
160
|
+
</div>
|
|
161
|
+
`)
|
|
162
|
+
const viz2 = container.querySelector('[data-download-id="viz2"]') as HTMLElement
|
|
163
|
+
|
|
164
|
+
const result = prepareClonedElements(viz2, true, 'viz2')
|
|
165
|
+
|
|
166
|
+
// Should include "Second Chart" but NOT "First Chart"
|
|
167
|
+
const headings = result.clonedTree.querySelectorAll('h2')
|
|
168
|
+
expect(headings.length).toBe(1)
|
|
169
|
+
expect(headings[0].textContent).toBe('Second Chart')
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
it('should ignore heading inside another viz', () => {
|
|
173
|
+
const container = createDOM(`
|
|
174
|
+
<div class="dfe-section">
|
|
175
|
+
<div class="cdc-open-viz-module" data-download-id="viz1">
|
|
176
|
+
<h2>Title Inside Viz1</h2>
|
|
177
|
+
<div>Chart 1</div>
|
|
178
|
+
</div>
|
|
179
|
+
<div class="cdc-open-viz-module" data-download-id="viz2">Chart 2</div>
|
|
180
|
+
</div>
|
|
181
|
+
`)
|
|
182
|
+
const viz2 = container.querySelector('[data-download-id="viz2"]') as HTMLElement
|
|
183
|
+
|
|
184
|
+
const result = prepareClonedElements(viz2, true, 'viz2')
|
|
185
|
+
|
|
186
|
+
// findNearestHeadingIndex stops at viz1 (returns -1), so we get viz-only
|
|
187
|
+
expect(result.clonedTree).toBe(result.clonedViz)
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
it('should return viz-only when viz3 follows viz1 and viz2', () => {
|
|
191
|
+
const container = createDOM(`
|
|
192
|
+
<div class="dfe-section">
|
|
193
|
+
<h2>Title</h2>
|
|
194
|
+
<div class="cdc-open-viz-module" data-download-id="viz1">Chart 1</div>
|
|
195
|
+
<div class="cdc-open-viz-module" data-download-id="viz2">Chart 2</div>
|
|
196
|
+
<div class="cdc-open-viz-module" data-download-id="viz3">Chart 3</div>
|
|
197
|
+
</div>
|
|
198
|
+
`)
|
|
199
|
+
const viz3 = container.querySelector('[data-download-id="viz3"]') as HTMLElement
|
|
200
|
+
|
|
201
|
+
const result = prepareClonedElements(viz3, true, 'viz3')
|
|
202
|
+
|
|
203
|
+
// Should stop at viz2, return viz-only
|
|
204
|
+
expect(result.clonedTree).toBe(result.clonedViz)
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
it('should prefer H3 over H2 between two vizs', () => {
|
|
208
|
+
const container = createDOM(`
|
|
209
|
+
<div class="dfe-section">
|
|
210
|
+
<div class="cdc-open-viz-module" data-download-id="viz1">Chart 1</div>
|
|
211
|
+
<h2>Section</h2>
|
|
212
|
+
<h3>Subsection</h3>
|
|
213
|
+
<p>Description</p>
|
|
214
|
+
<div class="cdc-open-viz-module" data-download-id="viz2">Chart 2</div>
|
|
215
|
+
</div>
|
|
216
|
+
`)
|
|
217
|
+
const viz2 = container.querySelector('[data-download-id="viz2"]') as HTMLElement
|
|
218
|
+
|
|
219
|
+
const result = prepareClonedElements(viz2, true, 'viz2')
|
|
220
|
+
|
|
221
|
+
// Should include H3 (nearest) but not H2
|
|
222
|
+
expect(result.clonedTree.querySelector('h2')).toBeNull()
|
|
223
|
+
expect(result.clonedTree.querySelector('h3')?.textContent).toBe('Subsection')
|
|
224
|
+
})
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
describe('Section boundaries', () => {
|
|
228
|
+
it('should stop at dfe-section boundary', () => {
|
|
229
|
+
const container = createDOM(`
|
|
230
|
+
<div class="cdc-dfe-body__center">
|
|
231
|
+
<div class="dfe-section">
|
|
232
|
+
<h2>Previous Section</h2>
|
|
233
|
+
<p>Previous content</p>
|
|
234
|
+
<div class="cdc-open-viz-module" data-download-id="viz0">Other viz</div>
|
|
235
|
+
</div>
|
|
236
|
+
<div class="dfe-section">
|
|
237
|
+
<div class="cdc-open-viz-module" data-download-id="viz1">Chart</div>
|
|
238
|
+
</div>
|
|
239
|
+
</div>
|
|
240
|
+
`)
|
|
241
|
+
const viz = container.querySelector('[data-download-id="viz1"]') as HTMLElement
|
|
242
|
+
|
|
243
|
+
const result = prepareClonedElements(viz, true, 'viz1')
|
|
244
|
+
|
|
245
|
+
// Should return viz-only (stopped at section boundary, no heading in same section)
|
|
246
|
+
expect(result.clonedTree).toBe(result.clonedViz)
|
|
247
|
+
expect(result.clonedTree.querySelector('h2')).toBeNull()
|
|
248
|
+
expect(result.clonedTree.textContent).not.toContain('Previous Section')
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
it('should work without dfe-section wrapper', () => {
|
|
252
|
+
const container = createDOM(`
|
|
253
|
+
<div class="some-container">
|
|
254
|
+
<h2>Title</h2>
|
|
255
|
+
<p>Description</p>
|
|
256
|
+
<div class="cdc-open-viz-module" data-download-id="viz1">Chart</div>
|
|
257
|
+
</div>
|
|
258
|
+
`)
|
|
259
|
+
const viz = container.querySelector('[data-download-id="viz1"]') as HTMLElement
|
|
260
|
+
|
|
261
|
+
const result = prepareClonedElements(viz, true, 'viz1')
|
|
262
|
+
const children = Array.from(result.clonedTree.children)
|
|
263
|
+
|
|
264
|
+
// Should include H2 and P even without dfe-section
|
|
265
|
+
expect(children.length).toBe(3)
|
|
266
|
+
expect(result.clonedTree.querySelector('h2')?.textContent).toBe('Title')
|
|
267
|
+
expect(result.clonedTree.querySelector('p')?.textContent).toBe('Description')
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
it('should stop at <section> tag boundary', () => {
|
|
271
|
+
const container = createDOM(`
|
|
272
|
+
<div class="outer-container">
|
|
273
|
+
<h2>Title Above Section</h2>
|
|
274
|
+
<section>
|
|
275
|
+
<div class="cdc-open-viz-module" data-download-id="viz1">Chart</div>
|
|
276
|
+
</section>
|
|
277
|
+
</div>
|
|
278
|
+
`)
|
|
279
|
+
const viz = container.querySelector('[data-download-id="viz1"]') as HTMLElement
|
|
280
|
+
|
|
281
|
+
const result = prepareClonedElements(viz, true, 'viz1')
|
|
282
|
+
|
|
283
|
+
// findParentWithContext stops at <section> boundary (returns null), so viz-only
|
|
284
|
+
expect(result.clonedTree).toBe(result.clonedViz)
|
|
285
|
+
})
|
|
286
|
+
|
|
287
|
+
it('should stop at innermost section boundary when nested', () => {
|
|
288
|
+
const container = createDOM(`
|
|
289
|
+
<div class="dfe-section">
|
|
290
|
+
<h2>Outer Title</h2>
|
|
291
|
+
<section>
|
|
292
|
+
<div class="cdc-open-viz-module" data-download-id="viz1">Chart</div>
|
|
293
|
+
</section>
|
|
294
|
+
</div>
|
|
295
|
+
`)
|
|
296
|
+
const viz = container.querySelector('[data-download-id="viz1"]') as HTMLElement
|
|
297
|
+
|
|
298
|
+
const result = prepareClonedElements(viz, true, 'viz1')
|
|
299
|
+
|
|
300
|
+
// Should stop at <section>, not climb to dfe-section
|
|
301
|
+
expect(result.clonedTree).toBe(result.clonedViz)
|
|
302
|
+
expect(result.clonedTree.textContent).not.toContain('Outer Title')
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
it('should include context when section tag is the parent with heading', () => {
|
|
306
|
+
const container = createDOM(`
|
|
307
|
+
<section>
|
|
308
|
+
<h2>Section Title</h2>
|
|
309
|
+
<p>Description</p>
|
|
310
|
+
<div class="cdc-open-viz-module" data-download-id="viz1">Chart</div>
|
|
311
|
+
</section>
|
|
312
|
+
`)
|
|
313
|
+
const viz = container.querySelector('[data-download-id="viz1"]') as HTMLElement
|
|
314
|
+
|
|
315
|
+
const result = prepareClonedElements(viz, true, 'viz1')
|
|
316
|
+
|
|
317
|
+
// Section with heading inside should work
|
|
318
|
+
expect(result.clonedTree.querySelector('h2')?.textContent).toBe('Section Title')
|
|
319
|
+
expect(result.clonedTree.querySelector('p')?.textContent).toBe('Description')
|
|
320
|
+
})
|
|
321
|
+
})
|
|
322
|
+
|
|
323
|
+
describe('Edge cases', () => {
|
|
324
|
+
it('should return viz-only when only paragraph (no heading) before viz', () => {
|
|
325
|
+
const container = createDOM(`
|
|
326
|
+
<div class="dfe-section">
|
|
327
|
+
<p>Some paragraph without heading</p>
|
|
328
|
+
<div class="cdc-open-viz-module" data-download-id="viz1">Chart</div>
|
|
329
|
+
</div>
|
|
330
|
+
`)
|
|
331
|
+
const viz = container.querySelector('[data-download-id="viz1"]') as HTMLElement
|
|
332
|
+
|
|
333
|
+
const result = prepareClonedElements(viz, true, 'viz1')
|
|
334
|
+
|
|
335
|
+
// Should return viz-only (no heading found)
|
|
336
|
+
expect(result.clonedTree).toBe(result.clonedViz)
|
|
337
|
+
expect(result.clonedTree.querySelector('p')).toBeNull()
|
|
338
|
+
})
|
|
339
|
+
|
|
340
|
+
it('should not include content after viz', () => {
|
|
341
|
+
const container = createDOM(`
|
|
342
|
+
<div class="dfe-section">
|
|
343
|
+
<h2>Title</h2>
|
|
344
|
+
<div class="cdc-open-viz-module" data-download-id="viz1">Chart</div>
|
|
345
|
+
<p>Content after viz should not be included</p>
|
|
346
|
+
</div>
|
|
347
|
+
`)
|
|
348
|
+
const viz = container.querySelector('[data-download-id="viz1"]') as HTMLElement
|
|
349
|
+
|
|
350
|
+
const result = prepareClonedElements(viz, true, 'viz1')
|
|
351
|
+
|
|
352
|
+
// Should include H2 and viz, but NOT the paragraph after
|
|
353
|
+
expect(result.clonedTree.querySelector('h2')).not.toBeNull()
|
|
354
|
+
expect(result.clonedTree.textContent).not.toContain('Content after viz should not be included')
|
|
355
|
+
})
|
|
356
|
+
|
|
357
|
+
it('should handle direct H2 element as child', () => {
|
|
358
|
+
const container = createDOM(`
|
|
359
|
+
<div class="dfe-section">
|
|
360
|
+
<h2>Direct H2 Child</h2>
|
|
361
|
+
<div class="cdc-open-viz-module" data-download-id="viz1">Chart</div>
|
|
362
|
+
</div>
|
|
363
|
+
`)
|
|
364
|
+
const viz = container.querySelector('[data-download-id="viz1"]') as HTMLElement
|
|
365
|
+
|
|
366
|
+
const result = prepareClonedElements(viz, true, 'viz1')
|
|
367
|
+
|
|
368
|
+
// Should find H2 even though it's a direct child (not nested in div)
|
|
369
|
+
expect(result.clonedTree.querySelector('h2')?.textContent).toBe('Direct H2 Child')
|
|
370
|
+
})
|
|
371
|
+
|
|
372
|
+
it('should handle H2 nested in div', () => {
|
|
373
|
+
const container = createDOM(`
|
|
374
|
+
<div class="dfe-section">
|
|
375
|
+
<div class="heading-wrapper">
|
|
376
|
+
<h2>Nested H2</h2>
|
|
377
|
+
</div>
|
|
378
|
+
<div class="cdc-open-viz-module" data-download-id="viz1">Chart</div>
|
|
379
|
+
</div>
|
|
380
|
+
`)
|
|
381
|
+
const viz = container.querySelector('[data-download-id="viz1"]') as HTMLElement
|
|
382
|
+
|
|
383
|
+
const result = prepareClonedElements(viz, true, 'viz1')
|
|
384
|
+
|
|
385
|
+
// Should find H2 even when nested
|
|
386
|
+
expect(result.clonedTree.querySelector('h2')?.textContent).toBe('Nested H2')
|
|
387
|
+
expect(result.clonedTree.querySelector('.heading-wrapper')).not.toBeNull()
|
|
388
|
+
})
|
|
389
|
+
|
|
390
|
+
it('should handle nested visualization wrapper', () => {
|
|
391
|
+
const container = createDOM(`
|
|
392
|
+
<div class="dfe-section">
|
|
393
|
+
<h2>Title</h2>
|
|
394
|
+
<p>Description</p>
|
|
395
|
+
<div class="outer-wrapper">
|
|
396
|
+
<div class="inner-wrapper">
|
|
397
|
+
<div class="cdc-open-viz-module" data-download-id="viz1">Chart</div>
|
|
398
|
+
</div>
|
|
399
|
+
</div>
|
|
400
|
+
</div>
|
|
401
|
+
`)
|
|
402
|
+
const viz = container.querySelector('[data-download-id="viz1"]') as HTMLElement
|
|
403
|
+
|
|
404
|
+
const result = prepareClonedElements(viz, true, 'viz1')
|
|
405
|
+
|
|
406
|
+
// Should include H2, P, and nested wrappers with viz
|
|
407
|
+
expect(result.clonedTree.querySelector('h2')).not.toBeNull()
|
|
408
|
+
expect(result.clonedTree.querySelector('p')).not.toBeNull()
|
|
409
|
+
expect(result.clonedTree.querySelector('.outer-wrapper')).not.toBeNull()
|
|
410
|
+
expect(result.clonedTree.querySelector('.inner-wrapper')).not.toBeNull()
|
|
411
|
+
expect(result.clonedViz.textContent).toBe('Chart')
|
|
412
|
+
})
|
|
413
|
+
})
|
|
414
|
+
})
|