Skip to content

Commit e38c98b

Browse files
committed
test: Add tests for container types, reordering, and grouping
14 new tests covering schema, reorder, and grouping logic.
1 parent ef081b8 commit e38c98b

3 files changed

Lines changed: 152 additions & 52 deletions

File tree

packages/app/src/DBDashboardPage.tsx

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,6 @@ import { ContactSupportText } from '@/components/ContactSupportText';
8484
import {
8585
SectionDropZone,
8686
SortableSectionWrapper,
87-
TileDragHandle,
8887
UngroupedDropZone,
8988
} from '@/components/DashboardDndComponents';
9089
import {
@@ -665,18 +664,6 @@ const Tile = forwardRef(
665664
onMouseUp={onMouseUp}
666665
onTouchEnd={onTouchEnd}
667666
>
668-
<Group justify="center" py={4}>
669-
{/* Cross-section drag: grip replaces the bar when containers exist */}
670-
{availableSections && availableSections.length > 0 ? (
671-
<TileDragHandle
672-
tileId={chart.id}
673-
tileName={chart.config.name ?? 'Tile'}
674-
containerId={chart.containerId}
675-
/>
676-
) : (
677-
<Box bg={hovered ? 'gray' : undefined} w={100} h={2} />
678-
)}
679-
</Group>
680667
<div
681668
className="fs-7 text-muted flex-grow-1 overflow-hidden cursor-default"
682669
onMouseDown={e => e.stopPropagation()}

packages/app/src/__tests__/dashboardSections.test.tsx

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,56 @@ describe('DashboardContainer schema', () => {
5151
}).success,
5252
).toBe(false);
5353
});
54+
55+
it('validates a tab container', () => {
56+
const result = DashboardContainerSchema.safeParse({
57+
id: 'tab-1',
58+
type: 'tab',
59+
title: 'Overview Tabs',
60+
collapsed: false,
61+
});
62+
expect(result.success).toBe(true);
63+
if (result.success) {
64+
expect(result.data.type).toBe('tab');
65+
}
66+
});
67+
68+
it('validates a group container', () => {
69+
const result = DashboardContainerSchema.safeParse({
70+
id: 'group-1',
71+
type: 'group',
72+
title: 'Key Metrics',
73+
collapsed: false,
74+
});
75+
expect(result.success).toBe(true);
76+
if (result.success) {
77+
expect(result.data.type).toBe('group');
78+
}
79+
});
80+
81+
it('validates a tab container with activeTabId', () => {
82+
const result = DashboardContainerSchema.safeParse({
83+
id: 'tab-1',
84+
type: 'tab',
85+
title: 'Tabs',
86+
collapsed: false,
87+
activeTabId: 'sub-tab-1',
88+
});
89+
expect(result.success).toBe(true);
90+
if (result.success) {
91+
expect(result.data.activeTabId).toBe('sub-tab-1');
92+
}
93+
});
94+
95+
it('rejects an invalid container type', () => {
96+
const result = DashboardContainerSchema.safeParse({
97+
id: 'c-1',
98+
type: 'invalid',
99+
title: 'Bad Type',
100+
collapsed: false,
101+
});
102+
expect(result.success).toBe(false);
103+
});
54104
});
55105

56106
describe('Tile schema with containerId', () => {
@@ -525,4 +575,105 @@ describe('section authoring operations', () => {
525575
expect(result.tiles[0].containerId).toBeUndefined();
526576
});
527577
});
578+
579+
describe('reorder sections', () => {
580+
function reorderSections(
581+
dashboard: SimpleDashboard,
582+
fromIndex: number,
583+
toIndex: number,
584+
) {
585+
if (!dashboard.containers) return dashboard;
586+
const containers = [...dashboard.containers];
587+
const [removed] = containers.splice(fromIndex, 1);
588+
containers.splice(toIndex, 0, removed);
589+
return { ...dashboard, containers };
590+
}
591+
592+
it('moves a section from first to last', () => {
593+
const dashboard: SimpleDashboard = {
594+
tiles: [],
595+
containers: [
596+
{ id: 's1', title: 'First', collapsed: false },
597+
{ id: 's2', title: 'Second', collapsed: false },
598+
{ id: 's3', title: 'Third', collapsed: false },
599+
],
600+
};
601+
const result = reorderSections(dashboard, 0, 2);
602+
expect(result.containers!.map(c => c.id)).toEqual(['s2', 's3', 's1']);
603+
});
604+
605+
it('moves a section from last to first', () => {
606+
const dashboard: SimpleDashboard = {
607+
tiles: [],
608+
containers: [
609+
{ id: 's1', title: 'First', collapsed: false },
610+
{ id: 's2', title: 'Second', collapsed: false },
611+
{ id: 's3', title: 'Third', collapsed: false },
612+
],
613+
};
614+
const result = reorderSections(dashboard, 2, 0);
615+
expect(result.containers!.map(c => c.id)).toEqual(['s3', 's1', 's2']);
616+
});
617+
618+
it('does not affect tiles when sections are reordered', () => {
619+
const dashboard: SimpleDashboard = {
620+
tiles: [
621+
{ id: 'a', containerId: 's1' },
622+
{ id: 'b', containerId: 's2' },
623+
],
624+
containers: [
625+
{ id: 's1', title: 'First', collapsed: false },
626+
{ id: 's2', title: 'Second', collapsed: false },
627+
],
628+
};
629+
const result = reorderSections(dashboard, 0, 1);
630+
expect(result.tiles).toEqual(dashboard.tiles);
631+
expect(result.containers!.map(c => c.id)).toEqual(['s2', 's1']);
632+
});
633+
});
634+
635+
describe('group selected tiles', () => {
636+
function groupTilesIntoSection(
637+
dashboard: SimpleDashboard,
638+
tileIds: string[],
639+
newSection: SimpleSection,
640+
) {
641+
const containers = [...(dashboard.containers ?? []), newSection];
642+
const tiles = dashboard.tiles.map(t =>
643+
tileIds.includes(t.id) ? { ...t, containerId: newSection.id } : t,
644+
);
645+
return { ...dashboard, containers, tiles };
646+
}
647+
648+
it('groups selected tiles into a new section', () => {
649+
const dashboard: SimpleDashboard = {
650+
tiles: [{ id: 'a' }, { id: 'b' }, { id: 'c' }],
651+
};
652+
const result = groupTilesIntoSection(dashboard, ['a', 'c'], {
653+
id: 'new-s',
654+
title: 'New Section',
655+
collapsed: false,
656+
});
657+
expect(result.containers).toHaveLength(1);
658+
expect(result.tiles.find(t => t.id === 'a')?.containerId).toBe('new-s');
659+
expect(result.tiles.find(t => t.id === 'b')?.containerId).toBeUndefined();
660+
expect(result.tiles.find(t => t.id === 'c')?.containerId).toBe('new-s');
661+
});
662+
663+
it('preserves existing sections when grouping', () => {
664+
const dashboard: SimpleDashboard = {
665+
tiles: [{ id: 'a', containerId: 's1' }, { id: 'b' }, { id: 'c' }],
666+
containers: [{ id: 's1', title: 'Existing', collapsed: false }],
667+
};
668+
const result = groupTilesIntoSection(dashboard, ['b', 'c'], {
669+
id: 'new-s',
670+
title: 'Grouped',
671+
collapsed: false,
672+
});
673+
expect(result.containers).toHaveLength(2);
674+
expect(result.tiles.find(t => t.id === 'a')?.containerId).toBe('s1');
675+
expect(result.tiles.find(t => t.id === 'b')?.containerId).toBe('new-s');
676+
expect(result.tiles.find(t => t.id === 'c')?.containerId).toBe('new-s');
677+
});
678+
});
528679
});

packages/app/src/components/DashboardDndComponents.tsx

Lines changed: 1 addition & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,11 @@
11
import React from 'react';
2-
import { useDraggable, useDroppable } from '@dnd-kit/core';
2+
import { useDroppable } from '@dnd-kit/core';
33
import { useSortable } from '@dnd-kit/sortable';
44
import { CSS } from '@dnd-kit/utilities';
55
import { Box, Text } from '@mantine/core';
66

77
import { type DragData, type DragHandleProps } from './DashboardDndContext';
88

9-
// --- Tile drag handle (useDraggable) ---
10-
11-
export function TileDragHandle({
12-
tileId,
13-
tileName,
14-
containerId,
15-
}: {
16-
tileId: string;
17-
tileName: string;
18-
containerId?: string;
19-
}) {
20-
const { attributes, listeners, setNodeRef, isDragging } = useDraggable({
21-
id: `drag-tile-${tileId}`,
22-
data: { type: 'tile', tileId, tileName, containerId } satisfies DragData,
23-
});
24-
25-
return (
26-
<div
27-
ref={setNodeRef}
28-
{...attributes}
29-
{...listeners}
30-
style={{
31-
cursor: isDragging ? 'grabbing' : 'grab',
32-
display: 'flex',
33-
alignItems: 'center',
34-
justifyContent: 'center',
35-
opacity: isDragging ? 0.4 : 1,
36-
width: 100,
37-
height: 2,
38-
background: 'var(--mantine-color-dimmed)',
39-
borderRadius: 1,
40-
}}
41-
title="Drag to move between sections"
42-
data-testid={`tile-drag-handle-${tileId}`}
43-
/>
44-
);
45-
}
46-
479
// --- Section drop zone (useDroppable) ---
4810

4911
export function SectionDropZone({

0 commit comments

Comments
 (0)