@cdc/markup-include 4.26.3 → 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 +113 -0
- package/README.md +38 -14
- package/dist/cdcmarkupinclude.js +20632 -12824
- package/examples/minimal-example.json +11 -0
- package/package.json +4 -4
- package/src/CdcMarkupInclude.tsx +146 -25
- package/src/ConfigContext.ts +1 -0
- package/src/_stories/MarkupInclude.Editor.stories.tsx +46 -1
- package/src/_stories/MarkupInclude.smoke.stories.tsx +33 -0
- package/src/_stories/MarkupInclude.stories.tsx +81 -0
- package/src/components/EditorPanel/EditorPanel.tsx +246 -47
- package/src/data/initial-state.js +7 -1
- package/src/scss/main.scss +69 -2
- package/src/test/CdcMarkupInclude.test.jsx +72 -1
- package/LICENSE +0 -201
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { useContext, useRef } from 'react'
|
|
1
|
+
import { useContext, useMemo, useRef } from 'react'
|
|
2
2
|
|
|
3
3
|
// Context
|
|
4
4
|
import ConfigContext from '../../ConfigContext'
|
|
5
5
|
|
|
6
6
|
// Helpers
|
|
7
7
|
import { updateFieldFactory } from '@cdc/core/helpers/updateFieldFactory'
|
|
8
|
+
import { useDataColumns } from '@cdc/core/hooks/useDataColumns'
|
|
8
9
|
|
|
9
10
|
// Components
|
|
10
11
|
import { EditorPanel as BaseEditorPanel } from '@cdc/core/components/EditorPanel/EditorPanel'
|
|
@@ -16,6 +17,9 @@ import MarkupVariablesEditor from '@cdc/core/components/EditorPanel/components/M
|
|
|
16
17
|
import FootnotesEditor from '@cdc/core/components/EditorPanel/FootnotesEditor'
|
|
17
18
|
import StyleTreatmentSection from '@cdc/core/components/EditorPanel/sections/StyleTreatmentSection'
|
|
18
19
|
import { HeaderThemeSelector } from '@cdc/core/components/HeaderThemeSelector'
|
|
20
|
+
import { DataColorSelector } from '@cdc/core/components/DataColorSelector'
|
|
21
|
+
import { DATA_COLOR_PRESETS } from '@cdc/core/helpers/dataColors'
|
|
22
|
+
import Button from '@cdc/core/components/elements/Button'
|
|
19
23
|
import { Datasets } from '@cdc/core/types/DataSet'
|
|
20
24
|
|
|
21
25
|
// styles
|
|
@@ -26,11 +30,106 @@ type MarkupIncludeEditorPanelProps = {
|
|
|
26
30
|
}
|
|
27
31
|
|
|
28
32
|
const EditorPanel: React.FC<MarkupIncludeEditorPanelProps> = ({ datasets }) => {
|
|
29
|
-
const { config, data, isDashboard, loading, setParentConfig, updateConfig } = useContext(ConfigContext)
|
|
33
|
+
const { config, data, editorData, isDashboard, loading, setParentConfig, updateConfig } = useContext(ConfigContext)
|
|
30
34
|
const { contentEditor, theme, visual } = config || {}
|
|
31
35
|
const { inlineHTML, srcUrl, title, useInlineHTML } = contentEditor || {}
|
|
36
|
+
const isTp5Style = contentEditor?.style === 'tp5'
|
|
32
37
|
const updateField = updateFieldFactory(config, updateConfig, true)
|
|
33
|
-
const styleTreatment = visual?.tp5Treatment ? 'tp5' : 'legacy'
|
|
38
|
+
const styleTreatment = (visual as any)?.tp5Treatment ? 'tp5' : 'legacy'
|
|
39
|
+
const markupEditorData = Array.isArray(editorData) ? editorData : data || []
|
|
40
|
+
const columns = useDataColumns(markupEditorData)
|
|
41
|
+
const dataColorMappings = config.dataColors?.mappings || []
|
|
42
|
+
|
|
43
|
+
const dataColorValues = useMemo(() => {
|
|
44
|
+
const colorColumn = config.dataColors?.column
|
|
45
|
+
if (!colorColumn) return []
|
|
46
|
+
|
|
47
|
+
const uniqueValues = new Set<string>()
|
|
48
|
+
markupEditorData?.forEach(row => {
|
|
49
|
+
const value = row?.[colorColumn]
|
|
50
|
+
if (value !== undefined && value !== null) {
|
|
51
|
+
uniqueValues.add(String(value))
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
return Array.from(uniqueValues).sort()
|
|
56
|
+
}, [markupEditorData, config.dataColors?.column])
|
|
57
|
+
|
|
58
|
+
type DataColorDisplayEntry = { sourceValue: string; fromData: boolean; key: string }
|
|
59
|
+
|
|
60
|
+
const dataColorDisplayList = useMemo<DataColorDisplayEntry[]>(() => {
|
|
61
|
+
const dataSet = new Set(dataColorValues)
|
|
62
|
+
const list: DataColorDisplayEntry[] = dataColorValues.map(v => ({
|
|
63
|
+
sourceValue: v,
|
|
64
|
+
fromData: true,
|
|
65
|
+
key: `data-${v}`
|
|
66
|
+
}))
|
|
67
|
+
dataColorMappings.forEach((m, i) => {
|
|
68
|
+
if (!dataSet.has(m.sourceValue)) {
|
|
69
|
+
list.push({ sourceValue: m.sourceValue, fromData: false, key: `custom-${i}` })
|
|
70
|
+
}
|
|
71
|
+
})
|
|
72
|
+
return list
|
|
73
|
+
}, [dataColorValues, dataColorMappings])
|
|
74
|
+
|
|
75
|
+
const updateDataColorMapping = (sourceValue: string, color: string) => {
|
|
76
|
+
const nextMappings = [...dataColorMappings]
|
|
77
|
+
const existingIndex = nextMappings.findIndex(m => m.sourceValue === sourceValue)
|
|
78
|
+
|
|
79
|
+
if (!color) {
|
|
80
|
+
if (existingIndex > -1) {
|
|
81
|
+
nextMappings.splice(existingIndex, 1)
|
|
82
|
+
}
|
|
83
|
+
} else {
|
|
84
|
+
const nextMapping = { sourceValue, color }
|
|
85
|
+
if (existingIndex > -1) {
|
|
86
|
+
nextMappings[existingIndex] = nextMapping
|
|
87
|
+
} else {
|
|
88
|
+
nextMappings.push(nextMapping)
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
updateConfig({
|
|
93
|
+
...config,
|
|
94
|
+
dataColors: {
|
|
95
|
+
...config.dataColors,
|
|
96
|
+
mappings: nextMappings
|
|
97
|
+
}
|
|
98
|
+
})
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const addCustomDataColorMapping = () => {
|
|
102
|
+
const nextMappings = [...dataColorMappings, { sourceValue: '', color: DATA_COLOR_PRESETS[0] }]
|
|
103
|
+
updateConfig({
|
|
104
|
+
...config,
|
|
105
|
+
dataColors: {
|
|
106
|
+
...config.dataColors,
|
|
107
|
+
mappings: nextMappings
|
|
108
|
+
}
|
|
109
|
+
})
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const updateDataColorMappingValue = (oldValue: string, newValue: string) => {
|
|
113
|
+
const nextMappings = dataColorMappings.map(m => (m.sourceValue === oldValue ? { ...m, sourceValue: newValue } : m))
|
|
114
|
+
updateConfig({
|
|
115
|
+
...config,
|
|
116
|
+
dataColors: {
|
|
117
|
+
...config.dataColors,
|
|
118
|
+
mappings: nextMappings
|
|
119
|
+
}
|
|
120
|
+
})
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const removeDataColorMapping = (sourceValue: string) => {
|
|
124
|
+
const nextMappings = dataColorMappings.filter(m => m.sourceValue !== sourceValue)
|
|
125
|
+
updateConfig({
|
|
126
|
+
...config,
|
|
127
|
+
dataColors: {
|
|
128
|
+
...config.dataColors,
|
|
129
|
+
mappings: nextMappings
|
|
130
|
+
}
|
|
131
|
+
})
|
|
132
|
+
}
|
|
34
133
|
|
|
35
134
|
const handleStyleTreatmentChange = (value: string) => {
|
|
36
135
|
const useTp5Treatment = value === 'tp5'
|
|
@@ -74,6 +173,17 @@ const EditorPanel: React.FC<MarkupIncludeEditorPanelProps> = ({ datasets }) => {
|
|
|
74
173
|
{() => (
|
|
75
174
|
<Accordion>
|
|
76
175
|
<Accordion.Section title='General'>
|
|
176
|
+
<Select
|
|
177
|
+
value={contentEditor?.style || 'default'}
|
|
178
|
+
section='contentEditor'
|
|
179
|
+
fieldName='style'
|
|
180
|
+
label='Style'
|
|
181
|
+
updateField={updateField}
|
|
182
|
+
options={[
|
|
183
|
+
{ value: 'default', label: 'Default' },
|
|
184
|
+
{ value: 'tp5', label: 'TP5' }
|
|
185
|
+
]}
|
|
186
|
+
/>
|
|
77
187
|
<TextField
|
|
78
188
|
value={title || ''}
|
|
79
189
|
section='contentEditor'
|
|
@@ -82,32 +192,34 @@ const EditorPanel: React.FC<MarkupIncludeEditorPanelProps> = ({ datasets }) => {
|
|
|
82
192
|
placeholder='Markup Include Title'
|
|
83
193
|
updateField={updateField}
|
|
84
194
|
/>
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
{
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
<Tooltip
|
|
99
|
-
<
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
<
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
195
|
+
{!isTp5Style && (
|
|
196
|
+
<Select
|
|
197
|
+
value={contentEditor?.titleStyle || 'small'}
|
|
198
|
+
section='contentEditor'
|
|
199
|
+
fieldName='titleStyle'
|
|
200
|
+
label='Title Style'
|
|
201
|
+
updateField={updateField}
|
|
202
|
+
options={[
|
|
203
|
+
{ value: 'small', label: 'Small (h3)' },
|
|
204
|
+
{ value: 'large', label: 'Large (h2)' },
|
|
205
|
+
{ value: 'legacy', label: 'Legacy' }
|
|
206
|
+
]}
|
|
207
|
+
tooltip={
|
|
208
|
+
<Tooltip style={{ textTransform: 'none' }}>
|
|
209
|
+
<Tooltip.Target>
|
|
210
|
+
<Icon display='question' style={{ marginLeft: '0.5rem' }} />
|
|
211
|
+
</Tooltip.Target>
|
|
212
|
+
<Tooltip.Content>
|
|
213
|
+
<p>Choose the visual style for the title.</p>
|
|
214
|
+
<p>
|
|
215
|
+
Consider heading order on your page when selecting the title style. For 508 reasons, ensure your
|
|
216
|
+
page follows a proper heading order.
|
|
217
|
+
</p>
|
|
218
|
+
</Tooltip.Content>
|
|
219
|
+
</Tooltip>
|
|
220
|
+
}
|
|
221
|
+
/>
|
|
222
|
+
)}
|
|
111
223
|
<Select
|
|
112
224
|
value={config.locale}
|
|
113
225
|
fieldName='locale'
|
|
@@ -170,24 +282,111 @@ const EditorPanel: React.FC<MarkupIncludeEditorPanelProps> = ({ datasets }) => {
|
|
|
170
282
|
</div>
|
|
171
283
|
</Accordion.Section>
|
|
172
284
|
<Accordion.Section title='Visual'>
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
285
|
+
{!isTp5Style && (
|
|
286
|
+
<HeaderThemeSelector
|
|
287
|
+
selectedTheme={config.theme}
|
|
288
|
+
onThemeSelect={theme => updateConfig({ ...config, theme })}
|
|
289
|
+
/>
|
|
290
|
+
)}
|
|
291
|
+
{isTp5Style ? (
|
|
292
|
+
<CheckBox
|
|
293
|
+
value={visual?.whiteBackground}
|
|
294
|
+
section='visual'
|
|
295
|
+
fieldName='whiteBackground'
|
|
296
|
+
label='Use White Background Style'
|
|
297
|
+
updateField={updateField}
|
|
298
|
+
/>
|
|
299
|
+
) : (
|
|
300
|
+
<StyleTreatmentSection
|
|
301
|
+
styleTreatment={styleTreatment}
|
|
302
|
+
onStyleTreatmentChange={handleStyleTreatmentChange}
|
|
303
|
+
showStyleTreatment={false}
|
|
304
|
+
border={config.visual?.border}
|
|
305
|
+
borderColorTheme={config.visual?.borderColorTheme}
|
|
306
|
+
accent={config.visual?.accent}
|
|
307
|
+
background={config.visual?.background}
|
|
308
|
+
hideBackgroundColor={config.visual?.hideBackgroundColor}
|
|
309
|
+
showBackground
|
|
310
|
+
showHideBackgroundColor
|
|
311
|
+
updateField={updateField}
|
|
312
|
+
/>
|
|
313
|
+
)}
|
|
190
314
|
</Accordion.Section>
|
|
315
|
+
{isTp5Style && (
|
|
316
|
+
<Accordion.Section title='Data-Driven Colors'>
|
|
317
|
+
<Select
|
|
318
|
+
value={config.dataColors?.column || ''}
|
|
319
|
+
section='dataColors'
|
|
320
|
+
fieldName='column'
|
|
321
|
+
label='Color Column'
|
|
322
|
+
updateField={updateField}
|
|
323
|
+
initial='Select'
|
|
324
|
+
options={columns}
|
|
325
|
+
tooltip={
|
|
326
|
+
<Tooltip style={{ textTransform: 'none' }}>
|
|
327
|
+
<Tooltip.Target>
|
|
328
|
+
<Icon display='question' style={{ marginLeft: '0.5rem' }} />
|
|
329
|
+
</Tooltip.Target>
|
|
330
|
+
<Tooltip.Content>
|
|
331
|
+
<p>
|
|
332
|
+
Choose a column whose values determine the background color of this visualization. Map each
|
|
333
|
+
value to a color below. Text color adjusts automatically for contrast.
|
|
334
|
+
</p>
|
|
335
|
+
</Tooltip.Content>
|
|
336
|
+
</Tooltip>
|
|
337
|
+
}
|
|
338
|
+
/>
|
|
339
|
+
{config.dataColors?.column && dataColorDisplayList.length > 0 && (
|
|
340
|
+
<div className='mt-2'>
|
|
341
|
+
{dataColorDisplayList.map(({ sourceValue, fromData, key }) => {
|
|
342
|
+
const selectedColor = dataColorMappings.find(m => m.sourceValue === sourceValue)?.color || ''
|
|
343
|
+
|
|
344
|
+
return (
|
|
345
|
+
<div className='cove-accordion__panel-row align-center mb-2' key={key}>
|
|
346
|
+
<div className='cove-accordion__panel-col' style={{ flex: '1 1 0', minWidth: 0 }}>
|
|
347
|
+
{fromData ? (
|
|
348
|
+
sourceValue
|
|
349
|
+
) : (
|
|
350
|
+
<input
|
|
351
|
+
type='text'
|
|
352
|
+
value={sourceValue}
|
|
353
|
+
placeholder='Enter value'
|
|
354
|
+
style={{ width: '100%' }}
|
|
355
|
+
onChange={e => updateDataColorMappingValue(sourceValue, e.target.value)}
|
|
356
|
+
/>
|
|
357
|
+
)}
|
|
358
|
+
</div>
|
|
359
|
+
<div className='cove-accordion__panel-col' style={{ flex: '0 0 4.5rem' }}>
|
|
360
|
+
<DataColorSelector
|
|
361
|
+
value={selectedColor}
|
|
362
|
+
onChange={color => updateDataColorMapping(sourceValue, color)}
|
|
363
|
+
/>
|
|
364
|
+
</div>
|
|
365
|
+
<div className='cove-accordion__panel-col' style={{ flex: '0 0 1.5rem' }}>
|
|
366
|
+
{!fromData && (
|
|
367
|
+
<button
|
|
368
|
+
type='button'
|
|
369
|
+
className='btn btn-danger'
|
|
370
|
+
style={{ padding: '0.15rem 0.45rem', lineHeight: 1 }}
|
|
371
|
+
title='Remove mapping'
|
|
372
|
+
onClick={() => removeDataColorMapping(sourceValue)}
|
|
373
|
+
>
|
|
374
|
+
−
|
|
375
|
+
</button>
|
|
376
|
+
)}
|
|
377
|
+
</div>
|
|
378
|
+
</div>
|
|
379
|
+
)
|
|
380
|
+
})}
|
|
381
|
+
</div>
|
|
382
|
+
)}
|
|
383
|
+
{config.dataColors?.column && (
|
|
384
|
+
<Button type='button' onClick={addCustomDataColorMapping} className='btn btn-primary full-width mt-3'>
|
|
385
|
+
Add Color Mapping
|
|
386
|
+
</Button>
|
|
387
|
+
)}
|
|
388
|
+
</Accordion.Section>
|
|
389
|
+
)}
|
|
191
390
|
{isDashboard && (
|
|
192
391
|
<Accordion.Section title='Footnotes'>
|
|
193
392
|
<FootnotesEditor config={config} updateField={updateField} datasets={datasets} />
|
|
@@ -196,7 +395,7 @@ const EditorPanel: React.FC<MarkupIncludeEditorPanelProps> = ({ datasets }) => {
|
|
|
196
395
|
<Accordion.Section title='Markup Variables'>
|
|
197
396
|
<MarkupVariablesEditor
|
|
198
397
|
markupVariables={config.markupVariables || []}
|
|
199
|
-
data={
|
|
398
|
+
data={markupEditorData}
|
|
200
399
|
datasets={datasets}
|
|
201
400
|
config={config}
|
|
202
401
|
onChange={handleMarkupVariablesChange}
|
|
@@ -2,6 +2,7 @@ export default {
|
|
|
2
2
|
contentEditor: {
|
|
3
3
|
inlineHTML: '<strong>Inline HTML</strong>',
|
|
4
4
|
showHeader: true,
|
|
5
|
+
style: 'default',
|
|
5
6
|
srcUrl: '#example',
|
|
6
7
|
title: '',
|
|
7
8
|
titleStyle: 'small',
|
|
@@ -22,7 +23,12 @@ export default {
|
|
|
22
23
|
accent: false,
|
|
23
24
|
background: false,
|
|
24
25
|
hideBackgroundColor: false,
|
|
25
|
-
borderColorTheme: false
|
|
26
|
+
borderColorTheme: false,
|
|
27
|
+
whiteBackground: false
|
|
28
|
+
},
|
|
29
|
+
dataColors: {
|
|
30
|
+
column: '',
|
|
31
|
+
mappings: []
|
|
26
32
|
},
|
|
27
33
|
markupVariables: [],
|
|
28
34
|
enableMarkupVariables: false
|
package/src/scss/main.scss
CHANGED
|
@@ -5,6 +5,73 @@
|
|
|
5
5
|
@include cove-visualization-body-padding;
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
+
.markup-include-component--tp5 {
|
|
9
|
+
padding: 0 !important;
|
|
10
|
+
border: none !important;
|
|
11
|
+
background: none !important;
|
|
12
|
+
container-type: inline-size;
|
|
13
|
+
|
|
14
|
+
.markup-include-tp5 {
|
|
15
|
+
gap: 0.7rem;
|
|
16
|
+
box-shadow: 0 2px 4px rgb(159 159 159 / 10%);
|
|
17
|
+
border: 1px solid var(--colors-cyan-15, #dff2f6) !important;
|
|
18
|
+
margin: 0 !important;
|
|
19
|
+
padding: 1.25rem;
|
|
20
|
+
border-radius: 0.25rem;
|
|
21
|
+
background-color: var(--colors-cyan-10, #eff9fa);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.cdc-callout__heading {
|
|
25
|
+
width: 100%;
|
|
26
|
+
font-size: 1.1rem;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.cdc-callout__body {
|
|
30
|
+
flex-wrap: nowrap;
|
|
31
|
+
flex: auto;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
@container (max-width: 576px) {
|
|
35
|
+
.cdc-callout__body {
|
|
36
|
+
flex-wrap: wrap;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.cdc-callout__content {
|
|
41
|
+
font-size: 1rem;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.cdc-callout--data-color {
|
|
45
|
+
.cdc-callout__heading,
|
|
46
|
+
.cdc-callout__content {
|
|
47
|
+
color: inherit;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
&.white-background-style {
|
|
52
|
+
.markup-include-tp5 {
|
|
53
|
+
border: 1px solid #009ec1 !important;
|
|
54
|
+
background: transparent;
|
|
55
|
+
box-shadow: 0 2px 4px rgb(159 159 159 / 10%);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.cdc-callout--data-color {
|
|
59
|
+
border: none !important;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
&.white-background-style.display-border {
|
|
64
|
+
.markup-include-tp5 {
|
|
65
|
+
box-shadow: 0 2px 4px rgb(159 159 159 / 10%);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.cove-visualization__body-wrap,
|
|
70
|
+
.cove-visualization__content-section {
|
|
71
|
+
padding: 0;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
8
75
|
.cove-visualization__body-wrap {
|
|
9
76
|
@include cove-visualization-body-wrap-inline-padding;
|
|
10
77
|
|
|
@@ -22,7 +89,7 @@
|
|
|
22
89
|
// keep markup include images inlines on dashboards
|
|
23
90
|
// e.g. requested to help here: https://www.cdc.gov/fluview/surveillance/2026-week-10.html
|
|
24
91
|
// needed for backwards compatibility with existing dashboards, but also to prevent any future issues with images in markup include visualizations being forced to display as blocks
|
|
25
|
-
span>img {
|
|
92
|
+
span > img {
|
|
26
93
|
display: inline;
|
|
27
94
|
}
|
|
28
95
|
}
|
|
@@ -54,4 +121,4 @@
|
|
|
54
121
|
.cove-editor .cove-editor__content {
|
|
55
122
|
padding-left: 350px;
|
|
56
123
|
display: flex;
|
|
57
|
-
}
|
|
124
|
+
}
|
|
@@ -1,6 +1,25 @@
|
|
|
1
1
|
import path from 'node:path'
|
|
2
|
+
import fs from 'node:fs'
|
|
3
|
+
import vm from 'node:vm'
|
|
4
|
+
import React from 'react'
|
|
5
|
+
import { render, screen } from '@testing-library/react'
|
|
2
6
|
import { testStandaloneBuild } from '@cdc/core/helpers/tests/testStandaloneBuild.ts'
|
|
3
|
-
import { describe, it, expect } from 'vitest'
|
|
7
|
+
import { describe, it, expect, vi } from 'vitest'
|
|
8
|
+
import CdcMarkupInclude from '../CdcMarkupInclude'
|
|
9
|
+
|
|
10
|
+
vi.mock('@cdc/core/components/EditorPanel/components/MarkupVariablesEditor', () => ({
|
|
11
|
+
default: ({ data }) => <div data-testid='markup-variables-editor-data'>{JSON.stringify(data)}</div>
|
|
12
|
+
}))
|
|
13
|
+
|
|
14
|
+
const extractMarkedExampleConfig = (content, label) => {
|
|
15
|
+
const match = content.match(
|
|
16
|
+
/<!-- README_EXAMPLE_CONFIG_START -->\s*```jsx\s*([\s\S]*?)\s*```\s*<!-- README_EXAMPLE_CONFIG_END -->/
|
|
17
|
+
)
|
|
18
|
+
expect(match, `${label} should contain a marked README example block`).toBeTruthy()
|
|
19
|
+
const configMatch = match[1].match(/const config = (\{[\s\S]*?\})\n\nfunction App\(\)/)
|
|
20
|
+
expect(configMatch, `${label} should define const config before function App()`).toBeTruthy()
|
|
21
|
+
return vm.runInNewContext(`(${configMatch[1]})`)
|
|
22
|
+
}
|
|
4
23
|
|
|
5
24
|
describe('Markup Include', () => {
|
|
6
25
|
it('Can be built in isolation', async () => {
|
|
@@ -8,4 +27,56 @@ describe('Markup Include', () => {
|
|
|
8
27
|
const result = await testStandaloneBuild(pkgDir)
|
|
9
28
|
expect(result).toBe(true)
|
|
10
29
|
}, 300000)
|
|
30
|
+
|
|
31
|
+
it('uses dashboard rawData for markup variable editor choices when editing', async () => {
|
|
32
|
+
const filteredData = [{ category: 'Filtered only' }]
|
|
33
|
+
const fullData = [{ category: 'Value A' }, { category: 'Value B' }]
|
|
34
|
+
|
|
35
|
+
render(
|
|
36
|
+
<CdcMarkupInclude
|
|
37
|
+
config={{
|
|
38
|
+
type: 'markup-include',
|
|
39
|
+
theme: 'theme-blue',
|
|
40
|
+
dataKey: 'test-dataset',
|
|
41
|
+
data: filteredData,
|
|
42
|
+
markupVariables: [],
|
|
43
|
+
contentEditor: {
|
|
44
|
+
title: '',
|
|
45
|
+
inlineHTML: '<p>Example</p>',
|
|
46
|
+
useInlineHTML: true,
|
|
47
|
+
srcUrl: ''
|
|
48
|
+
},
|
|
49
|
+
visual: {
|
|
50
|
+
border: false,
|
|
51
|
+
accent: false,
|
|
52
|
+
background: false,
|
|
53
|
+
hideBackgroundColor: false,
|
|
54
|
+
borderColorTheme: false
|
|
55
|
+
}
|
|
56
|
+
}}
|
|
57
|
+
datasets={{
|
|
58
|
+
'test-dataset': {
|
|
59
|
+
data: [{ category: 'Dataset fallback' }]
|
|
60
|
+
}
|
|
61
|
+
}}
|
|
62
|
+
rawData={fullData}
|
|
63
|
+
isDashboard={true}
|
|
64
|
+
isEditor={true}
|
|
65
|
+
/>
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
expect(JSON.parse((await screen.findByTestId('markup-variables-editor-data')).textContent)).toEqual(fullData)
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it('keeps the minimal example in sync with the README docs', () => {
|
|
72
|
+
const pkgRoot = path.join(__dirname, '..', '..')
|
|
73
|
+
const minimalExamplePath = path.join(pkgRoot, 'examples', 'minimal-example.json')
|
|
74
|
+
const readmePath = path.join(pkgRoot, 'README.md')
|
|
75
|
+
|
|
76
|
+
const minimalExample = JSON.parse(fs.readFileSync(minimalExamplePath, 'utf8'))
|
|
77
|
+
const readmeBlock = extractMarkedExampleConfig(fs.readFileSync(readmePath, 'utf8'), 'README.md')
|
|
78
|
+
|
|
79
|
+
expect(readmeBlock).toEqual(minimalExample)
|
|
80
|
+
expect(minimalExample.version).toBeTruthy()
|
|
81
|
+
})
|
|
11
82
|
})
|