@cdc/core 4.26.1 → 4.26.3
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/.claude/agents/qa-test-developer.md +126 -0
- package/CLAUDE.local.md +67 -0
- package/LICENSE +201 -0
- package/_stories/Gallery.Charts.stories.tsx +35 -42
- package/_stories/Gallery.DataBite.stories.tsx +15 -8
- package/_stories/Gallery.Maps.stories.tsx +37 -28
- package/_stories/Gallery.WaffleChart.stories.tsx +1 -1
- package/_stories/PageART.stories.tsx +5 -4
- package/_stories/PageBRFSS.stories.tsx +21 -16
- package/_stories/PageCancerRegistries.stories.tsx +15 -15
- package/_stories/PageEasternEquineEncephalitis.stories.tsx +33 -19
- package/_stories/PageExcessiveAlcoholUse.stories.tsx +148 -143
- package/_stories/PageMaternalMortality.stories.tsx +5 -4
- package/_stories/PageOralHealth.stories.tsx +15 -10
- package/_stories/PageRespiratory.stories.tsx +4 -4
- package/_stories/PageSmokingTobacco.stories.tsx +15 -10
- package/_stories/PageStateDiabetesProfiles.stories.tsx +15 -10
- package/_stories/PageWastewater.stories.tsx +44 -30
- package/_stories/VegaImport.stories.tsx +401 -0
- package/_stories/vega-fixtures/bars-with-line.json +444 -0
- package/_stories/vega-fixtures/bars.json +58 -0
- package/_stories/vega-fixtures/combo-bar-rolling-mean.json +88 -0
- package/_stories/vega-fixtures/combo.json +68 -0
- package/_stories/vega-fixtures/grouped-horizontal-bars.json +83 -0
- package/_stories/vega-fixtures/grouped-horizontal-bars2.json +231 -0
- package/_stories/vega-fixtures/horizontal-bar.json +427 -0
- package/_stories/vega-fixtures/horizontal-bars-with-bad-colors.json +197 -0
- package/_stories/vega-fixtures/horizontal-bars2.json +58 -0
- package/_stories/vega-fixtures/lines.json +227 -0
- package/_stories/vega-fixtures/measles-bars.json +348 -0
- package/_stories/vega-fixtures/measles-map.json +11101 -0
- package/_stories/vega-fixtures/measles-stacked-bars.json +2147 -0
- package/_stories/vega-fixtures/multi-dataset.json +255 -0
- package/_stories/vega-fixtures/no-data.json +14 -0
- package/_stories/vega-fixtures/pie-chart.json +94 -0
- package/_stories/vega-fixtures/repeat-spec.json +47 -0
- package/_stories/vega-fixtures/stacked-area.json +222 -0
- package/_stories/vega-fixtures/stacked-bar-with-rect.json +3412 -0
- package/_stories/vega-fixtures/stacked-bars-with-line.json +364 -0
- package/_stories/vega-fixtures/stacked-bars.json +212 -0
- package/_stories/vega-fixtures/stacked-horizontal-bars.json +140 -0
- package/_stories/vega-fixtures/warning-combo.json +59 -0
- package/_stories/vega-fixtures/warning-scatter-and-line.json +1182 -0
- package/assets/callout-flag.svg +7 -0
- package/assets/icon-chart-area.svg +1 -0
- package/assets/icon-chart-radar.svg +23 -0
- package/assets/logo2.svg +31 -0
- package/components/AdvancedEditor/EmbedEditor.tsx +270 -38
- package/components/Alert/components/Alert.styles.css +2 -2
- package/components/ComboBox/combobox.styles.css +48 -48
- package/components/CustomColorsEditor/CustomColorsEditor.css +53 -53
- package/components/CustomColorsEditor/CustomColorsEditor.tsx +3 -10
- package/components/DataTable/DataTable.tsx +46 -18
- package/components/DataTable/DataTableStandAlone.tsx +1 -0
- package/components/DataTable/components/ChartHeader.tsx +21 -12
- package/components/DataTable/components/MapHeader.tsx +34 -28
- package/components/DataTable/components/SortIcon/sort-icon.css +5 -5
- package/components/DataTable/data-table.css +50 -52
- package/components/DataTable/helpers/applyCustomOrder.ts +17 -0
- package/components/DataTable/helpers/getChartCellValue.ts +10 -7
- package/components/DataTable/helpers/getMapDataTableColumnKeys.ts +22 -0
- package/components/DataTable/helpers/getSeriesName.ts +6 -0
- package/components/DataTable/helpers/mapCellMatrix.tsx +33 -23
- package/components/DataTable/helpers/tests/mapCellMatrix.test.ts +33 -0
- package/components/DownloadButton.tsx +14 -6
- package/components/EditorPanel/ColumnsEditor.tsx +38 -31
- package/components/EditorPanel/CustomSortOrder.tsx +94 -0
- package/components/EditorPanel/DataTableEditor.tsx +139 -23
- package/components/EditorPanel/EditorPanel.styles.css +71 -71
- package/components/EditorPanel/EditorPanel.tsx +3 -8
- package/components/EditorPanel/EditorPanelDispatch.tsx +4 -4
- package/components/EditorPanel/FootnotesEditor.tsx +2 -2
- package/components/EditorPanel/VizFilterEditor/NestedDropdownEditor.tsx +21 -12
- package/components/EditorPanel/VizFilterEditor/VizFilterEditor.tsx +16 -10
- package/components/EditorPanel/VizFilterEditor/components/FilterOrder.tsx +33 -29
- package/components/EditorPanel/components/MarkupVariablesEditor.tsx +160 -106
- package/components/EditorPanel/components/PanelMarkup.tsx +5 -1
- package/{styles/v2/components → components/EditorPanel}/editor.scss +76 -22
- package/components/EditorPanel/sections/StyleTreatmentSection.tsx +99 -0
- package/components/EditorPanel/sections/VisualSection.tsx +11 -0
- package/components/EditorWrapper/editor-wrapper.style.css +1 -1
- package/components/Filters/Filters.tsx +3 -5
- package/components/Filters/components/Tabs.tsx +19 -7
- package/{styles → components/Filters}/filters.scss +3 -3
- package/components/Footnotes/FootnotesStandAlone.tsx +4 -2
- package/components/HeaderThemeSelector/HeaderThemeSelector.css +61 -5
- package/components/Layout/components/Responsive.tsx +14 -6
- package/components/Layout/components/Sidebar/components/Sidebar.tsx +1 -1
- package/components/Layout/components/Sidebar/components/sidebar.styles.scss +14 -20
- package/components/Layout/components/Visualization/index.tsx +50 -38
- package/components/Layout/components/Visualization/visualizations.scss +232 -15
- package/components/Layout/components/VisualizationContainer.test.tsx +67 -0
- package/components/Layout/components/VisualizationContainer.tsx +37 -0
- package/components/Layout/components/VisualizationContent.test.tsx +182 -0
- package/components/Layout/components/VisualizationContent.tsx +75 -0
- package/components/Layout/index.tsx +5 -5
- package/components/Layout/styles/editor-utils.scss +3 -3
- package/components/Layout/styles/editor.scss +4 -4
- package/components/Legend/Legend.Gradient.tsx +7 -1
- package/components/Loader/loader.styles.css +2 -2
- package/components/Loading.jsx +1 -1
- package/components/MediaControls.tsx +10 -3
- package/components/MultiSelect/multiselect.styles.css +19 -19
- package/components/NestedDropdown/nesteddropdown.styles.css +15 -15
- package/components/PaletteSelector/PaletteSelector.css +15 -15
- package/components/RichTooltip/richTooltip.css +6 -6
- package/components/Table/table.styles.css +2 -2
- package/components/Waiting.tsx +1 -1
- package/components/_stories/CustomColorsEditor.stories.tsx +37 -0
- package/components/_stories/DataTable.stories.tsx +1 -0
- package/components/_stories/Filters.stories.tsx +1 -1
- package/components/_stories/styles.scss +0 -1
- package/components/elements/Button.jsx +1 -1
- package/components/elements/Card.jsx +1 -1
- package/{styles/v2/components → components/elements}/button.scss +9 -8
- package/components/inputs/InputCheckbox.jsx +1 -1
- package/components/inputs/InputSelect.tsx +1 -1
- package/components/inputs/InputText.jsx +1 -1
- package/components/inputs/InputToggle.tsx +1 -1
- package/{styles/v2/components/input → components/inputs}/_input-check-radio.scss +2 -2
- package/{styles/v2/components/input → components/inputs}/_input-group.scss +3 -3
- package/{styles/v2/components/input → components/inputs}/_input-slider.scss +2 -2
- package/{styles/v2/components/input → components/inputs}/_input.scss +5 -5
- package/{styles/v2/components/input → components/inputs}/index.scss +2 -2
- package/{styles → components}/loading.scss +1 -1
- package/components/managers/DataDesigner.tsx +1 -1
- package/{styles/v2/components → components/managers}/data-designer.scss +6 -7
- package/components/ui/Accordion.jsx +1 -1
- package/components/ui/Icon.tsx +1 -1
- package/components/ui/LoadSpin.jsx +1 -1
- package/components/ui/Modal.jsx +1 -1
- package/components/ui/Overlay.jsx +1 -1
- package/components/ui/Title/index.test.tsx +34 -0
- package/components/ui/Title/index.tsx +24 -7
- package/components/ui/Title/title.styles.css +119 -25
- package/components/ui/Tooltip.tsx +1 -1
- package/components/ui/_stories/Title.stories.tsx +1 -1
- package/{styles/v2/components → components/ui}/accordion.scss +3 -3
- package/components/ui/accordion.styles.css +11 -11
- package/{styles/v2/components → components/ui}/modal.scss +2 -2
- package/{styles/v2/components → components/ui}/overlay.scss +6 -6
- package/{styles/v2/components → components}/ui/tooltip.scss +1 -1
- package/{styles → components}/waiting.scss +9 -3
- package/data/colorPalettes.ts +18 -5
- package/data/mapColorPalettes.ts +10 -0
- package/devTemplate/dev.js +285 -0
- package/devTemplate/index.html +30 -0
- package/devTemplate/preview.html +1503 -0
- package/devTemplate/sidebar.css +151 -0
- package/dist/cove-main.css +2530 -3901
- package/dist/cove-main.css.map +1 -1
- package/generateViteConfig.js +111 -2
- package/helpers/DataTransform.ts +1 -5
- package/helpers/backfillDefaults.ts +35 -0
- package/helpers/constants.ts +12 -0
- package/helpers/cove/date.ts +64 -3
- package/helpers/cove/number.ts +29 -15
- package/helpers/cove/string.ts +29 -0
- package/helpers/coveUpdateWorker.ts +14 -8
- package/helpers/displayDataAsText.ts +1 -1
- package/helpers/embed/embedCodeGenerator.ts +80 -0
- package/helpers/embed/embedHelper.js +169 -0
- package/helpers/embed/filterUtils.ts +121 -0
- package/helpers/embed/index.ts +17 -0
- package/helpers/embed/urlValidation.ts +119 -0
- package/helpers/extractDataAndMetadata.ts +20 -0
- package/helpers/fetchRemoteData.ts +14 -8
- package/helpers/filterVizData.ts +6 -1
- package/helpers/getFileExtension.ts +0 -6
- package/helpers/labelHash.ts +9 -0
- package/helpers/markupProcessor.ts +56 -38
- package/helpers/metrics/types.ts +3 -0
- package/helpers/palettes/colorDistributions.ts +1 -1
- package/helpers/palettes/utils.ts +12 -12
- package/helpers/parseCsvWithQuotes.ts +15 -14
- package/helpers/prepareScreenshot.ts +33 -10
- package/helpers/testing.ts +44 -0
- package/helpers/tests/DataTransform.test.ts +125 -0
- package/helpers/tests/abbreviateNumber.test.ts +59 -0
- package/helpers/tests/backfillDefaults.test.ts +253 -0
- package/helpers/tests/date.test.ts +110 -0
- package/helpers/tests/extractDataAndMetadata.test.ts +93 -0
- package/helpers/tests/markupProcessor.test.ts +315 -124
- package/helpers/tests/number.test.ts +42 -0
- package/helpers/tests/prepareScreenshot.test.ts +28 -28
- package/helpers/tests/testStandaloneBuild.ts +36 -26
- package/helpers/tests/useDataVizClasses.test.ts +66 -0
- package/helpers/tests/visualizationWrapperUsage.test.ts +57 -0
- package/helpers/useDataVizClasses.ts +13 -7
- package/helpers/vegaConfig.ts +1 -1
- package/helpers/vegaConfigImport.ts +160 -0
- package/helpers/ver/4.24.4.ts +24 -0
- package/helpers/ver/4.26.1.ts +1 -1
- package/helpers/ver/4.26.2.ts +84 -0
- package/helpers/ver/4.26.3.ts +44 -0
- package/helpers/ver/4.26.4.ts +31 -0
- package/helpers/ver/tests/4.26.1.test.ts +105 -0
- package/helpers/ver/tests/4.26.2.test.ts +298 -0
- package/helpers/ver/tests/4.26.3.test.ts +168 -0
- package/helpers/ver/tests/4.26.4.test.ts +88 -0
- package/helpers/ver/tests/coveUpdateWorker.test.ts +57 -0
- package/helpers/viewports.ts +2 -0
- package/package.json +27 -32
- package/styles/_global.scss +7 -7
- package/styles/_reset.scss +2 -2
- package/styles/{v2/base → base}/_file-selector.scss +4 -4
- package/styles/{v2/base → base}/_general.scss +2 -4
- package/styles/{v2/base → base}/index.scss +1 -1
- package/styles/base.scss +107 -165
- package/styles/cove-main.scss +3 -6
- package/styles/layout/_component.scss +110 -0
- package/styles/{v2/layout → layout}/_data-table.scss +7 -7
- package/styles/layout/_wrapper-padding.scss +27 -0
- package/styles/{v2/main.scss → main.scss} +3 -1
- package/styles/{v2/themes → themes}/_color-definitions.scss +46 -41
- package/styles/{_accessibility.scss → utils/_accessibility.scss} +1 -1
- package/styles/{v2/utils → utils}/_grid.scss +8 -3
- package/styles/{_global-variables.scss → utils/_properties.scss} +133 -112
- package/styles/{v2/utils → utils}/index.scss +2 -1
- package/types/Annotation.ts +10 -11
- package/types/Axis.ts +2 -0
- package/types/ComponentStyles.ts +1 -0
- package/types/ConfigureData.ts +1 -0
- package/types/General.ts +2 -0
- package/types/MarkupInclude.ts +1 -0
- package/types/MarkupVariable.ts +2 -1
- package/types/Palette.ts +22 -0
- package/types/Table.ts +9 -0
- package/types/Visualization.ts +7 -0
- package/_stories/StoryRenderingTests.stories.tsx +0 -164
- package/helpers/embedCodeGenerator.ts +0 -109
- package/styles/_common-components.css +0 -73
- package/styles/_variables.scss +0 -63
- package/styles/v2/layout/_component.scss +0 -21
- package/styles/v2/utils/_variables.scss +0 -9
- package/{styles/v2/components/card.scss → components/elements/card.css} +2 -2
- /package/{styles/v2/components → components/ui}/icon.scss +0 -0
- /package/{styles/v2/components → components/ui}/loadspin.scss +0 -0
- /package/styles/{v2/base → base}/_heading.scss +0 -0
- /package/styles/{v2/base → base}/_reset.scss +0 -0
- /package/styles/{v2/layout → layout}/_alert.scss +0 -0
- /package/styles/{v2/layout → layout}/_progression.scss +0 -0
- /package/styles/{v2/layout → layout}/_tooltip.scss +0 -0
- /package/styles/{v2/layout → layout}/index.scss +0 -0
- /package/styles/{v2/themes → themes}/index.scss +0 -0
- /package/styles/{v2/utils → utils}/_align.scss +0 -0
- /package/styles/{v2/utils → utils}/_animations.scss +0 -0
- /package/styles/{v2/utils → utils}/_breakpoints.scss +0 -0
- /package/styles/{v2/utils → utils}/_mixins.scss +0 -0
|
@@ -21,7 +21,7 @@ describe('processMarkupVariables', () => {
|
|
|
21
21
|
]
|
|
22
22
|
|
|
23
23
|
const content = 'The state is {{state}}'
|
|
24
|
-
const result = processMarkupVariables(content, testData, variables)
|
|
24
|
+
const result = processMarkupVariables(content, testData, variables, { locale: 'en-US' })
|
|
25
25
|
|
|
26
26
|
expect(result.processedContent).toBe('The state is California, Texas, and Florida')
|
|
27
27
|
expect(result.shouldHideSection).toBe(false)
|
|
@@ -35,7 +35,7 @@ describe('processMarkupVariables', () => {
|
|
|
35
35
|
]
|
|
36
36
|
|
|
37
37
|
const content = 'Data for {{state}} in {{year}}'
|
|
38
|
-
const result = processMarkupVariables(content, testData, variables)
|
|
38
|
+
const result = processMarkupVariables(content, testData, variables, { locale: 'en-US' })
|
|
39
39
|
|
|
40
40
|
expect(result.processedContent).toContain('California, Texas, and Florida')
|
|
41
41
|
expect(result.processedContent).toContain('2023')
|
|
@@ -43,18 +43,16 @@ describe('processMarkupVariables', () => {
|
|
|
43
43
|
|
|
44
44
|
it('should return original content if no variables defined', () => {
|
|
45
45
|
const content = 'Text with {{undefined}}'
|
|
46
|
-
const result = processMarkupVariables(content, testData, [])
|
|
46
|
+
const result = processMarkupVariables(content, testData, [], { locale: 'en-US' })
|
|
47
47
|
|
|
48
48
|
expect(result.processedContent).toBe('Text with {{undefined}}')
|
|
49
49
|
})
|
|
50
50
|
|
|
51
51
|
it('should leave unknown variable tags unchanged', () => {
|
|
52
|
-
const variables: MarkupVariable[] = [
|
|
53
|
-
{ name: 'State', tag: '{{state}}', columnName: 'state', conditions: [] }
|
|
54
|
-
]
|
|
52
|
+
const variables: MarkupVariable[] = [{ name: 'State', tag: '{{state}}', columnName: 'state', conditions: [] }]
|
|
55
53
|
|
|
56
54
|
const content = 'Known: {{state}}, Unknown: {{unknown}}'
|
|
57
|
-
const result = processMarkupVariables(content, testData, variables)
|
|
55
|
+
const result = processMarkupVariables(content, testData, variables, { locale: 'en-US' })
|
|
58
56
|
|
|
59
57
|
expect(result.processedContent).toContain('California, Texas, and Florida')
|
|
60
58
|
expect(result.processedContent).toContain('{{unknown}}')
|
|
@@ -74,7 +72,7 @@ describe('processMarkupVariables', () => {
|
|
|
74
72
|
]
|
|
75
73
|
|
|
76
74
|
const content = 'Population: {{population}}'
|
|
77
|
-
const result = processMarkupVariables(content, testData, variables)
|
|
75
|
+
const result = processMarkupVariables(content, testData, variables, { locale: 'en-US' })
|
|
78
76
|
|
|
79
77
|
expect(result.processedContent).toContain('39,538,223')
|
|
80
78
|
expect(result.processedContent).toContain('29,145,505')
|
|
@@ -93,7 +91,7 @@ describe('processMarkupVariables', () => {
|
|
|
93
91
|
]
|
|
94
92
|
|
|
95
93
|
const content = 'Population: {{population}}'
|
|
96
|
-
const result = processMarkupVariables(content, testData, variables)
|
|
94
|
+
const result = processMarkupVariables(content, testData, variables, { locale: 'en-US' })
|
|
97
95
|
|
|
98
96
|
expect(result.processedContent).toContain('39538223')
|
|
99
97
|
expect(result.processedContent).not.toContain('39,538,223')
|
|
@@ -107,14 +105,12 @@ describe('processMarkupVariables', () => {
|
|
|
107
105
|
name: 'State',
|
|
108
106
|
tag: '{{state}}',
|
|
109
107
|
columnName: 'state',
|
|
110
|
-
conditions: [
|
|
111
|
-
{ columnName: 'state', isOrIsNotEqualTo: 'is', value: 'California' }
|
|
112
|
-
]
|
|
108
|
+
conditions: [{ columnName: 'state', isOrIsNotEqualTo: 'is', value: 'California' }]
|
|
113
109
|
}
|
|
114
110
|
]
|
|
115
111
|
|
|
116
112
|
const content = 'The state is {{state}}'
|
|
117
|
-
const result = processMarkupVariables(content, testData, variables)
|
|
113
|
+
const result = processMarkupVariables(content, testData, variables, { locale: 'en-US' })
|
|
118
114
|
|
|
119
115
|
expect(result.processedContent).toBe('The state is California')
|
|
120
116
|
})
|
|
@@ -125,14 +121,12 @@ describe('processMarkupVariables', () => {
|
|
|
125
121
|
name: 'State',
|
|
126
122
|
tag: '{{state}}',
|
|
127
123
|
columnName: 'state',
|
|
128
|
-
conditions: [
|
|
129
|
-
{ columnName: 'state', isOrIsNotEqualTo: 'is not', value: 'California' }
|
|
130
|
-
]
|
|
124
|
+
conditions: [{ columnName: 'state', isOrIsNotEqualTo: 'is not', value: 'California' }]
|
|
131
125
|
}
|
|
132
126
|
]
|
|
133
127
|
|
|
134
128
|
const content = 'States: {{state}}'
|
|
135
|
-
const result = processMarkupVariables(content, testData, variables)
|
|
129
|
+
const result = processMarkupVariables(content, testData, variables, { locale: 'en-US' })
|
|
136
130
|
|
|
137
131
|
expect(result.processedContent).toBe('States: Texas and Florida')
|
|
138
132
|
expect(result.processedContent).not.toContain('California')
|
|
@@ -158,7 +152,7 @@ describe('processMarkupVariables', () => {
|
|
|
158
152
|
]
|
|
159
153
|
|
|
160
154
|
const content = 'State: {{state}}'
|
|
161
|
-
const result = processMarkupVariables(content, dataWithYears, variables)
|
|
155
|
+
const result = processMarkupVariables(content, dataWithYears, variables, { locale: 'en-US' })
|
|
162
156
|
|
|
163
157
|
expect(result.processedContent).toBe('State: California')
|
|
164
158
|
})
|
|
@@ -169,14 +163,12 @@ describe('processMarkupVariables', () => {
|
|
|
169
163
|
name: 'State',
|
|
170
164
|
tag: '{{state}}',
|
|
171
165
|
columnName: 'state',
|
|
172
|
-
conditions: [
|
|
173
|
-
{ columnName: 'state', isOrIsNotEqualTo: 'is', value: 'NonExistent' }
|
|
174
|
-
]
|
|
166
|
+
conditions: [{ columnName: 'state', isOrIsNotEqualTo: 'is', value: 'NonExistent' }]
|
|
175
167
|
}
|
|
176
168
|
]
|
|
177
169
|
|
|
178
170
|
const content = 'State: {{state}}'
|
|
179
|
-
const result = processMarkupVariables(content, testData, variables)
|
|
171
|
+
const result = processMarkupVariables(content, testData, variables, { locale: 'en-US' })
|
|
180
172
|
|
|
181
173
|
expect(result.processedContent).toBe('State: ')
|
|
182
174
|
})
|
|
@@ -184,70 +176,45 @@ describe('processMarkupVariables', () => {
|
|
|
184
176
|
|
|
185
177
|
describe('Empty Values and Null Handling', () => {
|
|
186
178
|
it('should filter out empty string values', () => {
|
|
187
|
-
const dataWithEmpty = [
|
|
188
|
-
{ name: 'Alice' },
|
|
189
|
-
{ name: '' },
|
|
190
|
-
{ name: 'Bob' },
|
|
191
|
-
{ name: '' }
|
|
192
|
-
]
|
|
179
|
+
const dataWithEmpty = [{ name: 'Alice' }, { name: '' }, { name: 'Bob' }, { name: '' }]
|
|
193
180
|
|
|
194
|
-
const variables: MarkupVariable[] = [
|
|
195
|
-
{ name: 'Name', tag: '{{name}}', columnName: 'name', conditions: [] }
|
|
196
|
-
]
|
|
181
|
+
const variables: MarkupVariable[] = [{ name: 'Name', tag: '{{name}}', columnName: 'name', conditions: [] }]
|
|
197
182
|
|
|
198
183
|
const content = 'Names: {{name}}'
|
|
199
|
-
const result = processMarkupVariables(content, dataWithEmpty, variables)
|
|
184
|
+
const result = processMarkupVariables(content, dataWithEmpty, variables, { locale: 'en-US' })
|
|
200
185
|
|
|
201
186
|
expect(result.processedContent).toBe('Names: Alice and Bob')
|
|
202
187
|
})
|
|
203
188
|
|
|
204
189
|
it('should handle null values gracefully', () => {
|
|
205
|
-
const dataWithNull = [
|
|
206
|
-
{ value: 'A' },
|
|
207
|
-
{ value: null },
|
|
208
|
-
{ value: 'B' }
|
|
209
|
-
]
|
|
190
|
+
const dataWithNull = [{ value: 'A' }, { value: null }, { value: 'B' }]
|
|
210
191
|
|
|
211
|
-
const variables: MarkupVariable[] = [
|
|
212
|
-
{ name: 'Value', tag: '{{value}}', columnName: 'value', conditions: [] }
|
|
213
|
-
]
|
|
192
|
+
const variables: MarkupVariable[] = [{ name: 'Value', tag: '{{value}}', columnName: 'value', conditions: [] }]
|
|
214
193
|
|
|
215
194
|
const content = 'Values: {{value}}'
|
|
216
|
-
const result = processMarkupVariables(content, dataWithNull, variables)
|
|
195
|
+
const result = processMarkupVariables(content, dataWithNull, variables, { locale: 'en-US' })
|
|
217
196
|
|
|
218
197
|
expect(result.processedContent).toBe('Values: A and B')
|
|
219
198
|
})
|
|
220
199
|
|
|
221
200
|
it('should handle undefined values', () => {
|
|
222
|
-
const dataWithUndefined = [
|
|
223
|
-
{ value: 'A' },
|
|
224
|
-
{ value: undefined },
|
|
225
|
-
{ value: 'B' }
|
|
226
|
-
]
|
|
201
|
+
const dataWithUndefined = [{ value: 'A' }, { value: undefined }, { value: 'B' }]
|
|
227
202
|
|
|
228
|
-
const variables: MarkupVariable[] = [
|
|
229
|
-
{ name: 'Value', tag: '{{value}}', columnName: 'value', conditions: [] }
|
|
230
|
-
]
|
|
203
|
+
const variables: MarkupVariable[] = [{ name: 'Value', tag: '{{value}}', columnName: 'value', conditions: [] }]
|
|
231
204
|
|
|
232
205
|
const content = 'Values: {{value}}'
|
|
233
|
-
const result = processMarkupVariables(content, dataWithUndefined, variables)
|
|
206
|
+
const result = processMarkupVariables(content, dataWithUndefined, variables, { locale: 'en-US' })
|
|
234
207
|
|
|
235
208
|
expect(result.processedContent).toBe('Values: A and B')
|
|
236
209
|
})
|
|
237
210
|
|
|
238
211
|
it('should return empty when all values are empty/null', () => {
|
|
239
|
-
const dataEmpty = [
|
|
240
|
-
{ value: '' },
|
|
241
|
-
{ value: null },
|
|
242
|
-
{ value: undefined }
|
|
243
|
-
]
|
|
212
|
+
const dataEmpty = [{ value: '' }, { value: null }, { value: undefined }]
|
|
244
213
|
|
|
245
|
-
const variables: MarkupVariable[] = [
|
|
246
|
-
{ name: 'Value', tag: '{{value}}', columnName: 'value', conditions: [] }
|
|
247
|
-
]
|
|
214
|
+
const variables: MarkupVariable[] = [{ name: 'Value', tag: '{{value}}', columnName: 'value', conditions: [] }]
|
|
248
215
|
|
|
249
216
|
const content = 'Values: {{value}}'
|
|
250
|
-
const result = processMarkupVariables(content, dataEmpty, variables)
|
|
217
|
+
const result = processMarkupVariables(content, dataEmpty, variables, { locale: 'en-US' })
|
|
251
218
|
|
|
252
219
|
expect(result.processedContent).toBe('Values: ')
|
|
253
220
|
})
|
|
@@ -256,36 +223,30 @@ describe('processMarkupVariables', () => {
|
|
|
256
223
|
describe('List Formatting', () => {
|
|
257
224
|
it('should format two items with "and" in production mode', () => {
|
|
258
225
|
const data = [{ state: 'CA' }, { state: 'TX' }]
|
|
259
|
-
const variables: MarkupVariable[] = [
|
|
260
|
-
{ name: 'State', tag: '{{state}}', columnName: 'state', conditions: [] }
|
|
261
|
-
]
|
|
226
|
+
const variables: MarkupVariable[] = [{ name: 'State', tag: '{{state}}', columnName: 'state', conditions: [] }]
|
|
262
227
|
|
|
263
228
|
const content = '{{state}}'
|
|
264
|
-
const result = processMarkupVariables(content, data, variables, { isEditor: false })
|
|
229
|
+
const result = processMarkupVariables(content, data, variables, { isEditor: false, locale: 'en-US' })
|
|
265
230
|
|
|
266
231
|
expect(result.processedContent).toBe('CA and TX')
|
|
267
232
|
})
|
|
268
233
|
|
|
269
234
|
it('should format three items with Oxford comma and "and"', () => {
|
|
270
235
|
const data = [{ state: 'CA' }, { state: 'TX' }, { state: 'FL' }]
|
|
271
|
-
const variables: MarkupVariable[] = [
|
|
272
|
-
{ name: 'State', tag: '{{state}}', columnName: 'state', conditions: [] }
|
|
273
|
-
]
|
|
236
|
+
const variables: MarkupVariable[] = [{ name: 'State', tag: '{{state}}', columnName: 'state', conditions: [] }]
|
|
274
237
|
|
|
275
238
|
const content = '{{state}}'
|
|
276
|
-
const result = processMarkupVariables(content, data, variables, { isEditor: false })
|
|
239
|
+
const result = processMarkupVariables(content, data, variables, { isEditor: false, locale: 'en-US' })
|
|
277
240
|
|
|
278
241
|
expect(result.processedContent).toBe('CA, TX, and FL')
|
|
279
242
|
})
|
|
280
243
|
|
|
281
244
|
it('should use "or" conjunction in editor mode', () => {
|
|
282
245
|
const data = [{ state: 'CA' }, { state: 'TX' }]
|
|
283
|
-
const variables: MarkupVariable[] = [
|
|
284
|
-
{ name: 'State', tag: '{{state}}', columnName: 'state', conditions: [] }
|
|
285
|
-
]
|
|
246
|
+
const variables: MarkupVariable[] = [{ name: 'State', tag: '{{state}}', columnName: 'state', conditions: [] }]
|
|
286
247
|
|
|
287
248
|
const content = '{{state}}'
|
|
288
|
-
const result = processMarkupVariables(content, data, variables, { isEditor: true })
|
|
249
|
+
const result = processMarkupVariables(content, data, variables, { isEditor: true, locale: 'en-US' })
|
|
289
250
|
|
|
290
251
|
expect(result.processedContent).toBe('CA or TX')
|
|
291
252
|
})
|
|
@@ -293,17 +254,12 @@ describe('processMarkupVariables', () => {
|
|
|
293
254
|
|
|
294
255
|
describe('XSS Prevention and Security', () => {
|
|
295
256
|
it('should handle data with HTML tags safely', () => {
|
|
296
|
-
const maliciousData = [
|
|
297
|
-
{ value: '<script>alert("xss")</script>' },
|
|
298
|
-
{ value: '<img src=x onerror=alert(1)>' }
|
|
299
|
-
]
|
|
257
|
+
const maliciousData = [{ value: '<script>alert("xss")</script>' }, { value: '<img src=x onerror=alert(1)>' }]
|
|
300
258
|
|
|
301
|
-
const variables: MarkupVariable[] = [
|
|
302
|
-
{ name: 'Value', tag: '{{value}}', columnName: 'value', conditions: [] }
|
|
303
|
-
]
|
|
259
|
+
const variables: MarkupVariable[] = [{ name: 'Value', tag: '{{value}}', columnName: 'value', conditions: [] }]
|
|
304
260
|
|
|
305
261
|
const content = 'Data: {{value}}'
|
|
306
|
-
const result = processMarkupVariables(content, maliciousData, variables)
|
|
262
|
+
const result = processMarkupVariables(content, maliciousData, variables, { locale: 'en-US' })
|
|
307
263
|
|
|
308
264
|
// Should return the raw strings, parsing responsibility is on the component using html-react-parser
|
|
309
265
|
expect(result.processedContent).toContain('<script>')
|
|
@@ -311,17 +267,12 @@ describe('processMarkupVariables', () => {
|
|
|
311
267
|
})
|
|
312
268
|
|
|
313
269
|
it('should handle special characters in data', () => {
|
|
314
|
-
const specialData = [
|
|
315
|
-
{ value: 'Test & Value' },
|
|
316
|
-
{ value: 'Price: $100 < $200' }
|
|
317
|
-
]
|
|
270
|
+
const specialData = [{ value: 'Test & Value' }, { value: 'Price: $100 < $200' }]
|
|
318
271
|
|
|
319
|
-
const variables: MarkupVariable[] = [
|
|
320
|
-
{ name: 'Value', tag: '{{value}}', columnName: 'value', conditions: [] }
|
|
321
|
-
]
|
|
272
|
+
const variables: MarkupVariable[] = [{ name: 'Value', tag: '{{value}}', columnName: 'value', conditions: [] }]
|
|
322
273
|
|
|
323
274
|
const content = '{{value}}'
|
|
324
|
-
const result = processMarkupVariables(content, specialData, variables)
|
|
275
|
+
const result = processMarkupVariables(content, specialData, variables, { locale: 'en-US' })
|
|
325
276
|
|
|
326
277
|
expect(result.processedContent).toContain('Test & Value')
|
|
327
278
|
expect(result.processedContent).toContain('Price: $100 < $200')
|
|
@@ -331,14 +282,13 @@ describe('processMarkupVariables', () => {
|
|
|
331
282
|
describe('Hide Section Logic', () => {
|
|
332
283
|
it('should set shouldHideSection when allowHideSection is true and values are empty', () => {
|
|
333
284
|
const emptyData = [{ value: '' }]
|
|
334
|
-
const variables: MarkupVariable[] = [
|
|
335
|
-
{ name: 'Value', tag: '{{value}}', columnName: 'value', conditions: [] }
|
|
336
|
-
]
|
|
285
|
+
const variables: MarkupVariable[] = [{ name: 'Value', tag: '{{value}}', columnName: 'value', conditions: [] }]
|
|
337
286
|
|
|
338
287
|
const content = '{{value}}'
|
|
339
288
|
const result = processMarkupVariables(content, emptyData, variables, {
|
|
340
289
|
allowHideSection: true,
|
|
341
|
-
isEditor: false
|
|
290
|
+
isEditor: false,
|
|
291
|
+
locale: 'en-US'
|
|
342
292
|
})
|
|
343
293
|
|
|
344
294
|
expect(result.shouldHideSection).toBe(true)
|
|
@@ -346,14 +296,13 @@ describe('processMarkupVariables', () => {
|
|
|
346
296
|
|
|
347
297
|
it('should not hide section in editor mode even if values are empty', () => {
|
|
348
298
|
const emptyData = [{ value: '' }]
|
|
349
|
-
const variables: MarkupVariable[] = [
|
|
350
|
-
{ name: 'Value', tag: '{{value}}', columnName: 'value', conditions: [] }
|
|
351
|
-
]
|
|
299
|
+
const variables: MarkupVariable[] = [{ name: 'Value', tag: '{{value}}', columnName: 'value', conditions: [] }]
|
|
352
300
|
|
|
353
301
|
const content = '{{value}}'
|
|
354
302
|
const result = processMarkupVariables(content, emptyData, variables, {
|
|
355
303
|
allowHideSection: true,
|
|
356
|
-
isEditor: true
|
|
304
|
+
isEditor: true,
|
|
305
|
+
locale: 'en-US'
|
|
357
306
|
})
|
|
358
307
|
|
|
359
308
|
expect(result.shouldHideSection).toBe(false)
|
|
@@ -363,67 +312,268 @@ describe('processMarkupVariables', () => {
|
|
|
363
312
|
describe('No Data Message Logic', () => {
|
|
364
313
|
it('should set shouldShowNoDataMessage when enabled and values are empty', () => {
|
|
365
314
|
const emptyData = [{ value: '' }]
|
|
366
|
-
const variables: MarkupVariable[] = [
|
|
367
|
-
{ name: 'Value', tag: '{{value}}', columnName: 'value', conditions: [] }
|
|
368
|
-
]
|
|
315
|
+
const variables: MarkupVariable[] = [{ name: 'Value', tag: '{{value}}', columnName: 'value', conditions: [] }]
|
|
369
316
|
|
|
370
317
|
const content = '{{value}}'
|
|
371
318
|
const result = processMarkupVariables(content, emptyData, variables, {
|
|
372
319
|
showNoDataMessage: true,
|
|
373
|
-
isEditor: false
|
|
320
|
+
isEditor: false,
|
|
321
|
+
locale: 'en-US'
|
|
374
322
|
})
|
|
375
323
|
|
|
376
324
|
expect(result.shouldShowNoDataMessage).toBe(true)
|
|
377
325
|
})
|
|
378
326
|
})
|
|
379
327
|
|
|
380
|
-
describe('
|
|
381
|
-
it('should
|
|
328
|
+
describe('Metadata-Sourced Variables', () => {
|
|
329
|
+
it('should resolve metadata variable from dataMetadata', () => {
|
|
330
|
+
const variables: MarkupVariable[] = [
|
|
331
|
+
{
|
|
332
|
+
name: 'Last Updated',
|
|
333
|
+
tag: '{{lastUpdated}}',
|
|
334
|
+
metadataKey: 'lastUpdated',
|
|
335
|
+
conditions: []
|
|
336
|
+
}
|
|
337
|
+
]
|
|
338
|
+
|
|
339
|
+
const content = 'Data last updated {{lastUpdated}}'
|
|
340
|
+
const result = processMarkupVariables(content, testData, variables, {
|
|
341
|
+
dataMetadata: { lastUpdated: 'January 15, 2026' }
|
|
342
|
+
})
|
|
343
|
+
|
|
344
|
+
expect(result.processedContent).toBe('Data last updated January 15, 2026')
|
|
345
|
+
expect(result.shouldHideSection).toBe(false)
|
|
346
|
+
expect(result.shouldShowNoDataMessage).toBe(false)
|
|
347
|
+
})
|
|
348
|
+
|
|
349
|
+
it('should return empty string when dataMetadata does not contain the key', () => {
|
|
350
|
+
const variables: MarkupVariable[] = [
|
|
351
|
+
{
|
|
352
|
+
name: 'Last Updated',
|
|
353
|
+
tag: '{{lastUpdated}}',
|
|
354
|
+
metadataKey: 'lastUpdated',
|
|
355
|
+
conditions: []
|
|
356
|
+
}
|
|
357
|
+
]
|
|
358
|
+
|
|
359
|
+
const content = 'Data last updated {{lastUpdated}}'
|
|
360
|
+
const result = processMarkupVariables(content, testData, variables, {
|
|
361
|
+
dataMetadata: { source: 'CDC' }
|
|
362
|
+
})
|
|
363
|
+
|
|
364
|
+
expect(result.processedContent).toBe('Data last updated ')
|
|
365
|
+
})
|
|
366
|
+
|
|
367
|
+
it('should resolve metadata variable even when conditions are defined', () => {
|
|
368
|
+
const variables: MarkupVariable[] = [
|
|
369
|
+
{
|
|
370
|
+
name: 'Last Updated',
|
|
371
|
+
tag: '{{lastUpdated}}',
|
|
372
|
+
metadataKey: 'lastUpdated',
|
|
373
|
+
conditions: [{ columnName: 'state', isOrIsNotEqualTo: 'is', value: 'California' }]
|
|
374
|
+
}
|
|
375
|
+
]
|
|
376
|
+
|
|
377
|
+
const content = '{{lastUpdated}}'
|
|
378
|
+
const result = processMarkupVariables(content, testData, variables, {
|
|
379
|
+
dataMetadata: { lastUpdated: 'January 15, 2026' }
|
|
380
|
+
})
|
|
381
|
+
|
|
382
|
+
expect(result.processedContent).toBe('January 15, 2026')
|
|
383
|
+
})
|
|
384
|
+
|
|
385
|
+
it('should handle mixed metadata and column variables in the same content', () => {
|
|
386
|
+
const variables: MarkupVariable[] = [
|
|
387
|
+
{
|
|
388
|
+
name: 'Last Updated',
|
|
389
|
+
tag: '{{lastUpdated}}',
|
|
390
|
+
metadataKey: 'lastUpdated',
|
|
391
|
+
conditions: []
|
|
392
|
+
},
|
|
393
|
+
{
|
|
394
|
+
name: 'State',
|
|
395
|
+
tag: '{{state}}',
|
|
396
|
+
columnName: 'state',
|
|
397
|
+
conditions: [{ columnName: 'state', isOrIsNotEqualTo: 'is', value: 'California' }]
|
|
398
|
+
}
|
|
399
|
+
]
|
|
400
|
+
|
|
401
|
+
const content = '{{state}} data last updated {{lastUpdated}}'
|
|
402
|
+
const result = processMarkupVariables(content, testData, variables, {
|
|
403
|
+
dataMetadata: { lastUpdated: 'January 15, 2026' }
|
|
404
|
+
})
|
|
405
|
+
|
|
406
|
+
expect(result.processedContent).toBe('California data last updated January 15, 2026')
|
|
407
|
+
})
|
|
408
|
+
|
|
409
|
+
it('should not trigger columnName warning for metadata variable without columnName', () => {
|
|
410
|
+
const variables: MarkupVariable[] = [
|
|
411
|
+
{
|
|
412
|
+
name: 'Source',
|
|
413
|
+
tag: '{{source}}',
|
|
414
|
+
metadataKey: 'source',
|
|
415
|
+
conditions: []
|
|
416
|
+
}
|
|
417
|
+
]
|
|
418
|
+
|
|
419
|
+
const content = 'Source: {{source}}'
|
|
420
|
+
const result = processMarkupVariables(content, testData, variables, {
|
|
421
|
+
dataMetadata: { source: 'CDC NREVSS' }
|
|
422
|
+
})
|
|
423
|
+
|
|
424
|
+
expect(result.processedContent).toBe('Source: CDC NREVSS')
|
|
425
|
+
})
|
|
426
|
+
|
|
427
|
+
it('should return empty string for metadata variable when dataMetadata is empty', () => {
|
|
428
|
+
const variables: MarkupVariable[] = [
|
|
429
|
+
{
|
|
430
|
+
name: 'Last Updated',
|
|
431
|
+
tag: '{{lastUpdated}}',
|
|
432
|
+
metadataKey: 'lastUpdated',
|
|
433
|
+
conditions: []
|
|
434
|
+
}
|
|
435
|
+
]
|
|
436
|
+
|
|
437
|
+
const content: string = 'Updated: {{lastUpdated}}'
|
|
438
|
+
const result = processMarkupVariables(content, testData, variables, {
|
|
439
|
+
dataMetadata: {}
|
|
440
|
+
})
|
|
441
|
+
|
|
442
|
+
expect(result.processedContent).toBe('Updated: ')
|
|
443
|
+
})
|
|
444
|
+
|
|
445
|
+
it('should return empty string for metadata variable when dataMetadata is undefined', () => {
|
|
446
|
+
const variables: MarkupVariable[] = [
|
|
447
|
+
{
|
|
448
|
+
name: 'Last Updated',
|
|
449
|
+
tag: '{{lastUpdated}}',
|
|
450
|
+
metadataKey: 'lastUpdated',
|
|
451
|
+
conditions: []
|
|
452
|
+
}
|
|
453
|
+
]
|
|
454
|
+
|
|
455
|
+
const content = 'Updated: {{lastUpdated}}'
|
|
456
|
+
const result = processMarkupVariables(content, testData, variables)
|
|
457
|
+
|
|
458
|
+
expect(result.processedContent).toBe('Updated: ')
|
|
459
|
+
})
|
|
460
|
+
|
|
461
|
+
it('should format numeric metadata value with commas when addCommas is true', () => {
|
|
462
|
+
const variables: MarkupVariable[] = [
|
|
463
|
+
{
|
|
464
|
+
name: 'Count',
|
|
465
|
+
tag: '{{count}}',
|
|
466
|
+
metadataKey: 'count',
|
|
467
|
+
conditions: [],
|
|
468
|
+
addCommas: true
|
|
469
|
+
}
|
|
470
|
+
]
|
|
471
|
+
|
|
472
|
+
const content = 'Total: {{count}}'
|
|
473
|
+
const result = processMarkupVariables(content, testData, variables, {
|
|
474
|
+
dataMetadata: { count: '1234567' }
|
|
475
|
+
})
|
|
476
|
+
|
|
477
|
+
expect(result.processedContent).toBe('Total: 1,234,567')
|
|
478
|
+
})
|
|
479
|
+
|
|
480
|
+
it('should not format non-numeric metadata value even when addCommas is true', () => {
|
|
382
481
|
const variables: MarkupVariable[] = [
|
|
383
|
-
{
|
|
482
|
+
{
|
|
483
|
+
name: 'Source',
|
|
484
|
+
tag: '{{source}}',
|
|
485
|
+
metadataKey: 'source',
|
|
486
|
+
conditions: [],
|
|
487
|
+
addCommas: true
|
|
488
|
+
}
|
|
384
489
|
]
|
|
385
490
|
|
|
491
|
+
const content = 'Source: {{source}}'
|
|
492
|
+
const result = processMarkupVariables(content, testData, variables, {
|
|
493
|
+
dataMetadata: { source: 'CDC' }
|
|
494
|
+
})
|
|
495
|
+
|
|
496
|
+
expect(result.processedContent).toBe('Source: CDC')
|
|
497
|
+
})
|
|
498
|
+
|
|
499
|
+
it('should set shouldHideSection when metadata value is empty and allowHideSection is true', () => {
|
|
500
|
+
const variables: MarkupVariable[] = [
|
|
501
|
+
{
|
|
502
|
+
name: 'Last Updated',
|
|
503
|
+
tag: '{{lastUpdated}}',
|
|
504
|
+
metadataKey: 'lastUpdated',
|
|
505
|
+
conditions: [],
|
|
506
|
+
hideOnNull: true
|
|
507
|
+
}
|
|
508
|
+
]
|
|
509
|
+
|
|
510
|
+
const content = '{{lastUpdated}}'
|
|
511
|
+
const result = processMarkupVariables(content, testData, variables, {
|
|
512
|
+
dataMetadata: {},
|
|
513
|
+
allowHideSection: true,
|
|
514
|
+
isEditor: false
|
|
515
|
+
})
|
|
516
|
+
|
|
517
|
+
expect(result.shouldHideSection).toBe(true)
|
|
518
|
+
})
|
|
519
|
+
|
|
520
|
+
it('should not set shouldHideSection when metadata value exists', () => {
|
|
521
|
+
const variables: MarkupVariable[] = [
|
|
522
|
+
{
|
|
523
|
+
name: 'Last Updated',
|
|
524
|
+
tag: '{{lastUpdated}}',
|
|
525
|
+
metadataKey: 'lastUpdated',
|
|
526
|
+
conditions: [],
|
|
527
|
+
hideOnNull: true
|
|
528
|
+
}
|
|
529
|
+
]
|
|
530
|
+
|
|
531
|
+
const content = '{{lastUpdated}}'
|
|
532
|
+
const result = processMarkupVariables(content, testData, variables, {
|
|
533
|
+
dataMetadata: { lastUpdated: 'Jan 2026' },
|
|
534
|
+
allowHideSection: true,
|
|
535
|
+
isEditor: false
|
|
536
|
+
})
|
|
537
|
+
|
|
538
|
+
expect(result.shouldHideSection).toBe(false)
|
|
539
|
+
})
|
|
540
|
+
})
|
|
541
|
+
|
|
542
|
+
describe('Edge Cases', () => {
|
|
543
|
+
it('should handle empty data array', () => {
|
|
544
|
+
const variables: MarkupVariable[] = [{ name: 'Value', tag: '{{value}}', columnName: 'value', conditions: [] }]
|
|
545
|
+
|
|
386
546
|
const content = '{{value}}'
|
|
387
|
-
const result = processMarkupVariables(content, [], variables)
|
|
547
|
+
const result = processMarkupVariables(content, [], variables, { locale: 'en-US' })
|
|
388
548
|
|
|
389
549
|
expect(result.processedContent).toBe('')
|
|
390
550
|
})
|
|
391
551
|
|
|
392
552
|
it('should handle empty content string', () => {
|
|
393
|
-
const variables: MarkupVariable[] = [
|
|
394
|
-
{ name: 'Value', tag: '{{value}}', columnName: 'value', conditions: [] }
|
|
395
|
-
]
|
|
553
|
+
const variables: MarkupVariable[] = [{ name: 'Value', tag: '{{value}}', columnName: 'value', conditions: [] }]
|
|
396
554
|
|
|
397
|
-
const result = processMarkupVariables('', testData, variables)
|
|
555
|
+
const result = processMarkupVariables('', testData, variables, { locale: 'en-US' })
|
|
398
556
|
|
|
399
557
|
expect(result.processedContent).toBe('')
|
|
400
558
|
})
|
|
401
559
|
|
|
402
560
|
it('should handle single item (no conjunction)', () => {
|
|
403
561
|
const data = [{ state: 'California' }]
|
|
404
|
-
const variables: MarkupVariable[] = [
|
|
405
|
-
{ name: 'State', tag: '{{state}}', columnName: 'state', conditions: [] }
|
|
406
|
-
]
|
|
562
|
+
const variables: MarkupVariable[] = [{ name: 'State', tag: '{{state}}', columnName: 'state', conditions: [] }]
|
|
407
563
|
|
|
408
564
|
const content = '{{state}}'
|
|
409
|
-
const result = processMarkupVariables(content, data, variables)
|
|
565
|
+
const result = processMarkupVariables(content, data, variables, { locale: 'en-US' })
|
|
410
566
|
|
|
411
567
|
expect(result.processedContent).toBe('California')
|
|
412
568
|
})
|
|
413
569
|
|
|
414
570
|
it('should remove duplicate values from list', () => {
|
|
415
|
-
const duplicateData = [
|
|
416
|
-
{ state: 'California' },
|
|
417
|
-
{ state: 'Texas' },
|
|
418
|
-
{ state: 'California' }
|
|
419
|
-
]
|
|
571
|
+
const duplicateData = [{ state: 'California' }, { state: 'Texas' }, { state: 'California' }]
|
|
420
572
|
|
|
421
|
-
const variables: MarkupVariable[] = [
|
|
422
|
-
{ name: 'State', tag: '{{state}}', columnName: 'state', conditions: [] }
|
|
423
|
-
]
|
|
573
|
+
const variables: MarkupVariable[] = [{ name: 'State', tag: '{{state}}', columnName: 'state', conditions: [] }]
|
|
424
574
|
|
|
425
575
|
const content = '{{state}}'
|
|
426
|
-
const result = processMarkupVariables(content, duplicateData, variables)
|
|
576
|
+
const result = processMarkupVariables(content, duplicateData, variables, { locale: 'en-US' })
|
|
427
577
|
|
|
428
578
|
expect(result.processedContent).toBe('California and Texas')
|
|
429
579
|
})
|
|
@@ -431,9 +581,7 @@ describe('processMarkupVariables', () => {
|
|
|
431
581
|
})
|
|
432
582
|
|
|
433
583
|
describe('validateMarkupVariables', () => {
|
|
434
|
-
const testData = [
|
|
435
|
-
{ state: 'CA', population: '1000' }
|
|
436
|
-
]
|
|
584
|
+
const testData = [{ state: 'CA', population: '1000' }]
|
|
437
585
|
|
|
438
586
|
it('should return no errors for valid configuration', () => {
|
|
439
587
|
const variables: MarkupVariable[] = [
|
|
@@ -535,4 +683,47 @@ describe('validateMarkupVariables', () => {
|
|
|
535
683
|
const errors3 = validateMarkupVariables('not an array' as any, testData)
|
|
536
684
|
expect(errors3).toHaveLength(0)
|
|
537
685
|
})
|
|
538
|
-
|
|
686
|
+
|
|
687
|
+
it('should skip columnName validation when variable has metadataKey', () => {
|
|
688
|
+
const variables: MarkupVariable[] = [
|
|
689
|
+
{
|
|
690
|
+
name: 'Last Updated',
|
|
691
|
+
tag: '{{lastUpdated}}',
|
|
692
|
+
metadataKey: 'lastUpdated',
|
|
693
|
+
conditions: []
|
|
694
|
+
}
|
|
695
|
+
]
|
|
696
|
+
|
|
697
|
+
const errors = validateMarkupVariables(variables, testData)
|
|
698
|
+
expect(errors).toHaveLength(0)
|
|
699
|
+
expect(errors).not.toContain('Variable 1: Column name is required')
|
|
700
|
+
})
|
|
701
|
+
|
|
702
|
+
it('should still require columnName when neither metadataKey nor columnName is set', () => {
|
|
703
|
+
const variables: MarkupVariable[] = [
|
|
704
|
+
{
|
|
705
|
+
name: 'Bad Variable',
|
|
706
|
+
tag: '{{bad}}',
|
|
707
|
+
conditions: []
|
|
708
|
+
}
|
|
709
|
+
]
|
|
710
|
+
|
|
711
|
+
const errors = validateMarkupVariables(variables, testData)
|
|
712
|
+
expect(errors).toContain('Variable 1: Column name is required')
|
|
713
|
+
})
|
|
714
|
+
|
|
715
|
+
it('should skip column-not-found validation when variable has metadataKey', () => {
|
|
716
|
+
const variables: MarkupVariable[] = [
|
|
717
|
+
{
|
|
718
|
+
name: 'Source',
|
|
719
|
+
tag: '{{source}}',
|
|
720
|
+
metadataKey: 'source',
|
|
721
|
+
conditions: []
|
|
722
|
+
}
|
|
723
|
+
]
|
|
724
|
+
|
|
725
|
+
const errors = validateMarkupVariables(variables, testData)
|
|
726
|
+
expect(errors).toHaveLength(0)
|
|
727
|
+
expect(errors.find(e => e.includes('not found in data'))).toBeUndefined()
|
|
728
|
+
})
|
|
729
|
+
})
|