@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,544 @@
|
|
|
1
|
+
import { render, screen, fireEvent } from '@testing-library/react'
|
|
2
|
+
import { GanttFilterBar } from '../GanttFilterBar'
|
|
3
|
+
import type { GanttFilterState } from '../types'
|
|
4
|
+
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// Fixtures
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
|
|
9
|
+
const emptyFilters: GanttFilterState = {
|
|
10
|
+
search: '',
|
|
11
|
+
statuses: [],
|
|
12
|
+
categories: [],
|
|
13
|
+
dateRange: { start: null, end: null },
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function makeFilters(overrides: Partial<GanttFilterState> = {}): GanttFilterState {
|
|
17
|
+
return { ...emptyFilters, ...overrides }
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const STATUSES = ['not_started', 'in_progress', 'completed']
|
|
21
|
+
const CATEGORIES = ['product', 'team', 'financial']
|
|
22
|
+
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// Search input
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
describe('GanttFilterBar — search input', () => {
|
|
28
|
+
it('renders a search input with placeholder text', () => {
|
|
29
|
+
const onFilterChange = jest.fn()
|
|
30
|
+
render(
|
|
31
|
+
<GanttFilterBar
|
|
32
|
+
filters={emptyFilters}
|
|
33
|
+
onFilterChange={onFilterChange}
|
|
34
|
+
uniqueStatuses={[]}
|
|
35
|
+
uniqueCategories={[]}
|
|
36
|
+
/>
|
|
37
|
+
)
|
|
38
|
+
expect(screen.getByPlaceholderText('Search items...')).toBeInTheDocument()
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it('reflects the current search value', () => {
|
|
42
|
+
const onFilterChange = jest.fn()
|
|
43
|
+
render(
|
|
44
|
+
<GanttFilterBar
|
|
45
|
+
filters={makeFilters({ search: 'hello' })}
|
|
46
|
+
onFilterChange={onFilterChange}
|
|
47
|
+
uniqueStatuses={[]}
|
|
48
|
+
uniqueCategories={[]}
|
|
49
|
+
/>
|
|
50
|
+
)
|
|
51
|
+
expect(screen.getByPlaceholderText('Search items...')).toHaveValue('hello')
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it('calls onFilterChange with updated search when text is typed', () => {
|
|
55
|
+
const onFilterChange = jest.fn()
|
|
56
|
+
render(
|
|
57
|
+
<GanttFilterBar
|
|
58
|
+
filters={emptyFilters}
|
|
59
|
+
onFilterChange={onFilterChange}
|
|
60
|
+
uniqueStatuses={[]}
|
|
61
|
+
uniqueCategories={[]}
|
|
62
|
+
/>
|
|
63
|
+
)
|
|
64
|
+
fireEvent.change(screen.getByPlaceholderText('Search items...'), {
|
|
65
|
+
target: { value: 'alpha' },
|
|
66
|
+
})
|
|
67
|
+
expect(onFilterChange).toHaveBeenCalledTimes(1)
|
|
68
|
+
expect(onFilterChange).toHaveBeenCalledWith(
|
|
69
|
+
expect.objectContaining({ search: 'alpha' })
|
|
70
|
+
)
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('preserves existing filter values when search changes', () => {
|
|
74
|
+
const onFilterChange = jest.fn()
|
|
75
|
+
const filters = makeFilters({ statuses: ['completed'], search: '' })
|
|
76
|
+
render(
|
|
77
|
+
<GanttFilterBar
|
|
78
|
+
filters={filters}
|
|
79
|
+
onFilterChange={onFilterChange}
|
|
80
|
+
uniqueStatuses={STATUSES}
|
|
81
|
+
uniqueCategories={[]}
|
|
82
|
+
/>
|
|
83
|
+
)
|
|
84
|
+
fireEvent.change(screen.getByPlaceholderText('Search items...'), {
|
|
85
|
+
target: { value: 'new term' },
|
|
86
|
+
})
|
|
87
|
+
const called = onFilterChange.mock.calls[0][0] as GanttFilterState
|
|
88
|
+
expect(called.statuses).toEqual(['completed'])
|
|
89
|
+
expect(called.search).toBe('new term')
|
|
90
|
+
})
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
// ---------------------------------------------------------------------------
|
|
94
|
+
// Status dropdown
|
|
95
|
+
// ---------------------------------------------------------------------------
|
|
96
|
+
|
|
97
|
+
describe('GanttFilterBar — status dropdown', () => {
|
|
98
|
+
it('does not render status select when uniqueStatuses is empty', () => {
|
|
99
|
+
const onFilterChange = jest.fn()
|
|
100
|
+
render(
|
|
101
|
+
<GanttFilterBar
|
|
102
|
+
filters={emptyFilters}
|
|
103
|
+
onFilterChange={onFilterChange}
|
|
104
|
+
uniqueStatuses={[]}
|
|
105
|
+
uniqueCategories={[]}
|
|
106
|
+
/>
|
|
107
|
+
)
|
|
108
|
+
// The select for statuses should be absent
|
|
109
|
+
const selects = screen.queryAllByRole('combobox')
|
|
110
|
+
expect(selects).toHaveLength(0)
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
it('renders status options for each unique status', () => {
|
|
114
|
+
const onFilterChange = jest.fn()
|
|
115
|
+
render(
|
|
116
|
+
<GanttFilterBar
|
|
117
|
+
filters={emptyFilters}
|
|
118
|
+
onFilterChange={onFilterChange}
|
|
119
|
+
uniqueStatuses={STATUSES}
|
|
120
|
+
uniqueCategories={[]}
|
|
121
|
+
/>
|
|
122
|
+
)
|
|
123
|
+
// The default empty option + one per status
|
|
124
|
+
const select = screen.getByRole('combobox')
|
|
125
|
+
expect(select).toBeInTheDocument()
|
|
126
|
+
expect(screen.getByRole('option', { name: /not started/ })).toBeInTheDocument()
|
|
127
|
+
expect(screen.getByRole('option', { name: /in progress/ })).toBeInTheDocument()
|
|
128
|
+
expect(screen.getByRole('option', { name: /completed/ })).toBeInTheDocument()
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
it('shows active count in the status label when statuses are selected', () => {
|
|
132
|
+
const onFilterChange = jest.fn()
|
|
133
|
+
render(
|
|
134
|
+
<GanttFilterBar
|
|
135
|
+
filters={makeFilters({ statuses: ['completed', 'in_progress'] })}
|
|
136
|
+
onFilterChange={onFilterChange}
|
|
137
|
+
uniqueStatuses={STATUSES}
|
|
138
|
+
uniqueCategories={[]}
|
|
139
|
+
/>
|
|
140
|
+
)
|
|
141
|
+
expect(screen.getByRole('option', { name: /Status \(2\)/ })).toBeInTheDocument()
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
it('calls onFilterChange with the new status added when a status is selected', () => {
|
|
145
|
+
const onFilterChange = jest.fn()
|
|
146
|
+
render(
|
|
147
|
+
<GanttFilterBar
|
|
148
|
+
filters={emptyFilters}
|
|
149
|
+
onFilterChange={onFilterChange}
|
|
150
|
+
uniqueStatuses={STATUSES}
|
|
151
|
+
uniqueCategories={[]}
|
|
152
|
+
/>
|
|
153
|
+
)
|
|
154
|
+
fireEvent.change(screen.getByRole('combobox'), { target: { value: 'completed' } })
|
|
155
|
+
const called = onFilterChange.mock.calls[0][0] as GanttFilterState
|
|
156
|
+
expect(called.statuses).toContain('completed')
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
it('removes a status when it is selected a second time (toggle off)', () => {
|
|
160
|
+
const onFilterChange = jest.fn()
|
|
161
|
+
// Start with 'completed' already active
|
|
162
|
+
render(
|
|
163
|
+
<GanttFilterBar
|
|
164
|
+
filters={makeFilters({ statuses: ['completed'] })}
|
|
165
|
+
onFilterChange={onFilterChange}
|
|
166
|
+
uniqueStatuses={STATUSES}
|
|
167
|
+
uniqueCategories={[]}
|
|
168
|
+
/>
|
|
169
|
+
)
|
|
170
|
+
fireEvent.change(screen.getByRole('combobox'), { target: { value: 'completed' } })
|
|
171
|
+
const called = onFilterChange.mock.calls[0][0] as GanttFilterState
|
|
172
|
+
expect(called.statuses).not.toContain('completed')
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
it('does not call onFilterChange when the empty placeholder option is selected', () => {
|
|
176
|
+
const onFilterChange = jest.fn()
|
|
177
|
+
render(
|
|
178
|
+
<GanttFilterBar
|
|
179
|
+
filters={emptyFilters}
|
|
180
|
+
onFilterChange={onFilterChange}
|
|
181
|
+
uniqueStatuses={STATUSES}
|
|
182
|
+
uniqueCategories={[]}
|
|
183
|
+
/>
|
|
184
|
+
)
|
|
185
|
+
fireEvent.change(screen.getByRole('combobox'), { target: { value: '' } })
|
|
186
|
+
expect(onFilterChange).not.toHaveBeenCalled()
|
|
187
|
+
})
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
// ---------------------------------------------------------------------------
|
|
191
|
+
// Category dropdown
|
|
192
|
+
// ---------------------------------------------------------------------------
|
|
193
|
+
|
|
194
|
+
describe('GanttFilterBar — category dropdown', () => {
|
|
195
|
+
it('does not render category select when uniqueCategories is empty', () => {
|
|
196
|
+
const onFilterChange = jest.fn()
|
|
197
|
+
render(
|
|
198
|
+
<GanttFilterBar
|
|
199
|
+
filters={emptyFilters}
|
|
200
|
+
onFilterChange={onFilterChange}
|
|
201
|
+
uniqueStatuses={[]}
|
|
202
|
+
uniqueCategories={[]}
|
|
203
|
+
/>
|
|
204
|
+
)
|
|
205
|
+
expect(screen.queryAllByRole('combobox')).toHaveLength(0)
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
it('renders a category select with one option per category', () => {
|
|
209
|
+
const onFilterChange = jest.fn()
|
|
210
|
+
render(
|
|
211
|
+
<GanttFilterBar
|
|
212
|
+
filters={emptyFilters}
|
|
213
|
+
onFilterChange={onFilterChange}
|
|
214
|
+
uniqueStatuses={[]}
|
|
215
|
+
uniqueCategories={CATEGORIES}
|
|
216
|
+
/>
|
|
217
|
+
)
|
|
218
|
+
const selects = screen.getAllByRole('combobox')
|
|
219
|
+
expect(selects).toHaveLength(1)
|
|
220
|
+
expect(screen.getByRole('option', { name: 'product' })).toBeInTheDocument()
|
|
221
|
+
expect(screen.getByRole('option', { name: 'team' })).toBeInTheDocument()
|
|
222
|
+
expect(screen.getByRole('option', { name: 'financial' })).toBeInTheDocument()
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
it('renders both status and category selects when both arrays have values', () => {
|
|
226
|
+
const onFilterChange = jest.fn()
|
|
227
|
+
render(
|
|
228
|
+
<GanttFilterBar
|
|
229
|
+
filters={emptyFilters}
|
|
230
|
+
onFilterChange={onFilterChange}
|
|
231
|
+
uniqueStatuses={STATUSES}
|
|
232
|
+
uniqueCategories={CATEGORIES}
|
|
233
|
+
/>
|
|
234
|
+
)
|
|
235
|
+
expect(screen.getAllByRole('combobox')).toHaveLength(2)
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
it('calls onFilterChange with category added when a category is selected', () => {
|
|
239
|
+
const onFilterChange = jest.fn()
|
|
240
|
+
render(
|
|
241
|
+
<GanttFilterBar
|
|
242
|
+
filters={emptyFilters}
|
|
243
|
+
onFilterChange={onFilterChange}
|
|
244
|
+
uniqueStatuses={[]}
|
|
245
|
+
uniqueCategories={CATEGORIES}
|
|
246
|
+
/>
|
|
247
|
+
)
|
|
248
|
+
fireEvent.change(screen.getByRole('combobox'), { target: { value: 'product' } })
|
|
249
|
+
const called = onFilterChange.mock.calls[0][0] as GanttFilterState
|
|
250
|
+
expect(called.categories).toContain('product')
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
it('removes a category when toggled off', () => {
|
|
254
|
+
const onFilterChange = jest.fn()
|
|
255
|
+
render(
|
|
256
|
+
<GanttFilterBar
|
|
257
|
+
filters={makeFilters({ categories: ['product'] })}
|
|
258
|
+
onFilterChange={onFilterChange}
|
|
259
|
+
uniqueStatuses={[]}
|
|
260
|
+
uniqueCategories={CATEGORIES}
|
|
261
|
+
/>
|
|
262
|
+
)
|
|
263
|
+
fireEvent.change(screen.getByRole('combobox'), { target: { value: 'product' } })
|
|
264
|
+
const called = onFilterChange.mock.calls[0][0] as GanttFilterState
|
|
265
|
+
expect(called.categories).not.toContain('product')
|
|
266
|
+
})
|
|
267
|
+
|
|
268
|
+
it('shows active count in the category label when categories are selected', () => {
|
|
269
|
+
const onFilterChange = jest.fn()
|
|
270
|
+
render(
|
|
271
|
+
<GanttFilterBar
|
|
272
|
+
filters={makeFilters({ categories: ['product', 'team'] })}
|
|
273
|
+
onFilterChange={onFilterChange}
|
|
274
|
+
uniqueStatuses={[]}
|
|
275
|
+
uniqueCategories={CATEGORIES}
|
|
276
|
+
/>
|
|
277
|
+
)
|
|
278
|
+
expect(screen.getByRole('option', { name: /Category \(2\)/ })).toBeInTheDocument()
|
|
279
|
+
})
|
|
280
|
+
})
|
|
281
|
+
|
|
282
|
+
// ---------------------------------------------------------------------------
|
|
283
|
+
// Date range inputs
|
|
284
|
+
// ---------------------------------------------------------------------------
|
|
285
|
+
|
|
286
|
+
describe('GanttFilterBar — date range inputs', () => {
|
|
287
|
+
it('renders two date inputs', () => {
|
|
288
|
+
const onFilterChange = jest.fn()
|
|
289
|
+
render(
|
|
290
|
+
<GanttFilterBar
|
|
291
|
+
filters={emptyFilters}
|
|
292
|
+
onFilterChange={onFilterChange}
|
|
293
|
+
uniqueStatuses={[]}
|
|
294
|
+
uniqueCategories={[]}
|
|
295
|
+
/>
|
|
296
|
+
)
|
|
297
|
+
const dateInputs = screen.getAllByDisplayValue('')
|
|
298
|
+
// Both date inputs start empty plus the search input — filter by type
|
|
299
|
+
const typedDateInputs = document.querySelectorAll('input[type="date"]')
|
|
300
|
+
expect(typedDateInputs).toHaveLength(2)
|
|
301
|
+
})
|
|
302
|
+
|
|
303
|
+
it('populates the start date input when dateRange.start is set', () => {
|
|
304
|
+
const onFilterChange = jest.fn()
|
|
305
|
+
// Use local-time constructor (year, month-1, day) to avoid UTC→local timezone shift
|
|
306
|
+
render(
|
|
307
|
+
<GanttFilterBar
|
|
308
|
+
filters={makeFilters({ dateRange: { start: new Date(2025, 2, 15), end: null } })}
|
|
309
|
+
onFilterChange={onFilterChange}
|
|
310
|
+
uniqueStatuses={[]}
|
|
311
|
+
uniqueCategories={[]}
|
|
312
|
+
/>
|
|
313
|
+
)
|
|
314
|
+
const dateInputs = document.querySelectorAll('input[type="date"]')
|
|
315
|
+
expect((dateInputs[0] as HTMLInputElement).value).toBe('2025-03-15')
|
|
316
|
+
})
|
|
317
|
+
|
|
318
|
+
it('populates the end date input when dateRange.end is set', () => {
|
|
319
|
+
const onFilterChange = jest.fn()
|
|
320
|
+
// Use local-time constructor (year, month-1, day) to avoid UTC→local timezone shift
|
|
321
|
+
render(
|
|
322
|
+
<GanttFilterBar
|
|
323
|
+
filters={makeFilters({ dateRange: { start: null, end: new Date(2025, 5, 30) } })}
|
|
324
|
+
onFilterChange={onFilterChange}
|
|
325
|
+
uniqueStatuses={[]}
|
|
326
|
+
uniqueCategories={[]}
|
|
327
|
+
/>
|
|
328
|
+
)
|
|
329
|
+
const dateInputs = document.querySelectorAll('input[type="date"]')
|
|
330
|
+
expect((dateInputs[1] as HTMLInputElement).value).toBe('2025-06-30')
|
|
331
|
+
})
|
|
332
|
+
|
|
333
|
+
it('calls onFilterChange with updated start date when start input changes', () => {
|
|
334
|
+
const onFilterChange = jest.fn()
|
|
335
|
+
render(
|
|
336
|
+
<GanttFilterBar
|
|
337
|
+
filters={emptyFilters}
|
|
338
|
+
onFilterChange={onFilterChange}
|
|
339
|
+
uniqueStatuses={[]}
|
|
340
|
+
uniqueCategories={[]}
|
|
341
|
+
/>
|
|
342
|
+
)
|
|
343
|
+
const dateInputs = document.querySelectorAll('input[type="date"]')
|
|
344
|
+
fireEvent.change(dateInputs[0], { target: { value: '2025-01-01' } })
|
|
345
|
+
const called = onFilterChange.mock.calls[0][0] as GanttFilterState
|
|
346
|
+
expect(called.dateRange.start).not.toBeNull()
|
|
347
|
+
})
|
|
348
|
+
|
|
349
|
+
it('sets start date to null when start input is cleared', () => {
|
|
350
|
+
const onFilterChange = jest.fn()
|
|
351
|
+
render(
|
|
352
|
+
<GanttFilterBar
|
|
353
|
+
filters={makeFilters({ dateRange: { start: new Date('2025-01-01'), end: null } })}
|
|
354
|
+
onFilterChange={onFilterChange}
|
|
355
|
+
uniqueStatuses={[]}
|
|
356
|
+
uniqueCategories={[]}
|
|
357
|
+
/>
|
|
358
|
+
)
|
|
359
|
+
const dateInputs = document.querySelectorAll('input[type="date"]')
|
|
360
|
+
fireEvent.change(dateInputs[0], { target: { value: '' } })
|
|
361
|
+
const called = onFilterChange.mock.calls[0][0] as GanttFilterState
|
|
362
|
+
expect(called.dateRange.start).toBeNull()
|
|
363
|
+
})
|
|
364
|
+
})
|
|
365
|
+
|
|
366
|
+
// ---------------------------------------------------------------------------
|
|
367
|
+
// Clear button
|
|
368
|
+
// ---------------------------------------------------------------------------
|
|
369
|
+
|
|
370
|
+
describe('GanttFilterBar — clear button', () => {
|
|
371
|
+
it('does not render the clear button when no filters are active', () => {
|
|
372
|
+
const onFilterChange = jest.fn()
|
|
373
|
+
render(
|
|
374
|
+
<GanttFilterBar
|
|
375
|
+
filters={emptyFilters}
|
|
376
|
+
onFilterChange={onFilterChange}
|
|
377
|
+
uniqueStatuses={[]}
|
|
378
|
+
uniqueCategories={[]}
|
|
379
|
+
/>
|
|
380
|
+
)
|
|
381
|
+
expect(screen.queryByRole('button', { name: 'Clear' })).not.toBeInTheDocument()
|
|
382
|
+
})
|
|
383
|
+
|
|
384
|
+
it('renders the clear button when search is active', () => {
|
|
385
|
+
const onFilterChange = jest.fn()
|
|
386
|
+
render(
|
|
387
|
+
<GanttFilterBar
|
|
388
|
+
filters={makeFilters({ search: 'foo' })}
|
|
389
|
+
onFilterChange={onFilterChange}
|
|
390
|
+
uniqueStatuses={[]}
|
|
391
|
+
uniqueCategories={[]}
|
|
392
|
+
/>
|
|
393
|
+
)
|
|
394
|
+
expect(screen.getByRole('button', { name: 'Clear' })).toBeInTheDocument()
|
|
395
|
+
})
|
|
396
|
+
|
|
397
|
+
it('renders the clear button when a status filter is active', () => {
|
|
398
|
+
const onFilterChange = jest.fn()
|
|
399
|
+
render(
|
|
400
|
+
<GanttFilterBar
|
|
401
|
+
filters={makeFilters({ statuses: ['completed'] })}
|
|
402
|
+
onFilterChange={onFilterChange}
|
|
403
|
+
uniqueStatuses={STATUSES}
|
|
404
|
+
uniqueCategories={[]}
|
|
405
|
+
/>
|
|
406
|
+
)
|
|
407
|
+
expect(screen.getByRole('button', { name: 'Clear' })).toBeInTheDocument()
|
|
408
|
+
})
|
|
409
|
+
|
|
410
|
+
it('renders the clear button when a category filter is active', () => {
|
|
411
|
+
const onFilterChange = jest.fn()
|
|
412
|
+
render(
|
|
413
|
+
<GanttFilterBar
|
|
414
|
+
filters={makeFilters({ categories: ['product'] })}
|
|
415
|
+
onFilterChange={onFilterChange}
|
|
416
|
+
uniqueStatuses={[]}
|
|
417
|
+
uniqueCategories={CATEGORIES}
|
|
418
|
+
/>
|
|
419
|
+
)
|
|
420
|
+
expect(screen.getByRole('button', { name: 'Clear' })).toBeInTheDocument()
|
|
421
|
+
})
|
|
422
|
+
|
|
423
|
+
it('calls onFilterChange with all filters reset when Clear is clicked', () => {
|
|
424
|
+
const onFilterChange = jest.fn()
|
|
425
|
+
render(
|
|
426
|
+
<GanttFilterBar
|
|
427
|
+
filters={makeFilters({ search: 'foo', statuses: ['completed'], categories: ['product'] })}
|
|
428
|
+
onFilterChange={onFilterChange}
|
|
429
|
+
uniqueStatuses={STATUSES}
|
|
430
|
+
uniqueCategories={CATEGORIES}
|
|
431
|
+
/>
|
|
432
|
+
)
|
|
433
|
+
fireEvent.click(screen.getByRole('button', { name: 'Clear' }))
|
|
434
|
+
expect(onFilterChange).toHaveBeenCalledWith({
|
|
435
|
+
search: '',
|
|
436
|
+
statuses: [],
|
|
437
|
+
categories: [],
|
|
438
|
+
dateRange: { start: null, end: null },
|
|
439
|
+
})
|
|
440
|
+
})
|
|
441
|
+
})
|
|
442
|
+
|
|
443
|
+
// ---------------------------------------------------------------------------
|
|
444
|
+
// Filter pills
|
|
445
|
+
// ---------------------------------------------------------------------------
|
|
446
|
+
|
|
447
|
+
describe('GanttFilterBar — filter pills', () => {
|
|
448
|
+
it('renders a pill for each active status', () => {
|
|
449
|
+
const onFilterChange = jest.fn()
|
|
450
|
+
render(
|
|
451
|
+
<GanttFilterBar
|
|
452
|
+
filters={makeFilters({ statuses: ['completed', 'in_progress'] })}
|
|
453
|
+
onFilterChange={onFilterChange}
|
|
454
|
+
uniqueStatuses={STATUSES}
|
|
455
|
+
uniqueCategories={[]}
|
|
456
|
+
/>
|
|
457
|
+
)
|
|
458
|
+
// Underscores replaced with spaces in pill text
|
|
459
|
+
expect(screen.getByText('completed')).toBeInTheDocument()
|
|
460
|
+
expect(screen.getByText('in progress')).toBeInTheDocument()
|
|
461
|
+
})
|
|
462
|
+
|
|
463
|
+
it('renders a pill for each active category', () => {
|
|
464
|
+
const onFilterChange = jest.fn()
|
|
465
|
+
render(
|
|
466
|
+
<GanttFilterBar
|
|
467
|
+
filters={makeFilters({ categories: ['product', 'team'] })}
|
|
468
|
+
onFilterChange={onFilterChange}
|
|
469
|
+
uniqueStatuses={[]}
|
|
470
|
+
uniqueCategories={CATEGORIES}
|
|
471
|
+
/>
|
|
472
|
+
)
|
|
473
|
+
expect(screen.getByText('product')).toBeInTheDocument()
|
|
474
|
+
expect(screen.getByText('team')).toBeInTheDocument()
|
|
475
|
+
})
|
|
476
|
+
|
|
477
|
+
it('removes a status pill when its dismiss button is clicked', () => {
|
|
478
|
+
const onFilterChange = jest.fn()
|
|
479
|
+
render(
|
|
480
|
+
<GanttFilterBar
|
|
481
|
+
filters={makeFilters({ statuses: ['completed'] })}
|
|
482
|
+
onFilterChange={onFilterChange}
|
|
483
|
+
uniqueStatuses={STATUSES}
|
|
484
|
+
uniqueCategories={[]}
|
|
485
|
+
/>
|
|
486
|
+
)
|
|
487
|
+
// The pill has a button labelled "x" next to the status text
|
|
488
|
+
const pillButtons = screen.getAllByRole('button')
|
|
489
|
+
// Filter out the 'Clear' button — this pill is the small x button inside the pill
|
|
490
|
+
const dismissButtons = pillButtons.filter((btn) => btn.textContent === 'x')
|
|
491
|
+
expect(dismissButtons).toHaveLength(1)
|
|
492
|
+
fireEvent.click(dismissButtons[0])
|
|
493
|
+
const called = onFilterChange.mock.calls[0][0] as GanttFilterState
|
|
494
|
+
expect(called.statuses).not.toContain('completed')
|
|
495
|
+
})
|
|
496
|
+
|
|
497
|
+
it('removes a category pill when its dismiss button is clicked', () => {
|
|
498
|
+
const onFilterChange = jest.fn()
|
|
499
|
+
render(
|
|
500
|
+
<GanttFilterBar
|
|
501
|
+
filters={makeFilters({ categories: ['product'] })}
|
|
502
|
+
onFilterChange={onFilterChange}
|
|
503
|
+
uniqueStatuses={[]}
|
|
504
|
+
uniqueCategories={CATEGORIES}
|
|
505
|
+
/>
|
|
506
|
+
)
|
|
507
|
+
const dismissButtons = screen.getAllByRole('button').filter((btn) => btn.textContent === 'x')
|
|
508
|
+
expect(dismissButtons).toHaveLength(1)
|
|
509
|
+
fireEvent.click(dismissButtons[0])
|
|
510
|
+
const called = onFilterChange.mock.calls[0][0] as GanttFilterState
|
|
511
|
+
expect(called.categories).not.toContain('product')
|
|
512
|
+
})
|
|
513
|
+
|
|
514
|
+
it('preserves other pills when one status pill is dismissed', () => {
|
|
515
|
+
const onFilterChange = jest.fn()
|
|
516
|
+
render(
|
|
517
|
+
<GanttFilterBar
|
|
518
|
+
filters={makeFilters({ statuses: ['completed', 'in_progress'] })}
|
|
519
|
+
onFilterChange={onFilterChange}
|
|
520
|
+
uniqueStatuses={STATUSES}
|
|
521
|
+
uniqueCategories={[]}
|
|
522
|
+
/>
|
|
523
|
+
)
|
|
524
|
+
// Two pills rendered — dismiss the first
|
|
525
|
+
const dismissButtons = screen.getAllByRole('button').filter((btn) => btn.textContent === 'x')
|
|
526
|
+
fireEvent.click(dismissButtons[0])
|
|
527
|
+
const called = onFilterChange.mock.calls[0][0] as GanttFilterState
|
|
528
|
+
expect(called.statuses).toHaveLength(1)
|
|
529
|
+
})
|
|
530
|
+
|
|
531
|
+
it('renders no pills when filters are empty', () => {
|
|
532
|
+
const onFilterChange = jest.fn()
|
|
533
|
+
render(
|
|
534
|
+
<GanttFilterBar
|
|
535
|
+
filters={emptyFilters}
|
|
536
|
+
onFilterChange={onFilterChange}
|
|
537
|
+
uniqueStatuses={STATUSES}
|
|
538
|
+
uniqueCategories={CATEGORIES}
|
|
539
|
+
/>
|
|
540
|
+
)
|
|
541
|
+
const dismissButtons = screen.queryAllByRole('button').filter((btn) => btn.textContent === 'x')
|
|
542
|
+
expect(dismissButtons).toHaveLength(0)
|
|
543
|
+
})
|
|
544
|
+
})
|