@startsimpli/ui 0.4.7 → 0.4.9

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.
Files changed (62) hide show
  1. package/package.json +21 -23
  2. package/src/__mocks__/next/link.js +11 -0
  3. package/src/components/account/__tests__/account.test.tsx +315 -0
  4. package/src/components/command-palette/CommandGroup.tsx +23 -0
  5. package/src/components/command-palette/CommandPalette.tsx +183 -200
  6. package/src/components/command-palette/CommandResultItem.tsx +59 -0
  7. package/src/components/command-palette/__tests__/CommandGroup.test.tsx +81 -0
  8. package/src/components/command-palette/__tests__/CommandResultItem.test.tsx +166 -0
  9. package/src/components/command-palette/__tests__/command-palette-context.test.tsx +166 -0
  10. package/src/components/command-palette/__tests__/useCommandPaletteSearch.test.ts +271 -0
  11. package/src/components/command-palette/index.ts +6 -0
  12. package/src/components/command-palette/useCommandPaletteSearch.ts +114 -0
  13. package/src/components/compose/__tests__/compose.test.tsx +656 -0
  14. package/src/components/dashboard/PipelineFunnel.tsx +126 -0
  15. package/src/components/dashboard/TopCampaigns.tsx +132 -0
  16. package/src/components/dashboard/__tests__/dashboard.test.tsx +785 -0
  17. package/src/components/dashboard/index.ts +6 -0
  18. package/src/components/dialog/ConfirmDialog.tsx +72 -0
  19. package/src/components/dialog/__tests__/ConfirmDialog.test.tsx +126 -0
  20. package/src/components/dialog/index.ts +3 -0
  21. package/src/components/email-dialogs/__tests__/email-dialogs.test.tsx +982 -0
  22. package/src/components/email-editor/BlockRenderer.tsx +120 -0
  23. package/src/components/email-editor/__tests__/BlockRenderer.test.tsx +332 -0
  24. package/src/components/email-editor/__tests__/block-renderers.test.ts +624 -0
  25. package/src/components/email-editor/__tests__/email-html-renderer.test.ts +376 -0
  26. package/src/components/email-editor/blocks/__tests__/blocks.test.tsx +818 -0
  27. package/src/components/email-editor/editor-sidebar.tsx +6 -731
  28. package/src/components/email-editor/email-editor.tsx +78 -467
  29. package/src/components/email-editor/hooks/__tests__/useDragDrop.test.ts +355 -0
  30. package/src/components/email-editor/hooks/__tests__/useEmailEditorState.test.ts +551 -0
  31. package/src/components/email-editor/hooks/useDragDrop.ts +181 -0
  32. package/src/components/email-editor/hooks/useEmailEditorState.ts +426 -0
  33. package/src/components/email-editor/index.ts +1 -0
  34. package/src/components/email-editor/panels/BlockPropertyPanel.tsx +637 -0
  35. package/src/components/email-editor/panels/GlobalStylesPanel.tsx +108 -0
  36. package/src/components/email-editor/panels/SectionSettingsPanel.tsx +80 -0
  37. package/src/components/email-editor/panels/__tests__/BlockPropertyPanel.test.tsx +707 -0
  38. package/src/components/email-editor/panels/__tests__/GlobalStylesPanel.test.tsx +226 -0
  39. package/src/components/email-editor/panels/index.ts +3 -0
  40. package/src/components/enrichment/__tests__/enrichment.test.tsx +184 -0
  41. package/src/components/gantt/GanttBoardView.tsx +71 -0
  42. package/src/components/gantt/GanttChart.tsx +134 -881
  43. package/src/components/gantt/GanttFilterBar.tsx +100 -0
  44. package/src/components/gantt/GanttListView.tsx +63 -0
  45. package/src/components/gantt/GanttTimelineView.tsx +215 -0
  46. package/src/components/gantt/__tests__/GanttBoardView.test.tsx +305 -0
  47. package/src/components/gantt/__tests__/GanttFilterBar.test.tsx +544 -0
  48. package/src/components/gantt/__tests__/GanttListView.test.tsx +337 -0
  49. package/src/components/gantt/__tests__/GanttTimelineView.test.tsx +375 -0
  50. package/src/components/gantt/__tests__/gantt-utils.test.ts +341 -0
  51. package/src/components/gantt/__tests__/useGanttState.test.ts +535 -0
  52. package/src/components/gantt/hooks/useGanttState.ts +644 -0
  53. package/src/components/gantt/index.ts +10 -0
  54. package/src/components/integrations/__tests__/integrations.test.tsx +191 -0
  55. package/src/components/kanban/__tests__/kanban.test.tsx +157 -0
  56. package/src/components/lists/__tests__/lists.test.tsx +263 -0
  57. package/src/components/loading/__tests__/loading.test.tsx +114 -0
  58. package/src/components/navigation/__tests__/navigation.test.tsx +194 -0
  59. package/src/components/pipeline/__tests__/pipeline.test.tsx +169 -0
  60. package/src/components/safe-html.tsx +9 -8
  61. package/src/components/settings/__tests__/settings.test.tsx +181 -0
  62. package/src/components/wizard/__tests__/wizard.test.tsx +97 -0
@@ -0,0 +1,305 @@
1
+ import { render, screen, fireEvent } from '@testing-library/react'
2
+ import { GanttBoardView } from '../GanttBoardView'
3
+ import type { GanttBoardViewProps } from '../GanttBoardView'
4
+ import type { TimelineItem } from '../types'
5
+
6
+ // ---------------------------------------------------------------------------
7
+ // Helpers
8
+ // ---------------------------------------------------------------------------
9
+
10
+ function makeItem(overrides: Partial<TimelineItem> & { id: string; title: string; status: string }): TimelineItem {
11
+ return { startDate: '2025-01-01', endDate: '2025-03-31', ...overrides }
12
+ }
13
+
14
+ const CATEGORY_COLORS: Record<string, string> = {
15
+ product: '#3b82f6',
16
+ team: '#a855f7',
17
+ other: '#6b7280',
18
+ }
19
+
20
+ function defaultProps(overrides: Partial<GanttBoardViewProps> = {}): GanttBoardViewProps {
21
+ return {
22
+ boardColumns: [],
23
+ boardDragItem: null,
24
+ setBoardDragItem: jest.fn(),
25
+ onStatusChange: undefined,
26
+ categoryColors: CATEGORY_COLORS,
27
+ handleItemClick: jest.fn(),
28
+ ...overrides,
29
+ }
30
+ }
31
+
32
+ // ---------------------------------------------------------------------------
33
+ // Column rendering
34
+ // ---------------------------------------------------------------------------
35
+
36
+ describe('GanttBoardView — column rendering', () => {
37
+ it('renders nothing when boardColumns is empty', () => {
38
+ const { container } = render(<GanttBoardView {...defaultProps()} />)
39
+ // The board wrapper div is rendered but contains no column children
40
+ expect(container.querySelector('.gantt-board-column')).not.toBeInTheDocument()
41
+ })
42
+
43
+ it('renders one column per status', () => {
44
+ const columns = [
45
+ { status: 'not_started', items: [] },
46
+ { status: 'in_progress', items: [] },
47
+ { status: 'completed', items: [] },
48
+ ]
49
+ const { container } = render(<GanttBoardView {...defaultProps({ boardColumns: columns })} />)
50
+ expect(container.querySelectorAll('.gantt-board-column')).toHaveLength(3)
51
+ })
52
+
53
+ it('renders a status badge in each column header', () => {
54
+ const columns = [
55
+ { status: 'not_started', items: [] },
56
+ { status: 'in_progress', items: [] },
57
+ ]
58
+ render(<GanttBoardView {...defaultProps({ boardColumns: columns })} />)
59
+ // Underscores are replaced with spaces in the badge
60
+ expect(screen.getByText('not started')).toBeInTheDocument()
61
+ expect(screen.getByText('in progress')).toBeInTheDocument()
62
+ })
63
+
64
+ it('displays the item count in each column header', () => {
65
+ const columns = [
66
+ { status: 'not_started', items: [makeItem({ id: 'a', title: 'Task A', status: 'not_started' })] },
67
+ { status: 'in_progress', items: [] },
68
+ ]
69
+ render(<GanttBoardView {...defaultProps({ boardColumns: columns })} />)
70
+ // Count badges: '1' for the first column, '0' for the second
71
+ const counts = screen.getAllByText('1')
72
+ expect(counts.length).toBeGreaterThanOrEqual(1)
73
+ expect(screen.getByText('0')).toBeInTheDocument()
74
+ })
75
+ })
76
+
77
+ // ---------------------------------------------------------------------------
78
+ // Card rendering
79
+ // ---------------------------------------------------------------------------
80
+
81
+ describe('GanttBoardView — card rendering', () => {
82
+ it('renders a card for each item in a column', () => {
83
+ const columns = [
84
+ {
85
+ status: 'in_progress',
86
+ items: [
87
+ makeItem({ id: 'a', title: 'Alpha Task', status: 'in_progress' }),
88
+ makeItem({ id: 'b', title: 'Beta Task', status: 'in_progress' }),
89
+ ],
90
+ },
91
+ ]
92
+ render(<GanttBoardView {...defaultProps({ boardColumns: columns })} />)
93
+ expect(screen.getByText('Alpha Task')).toBeInTheDocument()
94
+ expect(screen.getByText('Beta Task')).toBeInTheDocument()
95
+ })
96
+
97
+ it('renders a category badge on cards that have a category', () => {
98
+ const columns = [
99
+ {
100
+ status: 'in_progress',
101
+ items: [makeItem({ id: 'a', title: 'Task A', status: 'in_progress', category: 'product' })],
102
+ },
103
+ ]
104
+ render(<GanttBoardView {...defaultProps({ boardColumns: columns })} />)
105
+ expect(screen.getByText('product')).toBeInTheDocument()
106
+ })
107
+
108
+ it('does not render a category badge when the item has no category', () => {
109
+ const columns = [
110
+ {
111
+ status: 'in_progress',
112
+ items: [makeItem({ id: 'a', title: 'Task A', status: 'in_progress' })],
113
+ },
114
+ ]
115
+ const { container } = render(<GanttBoardView {...defaultProps({ boardColumns: columns })} />)
116
+ expect(container.querySelector('.gantt-category-badge')).not.toBeInTheDocument()
117
+ })
118
+
119
+ it('renders the end date on a card when the item has an endDate', () => {
120
+ // Use local-time ISO string to avoid UTC→local timezone shift
121
+ const columns = [
122
+ {
123
+ status: 'in_progress',
124
+ items: [makeItem({ id: 'a', title: 'Task A', status: 'in_progress', endDate: '2025-03-15T00:00:00' })],
125
+ },
126
+ ]
127
+ render(<GanttBoardView {...defaultProps({ boardColumns: columns })} />)
128
+ // date-fns format(new Date('2025-03-15T00:00:00'), 'MMM d') === 'Mar 15'
129
+ expect(screen.getByText('Mar 15')).toBeInTheDocument()
130
+ })
131
+
132
+ it('does not render an end date when the item has no endDate', () => {
133
+ const columns = [
134
+ {
135
+ status: 'in_progress',
136
+ items: [{ id: 'a', title: 'No Date Task', status: 'in_progress' }],
137
+ },
138
+ ]
139
+ const { container } = render(<GanttBoardView {...defaultProps({ boardColumns: columns })} />)
140
+ expect(container.querySelector('.gantt-board-card-date')).not.toBeInTheDocument()
141
+ })
142
+
143
+ it('uses the fallback "other" color when the item category is not in categoryColors', () => {
144
+ const columns = [
145
+ {
146
+ status: 'in_progress',
147
+ items: [makeItem({ id: 'a', title: 'Task A', status: 'in_progress', category: 'unknown' })],
148
+ },
149
+ ]
150
+ const { container } = render(<GanttBoardView {...defaultProps({ boardColumns: columns })} />)
151
+ const badge = container.querySelector('.gantt-category-badge') as HTMLElement | null
152
+ expect(badge).toBeInTheDocument()
153
+ expect(badge?.style.backgroundColor).toBe('rgb(107, 114, 128)') // #6b7280 in rgb
154
+ })
155
+ })
156
+
157
+ // ---------------------------------------------------------------------------
158
+ // Item click
159
+ // ---------------------------------------------------------------------------
160
+
161
+ describe('GanttBoardView — item click', () => {
162
+ it('calls handleItemClick when a card is clicked', () => {
163
+ const handleItemClick = jest.fn()
164
+ const item = makeItem({ id: 'a', title: 'Clickable', status: 'in_progress' })
165
+ const columns = [{ status: 'in_progress', items: [item] }]
166
+ render(<GanttBoardView {...defaultProps({ boardColumns: columns, handleItemClick })} />)
167
+ fireEvent.click(screen.getByText('Clickable'))
168
+ expect(handleItemClick).toHaveBeenCalledWith(item)
169
+ })
170
+
171
+ it('calls handleItemClick with the correct item when multiple cards are present', () => {
172
+ const handleItemClick = jest.fn()
173
+ const itemA = makeItem({ id: 'a', title: 'Task A', status: 'in_progress' })
174
+ const itemB = makeItem({ id: 'b', title: 'Task B', status: 'in_progress' })
175
+ const columns = [{ status: 'in_progress', items: [itemA, itemB] }]
176
+ render(<GanttBoardView {...defaultProps({ boardColumns: columns, handleItemClick })} />)
177
+ fireEvent.click(screen.getByText('Task B'))
178
+ expect(handleItemClick).toHaveBeenCalledWith(itemB)
179
+ })
180
+ })
181
+
182
+ // ---------------------------------------------------------------------------
183
+ // Drag and drop
184
+ // ---------------------------------------------------------------------------
185
+
186
+ describe('GanttBoardView — drag and drop', () => {
187
+ it('sets boardDragItem when drag starts on a card', () => {
188
+ const setBoardDragItem = jest.fn()
189
+ const onStatusChange = jest.fn()
190
+ const item = makeItem({ id: 'a', title: 'Draggable', status: 'not_started' })
191
+ const columns = [{ status: 'not_started', items: [item] }]
192
+ const { container } = render(
193
+ <GanttBoardView
194
+ {...defaultProps({ boardColumns: columns, setBoardDragItem, onStatusChange })}
195
+ />
196
+ )
197
+ const card = container.querySelector('.gantt-board-card') as HTMLElement
198
+ fireEvent.dragStart(card)
199
+ expect(setBoardDragItem).toHaveBeenCalledWith('a')
200
+ })
201
+
202
+ it('clears boardDragItem when drag ends', () => {
203
+ const setBoardDragItem = jest.fn()
204
+ const onStatusChange = jest.fn()
205
+ const item = makeItem({ id: 'a', title: 'Draggable', status: 'not_started' })
206
+ const columns = [{ status: 'not_started', items: [item] }]
207
+ const { container } = render(
208
+ <GanttBoardView
209
+ {...defaultProps({ boardColumns: columns, setBoardDragItem, onStatusChange })}
210
+ />
211
+ )
212
+ const card = container.querySelector('.gantt-board-card') as HTMLElement
213
+ fireEvent.dragEnd(card)
214
+ expect(setBoardDragItem).toHaveBeenCalledWith(null)
215
+ })
216
+
217
+ it('calls onStatusChange with the new status on drop', () => {
218
+ const setBoardDragItem = jest.fn()
219
+ const onStatusChange = jest.fn()
220
+ const item = makeItem({ id: 'a', title: 'Draggable', status: 'not_started' })
221
+ const columns = [
222
+ { status: 'not_started', items: [item] },
223
+ { status: 'completed', items: [] },
224
+ ]
225
+ const { container } = render(
226
+ <GanttBoardView
227
+ {...defaultProps({ boardColumns: columns, boardDragItem: 'a', setBoardDragItem, onStatusChange })}
228
+ />
229
+ )
230
+ const targetColumn = container.querySelectorAll('.gantt-board-column')[1] as HTMLElement
231
+ fireEvent.drop(targetColumn)
232
+ expect(onStatusChange).toHaveBeenCalledWith('a', 'completed')
233
+ })
234
+
235
+ it('does not call onStatusChange when onStatusChange is not provided', () => {
236
+ const setBoardDragItem = jest.fn()
237
+ const item = makeItem({ id: 'a', title: 'Draggable', status: 'not_started' })
238
+ const columns = [
239
+ { status: 'not_started', items: [item] },
240
+ { status: 'completed', items: [] },
241
+ ]
242
+ // Should not throw even without onStatusChange
243
+ const { container } = render(
244
+ <GanttBoardView
245
+ {...defaultProps({
246
+ boardColumns: columns,
247
+ boardDragItem: 'a',
248
+ setBoardDragItem,
249
+ onStatusChange: undefined,
250
+ })}
251
+ />
252
+ )
253
+ const targetColumn = container.querySelectorAll('.gantt-board-column')[1] as HTMLElement
254
+ expect(() => fireEvent.drop(targetColumn)).not.toThrow()
255
+ expect(setBoardDragItem).toHaveBeenCalledWith(null)
256
+ })
257
+
258
+ it('cards are not draggable when onStatusChange is undefined', () => {
259
+ const item = makeItem({ id: 'a', title: 'Static', status: 'not_started' })
260
+ const columns = [{ status: 'not_started', items: [item] }]
261
+ const { container } = render(
262
+ <GanttBoardView {...defaultProps({ boardColumns: columns, onStatusChange: undefined })} />
263
+ )
264
+ const card = container.querySelector('.gantt-board-card') as HTMLElement
265
+ // draggable attribute should be false (falsy) when onStatusChange is absent
266
+ expect(card.draggable).toBe(false)
267
+ })
268
+
269
+ it('cards are draggable when onStatusChange is provided', () => {
270
+ const onStatusChange = jest.fn()
271
+ const item = makeItem({ id: 'a', title: 'Draggable', status: 'not_started' })
272
+ const columns = [{ status: 'not_started', items: [item] }]
273
+ const { container } = render(
274
+ <GanttBoardView {...defaultProps({ boardColumns: columns, onStatusChange })} />
275
+ )
276
+ const card = container.querySelector('.gantt-board-card') as HTMLElement
277
+ expect(card.draggable).toBe(true)
278
+ })
279
+ })
280
+
281
+ // ---------------------------------------------------------------------------
282
+ // Drag-over visual state
283
+ // ---------------------------------------------------------------------------
284
+
285
+ describe('GanttBoardView — drop-target styling', () => {
286
+ it('applies gantt-board-drop-target class to all columns when boardDragItem is set', () => {
287
+ const columns = [
288
+ { status: 'not_started', items: [] },
289
+ { status: 'completed', items: [] },
290
+ ]
291
+ const { container } = render(
292
+ <GanttBoardView {...defaultProps({ boardColumns: columns, boardDragItem: 'some-id' })} />
293
+ )
294
+ const dropTargets = container.querySelectorAll('.gantt-board-drop-target')
295
+ expect(dropTargets).toHaveLength(2)
296
+ })
297
+
298
+ it('does not apply gantt-board-drop-target when boardDragItem is null', () => {
299
+ const columns = [{ status: 'not_started', items: [] }]
300
+ const { container } = render(
301
+ <GanttBoardView {...defaultProps({ boardColumns: columns, boardDragItem: null })} />
302
+ )
303
+ expect(container.querySelector('.gantt-board-drop-target')).not.toBeInTheDocument()
304
+ })
305
+ })