@object-ui/plugin-kanban 3.1.5 → 3.3.1

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 (77) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +24 -0
  3. package/dist/{KanbanEnhanced-CvxO2soF.js → KanbanEnhanced-Do9ZB1Mh.js} +36 -33
  4. package/dist/{KanbanImpl-ii52_k8g.js → KanbanImpl-BdocXM5T.js} +2 -2
  5. package/dist/{chevron-down-DpXJN6OX.js → chevron-down-C0JUlGjk.js} +1 -1
  6. package/dist/index.d.ts +1 -1
  7. package/dist/index.js +35 -26
  8. package/dist/index.umd.cjs +4 -4
  9. package/dist/packages/plugin-kanban/src/CardTemplates.d.ts.map +1 -0
  10. package/dist/packages/plugin-kanban/src/InlineQuickAdd.d.ts.map +1 -0
  11. package/dist/packages/plugin-kanban/src/KanbanEnhanced.d.ts.map +1 -0
  12. package/dist/packages/plugin-kanban/src/KanbanImpl.d.ts.map +1 -0
  13. package/dist/packages/plugin-kanban/src/ObjectKanban.EdgeCases.stories.d.ts.map +1 -0
  14. package/dist/packages/plugin-kanban/src/ObjectKanban.d.ts.map +1 -0
  15. package/dist/packages/plugin-kanban/src/ObjectKanban.stories.d.ts.map +1 -0
  16. package/dist/packages/plugin-kanban/src/index.d.ts.map +1 -0
  17. package/dist/packages/plugin-kanban/src/types.d.ts.map +1 -0
  18. package/dist/packages/plugin-kanban/src/useColumnWidths.d.ts.map +1 -0
  19. package/dist/packages/plugin-kanban/src/useCrossSwimlaneMove.d.ts.map +1 -0
  20. package/dist/packages/plugin-kanban/src/useQuickAddReorder.d.ts.map +1 -0
  21. package/dist/{plus-CAtTu4zt.js → plus-CHsXVJSY.js} +39 -36
  22. package/dist/{sortable.esm-DzUCoMzQ.js → sortable.esm-LJG1TjKd.js} +4 -4
  23. package/package.json +35 -12
  24. package/.turbo/turbo-build.log +0 -32
  25. package/dist/src/CardTemplates.d.ts.map +0 -1
  26. package/dist/src/InlineQuickAdd.d.ts.map +0 -1
  27. package/dist/src/KanbanEnhanced.d.ts.map +0 -1
  28. package/dist/src/KanbanImpl.d.ts.map +0 -1
  29. package/dist/src/ObjectKanban.EdgeCases.stories.d.ts.map +0 -1
  30. package/dist/src/ObjectKanban.d.ts.map +0 -1
  31. package/dist/src/ObjectKanban.stories.d.ts.map +0 -1
  32. package/dist/src/index.d.ts.map +0 -1
  33. package/dist/src/types.d.ts.map +0 -1
  34. package/dist/src/useColumnWidths.d.ts.map +0 -1
  35. package/dist/src/useCrossSwimlaneMove.d.ts.map +0 -1
  36. package/dist/src/useQuickAddReorder.d.ts.map +0 -1
  37. package/src/CardTemplates.tsx +0 -123
  38. package/src/InlineQuickAdd.tsx +0 -189
  39. package/src/KanbanEnhanced.tsx +0 -525
  40. package/src/KanbanImpl.tsx +0 -597
  41. package/src/ObjectKanban.EdgeCases.stories.tsx +0 -168
  42. package/src/ObjectKanban.msw.test.tsx +0 -91
  43. package/src/ObjectKanban.stories.tsx +0 -152
  44. package/src/ObjectKanban.tsx +0 -262
  45. package/src/__tests__/KanbanEnhanced.test.tsx +0 -260
  46. package/src/__tests__/KanbanGrouping.test.tsx +0 -164
  47. package/src/__tests__/KanbanSwimlanes.test.tsx +0 -194
  48. package/src/__tests__/ObjectKanbanTitle.test.tsx +0 -93
  49. package/src/__tests__/SwimlanePersistence.test.tsx +0 -159
  50. package/src/__tests__/accessibility.test.tsx +0 -296
  51. package/src/__tests__/dnd-undo-integration.test.tsx +0 -525
  52. package/src/__tests__/performance-benchmark.test.tsx +0 -306
  53. package/src/__tests__/phase13-features.test.tsx +0 -387
  54. package/src/__tests__/view-states.test.tsx +0 -403
  55. package/src/index.test.ts +0 -112
  56. package/src/index.tsx +0 -327
  57. package/src/registration.test.tsx +0 -26
  58. package/src/types.ts +0 -185
  59. package/src/useColumnWidths.ts +0 -125
  60. package/src/useCrossSwimlaneMove.ts +0 -116
  61. package/src/useQuickAddReorder.ts +0 -107
  62. package/tsconfig.json +0 -19
  63. package/vite.config.ts +0 -61
  64. package/vitest.config.ts +0 -12
  65. package/vitest.setup.ts +0 -1
  66. /package/dist/{src → packages/plugin-kanban/src}/CardTemplates.d.ts +0 -0
  67. /package/dist/{src → packages/plugin-kanban/src}/InlineQuickAdd.d.ts +0 -0
  68. /package/dist/{src → packages/plugin-kanban/src}/KanbanEnhanced.d.ts +0 -0
  69. /package/dist/{src → packages/plugin-kanban/src}/KanbanImpl.d.ts +0 -0
  70. /package/dist/{src → packages/plugin-kanban/src}/ObjectKanban.EdgeCases.stories.d.ts +0 -0
  71. /package/dist/{src → packages/plugin-kanban/src}/ObjectKanban.d.ts +0 -0
  72. /package/dist/{src → packages/plugin-kanban/src}/ObjectKanban.stories.d.ts +0 -0
  73. /package/dist/{src → packages/plugin-kanban/src}/index.d.ts +0 -0
  74. /package/dist/{src → packages/plugin-kanban/src}/types.d.ts +0 -0
  75. /package/dist/{src → packages/plugin-kanban/src}/useColumnWidths.d.ts +0 -0
  76. /package/dist/{src → packages/plugin-kanban/src}/useCrossSwimlaneMove.d.ts +0 -0
  77. /package/dist/{src → packages/plugin-kanban/src}/useQuickAddReorder.d.ts +0 -0
@@ -1,93 +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 { renderHook } from '@testing-library/react';
11
- import { useMemo } from 'react';
12
-
13
- /**
14
- * Tests for the title resolution fallback chain in ObjectKanban.
15
- *
16
- * The effectiveData logic tries fields in this order:
17
- * 1. Explicit cardTitle / titleField from schema
18
- * 2. objectDef.titleFormat (e.g. "{subject}")
19
- * 3. objectDef.NAME_FIELD_KEY
20
- * 4. Fallback chain: name → title → subject → label → display_name
21
- * 5. 'Untitled'
22
- */
23
-
24
- // Extract the title resolution logic from ObjectKanban to test it in isolation
25
- const TITLE_FALLBACK_FIELDS = ['name', 'title', 'subject', 'label', 'display_name'];
26
-
27
- function resolveTitle(item: Record<string, any>, titleField?: string): string {
28
- let resolvedTitle = titleField ? item[titleField] : undefined;
29
-
30
- if (!resolvedTitle) {
31
- for (const field of TITLE_FALLBACK_FIELDS) {
32
- if (item[field]) {
33
- resolvedTitle = item[field];
34
- break;
35
- }
36
- }
37
- }
38
-
39
- return resolvedTitle || 'Untitled';
40
- }
41
-
42
- describe('ObjectKanban title resolution', () => {
43
- it('uses explicit titleField when value exists', () => {
44
- const item = { id: '1', custom_title: 'My Custom Title', name: 'Fallback Name' };
45
- expect(resolveTitle(item, 'custom_title')).toBe('My Custom Title');
46
- });
47
-
48
- it('falls back to common fields when titleField value is empty', () => {
49
- const item = { id: '1', custom_title: '', name: 'Name Field' };
50
- expect(resolveTitle(item, 'custom_title')).toBe('Name Field');
51
- });
52
-
53
- it('resolves name field first in fallback chain', () => {
54
- const item = { id: '1', name: 'Name Value', title: 'Title Value', subject: 'Subject Value' };
55
- expect(resolveTitle(item)).toBe('Name Value');
56
- });
57
-
58
- it('resolves title field second in fallback chain', () => {
59
- const item = { id: '1', title: 'Title Value', subject: 'Subject Value' };
60
- expect(resolveTitle(item)).toBe('Title Value');
61
- });
62
-
63
- it('resolves subject field third in fallback chain', () => {
64
- const item = { id: '1', subject: 'Subject Value', label: 'Label Value' };
65
- expect(resolveTitle(item)).toBe('Subject Value');
66
- });
67
-
68
- it('resolves label field fourth in fallback chain', () => {
69
- const item = { id: '1', label: 'Label Value', display_name: 'Display Name' };
70
- expect(resolveTitle(item)).toBe('Label Value');
71
- });
72
-
73
- it('resolves display_name field fifth in fallback chain', () => {
74
- const item = { id: '1', display_name: 'Display Name' };
75
- expect(resolveTitle(item)).toBe('Display Name');
76
- });
77
-
78
- it('falls back to Untitled when no common fields exist', () => {
79
- const item = { id: '1', status: 'open', priority: 'high' };
80
- expect(resolveTitle(item)).toBe('Untitled');
81
- });
82
-
83
- it('skips falsy field values in fallback chain', () => {
84
- const item = { id: '1', name: '', title: null, subject: 'Bug Report' };
85
- expect(resolveTitle(item)).toBe('Bug Report');
86
- });
87
-
88
- it('handles todo_task objects with subject field', () => {
89
- // This is the exact scenario from the bug report
90
- const todoTask = { id: '1', status: 'in_progress', subject: 'Fix login bug', priority: 'high' };
91
- expect(resolveTitle(todoTask)).toBe('Fix login bug');
92
- });
93
- });
@@ -1,159 +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
- // Mock localStorage
77
- const localStorageMock = (() => {
78
- let store: Record<string, string> = {};
79
- return {
80
- getItem: vi.fn((key: string) => store[key] ?? null),
81
- setItem: vi.fn((key: string, value: string) => { store[key] = value; }),
82
- clear: () => { store = {}; },
83
- removeItem: vi.fn((key: string) => { delete store[key]; }),
84
- };
85
- })();
86
-
87
- Object.defineProperty(window, 'localStorage', { value: localStorageMock });
88
-
89
- const mockColumns = [
90
- {
91
- id: 'todo',
92
- title: 'To Do',
93
- cards: [
94
- { id: 'c1', title: 'Task 1', category: 'Frontend' },
95
- { id: 'c2', title: 'Task 2', category: 'Backend' },
96
- ],
97
- },
98
- {
99
- id: 'done',
100
- title: 'Done',
101
- cards: [
102
- { id: 'c3', title: 'Task 3', category: 'Frontend' },
103
- ],
104
- },
105
- ];
106
-
107
- describe('KanbanBoard swimlane persistence', () => {
108
- beforeEach(() => {
109
- localStorageMock.clear();
110
- localStorageMock.getItem.mockClear();
111
- localStorageMock.setItem.mockClear();
112
- });
113
-
114
- it('reads collapsed lanes from localStorage on mount when swimlaneField is set', () => {
115
- localStorageMock.setItem(
116
- 'objectui:kanban-collapsed:category',
117
- JSON.stringify(['Frontend']),
118
- );
119
- localStorageMock.getItem.mockClear();
120
-
121
- render(<KanbanBoard columns={mockColumns} swimlaneField="category" />);
122
-
123
- expect(localStorageMock.getItem).toHaveBeenCalledWith(
124
- 'objectui:kanban-collapsed:category',
125
- );
126
- });
127
-
128
- it('writes collapsed state to localStorage when a lane is toggled', () => {
129
- render(<KanbanBoard columns={mockColumns} swimlaneField="category" />);
130
-
131
- // Find a swimlane collapse button and click it
132
- const collapseButtons = screen.getAllByRole('button').filter(
133
- btn => btn.getAttribute('aria-label')?.includes('collapse') ||
134
- btn.getAttribute('aria-label')?.includes('Toggle') ||
135
- btn.textContent?.includes('▸') ||
136
- btn.textContent?.includes('▾'),
137
- );
138
-
139
- if (collapseButtons.length > 0) {
140
- fireEvent.click(collapseButtons[0]);
141
- expect(localStorageMock.setItem).toHaveBeenCalled();
142
- const lastCall = localStorageMock.setItem.mock.calls.at(-1);
143
- expect(lastCall?.[0]).toBe('objectui:kanban-collapsed:category');
144
- }
145
- });
146
-
147
- it('does not access localStorage when swimlaneField is not set', () => {
148
- localStorageMock.getItem.mockClear();
149
- localStorageMock.setItem.mockClear();
150
-
151
- render(<KanbanBoard columns={mockColumns} />);
152
-
153
- // No localStorage reads for collapsed state key
154
- const collapsedCalls = localStorageMock.getItem.mock.calls.filter(
155
- ([key]: [string]) => key.startsWith('objectui:kanban-collapsed:'),
156
- );
157
- expect(collapsedCalls).toHaveLength(0);
158
- });
159
- });
@@ -1,296 +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
- /**
10
- * Screen reader experience tests for KanbanEnhanced.
11
- *
12
- * Tests ARIA attributes, roles, landmarks, keyboard navigation,
13
- * and screen reader announcements for the kanban board plugin.
14
- * Part of P2.3 Accessibility & Inclusive Design roadmap.
15
- */
16
-
17
- import { describe, it, expect, vi } from 'vitest';
18
- import { render, screen, fireEvent } from '@testing-library/react';
19
- import '@testing-library/jest-dom';
20
- import React from 'react';
21
- import { KanbanEnhanced, type KanbanColumn } from '../KanbanEnhanced';
22
-
23
- // Mock @tanstack/react-virtual
24
- vi.mock('@tanstack/react-virtual', () => ({
25
- useVirtualizer: () => ({
26
- getTotalSize: () => 1000,
27
- getVirtualItems: () => [],
28
- measureElement: vi.fn(),
29
- }),
30
- }));
31
-
32
- // Mock @dnd-kit/core and utilities
33
- vi.mock('@dnd-kit/core', () => ({
34
- DndContext: ({ children }: any) => <div data-testid="dnd-context">{children}</div>,
35
- DragOverlay: ({ children }: any) => <div data-testid="drag-overlay">{children}</div>,
36
- PointerSensor: vi.fn(),
37
- TouchSensor: vi.fn(),
38
- useSensor: vi.fn(),
39
- useSensors: () => [],
40
- closestCorners: vi.fn(),
41
- }));
42
-
43
- vi.mock('@dnd-kit/sortable', () => ({
44
- SortableContext: ({ children }: any) => <div data-testid="sortable-context">{children}</div>,
45
- useSortable: () => ({
46
- attributes: {},
47
- listeners: {},
48
- setNodeRef: vi.fn(),
49
- transform: null,
50
- transition: null,
51
- isDragging: false,
52
- }),
53
- arrayMove: (array: any[], from: number, to: number) => {
54
- const newArray = [...array];
55
- newArray.splice(to, 0, newArray.splice(from, 1)[0]);
56
- return newArray;
57
- },
58
- verticalListSortingStrategy: vi.fn(),
59
- }));
60
-
61
- vi.mock('@dnd-kit/utilities', () => ({
62
- CSS: {
63
- Transform: {
64
- toString: () => '',
65
- },
66
- },
67
- }));
68
-
69
- const mockColumns: KanbanColumn[] = [
70
- {
71
- id: 'todo',
72
- title: 'To Do',
73
- cards: [
74
- {
75
- id: 'card-1',
76
- title: 'Design Landing Page',
77
- description: 'Create wireframes and mockups',
78
- badges: [{ label: 'High', variant: 'destructive' as const }],
79
- },
80
- {
81
- id: 'card-2',
82
- title: 'Write Documentation',
83
- description: 'API reference and guides',
84
- },
85
- ],
86
- },
87
- {
88
- id: 'in-progress',
89
- title: 'In Progress',
90
- limit: 3,
91
- cards: [
92
- {
93
- id: 'card-3',
94
- title: 'Build Components',
95
- description: 'Implement the design system',
96
- },
97
- ],
98
- },
99
- {
100
- id: 'done',
101
- title: 'Done',
102
- cards: [],
103
- },
104
- ];
105
-
106
- describe('KanbanEnhanced: Screen Reader & Accessibility', () => {
107
- describe('board structure and landmarks', () => {
108
- it('renders all column titles visible for screen readers', () => {
109
- render(<KanbanEnhanced columns={mockColumns} />);
110
-
111
- expect(screen.getByText('To Do')).toBeInTheDocument();
112
- expect(screen.getByText('In Progress')).toBeInTheDocument();
113
- expect(screen.getByText('Done')).toBeInTheDocument();
114
- });
115
-
116
- it('column titles use heading elements for hierarchy', () => {
117
- render(<KanbanEnhanced columns={mockColumns} />);
118
-
119
- const todoTitle = screen.getByText('To Do');
120
- expect(todoTitle.tagName).toBe('H3');
121
- });
122
-
123
- it('renders within a dnd-context container', () => {
124
- const { container } = render(<KanbanEnhanced columns={mockColumns} />);
125
-
126
- const dndContext = container.querySelector('[data-testid="dnd-context"]');
127
- expect(dndContext).toBeInTheDocument();
128
- });
129
- });
130
-
131
- describe('card content accessibility', () => {
132
- it('card titles are visible to screen readers', () => {
133
- render(<KanbanEnhanced columns={mockColumns} />);
134
-
135
- expect(screen.getByText('Design Landing Page')).toBeInTheDocument();
136
- expect(screen.getByText('Write Documentation')).toBeInTheDocument();
137
- expect(screen.getByText('Build Components')).toBeInTheDocument();
138
- });
139
-
140
- it('card descriptions are accessible', () => {
141
- render(<KanbanEnhanced columns={mockColumns} />);
142
-
143
- expect(screen.getByText('Create wireframes and mockups')).toBeInTheDocument();
144
- expect(screen.getByText('API reference and guides')).toBeInTheDocument();
145
- });
146
-
147
- it('card badges convey semantic information', () => {
148
- render(<KanbanEnhanced columns={mockColumns} />);
149
-
150
- const badge = screen.getByText('High');
151
- expect(badge).toBeInTheDocument();
152
- });
153
- });
154
-
155
- describe('column count and limit indicators', () => {
156
- it('displays card count per column for progress tracking', () => {
157
- render(<KanbanEnhanced columns={mockColumns} />);
158
-
159
- // "In Progress" has limit = 3, cards = 1 → shows "1 / 3"
160
- expect(screen.getByText('1 / 3')).toBeInTheDocument();
161
- });
162
-
163
- it('shows warning for columns near capacity', () => {
164
- const nearLimitColumns: KanbanColumn[] = [
165
- {
166
- id: 'limited',
167
- title: 'Limited Column',
168
- limit: 5,
169
- cards: Array(4)
170
- .fill(null)
171
- .map((_, i) => ({
172
- id: `card-${i}`,
173
- title: `Task ${i}`,
174
- })),
175
- },
176
- ];
177
-
178
- const { container } = render(<KanbanEnhanced columns={nearLimitColumns} />);
179
-
180
- // Near-limit indicator uses yellow color
181
- expect(container.querySelector('.text-yellow-500')).toBeTruthy();
182
- });
183
-
184
- it('shows error indicator for columns over limit', () => {
185
- const overLimitColumns: KanbanColumn[] = [
186
- {
187
- id: 'full',
188
- title: 'Full Column',
189
- limit: 2,
190
- cards: [
191
- { id: 'card-1', title: 'Task 1' },
192
- { id: 'card-2', title: 'Task 2' },
193
- ],
194
- },
195
- ];
196
-
197
- const { container } = render(<KanbanEnhanced columns={overLimitColumns} />);
198
-
199
- // Over-limit shows destructive badge "Full"
200
- const fullBadge = container.querySelector('[class*="destructive"]');
201
- expect(fullBadge).toBeTruthy();
202
- });
203
- });
204
-
205
- describe('collapse/expand behavior', () => {
206
- it('columns have toggle buttons for collapse/expand', () => {
207
- render(<KanbanEnhanced columns={mockColumns} />);
208
-
209
- const buttons = screen.getAllByRole('button');
210
- // Each column has at least one toggle button
211
- expect(buttons.length).toBeGreaterThanOrEqual(3);
212
- });
213
-
214
- it('collapsed column shows title in vertical orientation', () => {
215
- const collapsedColumns: KanbanColumn[] = [
216
- {
217
- id: 'collapsed',
218
- title: 'Collapsed Column',
219
- collapsed: true,
220
- cards: [{ id: 'card-1', title: 'Task 1' }],
221
- },
222
- ];
223
-
224
- render(<KanbanEnhanced columns={collapsedColumns} />);
225
-
226
- // Collapsed column still shows title (vertically)
227
- expect(screen.getByText('Collapsed Column')).toBeInTheDocument();
228
- });
229
-
230
- it('toggle button click changes column state', () => {
231
- render(<KanbanEnhanced columns={mockColumns} />);
232
-
233
- // Find the toggle buttons (ghost variant, small)
234
- const buttons = screen.getAllByRole('button');
235
- const toggleButton = buttons[0];
236
-
237
- // Click to collapse
238
- fireEvent.click(toggleButton);
239
-
240
- // The component should still render (no crash)
241
- expect(toggleButton).toBeInTheDocument();
242
- });
243
- });
244
-
245
- describe('drag and drop accessibility', () => {
246
- it('drag overlay container exists for visual feedback', () => {
247
- const { container } = render(<KanbanEnhanced columns={mockColumns} />);
248
-
249
- const overlay = container.querySelector('[data-testid="drag-overlay"]');
250
- expect(overlay).toBeTruthy();
251
- });
252
-
253
- it('sortable context wraps card list for DnD', () => {
254
- const { container } = render(<KanbanEnhanced columns={mockColumns} />);
255
-
256
- const sortableContexts = container.querySelectorAll('[data-testid="sortable-context"]');
257
- // Each non-collapsed column has a sortable context
258
- expect(sortableContexts.length).toBe(3);
259
- });
260
- });
261
-
262
- describe('empty state handling', () => {
263
- it('handles empty columns array without errors', () => {
264
- const { container } = render(<KanbanEnhanced columns={[]} />);
265
- expect(container).toBeTruthy();
266
- });
267
-
268
- it('renders columns with no cards correctly', () => {
269
- const emptyColumns: KanbanColumn[] = [
270
- { id: 'empty', title: 'Empty Column', cards: [] },
271
- ];
272
-
273
- render(<KanbanEnhanced columns={emptyColumns} />);
274
-
275
- expect(screen.getByText('Empty Column')).toBeInTheDocument();
276
- });
277
- });
278
-
279
- describe('visual hierarchy and semantics', () => {
280
- it('card uses Card component with proper structure', () => {
281
- render(<KanbanEnhanced columns={mockColumns} />);
282
-
283
- // Card titles use CardTitle which renders with proper styling
284
- const cardTitle = screen.getByText('Design Landing Page');
285
- expect(cardTitle).toBeInTheDocument();
286
- });
287
-
288
- it('custom className is applied to root container', () => {
289
- const { container } = render(
290
- <KanbanEnhanced columns={mockColumns} className="custom-board" />
291
- );
292
-
293
- expect(container.querySelector('.custom-board')).toBeTruthy();
294
- });
295
- });
296
- });