@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.
Files changed (39) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +24 -0
  3. package/dist/{KanbanEnhanced-TdUe0kQH.js → KanbanEnhanced-Do9ZB1Mh.js} +35 -32
  4. package/dist/{KanbanImpl-BtlPa7GE.js → KanbanImpl-BdocXM5T.js} +1 -1
  5. package/dist/{chevron-down-B6UH8BbF.js → chevron-down-C0JUlGjk.js} +1 -1
  6. package/dist/index.js +3 -3
  7. package/dist/index.umd.cjs +2 -2
  8. package/dist/{plus-BTqoaaEC.js → plus-CHsXVJSY.js} +1 -1
  9. package/package.json +34 -11
  10. package/.turbo/turbo-build.log +0 -32
  11. package/src/CardTemplates.tsx +0 -123
  12. package/src/InlineQuickAdd.tsx +0 -189
  13. package/src/KanbanEnhanced.tsx +0 -525
  14. package/src/KanbanImpl.tsx +0 -597
  15. package/src/ObjectKanban.EdgeCases.stories.tsx +0 -168
  16. package/src/ObjectKanban.msw.test.tsx +0 -95
  17. package/src/ObjectKanban.stories.tsx +0 -152
  18. package/src/ObjectKanban.tsx +0 -276
  19. package/src/__tests__/KanbanEnhanced.test.tsx +0 -260
  20. package/src/__tests__/KanbanGrouping.test.tsx +0 -164
  21. package/src/__tests__/KanbanSwimlanes.test.tsx +0 -194
  22. package/src/__tests__/ObjectKanbanTitle.test.tsx +0 -93
  23. package/src/__tests__/SwimlanePersistence.test.tsx +0 -159
  24. package/src/__tests__/accessibility.test.tsx +0 -296
  25. package/src/__tests__/dnd-undo-integration.test.tsx +0 -525
  26. package/src/__tests__/performance-benchmark.test.tsx +0 -306
  27. package/src/__tests__/phase13-features.test.tsx +0 -387
  28. package/src/__tests__/view-states.test.tsx +0 -403
  29. package/src/index.test.ts +0 -112
  30. package/src/index.tsx +0 -327
  31. package/src/registration.test.tsx +0 -26
  32. package/src/types.ts +0 -185
  33. package/src/useColumnWidths.ts +0 -125
  34. package/src/useCrossSwimlaneMove.ts +0 -116
  35. package/src/useQuickAddReorder.ts +0 -107
  36. package/tsconfig.json +0 -19
  37. package/vite.config.ts +0 -62
  38. package/vitest.config.ts +0 -12
  39. package/vitest.setup.ts +0 -1
@@ -1,403 +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
- * P3.3 Plugin View Robustness - Kanban View States
11
- *
12
- * Tests empty, populated, and edge-case states for KanbanEnhanced component.
13
- */
14
-
15
- import { describe, it, expect, vi } from 'vitest';
16
- import { render, screen } from '@testing-library/react';
17
- import '@testing-library/jest-dom';
18
- import React from 'react';
19
- import { KanbanEnhanced, type KanbanColumn } from '../KanbanEnhanced';
20
- import KanbanBoard from '../KanbanImpl';
21
-
22
- // Mock @tanstack/react-virtual
23
- vi.mock('@tanstack/react-virtual', () => ({
24
- useVirtualizer: () => ({
25
- getTotalSize: () => 1000,
26
- getVirtualItems: () => [],
27
- measureElement: vi.fn(),
28
- }),
29
- }));
30
-
31
- // Mock @dnd-kit/core
32
- vi.mock('@dnd-kit/core', () => ({
33
- DndContext: ({ children }: any) => <div data-testid="dnd-context">{children}</div>,
34
- DragOverlay: ({ children }: any) => <div data-testid="drag-overlay">{children}</div>,
35
- PointerSensor: vi.fn(),
36
- TouchSensor: vi.fn(),
37
- useSensor: vi.fn(),
38
- useSensors: () => [],
39
- closestCorners: vi.fn(),
40
- }));
41
-
42
- vi.mock('@dnd-kit/sortable', () => ({
43
- SortableContext: ({ children }: any) => <div data-testid="sortable-context">{children}</div>,
44
- useSortable: () => ({
45
- attributes: {},
46
- listeners: {},
47
- setNodeRef: vi.fn(),
48
- transform: null,
49
- transition: null,
50
- isDragging: false,
51
- }),
52
- arrayMove: (array: any[], from: number, to: number) => {
53
- const newArray = [...array];
54
- newArray.splice(to, 0, newArray.splice(from, 1)[0]);
55
- return newArray;
56
- },
57
- verticalListSortingStrategy: vi.fn(),
58
- }));
59
-
60
- vi.mock('@dnd-kit/utilities', () => ({
61
- CSS: {
62
- Transform: {
63
- toString: () => '',
64
- },
65
- },
66
- }));
67
-
68
- vi.mock('@object-ui/react', () => ({
69
- useHasDndProvider: () => false,
70
- useDnd: vi.fn(),
71
- }));
72
-
73
- describe('P3.3 Kanban View States', () => {
74
- // ---------------------------------------------------------------
75
- // Empty state
76
- // ---------------------------------------------------------------
77
- describe('empty state', () => {
78
- it('renders with empty columns array', () => {
79
- const { container } = render(
80
- <KanbanEnhanced columns={[]} />
81
- );
82
- expect(container.firstElementChild).toBeInTheDocument();
83
- });
84
-
85
- it('renders columns with no cards', () => {
86
- const emptyColumns: KanbanColumn[] = [
87
- { id: 'todo', title: 'To Do', cards: [] },
88
- { id: 'done', title: 'Done', cards: [] },
89
- ];
90
- render(<KanbanEnhanced columns={emptyColumns} />);
91
- expect(screen.getByText('To Do')).toBeInTheDocument();
92
- expect(screen.getByText('Done')).toBeInTheDocument();
93
- });
94
-
95
- it('shows 0 count for empty columns', () => {
96
- const emptyColumns: KanbanColumn[] = [
97
- { id: 'todo', title: 'To Do', cards: [] },
98
- ];
99
- render(<KanbanEnhanced columns={emptyColumns} />);
100
- expect(screen.getByText('0')).toBeInTheDocument();
101
- });
102
- });
103
-
104
- // ---------------------------------------------------------------
105
- // Populated state
106
- // ---------------------------------------------------------------
107
- describe('populated state', () => {
108
- const populatedColumns: KanbanColumn[] = [
109
- {
110
- id: 'todo',
111
- title: 'To Do',
112
- cards: [
113
- { id: 'c1', title: 'Task 1', description: 'First task' },
114
- { id: 'c2', title: 'Task 2' },
115
- ],
116
- },
117
- {
118
- id: 'done',
119
- title: 'Done',
120
- cards: [
121
- { id: 'c3', title: 'Task 3', description: 'Completed' },
122
- ],
123
- },
124
- ];
125
-
126
- it('renders column titles', () => {
127
- render(<KanbanEnhanced columns={populatedColumns} />);
128
- expect(screen.getByText('To Do')).toBeInTheDocument();
129
- expect(screen.getByText('Done')).toBeInTheDocument();
130
- });
131
-
132
- it('renders card titles', () => {
133
- render(<KanbanEnhanced columns={populatedColumns} />);
134
- expect(screen.getByText('Task 1')).toBeInTheDocument();
135
- expect(screen.getByText('Task 2')).toBeInTheDocument();
136
- expect(screen.getByText('Task 3')).toBeInTheDocument();
137
- });
138
-
139
- it('renders card descriptions', () => {
140
- render(<KanbanEnhanced columns={populatedColumns} />);
141
- expect(screen.getByText('First task')).toBeInTheDocument();
142
- expect(screen.getByText('Completed')).toBeInTheDocument();
143
- });
144
-
145
- it('renders card counts', () => {
146
- render(<KanbanEnhanced columns={populatedColumns} />);
147
- expect(screen.getByText('2')).toBeInTheDocument(); // To Do has 2
148
- expect(screen.getByText('1')).toBeInTheDocument(); // Done has 1
149
- });
150
- });
151
-
152
- // ---------------------------------------------------------------
153
- // Columns with limits
154
- // ---------------------------------------------------------------
155
- describe('columns with limits', () => {
156
- it('shows limit indicator', () => {
157
- const columns: KanbanColumn[] = [
158
- {
159
- id: 'wip',
160
- title: 'In Progress',
161
- limit: 3,
162
- cards: [
163
- { id: 'c1', title: 'Task 1' },
164
- ],
165
- },
166
- ];
167
- render(<KanbanEnhanced columns={columns} />);
168
- expect(screen.getByText('In Progress')).toBeInTheDocument();
169
- expect(screen.getByText(/1\s*\/\s*3/)).toBeInTheDocument();
170
- });
171
-
172
- it('shows warning at 80% capacity', () => {
173
- const columns: KanbanColumn[] = [
174
- {
175
- id: 'wip',
176
- title: 'WIP',
177
- limit: 5,
178
- cards: Array.from({ length: 4 }, (_, i) => ({
179
- id: `c${i}`,
180
- title: `Task ${i}`,
181
- })),
182
- },
183
- ];
184
- const { container } = render(<KanbanEnhanced columns={columns} />);
185
- // At 80% (4/5), should show warning styling
186
- expect(container.querySelector('[class*="text-yellow"]')).toBeInTheDocument();
187
- });
188
-
189
- it('shows error when over limit', () => {
190
- const columns: KanbanColumn[] = [
191
- {
192
- id: 'wip',
193
- title: 'WIP',
194
- limit: 2,
195
- cards: Array.from({ length: 3 }, (_, i) => ({
196
- id: `c${i}`,
197
- title: `Task ${i}`,
198
- })),
199
- },
200
- ];
201
- const { container } = render(<KanbanEnhanced columns={columns} />);
202
- // Over limit uses text-destructive class
203
- expect(container.querySelector('[class*="text-destructive"]')).toBeInTheDocument();
204
- });
205
- });
206
-
207
- // ---------------------------------------------------------------
208
- // Edge cases
209
- // ---------------------------------------------------------------
210
- describe('edge cases', () => {
211
- it('handles single column', () => {
212
- const columns: KanbanColumn[] = [
213
- { id: 'only', title: 'Only Column', cards: [{ id: 'c1', title: 'Alone' }] },
214
- ];
215
- render(<KanbanEnhanced columns={columns} />);
216
- expect(screen.getByText('Only Column')).toBeInTheDocument();
217
- expect(screen.getByText('Alone')).toBeInTheDocument();
218
- });
219
-
220
- it('handles many columns', () => {
221
- const columns: KanbanColumn[] = Array.from({ length: 10 }, (_, i) => ({
222
- id: `col-${i}`,
223
- title: `Column ${i}`,
224
- cards: [],
225
- }));
226
- render(<KanbanEnhanced columns={columns} />);
227
- expect(screen.getByText('Column 0')).toBeInTheDocument();
228
- expect(screen.getByText('Column 9')).toBeInTheDocument();
229
- });
230
-
231
- it('handles cards with badges', () => {
232
- const columns: KanbanColumn[] = [
233
- {
234
- id: 'todo',
235
- title: 'To Do',
236
- cards: [
237
- {
238
- id: 'c1',
239
- title: 'Badged Task',
240
- badges: [
241
- { label: 'Urgent', variant: 'destructive' },
242
- { label: 'Feature', variant: 'default' },
243
- ],
244
- },
245
- ],
246
- },
247
- ];
248
- render(<KanbanEnhanced columns={columns} />);
249
- expect(screen.getByText('Urgent')).toBeInTheDocument();
250
- expect(screen.getByText('Feature')).toBeInTheDocument();
251
- });
252
-
253
- it('handles card with empty title', () => {
254
- const columns: KanbanColumn[] = [
255
- { id: 'col', title: 'Col', cards: [{ id: 'c1', title: '' }] },
256
- ];
257
- const { container } = render(<KanbanEnhanced columns={columns} />);
258
- expect(container).toBeInTheDocument();
259
- });
260
-
261
- it('accepts className prop', () => {
262
- const { container } = render(
263
- <KanbanEnhanced columns={[]} className="my-kanban" />
264
- );
265
- expect(container.innerHTML).toContain('my-kanban');
266
- });
267
- });
268
- });
269
-
270
- // ---------------------------------------------------------------
271
- // KanbanBoard (from KanbanImpl) View States
272
- // ---------------------------------------------------------------
273
- describe('P3.3 KanbanBoard (KanbanImpl) View States', () => {
274
- describe('empty state', () => {
275
- it('renders with empty columns array', () => {
276
- const { container } = render(<KanbanBoard columns={[]} />);
277
- expect(container.querySelector('[role="region"]')).toBeInTheDocument();
278
- });
279
-
280
- it('renders columns with no cards', () => {
281
- const columns: KanbanColumn[] = [
282
- { id: 'backlog', title: 'Backlog', cards: [] },
283
- { id: 'active', title: 'Active', cards: [] },
284
- ];
285
- render(<KanbanBoard columns={columns} />);
286
- expect(screen.getByText('Backlog')).toBeInTheDocument();
287
- expect(screen.getByText('Active')).toBeInTheDocument();
288
- });
289
-
290
- it('shows card count of 0 for empty columns', () => {
291
- const columns: KanbanColumn[] = [
292
- { id: 'empty', title: 'Empty Col', cards: [] },
293
- ];
294
- render(<KanbanBoard columns={columns} />);
295
- expect(screen.getByText('0')).toBeInTheDocument();
296
- });
297
- });
298
-
299
- describe('single column with many cards', () => {
300
- it('renders column with 50 cards without crashing', () => {
301
- const manyCards = Array.from({ length: 50 }, (_, i) => ({
302
- id: `card-${i}`,
303
- title: `Card ${i}`,
304
- }));
305
- const columns: KanbanColumn[] = [
306
- { id: 'big', title: 'Big Column', cards: manyCards },
307
- ];
308
- render(<KanbanBoard columns={columns} />);
309
- expect(screen.getByText('Big Column')).toBeInTheDocument();
310
- expect(screen.getByText('Card 0')).toBeInTheDocument();
311
- expect(screen.getByText('Card 49')).toBeInTheDocument();
312
- });
313
-
314
- it('shows correct count for many cards', () => {
315
- const manyCards = Array.from({ length: 25 }, (_, i) => ({
316
- id: `card-${i}`,
317
- title: `Task ${i}`,
318
- }));
319
- const columns: KanbanColumn[] = [
320
- { id: 'col', title: 'Tasks', cards: manyCards },
321
- ];
322
- render(<KanbanBoard columns={columns} />);
323
- expect(screen.getByText('25')).toBeInTheDocument();
324
- });
325
- });
326
-
327
- describe('WIP limit exceeded', () => {
328
- it('shows Full badge when cards reach limit', () => {
329
- const columns: KanbanColumn[] = [
330
- {
331
- id: 'wip',
332
- title: 'In Progress',
333
- limit: 3,
334
- cards: Array.from({ length: 3 }, (_, i) => ({
335
- id: `c${i}`,
336
- title: `WIP Task ${i}`,
337
- })),
338
- },
339
- ];
340
- render(<KanbanBoard columns={columns} />);
341
- expect(screen.getByText('Full')).toBeInTheDocument();
342
- });
343
-
344
- it('shows Full badge when cards exceed limit', () => {
345
- const columns: KanbanColumn[] = [
346
- {
347
- id: 'wip',
348
- title: 'In Progress',
349
- limit: 2,
350
- cards: Array.from({ length: 5 }, (_, i) => ({
351
- id: `c${i}`,
352
- title: `Over Task ${i}`,
353
- })),
354
- },
355
- ];
356
- render(<KanbanBoard columns={columns} />);
357
- expect(screen.getByText('Full')).toBeInTheDocument();
358
- });
359
-
360
- it('does not show Full badge when under limit', () => {
361
- const columns: KanbanColumn[] = [
362
- {
363
- id: 'wip',
364
- title: 'In Progress',
365
- limit: 10,
366
- cards: [{ id: 'c1', title: 'Solo Task' }],
367
- },
368
- ];
369
- render(<KanbanBoard columns={columns} />);
370
- expect(screen.queryByText('Full')).not.toBeInTheDocument();
371
- });
372
-
373
- it('shows count with limit format', () => {
374
- const columns: KanbanColumn[] = [
375
- {
376
- id: 'wip',
377
- title: 'WIP',
378
- limit: 5,
379
- cards: Array.from({ length: 3 }, (_, i) => ({
380
- id: `c${i}`,
381
- title: `Task ${i}`,
382
- })),
383
- },
384
- ];
385
- render(<KanbanBoard columns={columns} />);
386
- expect(screen.getByText(/3\s*\/\s*5/)).toBeInTheDocument();
387
- });
388
- });
389
-
390
- describe('className and structure', () => {
391
- it('applies className prop', () => {
392
- const { container } = render(
393
- <KanbanBoard columns={[]} className="custom-board" />
394
- );
395
- expect(container.innerHTML).toContain('custom-board');
396
- });
397
-
398
- it('renders kanban board with region role', () => {
399
- render(<KanbanBoard columns={[]} />);
400
- expect(screen.getByRole('region', { name: 'Kanban board' })).toBeInTheDocument();
401
- });
402
- });
403
- });
package/src/index.test.ts DELETED
@@ -1,112 +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, beforeAll } from 'vitest';
10
- import { ComponentRegistry } from '@object-ui/core';
11
-
12
- describe('Plugin Kanban', () => {
13
- // Import all renderers to register them
14
- beforeAll(async () => {
15
- await import('./index');
16
- }, 15000); // Increase timeout to 15 seconds for async import
17
-
18
- describe('kanban component', () => {
19
- it('should be registered in ComponentRegistry', () => {
20
- const kanbanRenderer = ComponentRegistry.get('kanban-ui');
21
- expect(kanbanRenderer).toBeDefined();
22
- });
23
-
24
- it('should have proper metadata', () => {
25
- const config = ComponentRegistry.getConfig('kanban-ui');
26
- expect(config).toBeDefined();
27
- expect(config?.label).toBe('Kanban Board');
28
- expect(config?.icon).toBe('LayoutDashboard');
29
- expect(config?.category).toBe('plugin');
30
- expect(config?.inputs).toBeDefined();
31
- expect(config?.defaultProps).toBeDefined();
32
- });
33
-
34
- it('should have expected inputs', () => {
35
- const config = ComponentRegistry.getConfig('kanban-ui');
36
- const inputNames = config?.inputs?.map((input: any) => input.name) || [];
37
-
38
- expect(inputNames).toContain('columns');
39
- expect(inputNames).toContain('onCardMove');
40
- expect(inputNames).toContain('className');
41
- });
42
-
43
- it('should have columns as required input', () => {
44
- const config = ComponentRegistry.getConfig('kanban-ui');
45
- const columnsInput = config?.inputs?.find((input: any) => input.name === 'columns');
46
-
47
- expect(columnsInput).toBeDefined();
48
- expect(columnsInput?.required).toBe(true);
49
- expect(columnsInput?.type).toBe('array');
50
- expect(columnsInput?.description).toBeDefined();
51
- });
52
-
53
- it('should have onCardMove as code input', () => {
54
- const config = ComponentRegistry.getConfig('kanban-ui');
55
- const onCardMoveInput = config?.inputs?.find((input: any) => input.name === 'onCardMove');
56
-
57
- expect(onCardMoveInput).toBeDefined();
58
- expect(onCardMoveInput?.type).toBe('code');
59
- expect(onCardMoveInput?.advanced).toBe(true);
60
- expect(onCardMoveInput?.description).toBeDefined();
61
- });
62
-
63
- it('should have sensible default props', () => {
64
- const config = ComponentRegistry.getConfig('kanban-ui');
65
- const defaults = config?.defaultProps;
66
-
67
- expect(defaults).toBeDefined();
68
- expect(defaults?.columns).toBeDefined();
69
- expect(Array.isArray(defaults?.columns)).toBe(true);
70
- expect(defaults?.columns.length).toBeGreaterThan(0);
71
- expect(defaults?.className).toBe('w-full');
72
- });
73
-
74
- it('should have default columns with proper structure', () => {
75
- const config = ComponentRegistry.getConfig('kanban-ui');
76
- const defaults = config?.defaultProps;
77
- const columns = defaults?.columns || [];
78
-
79
- // Verify at least 3 columns exist (todo, in-progress, done)
80
- expect(columns.length).toBeGreaterThanOrEqual(3);
81
-
82
- // Verify each column has required properties
83
- columns.forEach((column: any) => {
84
- expect(column.id).toBeDefined();
85
- expect(column.title).toBeDefined();
86
- expect(column.cards).toBeDefined();
87
- expect(Array.isArray(column.cards)).toBe(true);
88
- });
89
-
90
- // Verify at least one column has cards
91
- const hasCards = columns.some((column: any) => column.cards.length > 0);
92
- expect(hasCards).toBe(true);
93
- });
94
-
95
- it('should have cards with proper structure', () => {
96
- const config = ComponentRegistry.getConfig('kanban-ui');
97
- const defaults = config?.defaultProps;
98
- const columns = defaults?.columns || [];
99
-
100
- // Find a column with cards
101
- const columnWithCards = columns.find((column: any) => column.cards.length > 0);
102
- expect(columnWithCards).toBeDefined();
103
-
104
- const card = columnWithCards.cards[0];
105
- expect(card.id).toBeDefined();
106
- expect(card.title).toBeDefined();
107
- expect(card.description).toBeDefined();
108
- expect(card.badges).toBeDefined();
109
- expect(Array.isArray(card.badges)).toBe(true);
110
- });
111
- });
112
- });