@startsimpli/ui 0.4.7 → 0.4.8
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/package.json +1 -1
- package/src/__mocks__/next/link.js +11 -0
- package/src/components/account/__tests__/account.test.tsx +315 -0
- package/src/components/command-palette/CommandGroup.tsx +23 -0
- package/src/components/command-palette/CommandPalette.tsx +183 -200
- package/src/components/command-palette/CommandResultItem.tsx +59 -0
- package/src/components/command-palette/__tests__/CommandGroup.test.tsx +81 -0
- package/src/components/command-palette/__tests__/CommandResultItem.test.tsx +166 -0
- package/src/components/command-palette/__tests__/command-palette-context.test.tsx +166 -0
- package/src/components/command-palette/__tests__/useCommandPaletteSearch.test.ts +271 -0
- package/src/components/command-palette/index.ts +6 -0
- package/src/components/command-palette/useCommandPaletteSearch.ts +114 -0
- package/src/components/compose/__tests__/compose.test.tsx +656 -0
- package/src/components/dashboard/PipelineFunnel.tsx +126 -0
- package/src/components/dashboard/TopCampaigns.tsx +132 -0
- package/src/components/dashboard/__tests__/dashboard.test.tsx +785 -0
- package/src/components/dashboard/index.ts +6 -0
- package/src/components/dialog/ConfirmDialog.tsx +72 -0
- package/src/components/dialog/__tests__/ConfirmDialog.test.tsx +126 -0
- package/src/components/dialog/index.ts +3 -0
- package/src/components/email-dialogs/__tests__/email-dialogs.test.tsx +982 -0
- package/src/components/email-editor/BlockRenderer.tsx +120 -0
- package/src/components/email-editor/__tests__/BlockRenderer.test.tsx +332 -0
- package/src/components/email-editor/__tests__/block-renderers.test.ts +624 -0
- package/src/components/email-editor/__tests__/email-html-renderer.test.ts +376 -0
- package/src/components/email-editor/blocks/__tests__/blocks.test.tsx +818 -0
- package/src/components/email-editor/editor-sidebar.tsx +6 -731
- package/src/components/email-editor/email-editor.tsx +78 -467
- package/src/components/email-editor/hooks/__tests__/useDragDrop.test.ts +355 -0
- package/src/components/email-editor/hooks/__tests__/useEmailEditorState.test.ts +551 -0
- package/src/components/email-editor/hooks/useDragDrop.ts +181 -0
- package/src/components/email-editor/hooks/useEmailEditorState.ts +426 -0
- package/src/components/email-editor/index.ts +1 -0
- package/src/components/email-editor/panels/BlockPropertyPanel.tsx +637 -0
- package/src/components/email-editor/panels/GlobalStylesPanel.tsx +108 -0
- package/src/components/email-editor/panels/SectionSettingsPanel.tsx +80 -0
- package/src/components/email-editor/panels/__tests__/BlockPropertyPanel.test.tsx +707 -0
- package/src/components/email-editor/panels/__tests__/GlobalStylesPanel.test.tsx +226 -0
- package/src/components/email-editor/panels/index.ts +3 -0
- package/src/components/enrichment/__tests__/enrichment.test.tsx +184 -0
- package/src/components/gantt/GanttBoardView.tsx +71 -0
- package/src/components/gantt/GanttChart.tsx +134 -881
- package/src/components/gantt/GanttFilterBar.tsx +100 -0
- package/src/components/gantt/GanttListView.tsx +63 -0
- package/src/components/gantt/GanttTimelineView.tsx +215 -0
- package/src/components/gantt/__tests__/GanttBoardView.test.tsx +305 -0
- package/src/components/gantt/__tests__/GanttFilterBar.test.tsx +544 -0
- package/src/components/gantt/__tests__/GanttListView.test.tsx +337 -0
- package/src/components/gantt/__tests__/GanttTimelineView.test.tsx +375 -0
- package/src/components/gantt/__tests__/gantt-utils.test.ts +341 -0
- package/src/components/gantt/__tests__/useGanttState.test.ts +535 -0
- package/src/components/gantt/hooks/useGanttState.ts +644 -0
- package/src/components/gantt/index.ts +10 -0
- package/src/components/integrations/__tests__/integrations.test.tsx +191 -0
- package/src/components/kanban/__tests__/kanban.test.tsx +157 -0
- package/src/components/lists/__tests__/lists.test.tsx +263 -0
- package/src/components/loading/__tests__/loading.test.tsx +114 -0
- package/src/components/navigation/__tests__/navigation.test.tsx +194 -0
- package/src/components/pipeline/__tests__/pipeline.test.tsx +169 -0
- package/src/components/settings/__tests__/settings.test.tsx +181 -0
- package/src/components/wizard/__tests__/wizard.test.tsx +97 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import * as React from 'react'
|
|
4
|
+
import { Block, MergeFieldDefinition } from './types'
|
|
5
|
+
import {
|
|
6
|
+
TextBlockEditor,
|
|
7
|
+
MetricsBlockEditor,
|
|
8
|
+
DividerBlockEditor,
|
|
9
|
+
ButtonBlockEditor,
|
|
10
|
+
ImageBlockEditor,
|
|
11
|
+
SpacerBlockEditor,
|
|
12
|
+
SocialBlockEditor,
|
|
13
|
+
HeaderBlockEditor,
|
|
14
|
+
FooterBlockEditor,
|
|
15
|
+
} from './blocks'
|
|
16
|
+
|
|
17
|
+
interface BlockRendererProps {
|
|
18
|
+
block: Block
|
|
19
|
+
editing: boolean
|
|
20
|
+
onChange: (updates: Partial<Block>) => void
|
|
21
|
+
mergeFields: MergeFieldDefinition[]
|
|
22
|
+
/** Optional custom rich text editor for the text block. */
|
|
23
|
+
renderTextEditor?: (props: {
|
|
24
|
+
content: string
|
|
25
|
+
onChange: (html: string) => void
|
|
26
|
+
placeholder?: string
|
|
27
|
+
className?: string
|
|
28
|
+
}) => React.ReactNode
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function BlockRenderer({
|
|
32
|
+
block,
|
|
33
|
+
editing,
|
|
34
|
+
onChange,
|
|
35
|
+
mergeFields: _mergeFields,
|
|
36
|
+
renderTextEditor,
|
|
37
|
+
}: BlockRendererProps) {
|
|
38
|
+
const commonProps = {
|
|
39
|
+
isEditing: editing,
|
|
40
|
+
onChange: onChange as (block: Block) => void,
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
switch (block.type) {
|
|
44
|
+
case 'text':
|
|
45
|
+
return (
|
|
46
|
+
<TextBlockEditor
|
|
47
|
+
block={block}
|
|
48
|
+
{...commonProps}
|
|
49
|
+
onChange={(b) => onChange(b)}
|
|
50
|
+
renderEditor={renderTextEditor}
|
|
51
|
+
/>
|
|
52
|
+
)
|
|
53
|
+
case 'metrics':
|
|
54
|
+
return (
|
|
55
|
+
<MetricsBlockEditor
|
|
56
|
+
block={block}
|
|
57
|
+
{...commonProps}
|
|
58
|
+
onChange={(b) => onChange(b)}
|
|
59
|
+
/>
|
|
60
|
+
)
|
|
61
|
+
case 'divider':
|
|
62
|
+
return (
|
|
63
|
+
<DividerBlockEditor
|
|
64
|
+
block={block}
|
|
65
|
+
{...commonProps}
|
|
66
|
+
onChange={(b) => onChange(b)}
|
|
67
|
+
/>
|
|
68
|
+
)
|
|
69
|
+
case 'cta':
|
|
70
|
+
return (
|
|
71
|
+
<ButtonBlockEditor
|
|
72
|
+
block={block}
|
|
73
|
+
{...commonProps}
|
|
74
|
+
onChange={(b) => onChange(b)}
|
|
75
|
+
/>
|
|
76
|
+
)
|
|
77
|
+
case 'image':
|
|
78
|
+
return (
|
|
79
|
+
<ImageBlockEditor
|
|
80
|
+
block={block}
|
|
81
|
+
{...commonProps}
|
|
82
|
+
onChange={(b) => onChange(b)}
|
|
83
|
+
/>
|
|
84
|
+
)
|
|
85
|
+
case 'spacer':
|
|
86
|
+
return (
|
|
87
|
+
<SpacerBlockEditor
|
|
88
|
+
block={block}
|
|
89
|
+
{...commonProps}
|
|
90
|
+
onChange={(b) => onChange(b)}
|
|
91
|
+
/>
|
|
92
|
+
)
|
|
93
|
+
case 'social':
|
|
94
|
+
return (
|
|
95
|
+
<SocialBlockEditor
|
|
96
|
+
block={block}
|
|
97
|
+
{...commonProps}
|
|
98
|
+
onChange={(b) => onChange(b)}
|
|
99
|
+
/>
|
|
100
|
+
)
|
|
101
|
+
case 'header':
|
|
102
|
+
return (
|
|
103
|
+
<HeaderBlockEditor
|
|
104
|
+
block={block}
|
|
105
|
+
{...commonProps}
|
|
106
|
+
onChange={(b) => onChange(b)}
|
|
107
|
+
/>
|
|
108
|
+
)
|
|
109
|
+
case 'footer':
|
|
110
|
+
return (
|
|
111
|
+
<FooterBlockEditor
|
|
112
|
+
block={block}
|
|
113
|
+
{...commonProps}
|
|
114
|
+
onChange={(b) => onChange(b)}
|
|
115
|
+
/>
|
|
116
|
+
)
|
|
117
|
+
default:
|
|
118
|
+
return null
|
|
119
|
+
}
|
|
120
|
+
}
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
import { render, screen, fireEvent } from '@testing-library/react'
|
|
2
|
+
import { BlockRenderer } from '../BlockRenderer'
|
|
3
|
+
import type {
|
|
4
|
+
TextBlock,
|
|
5
|
+
MetricsBlock,
|
|
6
|
+
DividerBlock,
|
|
7
|
+
CTABlock,
|
|
8
|
+
ImageBlock,
|
|
9
|
+
SpacerBlock,
|
|
10
|
+
SocialBlock,
|
|
11
|
+
HeaderBlock,
|
|
12
|
+
FooterBlock,
|
|
13
|
+
Block,
|
|
14
|
+
} from '../types'
|
|
15
|
+
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
// Fixtures
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
|
|
20
|
+
const textBlock: TextBlock = {
|
|
21
|
+
id: 'text-1',
|
|
22
|
+
type: 'text',
|
|
23
|
+
content: '<p>Hello dispatch</p>',
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const metricsBlock: MetricsBlock = {
|
|
27
|
+
id: 'metrics-1',
|
|
28
|
+
type: 'metrics',
|
|
29
|
+
title: 'Dispatch Metrics',
|
|
30
|
+
metrics: [
|
|
31
|
+
{ id: 'm1', label: 'ARR', value: '$5k', changeType: 'positive' },
|
|
32
|
+
],
|
|
33
|
+
columns: 2,
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const dividerBlock: DividerBlock = {
|
|
37
|
+
id: 'divider-1',
|
|
38
|
+
type: 'divider',
|
|
39
|
+
dividerStyle: 'solid',
|
|
40
|
+
color: '#d1d5db',
|
|
41
|
+
thickness: 1,
|
|
42
|
+
width: 100,
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const ctaBlock: CTABlock = {
|
|
46
|
+
id: 'cta-1',
|
|
47
|
+
type: 'cta',
|
|
48
|
+
text: 'Dispatch CTA',
|
|
49
|
+
url: 'https://example.com',
|
|
50
|
+
buttonColor: '#2563eb',
|
|
51
|
+
textColor: '#ffffff',
|
|
52
|
+
borderRadius: 6,
|
|
53
|
+
paddingH: 24,
|
|
54
|
+
paddingV: 12,
|
|
55
|
+
alignment: 'center',
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const imageBlock: ImageBlock = {
|
|
59
|
+
id: 'image-1',
|
|
60
|
+
type: 'image',
|
|
61
|
+
url: 'https://example.com/img.png',
|
|
62
|
+
alt: 'dispatch img',
|
|
63
|
+
alignment: 'center',
|
|
64
|
+
width: 100,
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const spacerBlock: SpacerBlock = {
|
|
68
|
+
id: 'spacer-1',
|
|
69
|
+
type: 'spacer',
|
|
70
|
+
height: 40,
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const socialBlock: SocialBlock = {
|
|
74
|
+
id: 'social-1',
|
|
75
|
+
type: 'social',
|
|
76
|
+
links: [{ id: 'sl1', platform: 'linkedin', url: 'https://linkedin.com/test' }],
|
|
77
|
+
iconSize: 24,
|
|
78
|
+
alignment: 'center',
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const headerBlock: HeaderBlock = {
|
|
82
|
+
id: 'header-1',
|
|
83
|
+
type: 'header',
|
|
84
|
+
companyName: 'DispatchCo',
|
|
85
|
+
alignment: 'center',
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const footerBlock: FooterBlock = {
|
|
89
|
+
id: 'footer-1',
|
|
90
|
+
type: 'footer',
|
|
91
|
+
companyName: 'DispatchCo',
|
|
92
|
+
showUnsubscribe: false,
|
|
93
|
+
alignment: 'center',
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
// Helper
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
|
|
100
|
+
function renderBlock(block: Block, editing = false) {
|
|
101
|
+
return render(
|
|
102
|
+
<BlockRenderer
|
|
103
|
+
block={block}
|
|
104
|
+
editing={editing}
|
|
105
|
+
onChange={jest.fn()}
|
|
106
|
+
mergeFields={[]}
|
|
107
|
+
/>
|
|
108
|
+
)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ---------------------------------------------------------------------------
|
|
112
|
+
// Dispatches to correct editor
|
|
113
|
+
// ---------------------------------------------------------------------------
|
|
114
|
+
|
|
115
|
+
describe('BlockRenderer — dispatches to correct block editor', () => {
|
|
116
|
+
it('renders TextBlockEditor for text block (view mode shows .prose)', () => {
|
|
117
|
+
const { container } = renderBlock(textBlock, false)
|
|
118
|
+
expect(container.querySelector('.prose')).toBeInTheDocument()
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
it('renders TextBlockEditor for text block (editing mode shows contentEditable)', () => {
|
|
122
|
+
const { container } = renderBlock(textBlock, true)
|
|
123
|
+
expect(container.querySelector('[contenteditable="true"]')).toBeInTheDocument()
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
it('renders MetricsBlockEditor for metrics block — shows title', () => {
|
|
127
|
+
renderBlock(metricsBlock, false)
|
|
128
|
+
expect(screen.getByText('Dispatch Metrics')).toBeInTheDocument()
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
it('renders MetricsBlockEditor for metrics block — shows metric value', () => {
|
|
132
|
+
renderBlock(metricsBlock, false)
|
|
133
|
+
expect(screen.getByText('$5k')).toBeInTheDocument()
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
it('renders DividerBlockEditor for divider block — shows hr element', () => {
|
|
137
|
+
const { container } = renderBlock(dividerBlock, false)
|
|
138
|
+
expect(container.querySelector('hr')).toBeInTheDocument()
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
it('renders ButtonBlockEditor for cta block — shows button text', () => {
|
|
142
|
+
renderBlock(ctaBlock, false)
|
|
143
|
+
expect(screen.getByText('Dispatch CTA')).toBeInTheDocument()
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
it('renders ImageBlockEditor for image block — shows img element', () => {
|
|
147
|
+
const { container } = renderBlock(imageBlock, false)
|
|
148
|
+
expect(container.querySelector('img')).toBeInTheDocument()
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
it('renders SpacerBlockEditor for spacer block — shows correct height in editing mode', () => {
|
|
152
|
+
renderBlock(spacerBlock, true)
|
|
153
|
+
expect(screen.getByText('40px')).toBeInTheDocument()
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
it('renders SocialBlockEditor for social block — shows social icon wrapper', () => {
|
|
157
|
+
const { container } = renderBlock(socialBlock, false)
|
|
158
|
+
expect(container.querySelector('div')).toBeInTheDocument()
|
|
159
|
+
// LinkedIn link present since url is set
|
|
160
|
+
expect(container.querySelector('a')).toBeInTheDocument()
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
it('renders HeaderBlockEditor for header block — shows company name', () => {
|
|
164
|
+
renderBlock(headerBlock, false)
|
|
165
|
+
expect(screen.getByText('DispatchCo')).toBeInTheDocument()
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
it('renders FooterBlockEditor for footer block — shows company name', () => {
|
|
169
|
+
renderBlock(footerBlock, false)
|
|
170
|
+
// Footer renders company name
|
|
171
|
+
expect(screen.getByText('DispatchCo')).toBeInTheDocument()
|
|
172
|
+
})
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
// ---------------------------------------------------------------------------
|
|
176
|
+
// editing prop is forwarded correctly
|
|
177
|
+
// ---------------------------------------------------------------------------
|
|
178
|
+
|
|
179
|
+
describe('BlockRenderer — editing prop forwarding', () => {
|
|
180
|
+
it('text block: editing=false renders view mode (.prose)', () => {
|
|
181
|
+
const { container } = renderBlock(textBlock, false)
|
|
182
|
+
expect(container.querySelector('.prose')).toBeInTheDocument()
|
|
183
|
+
expect(container.querySelector('[contenteditable="true"]')).not.toBeInTheDocument()
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
it('text block: editing=true renders edit mode (contentEditable)', () => {
|
|
187
|
+
const { container } = renderBlock(textBlock, true)
|
|
188
|
+
expect(container.querySelector('[contenteditable="true"]')).toBeInTheDocument()
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
it('spacer block: editing=false renders plain div without label text', () => {
|
|
192
|
+
const { container } = renderBlock(spacerBlock, false)
|
|
193
|
+
// view mode renders a plain height div, no "px" label
|
|
194
|
+
expect(screen.queryByText('40px')).not.toBeInTheDocument()
|
|
195
|
+
const spacer = container.querySelector('div') as HTMLElement
|
|
196
|
+
expect(spacer.style.height).toBe('40px')
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
it('spacer block: editing=true shows height label', () => {
|
|
200
|
+
renderBlock(spacerBlock, true)
|
|
201
|
+
expect(screen.getByText('40px')).toBeInTheDocument()
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
it('image block: editing=true shows placeholder when url empty', () => {
|
|
205
|
+
const emptyImage: ImageBlock = { ...imageBlock, url: '' }
|
|
206
|
+
render(
|
|
207
|
+
<BlockRenderer
|
|
208
|
+
block={emptyImage}
|
|
209
|
+
editing={true}
|
|
210
|
+
onChange={jest.fn()}
|
|
211
|
+
mergeFields={[]}
|
|
212
|
+
/>
|
|
213
|
+
)
|
|
214
|
+
expect(screen.getByText('Click to set image URL')).toBeInTheDocument()
|
|
215
|
+
})
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
// ---------------------------------------------------------------------------
|
|
219
|
+
// onChange is forwarded through to child editors
|
|
220
|
+
// ---------------------------------------------------------------------------
|
|
221
|
+
|
|
222
|
+
describe('BlockRenderer — onChange forwarding', () => {
|
|
223
|
+
it('text block: onChange is called when contentEditable blurs', () => {
|
|
224
|
+
const onChange = jest.fn()
|
|
225
|
+
const { container } = render(
|
|
226
|
+
<BlockRenderer
|
|
227
|
+
block={textBlock}
|
|
228
|
+
editing={true}
|
|
229
|
+
onChange={onChange}
|
|
230
|
+
mergeFields={[]}
|
|
231
|
+
/>
|
|
232
|
+
)
|
|
233
|
+
const editable = container.querySelector('[contenteditable="true"]') as HTMLElement
|
|
234
|
+
fireEvent.blur(editable)
|
|
235
|
+
expect(onChange).toHaveBeenCalledTimes(1)
|
|
236
|
+
expect(onChange).toHaveBeenCalledWith(
|
|
237
|
+
expect.objectContaining({ type: 'text', id: 'text-1' })
|
|
238
|
+
)
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
it('metrics block: onChange is called when title input changes', () => {
|
|
242
|
+
const onChange = jest.fn()
|
|
243
|
+
render(
|
|
244
|
+
<BlockRenderer
|
|
245
|
+
block={metricsBlock}
|
|
246
|
+
editing={true}
|
|
247
|
+
onChange={onChange}
|
|
248
|
+
mergeFields={[]}
|
|
249
|
+
/>
|
|
250
|
+
)
|
|
251
|
+
const titleInput = screen.getByPlaceholderText('Key Metrics')
|
|
252
|
+
fireEvent.change(titleInput, { target: { value: 'New Title' } })
|
|
253
|
+
expect(onChange).toHaveBeenCalledTimes(1)
|
|
254
|
+
expect(onChange).toHaveBeenCalledWith(
|
|
255
|
+
expect.objectContaining({ type: 'metrics', title: 'New Title' })
|
|
256
|
+
)
|
|
257
|
+
})
|
|
258
|
+
})
|
|
259
|
+
|
|
260
|
+
// ---------------------------------------------------------------------------
|
|
261
|
+
// renderTextEditor prop is forwarded to TextBlockEditor
|
|
262
|
+
// ---------------------------------------------------------------------------
|
|
263
|
+
|
|
264
|
+
describe('BlockRenderer — renderTextEditor prop', () => {
|
|
265
|
+
it('renders custom editor when renderTextEditor is provided for text block', () => {
|
|
266
|
+
const renderTextEditor = jest.fn(() => <div data-testid="custom-rich-editor" />)
|
|
267
|
+
render(
|
|
268
|
+
<BlockRenderer
|
|
269
|
+
block={textBlock}
|
|
270
|
+
editing={true}
|
|
271
|
+
onChange={jest.fn()}
|
|
272
|
+
mergeFields={[]}
|
|
273
|
+
renderTextEditor={renderTextEditor}
|
|
274
|
+
/>
|
|
275
|
+
)
|
|
276
|
+
expect(screen.getByTestId('custom-rich-editor')).toBeInTheDocument()
|
|
277
|
+
expect(renderTextEditor).toHaveBeenCalledWith(
|
|
278
|
+
expect.objectContaining({
|
|
279
|
+
content: textBlock.content,
|
|
280
|
+
})
|
|
281
|
+
)
|
|
282
|
+
})
|
|
283
|
+
|
|
284
|
+
it('renderTextEditor is ignored for non-text block types', () => {
|
|
285
|
+
const renderTextEditor = jest.fn(() => <div data-testid="custom-rich-editor" />)
|
|
286
|
+
render(
|
|
287
|
+
<BlockRenderer
|
|
288
|
+
block={dividerBlock}
|
|
289
|
+
editing={true}
|
|
290
|
+
onChange={jest.fn()}
|
|
291
|
+
mergeFields={[]}
|
|
292
|
+
renderTextEditor={renderTextEditor}
|
|
293
|
+
/>
|
|
294
|
+
)
|
|
295
|
+
// DividerBlockEditor should render instead
|
|
296
|
+
expect(screen.queryByTestId('custom-rich-editor')).not.toBeInTheDocument()
|
|
297
|
+
const { container } = render(
|
|
298
|
+
<BlockRenderer
|
|
299
|
+
block={dividerBlock}
|
|
300
|
+
editing={true}
|
|
301
|
+
onChange={jest.fn()}
|
|
302
|
+
mergeFields={[]}
|
|
303
|
+
renderTextEditor={renderTextEditor}
|
|
304
|
+
/>
|
|
305
|
+
)
|
|
306
|
+
expect(container.querySelector('hr')).toBeInTheDocument()
|
|
307
|
+
expect(renderTextEditor).not.toHaveBeenCalled()
|
|
308
|
+
})
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
// ---------------------------------------------------------------------------
|
|
312
|
+
// mergeFields prop does not cause errors
|
|
313
|
+
// ---------------------------------------------------------------------------
|
|
314
|
+
|
|
315
|
+
describe('BlockRenderer — mergeFields prop', () => {
|
|
316
|
+
it('renders without error when mergeFields is empty', () => {
|
|
317
|
+
expect(() => renderBlock(textBlock, false)).not.toThrow()
|
|
318
|
+
})
|
|
319
|
+
|
|
320
|
+
it('renders without error when mergeFields has items', () => {
|
|
321
|
+
expect(() =>
|
|
322
|
+
render(
|
|
323
|
+
<BlockRenderer
|
|
324
|
+
block={textBlock}
|
|
325
|
+
editing={false}
|
|
326
|
+
onChange={jest.fn()}
|
|
327
|
+
mergeFields={[{ key: '{{name}}', label: 'Name', example: 'John', category: 'contact' }]}
|
|
328
|
+
/>
|
|
329
|
+
)
|
|
330
|
+
).not.toThrow()
|
|
331
|
+
})
|
|
332
|
+
})
|