@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.
- package/package.json +21 -23
- 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/safe-html.tsx +9 -8
- package/src/components/settings/__tests__/settings.test.tsx +181 -0
- package/src/components/wizard/__tests__/wizard.test.tsx +97 -0
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
import { render, screen, fireEvent } from '@testing-library/react'
|
|
2
|
+
import { GanttTimelineView } from '../GanttTimelineView'
|
|
3
|
+
import type { GanttTimelineViewProps } from '../GanttTimelineView'
|
|
4
|
+
import type { GanttTask, TimelineItem } from '../types'
|
|
5
|
+
import { ZOOM_LEVELS, ROW_HEIGHT } from '../hooks/useGanttState'
|
|
6
|
+
import React from 'react'
|
|
7
|
+
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
// Helpers
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
function makeItem(overrides: Partial<TimelineItem> & { id: string; title: string; status: string }): TimelineItem {
|
|
13
|
+
return { startDate: '2025-01-01', endDate: '2025-03-31', ...overrides }
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function makeTask(item: TimelineItem, overrides: Partial<GanttTask> = {}): GanttTask {
|
|
17
|
+
const start = new Date('2025-01-01')
|
|
18
|
+
const end = new Date('2025-03-31')
|
|
19
|
+
return {
|
|
20
|
+
item,
|
|
21
|
+
start,
|
|
22
|
+
end,
|
|
23
|
+
progress: 50,
|
|
24
|
+
timeProgress: 30,
|
|
25
|
+
depth: 0,
|
|
26
|
+
hasChildren: false,
|
|
27
|
+
parentId: null,
|
|
28
|
+
healthStatus: 'on_track',
|
|
29
|
+
...overrides,
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
// Minimal default props factory
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
function defaultProps(overrides: Partial<GanttTimelineViewProps> = {}): GanttTimelineViewProps {
|
|
38
|
+
const ZOOM_INDEX = 3
|
|
39
|
+
const DAY_WIDTH = ZOOM_LEVELS[ZOOM_INDEX]
|
|
40
|
+
const startDate = new Date('2025-01-01')
|
|
41
|
+
const days: Date[] = []
|
|
42
|
+
for (let i = 0; i < 90; i++) {
|
|
43
|
+
const d = new Date(startDate)
|
|
44
|
+
d.setDate(d.getDate() + i)
|
|
45
|
+
days.push(d)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const months = [
|
|
49
|
+
{ month: new Date('2025-01-01'), days: 31, label: 'Jan 2025' },
|
|
50
|
+
{ month: new Date('2025-02-01'), days: 28, label: 'Feb 2025' },
|
|
51
|
+
{ month: new Date('2025-03-01'), days: 31, label: 'Mar 2025' },
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
const timelineWidth = days.length * DAY_WIDTH
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
infoColumnWidth: 320,
|
|
58
|
+
infoColumnLabel: 'Item',
|
|
59
|
+
showCategory: true,
|
|
60
|
+
showStatus: true,
|
|
61
|
+
showFullscreen: false,
|
|
62
|
+
zoomIndex: ZOOM_INDEX,
|
|
63
|
+
setZoomIndex: jest.fn(),
|
|
64
|
+
collapsed: new Set<string>(),
|
|
65
|
+
mounted: true,
|
|
66
|
+
dragState: null,
|
|
67
|
+
focusedRowIndex: -1,
|
|
68
|
+
dayWidth: DAY_WIDTH,
|
|
69
|
+
tasks: [],
|
|
70
|
+
startDate,
|
|
71
|
+
days,
|
|
72
|
+
months,
|
|
73
|
+
timeHeaderUnits: {
|
|
74
|
+
mode: 'days',
|
|
75
|
+
units: days.map((day) => ({
|
|
76
|
+
date: day,
|
|
77
|
+
width: DAY_WIDTH,
|
|
78
|
+
label: String(day.getDate()),
|
|
79
|
+
isWeekend: day.getDay() === 0 || day.getDay() === 6,
|
|
80
|
+
isToday: false,
|
|
81
|
+
})),
|
|
82
|
+
},
|
|
83
|
+
dependencyPaths: [],
|
|
84
|
+
timelineWidth,
|
|
85
|
+
bodyHeight: 0,
|
|
86
|
+
isFullscreen: false,
|
|
87
|
+
categoryColors: { product: '#3b82f6', team: '#a855f7', other: '#6b7280' },
|
|
88
|
+
onDateChange: undefined,
|
|
89
|
+
bodyScrollRef: { current: null },
|
|
90
|
+
headerTimelineRef: { current: null },
|
|
91
|
+
infoColumnRef: { current: null },
|
|
92
|
+
toggleFullscreen: jest.fn(),
|
|
93
|
+
handleItemClick: jest.fn(),
|
|
94
|
+
toggleCollapse: jest.fn(),
|
|
95
|
+
handleBodyScroll: jest.fn(),
|
|
96
|
+
getBarPosition: jest.fn().mockReturnValue({ left: 0, width: 100, top: 0, height: 20 }),
|
|
97
|
+
handleDragStart: jest.fn(),
|
|
98
|
+
...overrides,
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ---------------------------------------------------------------------------
|
|
103
|
+
// Header rendering
|
|
104
|
+
// ---------------------------------------------------------------------------
|
|
105
|
+
|
|
106
|
+
describe('GanttTimelineView — header', () => {
|
|
107
|
+
it('renders the info column header with the infoColumnLabel', () => {
|
|
108
|
+
render(<GanttTimelineView {...defaultProps({ infoColumnLabel: 'Goal' })} />)
|
|
109
|
+
expect(screen.getByText('Goal')).toBeInTheDocument()
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
it('renders month labels in the timeline header', () => {
|
|
113
|
+
render(<GanttTimelineView {...defaultProps()} />)
|
|
114
|
+
expect(screen.getByText('Jan 2025')).toBeInTheDocument()
|
|
115
|
+
expect(screen.getByText('Feb 2025')).toBeInTheDocument()
|
|
116
|
+
expect(screen.getByText('Mar 2025')).toBeInTheDocument()
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
it('renders day unit labels in days mode', () => {
|
|
120
|
+
render(<GanttTimelineView {...defaultProps()} />)
|
|
121
|
+
// Day 1 label should appear at start
|
|
122
|
+
const dayOnes = screen.getAllByText('1')
|
|
123
|
+
expect(dayOnes.length).toBeGreaterThan(0)
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
it('renders week unit labels in weeks mode', () => {
|
|
127
|
+
const weekUnits = [
|
|
128
|
+
{ startDate: new Date('2025-01-06'), endDate: new Date('2025-01-12'), days: 7, label: 'Jan 6', hasToday: false, width: 56 },
|
|
129
|
+
{ startDate: new Date('2025-01-13'), endDate: new Date('2025-01-19'), days: 7, label: 'Jan 13', hasToday: false, width: 56 },
|
|
130
|
+
]
|
|
131
|
+
render(
|
|
132
|
+
<GanttTimelineView
|
|
133
|
+
{...defaultProps({
|
|
134
|
+
timeHeaderUnits: { mode: 'weeks', units: weekUnits },
|
|
135
|
+
})}
|
|
136
|
+
/>
|
|
137
|
+
)
|
|
138
|
+
expect(screen.getByText('Jan 6')).toBeInTheDocument()
|
|
139
|
+
expect(screen.getByText('Jan 13')).toBeInTheDocument()
|
|
140
|
+
})
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
// ---------------------------------------------------------------------------
|
|
144
|
+
// Zoom controls
|
|
145
|
+
// ---------------------------------------------------------------------------
|
|
146
|
+
|
|
147
|
+
describe('GanttTimelineView — zoom controls', () => {
|
|
148
|
+
it('renders zoom out button', () => {
|
|
149
|
+
render(<GanttTimelineView {...defaultProps()} />)
|
|
150
|
+
expect(screen.getByRole('button', { name: 'Zoom out' })).toBeInTheDocument()
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
it('renders zoom in button', () => {
|
|
154
|
+
render(<GanttTimelineView {...defaultProps()} />)
|
|
155
|
+
expect(screen.getByRole('button', { name: 'Zoom in' })) .toBeInTheDocument()
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
it('disables zoom out button at minimum zoom', () => {
|
|
159
|
+
render(<GanttTimelineView {...defaultProps({ zoomIndex: 0 })} />)
|
|
160
|
+
expect(screen.getByRole('button', { name: 'Zoom out' })).toBeDisabled()
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
it('disables zoom in button at maximum zoom', () => {
|
|
164
|
+
render(<GanttTimelineView {...defaultProps({ zoomIndex: ZOOM_LEVELS.length - 1 })} />)
|
|
165
|
+
expect(screen.getByRole('button', { name: 'Zoom in' })).toBeDisabled()
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
it('calls setZoomIndex when zoom out is clicked', () => {
|
|
169
|
+
const setZoomIndex = jest.fn()
|
|
170
|
+
render(<GanttTimelineView {...defaultProps({ setZoomIndex, zoomIndex: 3 })} />)
|
|
171
|
+
fireEvent.click(screen.getByRole('button', { name: 'Zoom out' }))
|
|
172
|
+
expect(setZoomIndex).toHaveBeenCalledTimes(1)
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
it('calls setZoomIndex when zoom in is clicked', () => {
|
|
176
|
+
const setZoomIndex = jest.fn()
|
|
177
|
+
render(<GanttTimelineView {...defaultProps({ setZoomIndex, zoomIndex: 3 })} />)
|
|
178
|
+
fireEvent.click(screen.getByRole('button', { name: 'Zoom in' }))
|
|
179
|
+
expect(setZoomIndex).toHaveBeenCalledTimes(1)
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
it('does not render fullscreen button when showFullscreen is false', () => {
|
|
183
|
+
render(<GanttTimelineView {...defaultProps({ showFullscreen: false })} />)
|
|
184
|
+
expect(screen.queryByRole('button', { name: /fullscreen/i })).not.toBeInTheDocument()
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
it('renders fullscreen button when showFullscreen is true', () => {
|
|
188
|
+
render(<GanttTimelineView {...defaultProps({ showFullscreen: true })} />)
|
|
189
|
+
expect(screen.getByRole('button', { name: /fullscreen/i })).toBeInTheDocument()
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
it('calls toggleFullscreen when the fullscreen button is clicked', () => {
|
|
193
|
+
const toggleFullscreen = jest.fn()
|
|
194
|
+
render(<GanttTimelineView {...defaultProps({ showFullscreen: true, toggleFullscreen })} />)
|
|
195
|
+
fireEvent.click(screen.getByRole('button', { name: /fullscreen/i }))
|
|
196
|
+
expect(toggleFullscreen).toHaveBeenCalledTimes(1)
|
|
197
|
+
})
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
// ---------------------------------------------------------------------------
|
|
201
|
+
// Empty state
|
|
202
|
+
// ---------------------------------------------------------------------------
|
|
203
|
+
|
|
204
|
+
describe('GanttTimelineView — empty state', () => {
|
|
205
|
+
it('renders no info rows when tasks array is empty', () => {
|
|
206
|
+
render(<GanttTimelineView {...defaultProps({ tasks: [] })} />)
|
|
207
|
+
// No gantt-info-row elements means no task title links
|
|
208
|
+
expect(screen.queryAllByRole('button', { name: /expand|collapse/i })).toHaveLength(0)
|
|
209
|
+
})
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
// ---------------------------------------------------------------------------
|
|
213
|
+
// Task bars and info rows
|
|
214
|
+
// ---------------------------------------------------------------------------
|
|
215
|
+
|
|
216
|
+
describe('GanttTimelineView — task rows', () => {
|
|
217
|
+
it('renders a row title for each task', () => {
|
|
218
|
+
const tasks = [
|
|
219
|
+
makeTask(makeItem({ id: 'a', title: 'Alpha Task', status: 'in_progress' })),
|
|
220
|
+
makeTask(makeItem({ id: 'b', title: 'Beta Task', status: 'completed' })),
|
|
221
|
+
]
|
|
222
|
+
// Use width <= 80 so bar label is empty (avoids duplicate title text nodes)
|
|
223
|
+
const getBarPosition = jest.fn().mockReturnValue({ left: 0, width: 80, top: 0, height: 20 })
|
|
224
|
+
render(<GanttTimelineView {...defaultProps({ tasks, getBarPosition })} />)
|
|
225
|
+
// Titles appear in the info column
|
|
226
|
+
expect(screen.getAllByText('Alpha Task').length).toBeGreaterThanOrEqual(1)
|
|
227
|
+
expect(screen.getAllByText('Beta Task').length).toBeGreaterThanOrEqual(1)
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
it('calls handleItemClick when a row title is clicked', () => {
|
|
231
|
+
const handleItemClick = jest.fn()
|
|
232
|
+
const item = makeItem({ id: 'a', title: 'Clickable Task', status: 'not_started' })
|
|
233
|
+
const tasks = [makeTask(item)]
|
|
234
|
+
// Use width <= 80 so the bar label renders empty and title only appears in the info row
|
|
235
|
+
const getBarPosition = jest.fn().mockReturnValue({ left: 0, width: 80, top: 0, height: 20 })
|
|
236
|
+
const { container } = render(<GanttTimelineView {...defaultProps({ tasks, handleItemClick, getBarPosition })} />)
|
|
237
|
+
// Target the info-column title span specifically to avoid the bar label span
|
|
238
|
+
const titleSpan = container.querySelector('.gantt-row-title') as HTMLElement
|
|
239
|
+
expect(titleSpan).toBeInTheDocument()
|
|
240
|
+
fireEvent.click(titleSpan)
|
|
241
|
+
expect(handleItemClick).toHaveBeenCalledWith(item)
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
it('renders a collapse button for tasks that have children', () => {
|
|
245
|
+
const tasks = [
|
|
246
|
+
makeTask(makeItem({ id: 'p', title: 'Parent', status: 'in_progress' }), { hasChildren: true }),
|
|
247
|
+
]
|
|
248
|
+
const getBarPosition = jest.fn().mockReturnValue({ left: 0, width: 200, top: 0, height: 20 })
|
|
249
|
+
render(<GanttTimelineView {...defaultProps({ tasks, getBarPosition })} />)
|
|
250
|
+
expect(screen.getByRole('button', { name: /collapse/i })).toBeInTheDocument()
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
it('shows expand button for a collapsed parent task', () => {
|
|
254
|
+
const tasks = [
|
|
255
|
+
makeTask(makeItem({ id: 'p', title: 'Parent', status: 'in_progress' }), { hasChildren: true }),
|
|
256
|
+
]
|
|
257
|
+
const getBarPosition = jest.fn().mockReturnValue({ left: 0, width: 200, top: 0, height: 20 })
|
|
258
|
+
render(
|
|
259
|
+
<GanttTimelineView
|
|
260
|
+
{...defaultProps({ tasks, collapsed: new Set(['p']), getBarPosition })}
|
|
261
|
+
/>
|
|
262
|
+
)
|
|
263
|
+
expect(screen.getByRole('button', { name: 'Expand' })).toBeInTheDocument()
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
it('calls toggleCollapse when the collapse button is clicked', () => {
|
|
267
|
+
const toggleCollapse = jest.fn()
|
|
268
|
+
const tasks = [
|
|
269
|
+
makeTask(makeItem({ id: 'p', title: 'Parent', status: 'in_progress' }), { hasChildren: true }),
|
|
270
|
+
]
|
|
271
|
+
const getBarPosition = jest.fn().mockReturnValue({ left: 0, width: 200, top: 0, height: 20 })
|
|
272
|
+
render(<GanttTimelineView {...defaultProps({ tasks, toggleCollapse, getBarPosition })} />)
|
|
273
|
+
fireEvent.click(screen.getByRole('button', { name: 'Collapse' }))
|
|
274
|
+
expect(toggleCollapse).toHaveBeenCalledWith('p')
|
|
275
|
+
})
|
|
276
|
+
|
|
277
|
+
it('renders category badge when showCategory is true and item has a category', () => {
|
|
278
|
+
const tasks = [
|
|
279
|
+
makeTask(makeItem({ id: 'a', title: 'Task A', status: 'in_progress', category: 'product' })),
|
|
280
|
+
]
|
|
281
|
+
const getBarPosition = jest.fn().mockReturnValue({ left: 0, width: 200, top: 0, height: 20 })
|
|
282
|
+
render(<GanttTimelineView {...defaultProps({ tasks, showCategory: true, getBarPosition })} />)
|
|
283
|
+
expect(screen.getByText('product')).toBeInTheDocument()
|
|
284
|
+
})
|
|
285
|
+
|
|
286
|
+
it('does not render category badge when showCategory is false', () => {
|
|
287
|
+
const tasks = [
|
|
288
|
+
makeTask(makeItem({ id: 'a', title: 'Task A', status: 'in_progress', category: 'product' })),
|
|
289
|
+
]
|
|
290
|
+
const getBarPosition = jest.fn().mockReturnValue({ left: 0, width: 200, top: 0, height: 20 })
|
|
291
|
+
render(<GanttTimelineView {...defaultProps({ tasks, showCategory: false, getBarPosition })} />)
|
|
292
|
+
expect(screen.queryByText('product')).not.toBeInTheDocument()
|
|
293
|
+
})
|
|
294
|
+
|
|
295
|
+
it('renders status badge when showStatus is true', () => {
|
|
296
|
+
const tasks = [
|
|
297
|
+
makeTask(makeItem({ id: 'a', title: 'Task A', status: 'in_progress' })),
|
|
298
|
+
]
|
|
299
|
+
const getBarPosition = jest.fn().mockReturnValue({ left: 0, width: 200, top: 0, height: 20 })
|
|
300
|
+
render(<GanttTimelineView {...defaultProps({ tasks, showStatus: true, getBarPosition })} />)
|
|
301
|
+
expect(screen.getByText('in progress')).toBeInTheDocument()
|
|
302
|
+
})
|
|
303
|
+
|
|
304
|
+
it('does not render status badge when showStatus is false', () => {
|
|
305
|
+
const tasks = [
|
|
306
|
+
makeTask(makeItem({ id: 'a', title: 'Task A', status: 'in_progress' })),
|
|
307
|
+
]
|
|
308
|
+
const getBarPosition = jest.fn().mockReturnValue({ left: 0, width: 200, top: 0, height: 20 })
|
|
309
|
+
render(<GanttTimelineView {...defaultProps({ tasks, showStatus: false, getBarPosition })} />)
|
|
310
|
+
expect(screen.queryByText('in progress')).not.toBeInTheDocument()
|
|
311
|
+
})
|
|
312
|
+
|
|
313
|
+
it('applies gantt-row-focused class to the focused row', () => {
|
|
314
|
+
const tasks = [
|
|
315
|
+
makeTask(makeItem({ id: 'a', title: 'Alpha', status: 'not_started' })),
|
|
316
|
+
makeTask(makeItem({ id: 'b', title: 'Beta', status: 'not_started' })),
|
|
317
|
+
]
|
|
318
|
+
const getBarPosition = jest.fn().mockReturnValue({ left: 0, width: 200, top: 0, height: 20 })
|
|
319
|
+
const { container } = render(
|
|
320
|
+
<GanttTimelineView {...defaultProps({ tasks, focusedRowIndex: 0, getBarPosition })} />
|
|
321
|
+
)
|
|
322
|
+
const focusedRows = container.querySelectorAll('.gantt-row-focused')
|
|
323
|
+
expect(focusedRows).toHaveLength(1)
|
|
324
|
+
})
|
|
325
|
+
})
|
|
326
|
+
|
|
327
|
+
// ---------------------------------------------------------------------------
|
|
328
|
+
// Legend
|
|
329
|
+
// ---------------------------------------------------------------------------
|
|
330
|
+
|
|
331
|
+
describe('GanttTimelineView — legend', () => {
|
|
332
|
+
it('renders the On Track legend entry', () => {
|
|
333
|
+
render(<GanttTimelineView {...defaultProps()} />)
|
|
334
|
+
expect(screen.getByText('On Track')).toBeInTheDocument()
|
|
335
|
+
})
|
|
336
|
+
|
|
337
|
+
it('renders the At Risk legend entry', () => {
|
|
338
|
+
render(<GanttTimelineView {...defaultProps()} />)
|
|
339
|
+
expect(screen.getByText('At Risk')).toBeInTheDocument()
|
|
340
|
+
})
|
|
341
|
+
|
|
342
|
+
it('renders the Blocked legend entry', () => {
|
|
343
|
+
render(<GanttTimelineView {...defaultProps()} />)
|
|
344
|
+
expect(screen.getByText('Blocked')).toBeInTheDocument()
|
|
345
|
+
})
|
|
346
|
+
|
|
347
|
+
it('renders the Not Started legend entry', () => {
|
|
348
|
+
render(<GanttTimelineView {...defaultProps()} />)
|
|
349
|
+
expect(screen.getByText('Not Started')).toBeInTheDocument()
|
|
350
|
+
})
|
|
351
|
+
})
|
|
352
|
+
|
|
353
|
+
// ---------------------------------------------------------------------------
|
|
354
|
+
// Dependency paths
|
|
355
|
+
// ---------------------------------------------------------------------------
|
|
356
|
+
|
|
357
|
+
describe('GanttTimelineView — dependency paths', () => {
|
|
358
|
+
it('does not render an SVG overlay when dependencyPaths is empty', () => {
|
|
359
|
+
const { container } = render(<GanttTimelineView {...defaultProps({ dependencyPaths: [] })} />)
|
|
360
|
+
expect(container.querySelector('.gantt-deps-svg')).not.toBeInTheDocument()
|
|
361
|
+
})
|
|
362
|
+
|
|
363
|
+
it('renders an SVG overlay when dependencyPaths has entries', () => {
|
|
364
|
+
const tasks = [
|
|
365
|
+
makeTask(makeItem({ id: 'a', title: 'A', status: 'in_progress' })),
|
|
366
|
+
makeTask(makeItem({ id: 'b', title: 'B', status: 'not_started' })),
|
|
367
|
+
]
|
|
368
|
+
const getBarPosition = jest.fn().mockReturnValue({ left: 0, width: 100, top: 0, height: 20 })
|
|
369
|
+
const paths = [{ path: 'M 100 10 C 150 10, 150 46, 200 46', fromId: 'a', toId: 'b' }]
|
|
370
|
+
const { container } = render(
|
|
371
|
+
<GanttTimelineView {...defaultProps({ tasks, dependencyPaths: paths, getBarPosition })} />
|
|
372
|
+
)
|
|
373
|
+
expect(container.querySelector('.gantt-deps-svg')).toBeInTheDocument()
|
|
374
|
+
})
|
|
375
|
+
})
|