@object-ui/plugin-kanban 3.3.0 → 3.3.2
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/CHANGELOG.md +19 -0
- package/README.md +24 -0
- package/dist/{KanbanEnhanced-TdUe0kQH.js → KanbanEnhanced-Do9ZB1Mh.js} +35 -32
- package/dist/{KanbanImpl-BtlPa7GE.js → KanbanImpl-BdocXM5T.js} +1 -1
- package/dist/{chevron-down-B6UH8BbF.js → chevron-down-C0JUlGjk.js} +1 -1
- package/dist/index.js +3 -3
- package/dist/index.umd.cjs +2 -2
- package/dist/{plus-BTqoaaEC.js → plus-CHsXVJSY.js} +1 -1
- package/package.json +34 -11
- package/.turbo/turbo-build.log +0 -32
- package/src/CardTemplates.tsx +0 -123
- package/src/InlineQuickAdd.tsx +0 -189
- package/src/KanbanEnhanced.tsx +0 -525
- package/src/KanbanImpl.tsx +0 -597
- package/src/ObjectKanban.EdgeCases.stories.tsx +0 -168
- package/src/ObjectKanban.msw.test.tsx +0 -95
- package/src/ObjectKanban.stories.tsx +0 -152
- package/src/ObjectKanban.tsx +0 -276
- package/src/__tests__/KanbanEnhanced.test.tsx +0 -260
- package/src/__tests__/KanbanGrouping.test.tsx +0 -164
- package/src/__tests__/KanbanSwimlanes.test.tsx +0 -194
- package/src/__tests__/ObjectKanbanTitle.test.tsx +0 -93
- package/src/__tests__/SwimlanePersistence.test.tsx +0 -159
- package/src/__tests__/accessibility.test.tsx +0 -296
- package/src/__tests__/dnd-undo-integration.test.tsx +0 -525
- package/src/__tests__/performance-benchmark.test.tsx +0 -306
- package/src/__tests__/phase13-features.test.tsx +0 -387
- package/src/__tests__/view-states.test.tsx +0 -403
- package/src/index.test.ts +0 -112
- package/src/index.tsx +0 -327
- package/src/registration.test.tsx +0 -26
- package/src/types.ts +0 -185
- package/src/useColumnWidths.ts +0 -125
- package/src/useCrossSwimlaneMove.ts +0 -116
- package/src/useQuickAddReorder.ts +0 -107
- package/tsconfig.json +0 -19
- package/vite.config.ts +0 -62
- package/vitest.config.ts +0 -12
- package/vitest.setup.ts +0 -1
|
@@ -1,260 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ObjectUI
|
|
3
|
-
* Copyright (c) 2024-present ObjectStack Inc.
|
|
4
|
-
*
|
|
5
|
-
* This source code is licensed under the MIT license found in the
|
|
6
|
-
* LICENSE file in the root directory of this source tree.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { describe, it, expect, vi } from 'vitest';
|
|
10
|
-
import { render, screen, fireEvent } from '@testing-library/react';
|
|
11
|
-
import { KanbanEnhanced, type KanbanColumn } from '../KanbanEnhanced';
|
|
12
|
-
|
|
13
|
-
// Mock @tanstack/react-virtual
|
|
14
|
-
vi.mock('@tanstack/react-virtual', () => ({
|
|
15
|
-
useVirtualizer: () => ({
|
|
16
|
-
getTotalSize: () => 1000,
|
|
17
|
-
getVirtualItems: () => [],
|
|
18
|
-
measureElement: vi.fn(),
|
|
19
|
-
}),
|
|
20
|
-
}));
|
|
21
|
-
|
|
22
|
-
// Mock @dnd-kit/core and utilities
|
|
23
|
-
vi.mock('@dnd-kit/core', () => ({
|
|
24
|
-
DndContext: ({ children }: any) => <div data-testid="dnd-context">{children}</div>,
|
|
25
|
-
DragOverlay: ({ children }: any) => <div data-testid="drag-overlay">{children}</div>,
|
|
26
|
-
PointerSensor: vi.fn(),
|
|
27
|
-
TouchSensor: vi.fn(),
|
|
28
|
-
useSensor: vi.fn(),
|
|
29
|
-
useSensors: () => [],
|
|
30
|
-
closestCorners: vi.fn(),
|
|
31
|
-
}));
|
|
32
|
-
|
|
33
|
-
vi.mock('@dnd-kit/sortable', () => ({
|
|
34
|
-
SortableContext: ({ children }: any) => <div data-testid="sortable-context">{children}</div>,
|
|
35
|
-
useSortable: () => ({
|
|
36
|
-
attributes: {},
|
|
37
|
-
listeners: {},
|
|
38
|
-
setNodeRef: vi.fn(),
|
|
39
|
-
transform: null,
|
|
40
|
-
transition: null,
|
|
41
|
-
isDragging: false,
|
|
42
|
-
}),
|
|
43
|
-
arrayMove: (array: any[], from: number, to: number) => {
|
|
44
|
-
const newArray = [...array];
|
|
45
|
-
newArray.splice(to, 0, newArray.splice(from, 1)[0]);
|
|
46
|
-
return newArray;
|
|
47
|
-
},
|
|
48
|
-
verticalListSortingStrategy: vi.fn(),
|
|
49
|
-
}));
|
|
50
|
-
|
|
51
|
-
vi.mock('@dnd-kit/utilities', () => ({
|
|
52
|
-
CSS: {
|
|
53
|
-
Transform: {
|
|
54
|
-
toString: () => '',
|
|
55
|
-
},
|
|
56
|
-
},
|
|
57
|
-
}));
|
|
58
|
-
|
|
59
|
-
describe('KanbanEnhanced', () => {
|
|
60
|
-
const mockColumns: KanbanColumn[] = [
|
|
61
|
-
{
|
|
62
|
-
id: 'todo',
|
|
63
|
-
title: 'To Do',
|
|
64
|
-
cards: [
|
|
65
|
-
{
|
|
66
|
-
id: 'card-1',
|
|
67
|
-
title: 'Task 1',
|
|
68
|
-
description: 'Description 1',
|
|
69
|
-
badges: [{ label: 'High', variant: 'destructive' }],
|
|
70
|
-
},
|
|
71
|
-
{
|
|
72
|
-
id: 'card-2',
|
|
73
|
-
title: 'Task 2',
|
|
74
|
-
description: 'Description 2',
|
|
75
|
-
},
|
|
76
|
-
],
|
|
77
|
-
},
|
|
78
|
-
{
|
|
79
|
-
id: 'in-progress',
|
|
80
|
-
title: 'In Progress',
|
|
81
|
-
limit: 3,
|
|
82
|
-
cards: [
|
|
83
|
-
{
|
|
84
|
-
id: 'card-3',
|
|
85
|
-
title: 'Task 3',
|
|
86
|
-
},
|
|
87
|
-
],
|
|
88
|
-
},
|
|
89
|
-
{
|
|
90
|
-
id: 'done',
|
|
91
|
-
title: 'Done',
|
|
92
|
-
cards: [],
|
|
93
|
-
},
|
|
94
|
-
];
|
|
95
|
-
|
|
96
|
-
it('should render without crashing', () => {
|
|
97
|
-
const { container } = render(<KanbanEnhanced columns={mockColumns} />);
|
|
98
|
-
expect(container).toBeTruthy();
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
it('should render all columns', () => {
|
|
102
|
-
render(<KanbanEnhanced columns={mockColumns} />);
|
|
103
|
-
|
|
104
|
-
expect(screen.getByText('To Do')).toBeInTheDocument();
|
|
105
|
-
expect(screen.getByText('In Progress')).toBeInTheDocument();
|
|
106
|
-
expect(screen.getByText('Done')).toBeInTheDocument();
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
it('should render all cards', () => {
|
|
110
|
-
render(<KanbanEnhanced columns={mockColumns} />);
|
|
111
|
-
|
|
112
|
-
expect(screen.getByText('Task 1')).toBeInTheDocument();
|
|
113
|
-
expect(screen.getByText('Task 2')).toBeInTheDocument();
|
|
114
|
-
expect(screen.getByText('Task 3')).toBeInTheDocument();
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
it('should display card count for each column', () => {
|
|
118
|
-
render(<KanbanEnhanced columns={mockColumns} />);
|
|
119
|
-
|
|
120
|
-
// Columns should show card counts in the format "count" or "count / limit"
|
|
121
|
-
expect(screen.getByText('To Do')).toBeInTheDocument();
|
|
122
|
-
expect(screen.getByText('In Progress')).toBeInTheDocument();
|
|
123
|
-
expect(screen.getByText('1 / 3')).toBeInTheDocument(); // In Progress has 1 card with limit 3
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
it('should display column limit warning when at 80% capacity', () => {
|
|
127
|
-
const columnsNearLimit: KanbanColumn[] = [
|
|
128
|
-
{
|
|
129
|
-
id: 'limited',
|
|
130
|
-
title: 'Limited Column',
|
|
131
|
-
limit: 5,
|
|
132
|
-
cards: Array(4).fill(null).map((_, i) => ({
|
|
133
|
-
id: `card-${i}`,
|
|
134
|
-
title: `Task ${i}`,
|
|
135
|
-
})),
|
|
136
|
-
},
|
|
137
|
-
];
|
|
138
|
-
|
|
139
|
-
const { container } = render(<KanbanEnhanced columns={columnsNearLimit} />);
|
|
140
|
-
|
|
141
|
-
// Should show warning indicator (80% of 5 = 4) - AlertTriangle icon with yellow color
|
|
142
|
-
expect(container.querySelector('.text-yellow-500')).toBeTruthy();
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
it('should collapse/expand columns when toggle button is clicked', () => {
|
|
146
|
-
render(<KanbanEnhanced columns={mockColumns} enableCollapse={true} />);
|
|
147
|
-
|
|
148
|
-
// Find collapse toggle button (chevron icons)
|
|
149
|
-
const toggleButtons = screen.getAllByRole('button');
|
|
150
|
-
const collapseButton = toggleButtons.find(btn =>
|
|
151
|
-
btn.querySelector('svg') !== null
|
|
152
|
-
);
|
|
153
|
-
|
|
154
|
-
if (collapseButton) {
|
|
155
|
-
fireEvent.click(collapseButton);
|
|
156
|
-
// After clicking, the column state would change
|
|
157
|
-
// In a real test with proper DOM, we would verify:
|
|
158
|
-
// - Column content is hidden
|
|
159
|
-
// - Icon changes from ChevronDown to ChevronRight
|
|
160
|
-
expect(collapseButton).toBeTruthy();
|
|
161
|
-
}
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
it('should call onCardMove when a card is moved', () => {
|
|
165
|
-
const onCardMove = vi.fn();
|
|
166
|
-
render(<KanbanEnhanced columns={mockColumns} onCardMove={onCardMove} />);
|
|
167
|
-
|
|
168
|
-
// In our mocked environment with mocked dnd-kit,
|
|
169
|
-
// we can't easily simulate the full drag and drop interaction.
|
|
170
|
-
// In a real integration test, this would verify:
|
|
171
|
-
// - Dragging a card from one column to another
|
|
172
|
-
// - onCardMove is called with correct parameters (cardId, fromColumn, toColumn)
|
|
173
|
-
expect(onCardMove).toBeDefined();
|
|
174
|
-
|
|
175
|
-
// Example of what the callback would receive:
|
|
176
|
-
// expect(onCardMove).toHaveBeenCalledWith('card-1', 'todo', 'in-progress');
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
it('should call onColumnToggle when a column is collapsed', () => {
|
|
180
|
-
const onColumnToggle = vi.fn();
|
|
181
|
-
render(
|
|
182
|
-
<KanbanEnhanced
|
|
183
|
-
columns={mockColumns}
|
|
184
|
-
enableCollapse={true}
|
|
185
|
-
onColumnToggle={onColumnToggle}
|
|
186
|
-
/>
|
|
187
|
-
);
|
|
188
|
-
|
|
189
|
-
expect(onColumnToggle).toBeDefined();
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
it('should render card badges', () => {
|
|
193
|
-
render(<KanbanEnhanced columns={mockColumns} />);
|
|
194
|
-
|
|
195
|
-
expect(screen.getByText('High')).toBeInTheDocument();
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
it('should render card descriptions', () => {
|
|
199
|
-
render(<KanbanEnhanced columns={mockColumns} />);
|
|
200
|
-
|
|
201
|
-
expect(screen.getByText('Description 1')).toBeInTheDocument();
|
|
202
|
-
expect(screen.getByText('Description 2')).toBeInTheDocument();
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
it('should apply custom className', () => {
|
|
206
|
-
const { container } = render(
|
|
207
|
-
<KanbanEnhanced columns={mockColumns} className="custom-class" />
|
|
208
|
-
);
|
|
209
|
-
|
|
210
|
-
const kanbanContainer = container.querySelector('.custom-class');
|
|
211
|
-
expect(kanbanContainer).toBeTruthy();
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
it('should render empty columns', () => {
|
|
215
|
-
render(<KanbanEnhanced columns={mockColumns} />);
|
|
216
|
-
|
|
217
|
-
// "Done" column has 0 cards
|
|
218
|
-
expect(screen.getByText('Done')).toBeInTheDocument();
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
it('should enable virtual scrolling when specified', () => {
|
|
222
|
-
render(<KanbanEnhanced columns={mockColumns} enableVirtualScrolling={true} />);
|
|
223
|
-
|
|
224
|
-
// Virtual scrolling should be active
|
|
225
|
-
const { container } = render(<KanbanEnhanced columns={mockColumns} enableVirtualScrolling={true} />);
|
|
226
|
-
expect(container).toBeTruthy();
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
it('should render drag overlay when dragging', () => {
|
|
230
|
-
const { container } = render(<KanbanEnhanced columns={mockColumns} />);
|
|
231
|
-
|
|
232
|
-
const dragOverlay = container.querySelector('[data-testid="drag-overlay"]');
|
|
233
|
-
expect(dragOverlay).toBeTruthy();
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
it('should show error state when column is over limit', () => {
|
|
237
|
-
const columnsOverLimit: KanbanColumn[] = [
|
|
238
|
-
{
|
|
239
|
-
id: 'limited',
|
|
240
|
-
title: 'Over Limit',
|
|
241
|
-
limit: 3,
|
|
242
|
-
cards: Array(4).fill(null).map((_, i) => ({
|
|
243
|
-
id: `card-${i}`,
|
|
244
|
-
title: `Task ${i}`,
|
|
245
|
-
})),
|
|
246
|
-
},
|
|
247
|
-
];
|
|
248
|
-
|
|
249
|
-
render(<KanbanEnhanced columns={columnsOverLimit} />);
|
|
250
|
-
|
|
251
|
-
// Should show error indicator (over 100% of limit)
|
|
252
|
-
const { container } = render(<KanbanEnhanced columns={columnsOverLimit} />);
|
|
253
|
-
expect(container.querySelector('[class*="destructive"]')).toBeTruthy();
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
it('should handle empty columns array', () => {
|
|
257
|
-
const { container } = render(<KanbanEnhanced columns={[]} />);
|
|
258
|
-
expect(container).toBeTruthy();
|
|
259
|
-
});
|
|
260
|
-
});
|
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ObjectUI
|
|
3
|
-
* Copyright (c) 2024-present ObjectStack Inc.
|
|
4
|
-
*
|
|
5
|
-
* This source code is licensed under the MIT license found in the
|
|
6
|
-
* LICENSE file in the root directory of this source tree.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { describe, it, expect, vi } from 'vitest';
|
|
10
|
-
import { render, screen, act } from '@testing-library/react';
|
|
11
|
-
import React, { Suspense } from 'react';
|
|
12
|
-
|
|
13
|
-
// Mock dnd-kit
|
|
14
|
-
vi.mock('@dnd-kit/core', () => ({
|
|
15
|
-
DndContext: ({ children }: any) => <div data-testid="dnd-context">{children}</div>,
|
|
16
|
-
DragOverlay: ({ children }: any) => <div data-testid="drag-overlay">{children}</div>,
|
|
17
|
-
PointerSensor: vi.fn(),
|
|
18
|
-
TouchSensor: vi.fn(),
|
|
19
|
-
useSensor: vi.fn(),
|
|
20
|
-
useSensors: () => [],
|
|
21
|
-
closestCorners: vi.fn(),
|
|
22
|
-
}));
|
|
23
|
-
|
|
24
|
-
vi.mock('@dnd-kit/sortable', () => ({
|
|
25
|
-
SortableContext: ({ children }: any) => <div data-testid="sortable-context">{children}</div>,
|
|
26
|
-
useSortable: () => ({
|
|
27
|
-
attributes: {},
|
|
28
|
-
listeners: {},
|
|
29
|
-
setNodeRef: vi.fn(),
|
|
30
|
-
transform: null,
|
|
31
|
-
transition: null,
|
|
32
|
-
isDragging: false,
|
|
33
|
-
}),
|
|
34
|
-
arrayMove: (array: any[], from: number, to: number) => {
|
|
35
|
-
const newArray = [...array];
|
|
36
|
-
newArray.splice(to, 0, newArray.splice(from, 1)[0]);
|
|
37
|
-
return newArray;
|
|
38
|
-
},
|
|
39
|
-
verticalListSortingStrategy: vi.fn(),
|
|
40
|
-
}));
|
|
41
|
-
|
|
42
|
-
vi.mock('@dnd-kit/utilities', () => ({
|
|
43
|
-
CSS: {
|
|
44
|
-
Transform: {
|
|
45
|
-
toString: () => '',
|
|
46
|
-
},
|
|
47
|
-
},
|
|
48
|
-
}));
|
|
49
|
-
|
|
50
|
-
vi.mock('@object-ui/components', () => ({
|
|
51
|
-
Badge: ({ children, ...props }: any) => <span {...props}>{children}</span>,
|
|
52
|
-
Card: ({ children, ...props }: any) => <div {...props}>{children}</div>,
|
|
53
|
-
CardHeader: ({ children, ...props }: any) => <div {...props}>{children}</div>,
|
|
54
|
-
CardTitle: ({ children, ...props }: any) => <div {...props}>{children}</div>,
|
|
55
|
-
CardDescription: ({ children, ...props }: any) => <div {...props}>{children}</div>,
|
|
56
|
-
CardContent: ({ children, ...props }: any) => <div {...props}>{children}</div>,
|
|
57
|
-
ScrollArea: ({ children, ...props }: any) => <div {...props}>{children}</div>,
|
|
58
|
-
Button: ({ children, ...props }: any) => <button {...props}>{children}</button>,
|
|
59
|
-
Input: (props: any) => <input {...props} />,
|
|
60
|
-
Skeleton: ({ className }: any) => <div data-testid="skeleton" className={className} />,
|
|
61
|
-
NavigationOverlay: ({ children, selectedRecord }: any) => (
|
|
62
|
-
selectedRecord ? <div data-testid="navigation-overlay">{children(selectedRecord)}</div> : null
|
|
63
|
-
),
|
|
64
|
-
}));
|
|
65
|
-
|
|
66
|
-
vi.mock('@object-ui/react', () => ({
|
|
67
|
-
useHasDndProvider: () => false,
|
|
68
|
-
useDnd: () => ({
|
|
69
|
-
startDrag: vi.fn(),
|
|
70
|
-
endDrag: vi.fn(),
|
|
71
|
-
}),
|
|
72
|
-
useDataScope: () => undefined,
|
|
73
|
-
useNavigationOverlay: () => ({
|
|
74
|
-
isOverlay: false,
|
|
75
|
-
handleClick: vi.fn(),
|
|
76
|
-
selectedRecord: null,
|
|
77
|
-
isOpen: false,
|
|
78
|
-
close: vi.fn(),
|
|
79
|
-
setIsOpen: vi.fn(),
|
|
80
|
-
mode: 'page' as const,
|
|
81
|
-
}),
|
|
82
|
-
}));
|
|
83
|
-
|
|
84
|
-
vi.mock('lucide-react', () => ({
|
|
85
|
-
Plus: () => <span>+</span>,
|
|
86
|
-
}));
|
|
87
|
-
|
|
88
|
-
// Import KanbanBoard (the impl) directly to avoid lazy-loading issues in tests
|
|
89
|
-
import KanbanBoard from '../KanbanImpl';
|
|
90
|
-
|
|
91
|
-
const mockColumns = [
|
|
92
|
-
{
|
|
93
|
-
id: 'todo',
|
|
94
|
-
title: 'To Do',
|
|
95
|
-
cards: [
|
|
96
|
-
{ id: 'c1', title: 'Task 1', priority: 'High', team: 'Frontend' },
|
|
97
|
-
{ id: 'c2', title: 'Task 2', priority: 'Low', team: 'Backend' },
|
|
98
|
-
],
|
|
99
|
-
},
|
|
100
|
-
{
|
|
101
|
-
id: 'done',
|
|
102
|
-
title: 'Done',
|
|
103
|
-
cards: [
|
|
104
|
-
{ id: 'c3', title: 'Task 3', priority: 'High', team: 'Frontend' },
|
|
105
|
-
{ id: 'c4', title: 'Task 4', priority: 'Medium', team: 'Backend' },
|
|
106
|
-
],
|
|
107
|
-
},
|
|
108
|
-
];
|
|
109
|
-
|
|
110
|
-
describe('ObjectKanban grouping config → swimlaneField mapping', () => {
|
|
111
|
-
it('uses grouping field as swimlane when passed to KanbanImpl', () => {
|
|
112
|
-
// This simulates what ObjectKanban does: map grouping.fields[0].field to swimlaneField
|
|
113
|
-
render(<KanbanBoard columns={mockColumns} swimlaneField="team" />);
|
|
114
|
-
|
|
115
|
-
// Swimlane layout should render
|
|
116
|
-
expect(screen.getByRole('region', { name: 'Kanban board with swimlanes' })).toBeInTheDocument();
|
|
117
|
-
|
|
118
|
-
// Swimlane headers for each team
|
|
119
|
-
expect(screen.getByText('Backend')).toBeInTheDocument();
|
|
120
|
-
expect(screen.getByText('Frontend')).toBeInTheDocument();
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
it('renders flat kanban when no swimlane/grouping is provided', () => {
|
|
124
|
-
render(<KanbanBoard columns={mockColumns} />);
|
|
125
|
-
|
|
126
|
-
// Flat layout renders "Kanban board"
|
|
127
|
-
expect(screen.getByRole('region', { name: 'Kanban board' })).toBeInTheDocument();
|
|
128
|
-
expect(screen.queryByRole('region', { name: 'Kanban board with swimlanes' })).not.toBeInTheDocument();
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
describe('ObjectKanban swimlaneField resolution logic', () => {
|
|
132
|
-
// Test the resolution logic independently (same as ObjectKanban.tsx effectiveSwimlaneField)
|
|
133
|
-
function resolveEffectiveSwimlaneField(
|
|
134
|
-
swimlaneField?: string,
|
|
135
|
-
grouping?: { fields: Array<{ field: string; order: string; collapsed: boolean }> },
|
|
136
|
-
): string | undefined {
|
|
137
|
-
return swimlaneField || grouping?.fields?.[0]?.field;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
it('prefers explicit swimlaneField over grouping', () => {
|
|
141
|
-
const result = resolveEffectiveSwimlaneField('priority', {
|
|
142
|
-
fields: [{ field: 'team', order: 'asc', collapsed: false }],
|
|
143
|
-
});
|
|
144
|
-
expect(result).toBe('priority');
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
it('falls back to grouping.fields[0].field when no swimlaneField', () => {
|
|
148
|
-
const result = resolveEffectiveSwimlaneField(undefined, {
|
|
149
|
-
fields: [{ field: 'team', order: 'asc', collapsed: false }],
|
|
150
|
-
});
|
|
151
|
-
expect(result).toBe('team');
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
it('returns undefined when neither swimlaneField nor grouping is set', () => {
|
|
155
|
-
const result = resolveEffectiveSwimlaneField(undefined, undefined);
|
|
156
|
-
expect(result).toBeUndefined();
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
it('returns undefined when grouping has empty fields array', () => {
|
|
160
|
-
const result = resolveEffectiveSwimlaneField(undefined, { fields: [] });
|
|
161
|
-
expect(result).toBeUndefined();
|
|
162
|
-
});
|
|
163
|
-
});
|
|
164
|
-
});
|
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ObjectUI
|
|
3
|
-
* Copyright (c) 2024-present ObjectStack Inc.
|
|
4
|
-
*
|
|
5
|
-
* This source code is licensed under the MIT license found in the
|
|
6
|
-
* LICENSE file in the root directory of this source tree.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
10
|
-
import { render, screen, fireEvent } from '@testing-library/react';
|
|
11
|
-
import React from 'react';
|
|
12
|
-
|
|
13
|
-
// Mock @dnd-kit/core and utilities
|
|
14
|
-
vi.mock('@dnd-kit/core', () => ({
|
|
15
|
-
DndContext: ({ children }: any) => <div data-testid="dnd-context">{children}</div>,
|
|
16
|
-
DragOverlay: ({ children }: any) => <div data-testid="drag-overlay">{children}</div>,
|
|
17
|
-
PointerSensor: vi.fn(),
|
|
18
|
-
TouchSensor: vi.fn(),
|
|
19
|
-
useSensor: vi.fn(),
|
|
20
|
-
useSensors: () => [],
|
|
21
|
-
closestCorners: vi.fn(),
|
|
22
|
-
}));
|
|
23
|
-
|
|
24
|
-
vi.mock('@dnd-kit/sortable', () => ({
|
|
25
|
-
SortableContext: ({ children }: any) => <div data-testid="sortable-context">{children}</div>,
|
|
26
|
-
useSortable: () => ({
|
|
27
|
-
attributes: {},
|
|
28
|
-
listeners: {},
|
|
29
|
-
setNodeRef: vi.fn(),
|
|
30
|
-
transform: null,
|
|
31
|
-
transition: null,
|
|
32
|
-
isDragging: false,
|
|
33
|
-
}),
|
|
34
|
-
arrayMove: (array: any[], from: number, to: number) => {
|
|
35
|
-
const newArray = [...array];
|
|
36
|
-
newArray.splice(to, 0, newArray.splice(from, 1)[0]);
|
|
37
|
-
return newArray;
|
|
38
|
-
},
|
|
39
|
-
verticalListSortingStrategy: vi.fn(),
|
|
40
|
-
}));
|
|
41
|
-
|
|
42
|
-
vi.mock('@dnd-kit/utilities', () => ({
|
|
43
|
-
CSS: {
|
|
44
|
-
Transform: {
|
|
45
|
-
toString: () => '',
|
|
46
|
-
},
|
|
47
|
-
},
|
|
48
|
-
}));
|
|
49
|
-
|
|
50
|
-
vi.mock('@object-ui/components', () => ({
|
|
51
|
-
Badge: ({ children, ...props }: any) => <span {...props}>{children}</span>,
|
|
52
|
-
Card: ({ children, ...props }: any) => <div {...props}>{children}</div>,
|
|
53
|
-
CardHeader: ({ children, ...props }: any) => <div {...props}>{children}</div>,
|
|
54
|
-
CardTitle: ({ children, ...props }: any) => <div {...props}>{children}</div>,
|
|
55
|
-
CardDescription: ({ children, ...props }: any) => <div {...props}>{children}</div>,
|
|
56
|
-
CardContent: ({ children, ...props }: any) => <div {...props}>{children}</div>,
|
|
57
|
-
ScrollArea: ({ children, ...props }: any) => <div {...props}>{children}</div>,
|
|
58
|
-
Button: ({ children, ...props }: any) => <button {...props}>{children}</button>,
|
|
59
|
-
Input: (props: any) => <input {...props} />,
|
|
60
|
-
}));
|
|
61
|
-
|
|
62
|
-
vi.mock('@object-ui/react', () => ({
|
|
63
|
-
useHasDndProvider: () => false,
|
|
64
|
-
useDnd: () => ({
|
|
65
|
-
startDrag: vi.fn(),
|
|
66
|
-
endDrag: vi.fn(),
|
|
67
|
-
}),
|
|
68
|
-
}));
|
|
69
|
-
|
|
70
|
-
vi.mock('lucide-react', () => ({
|
|
71
|
-
Plus: () => <span>+</span>,
|
|
72
|
-
}));
|
|
73
|
-
|
|
74
|
-
import KanbanBoard from '../KanbanImpl';
|
|
75
|
-
|
|
76
|
-
const mockColumns = [
|
|
77
|
-
{
|
|
78
|
-
id: 'todo',
|
|
79
|
-
title: 'To Do',
|
|
80
|
-
cards: [
|
|
81
|
-
{ id: 'c1', title: 'Task 1', priority: 'High', category: 'Frontend' },
|
|
82
|
-
{ id: 'c2', title: 'Task 2', priority: 'Low', category: 'Backend' },
|
|
83
|
-
],
|
|
84
|
-
},
|
|
85
|
-
{
|
|
86
|
-
id: 'done',
|
|
87
|
-
title: 'Done',
|
|
88
|
-
cards: [
|
|
89
|
-
{ id: 'c3', title: 'Task 3', priority: 'High', category: 'Frontend' },
|
|
90
|
-
{ id: 'c4', title: 'Task 4', priority: 'Medium', category: 'Backend' },
|
|
91
|
-
],
|
|
92
|
-
},
|
|
93
|
-
];
|
|
94
|
-
|
|
95
|
-
describe('KanbanSwimlanes', () => {
|
|
96
|
-
describe('without swimlaneField', () => {
|
|
97
|
-
it('renders the standard flat layout with no swimlane headers', () => {
|
|
98
|
-
render(<KanbanBoard columns={mockColumns} />);
|
|
99
|
-
|
|
100
|
-
// Flat layout renders a region labelled "Kanban board"
|
|
101
|
-
expect(screen.getByRole('region', { name: 'Kanban board' })).toBeInTheDocument();
|
|
102
|
-
|
|
103
|
-
// Column titles are visible
|
|
104
|
-
expect(screen.getByText('To Do')).toBeInTheDocument();
|
|
105
|
-
expect(screen.getByText('Done')).toBeInTheDocument();
|
|
106
|
-
|
|
107
|
-
// All cards are visible
|
|
108
|
-
expect(screen.getByText('Task 1')).toBeInTheDocument();
|
|
109
|
-
expect(screen.getByText('Task 2')).toBeInTheDocument();
|
|
110
|
-
expect(screen.getByText('Task 3')).toBeInTheDocument();
|
|
111
|
-
expect(screen.getByText('Task 4')).toBeInTheDocument();
|
|
112
|
-
|
|
113
|
-
// No swimlane region
|
|
114
|
-
expect(screen.queryByRole('region', { name: 'Kanban board with swimlanes' })).not.toBeInTheDocument();
|
|
115
|
-
});
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
describe('with swimlaneField', () => {
|
|
119
|
-
it('renders swimlane row headers', () => {
|
|
120
|
-
render(<KanbanBoard columns={mockColumns} swimlaneField="category" />);
|
|
121
|
-
|
|
122
|
-
// Swimlane layout renders a region labelled "Kanban board with swimlanes"
|
|
123
|
-
expect(screen.getByRole('region', { name: 'Kanban board with swimlanes' })).toBeInTheDocument();
|
|
124
|
-
|
|
125
|
-
// Swimlane headers for each unique category value (sorted)
|
|
126
|
-
expect(screen.getByText('Backend')).toBeInTheDocument();
|
|
127
|
-
expect(screen.getByText('Frontend')).toBeInTheDocument();
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
it('renders collapsible swimlane rows with aria-expanded', () => {
|
|
131
|
-
render(<KanbanBoard columns={mockColumns} swimlaneField="category" />);
|
|
132
|
-
|
|
133
|
-
// Each swimlane header is a button with aria-expanded
|
|
134
|
-
const swimlaneButtons = screen.getAllByRole('button', { expanded: true });
|
|
135
|
-
expect(swimlaneButtons.length).toBe(2);
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
it('collapses a swimlane and hides its cards', () => {
|
|
139
|
-
render(<KanbanBoard columns={mockColumns} swimlaneField="category" />);
|
|
140
|
-
|
|
141
|
-
// Frontend cards are visible initially
|
|
142
|
-
expect(screen.getByText('Task 1')).toBeInTheDocument();
|
|
143
|
-
expect(screen.getByText('Task 3')).toBeInTheDocument();
|
|
144
|
-
|
|
145
|
-
// Click the "Frontend" swimlane toggle to collapse it
|
|
146
|
-
const frontendBtn = screen.getByRole('button', { name: /Frontend/i });
|
|
147
|
-
fireEvent.click(frontendBtn);
|
|
148
|
-
|
|
149
|
-
// After collapse, Frontend cards should not be visible
|
|
150
|
-
// (Task 1 and Task 3 belong to Frontend)
|
|
151
|
-
expect(screen.queryByText('Task 1')).not.toBeInTheDocument();
|
|
152
|
-
expect(screen.queryByText('Task 3')).not.toBeInTheDocument();
|
|
153
|
-
|
|
154
|
-
// Backend cards should still be visible
|
|
155
|
-
expect(screen.getByText('Task 2')).toBeInTheDocument();
|
|
156
|
-
expect(screen.getByText('Task 4')).toBeInTheDocument();
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
it('expands a collapsed swimlane and shows its cards again', () => {
|
|
160
|
-
render(<KanbanBoard columns={mockColumns} swimlaneField="category" />);
|
|
161
|
-
|
|
162
|
-
const backendBtn = screen.getByRole('button', { name: /Backend/i });
|
|
163
|
-
|
|
164
|
-
// Collapse Backend
|
|
165
|
-
fireEvent.click(backendBtn);
|
|
166
|
-
expect(screen.queryByText('Task 2')).not.toBeInTheDocument();
|
|
167
|
-
expect(screen.queryByText('Task 4')).not.toBeInTheDocument();
|
|
168
|
-
|
|
169
|
-
// Expand Backend
|
|
170
|
-
fireEvent.click(backendBtn);
|
|
171
|
-
expect(screen.getByText('Task 2')).toBeInTheDocument();
|
|
172
|
-
expect(screen.getByText('Task 4')).toBeInTheDocument();
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
it('shows correct card counts in swimlane headers', () => {
|
|
176
|
-
render(<KanbanBoard columns={mockColumns} swimlaneField="category" />);
|
|
177
|
-
|
|
178
|
-
// Each swimlane header button contains the lane name and card count
|
|
179
|
-
const backendBtn = screen.getByRole('button', { name: /Backend/i });
|
|
180
|
-
expect(backendBtn.textContent).toContain('(2)');
|
|
181
|
-
|
|
182
|
-
const frontendBtn = screen.getByRole('button', { name: /Frontend/i });
|
|
183
|
-
expect(frontendBtn.textContent).toContain('(2)');
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
it('shows column headers above swimlane rows', () => {
|
|
187
|
-
render(<KanbanBoard columns={mockColumns} swimlaneField="category" />);
|
|
188
|
-
|
|
189
|
-
// Column titles still appear in the column headers
|
|
190
|
-
expect(screen.getByText('To Do')).toBeInTheDocument();
|
|
191
|
-
expect(screen.getByText('Done')).toBeInTheDocument();
|
|
192
|
-
});
|
|
193
|
-
});
|
|
194
|
-
});
|