Calendar/test/managers/AllDayLayoutEngine.test.ts
Janus C. H. Knudsen 7fc401b1df Refactors layout engine tests to use arrays
Updates tests to use arrays instead of Maps for storing layouts.
This simplifies the data structures and allows for more straightforward assertions.
2025-09-28 13:45:15 +02:00

270 lines
No EOL
13 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import { AllDayLayoutEngine } from '../../src/utils/AllDayLayoutEngine';
import { CalendarEvent } from '../../src/types/CalendarTypes';
describe('AllDay Layout Engine - Pure Data Tests', () => {
const weekDates = [
'2025-09-22', '2025-09-23', '2025-09-24', '2025-09-25',
'2025-09-26', '2025-09-27', '2025-09-28'
];
const layoutEngine = new AllDayLayoutEngine(weekDates);
// Test data: events med start/end datoer og forventet grid-area
const testCases = [
{
name: 'Single day events - no overlap',
events: [
{ id: '1', title: 'Event 1', start: new Date('2025-09-22'), end: new Date('2025-09-22'), type: 'work', allDay: true, syncStatus: 'synced' } as CalendarEvent,
{ id: '2', title: 'Event 2', start: new Date('2025-09-24'), end: new Date('2025-09-24'), type: 'work', allDay: true, syncStatus: 'synced' } as CalendarEvent
],
expected: [
{ id: '1', gridArea: '1 / 1 / 2 / 2' }, // row 1, column 1 (Sept 22)
{ id: '2', gridArea: '1 / 3 / 2 / 4' } // row 1, column 3 (Sept 24)
]
},
{
name: 'Overlapping multi-day events - Autumn Equinox vs Teknisk Workshop',
events: [
{ id: 'autumn', title: 'Autumn Equinox', start: new Date('2025-09-22'), end: new Date('2025-09-23'), type: 'work', allDay: true, syncStatus: 'synced' } as CalendarEvent,
{ id: 'workshop', title: 'Teknisk Workshop', start: new Date('2025-09-23'), end: new Date('2025-09-26'), type: 'work', allDay: true, syncStatus: 'synced' } as CalendarEvent
],
expected: [
{ id: 'autumn', gridArea: '1 / 1 / 2 / 3' }, // row 1, columns 1-2 (2 dage, processed first)
{ id: 'workshop', gridArea: '2 / 2 / 3 / 6' } // row 2, columns 2-5 (4 dage, processed second)
]
},
{
name: 'Multiple events same column',
events: [
{ id: '1', title: 'Event 1', start: new Date('2025-09-23'), end: new Date('2025-09-23'), type: 'work', allDay: true, syncStatus: 'synced' } as CalendarEvent,
{ id: '2', title: 'Event 2', start: new Date('2025-09-23'), end: new Date('2025-09-23'), type: 'work', allDay: true, syncStatus: 'synced' } as CalendarEvent,
{ id: '3', title: 'Event 3', start: new Date('2025-09-23'), end: new Date('2025-09-23'), type: 'work', allDay: true, syncStatus: 'synced' } as CalendarEvent
],
expected: [
{ id: '1', gridArea: '1 / 2 / 2 / 3' }, // row 1, column 2 (Sept 23)
{ id: '2', gridArea: '2 / 2 / 3 / 3' }, // row 2, column 2 (Sept 23)
{ id: '3', gridArea: '3 / 2 / 4 / 3' } // row 3, column 2 (Sept 23)
]
},
{
name: 'Partial overlaps',
events: [
{ id: '1', title: 'Event 1', start: new Date('2025-09-22'), end: new Date('2025-09-23'), type: 'work', allDay: true, syncStatus: 'synced' } as CalendarEvent,
{ id: '2', title: 'Event 2', start: new Date('2025-09-23'), end: new Date('2025-09-24'), type: 'work', allDay: true, syncStatus: 'synced' } as CalendarEvent,
{ id: '3', title: 'Event 3', start: new Date('2025-09-25'), end: new Date('2025-09-26'), type: 'work', allDay: true, syncStatus: 'synced' } as CalendarEvent
],
expected: [
{ id: '1', gridArea: '1 / 1 / 2 / 3' }, // row 1, columns 1-2 (Sept 22-23)
{ id: '2', gridArea: '2 / 2 / 3 / 4' }, // row 2, columns 2-3 (Sept 23-24, overlap på column 2)
{ id: '3', gridArea: '1 / 4 / 2 / 6' } // row 1, columns 4-5 (Sept 25-26, no overlap)
]
},
{
name: 'Complex overlapping pattern',
events: [
{ id: '1', title: 'Long Event', start: new Date('2025-09-22'), end: new Date('2025-09-25'), type: 'work', allDay: true, syncStatus: 'synced' } as CalendarEvent,
{ id: '2', title: 'Short Event', start: new Date('2025-09-23'), end: new Date('2025-09-24'), type: 'work', allDay: true, syncStatus: 'synced' } as CalendarEvent,
{ id: '3', title: 'Another Event', start: new Date('2025-09-24'), end: new Date('2025-09-26'), type: 'work', allDay: true, syncStatus: 'synced' } as CalendarEvent
],
expected: [
{ id: '1', gridArea: '1 / 1 / 2 / 5' }, // row 1, columns 1-4 (4 dage, processed first)
{ id: '2', gridArea: '2 / 2 / 3 / 4' }, // row 2, columns 2-3 (2 dage, processed second)
{ id: '3', gridArea: '3 / 3 / 4 / 6' } // row 3, columns 3-5 (3 dage, processed third)
]
},
{
name: 'Real-world bug scenario - Multiple overlapping events (Sept 21-28)',
events: [
{ id: '112', title: 'Autumn Equinox', start: new Date('2025-09-22'), end: new Date('2025-09-23'), type: 'work', allDay: true, syncStatus: 'synced' } as CalendarEvent,
{ id: '122', title: 'Multi-Day Conference', start: new Date('2025-09-21'), end: new Date('2025-09-24'), type: 'work', allDay: true, syncStatus: 'synced' } as CalendarEvent,
{ id: '123', title: 'Project Sprint', start: new Date('2025-09-22'), end: new Date('2025-09-25'), type: 'work', allDay: true, syncStatus: 'synced' } as CalendarEvent,
{ id: '143', title: 'Weekend Hackathon', start: new Date('2025-09-26'), end: new Date('2025-09-28'), type: 'work', allDay: true, syncStatus: 'synced' } as CalendarEvent,
{ id: '161', title: 'Teknisk Workshop', start: new Date('2025-09-23'), end: new Date('2025-09-26'), type: 'work', allDay: true, syncStatus: 'synced' } as CalendarEvent
],
expected: [
{ id: '112', gridArea: '1 / 1 / 2 / 3' }, // Autumn Equinox: row 1, columns 1-2 (2 dage, processed first)
{ id: '122', gridArea: '2 / 1 / 3 / 4' }, // Multi-Day Conference: row 2, columns 1-3 (4 dage, starts 21/9, processed second)
{ id: '123', gridArea: '3 / 1 / 4 / 5' }, // Project Sprint: row 3, columns 1-4 (4 dage, starts 22/9, processed third)
{ id: '143', gridArea: '1 / 5 / 2 / 8' }, // Weekend Hackathon: row 1, columns 5-7 (3 dage, no overlap, reuse row 1)
{ id: '161', gridArea: '4 / 2 / 5 / 6' } // Teknisk Workshop: row 4, columns 2-5 (4 dage, starts 23/9, processed fourth)
]
}
];
testCases.forEach(testCase => {
it(testCase.name, () => {
// Calculate actual layouts using AllDayLayoutEngine
const layouts = layoutEngine.calculateLayout(testCase.events);
// Verify we got layouts for all events
expect(layouts.length).toBe(testCase.events.length);
// Check each expected result
testCase.expected.forEach(expected => {
const actualLayout = layouts.find(layout => layout.calenderEvent.id === expected.id);
expect(actualLayout).toBeDefined();
expect(actualLayout!.gridArea).toBe(expected.gridArea);
});
});
});
it('Grid-area format validation', () => {
// Test at grid-area format er korrekt
const gridArea = '2 / 3 / 3 / 5'; // row 2, columns 3-4
const parts = gridArea.split(' / ');
const rowStart = parseInt(parts[0]); // 2
const colStart = parseInt(parts[1]); // 3
const rowEnd = parseInt(parts[2]); // 3
const colEnd = parseInt(parts[3]); // 5
expect(rowStart).toBe(2);
expect(colStart).toBe(3);
expect(rowEnd).toBe(3);
expect(colEnd).toBe(5);
// Verify spans
const rowSpan = rowEnd - rowStart; // 1 row
const colSpan = colEnd - colStart; // 2 columns
expect(rowSpan).toBe(1);
expect(colSpan).toBe(2);
});
});
describe('AllDay Layout Engine - Partial Week Views', () => {
describe('Date Range Filtering', () => {
it('should filter out events that do not overlap with visible dates', () => {
// 3-day workweek: Wed-Fri (like user's scenario)
const weekDates = ['2025-09-24', '2025-09-25', '2025-09-26']; // Wed, Thu, Fri
const engine = new AllDayLayoutEngine(weekDates);
const events: CalendarEvent[] = [
{
id: '112',
title: 'Autumn Equinox',
start: new Date('2025-09-22T00:00:00'), // Monday - OUTSIDE visible range
end: new Date('2025-09-24T00:00:00'), // Wednesday - OUTSIDE visible range
type: 'milestone',
allDay: true,
syncStatus: 'synced'
},
{
id: '113',
title: 'Visible Event',
start: new Date('2025-09-25T00:00:00'), // Thursday - INSIDE visible range
end: new Date('2025-09-26T00:00:00'), // Friday - INSIDE visible range
type: 'work',
allDay: true,
syncStatus: 'synced'
}
];
const layouts = engine.calculateLayout(events);
// Both events are now visible since '112' ends on Wednesday (visible range start)
expect(layouts.length).toBe(2);
expect(layouts.some(layout => layout.calenderEvent.id === '112')).toBe(true); // Now visible since it ends on Wed
expect(layouts.some(layout => layout.calenderEvent.id === '113')).toBe(true); // Still visible
const layout112 = layouts.find(layout => layout.calenderEvent.id === '112')!;
expect(layout112.startColumn).toBe(1); // Clipped to Wed (first visible day)
expect(layout112.endColumn).toBe(1); // Wed only
expect(layout112.row).toBe(1);
const layout113 = layouts.find(layout => layout.calenderEvent.id === '113')!;
expect(layout113.startColumn).toBe(2); // Thursday = column 2 in Wed-Fri view
expect(layout113.endColumn).toBe(3); // Friday = column 3
expect(layout113.row).toBe(1);
});
it('should clip events that partially overlap with visible dates', () => {
// 3-day workweek: Wed-Fri
const weekDates = ['2025-09-24', '2025-09-25', '2025-09-26']; // Wed, Thu, Fri
const engine = new AllDayLayoutEngine(weekDates);
const events: CalendarEvent[] = [
{
id: '114',
title: 'Spans Before and Into Week',
start: new Date('2025-09-22T00:00:00'), // Monday - before visible range
end: new Date('2025-09-26T00:00:00'), // Friday - inside visible range
type: 'work',
allDay: true,
syncStatus: 'synced'
},
{
id: '115',
title: 'Spans From Week and After',
start: new Date('2025-09-25T00:00:00'), // Thursday - inside visible range
end: new Date('2025-09-29T00:00:00'), // Monday - after visible range
type: 'work',
allDay: true,
syncStatus: 'synced'
}
];
const layouts = engine.calculateLayout(events);
expect(layouts.length).toBe(2);
// First event should be clipped to start at Wed (column 1) and end at Fri (column 3)
const firstLayout = layouts.find(layout => layout.calenderEvent.id === '114')!;
expect(firstLayout.startColumn).toBe(1); // Clipped to Wed (first visible day)
expect(firstLayout.endColumn).toBe(3); // Fri (now ends on Friday due to 2025-09-26T00:00:00)
expect(firstLayout.columnSpan).toBe(3);
expect(firstLayout.gridArea).toBe('1 / 1 / 2 / 4');
// Second event should span Thu-Fri, but clipped beyond visible range
const secondLayout = layouts.find(layout => layout.calenderEvent.id === '115')!;
expect(secondLayout.startColumn).toBe(2); // Thu (actual start date) = column 2 in Wed-Fri view
expect(secondLayout.endColumn).toBe(3); // Clipped to Fri (last visible day) = column 3
expect(secondLayout.columnSpan).toBe(2);
expect(secondLayout.gridArea).toBe('2 / 2 / 3 / 4'); // Row 2 due to overlap
});
it('should handle 5-day workweek correctly', () => {
// 5-day workweek: Mon-Fri
const weekDates = ['2025-09-22', '2025-09-23', '2025-09-24', '2025-09-25', '2025-09-26']; // Mon-Fri
const engine = new AllDayLayoutEngine(weekDates);
const events: CalendarEvent[] = [
{
id: '116',
title: 'Monday Event',
start: new Date('2025-09-22T00:00:00'), // Monday
end: new Date('2025-09-23T00:00:00'), // Tuesday
type: 'work',
allDay: true,
syncStatus: 'synced'
},
{
id: '117',
title: 'Weekend Event',
start: new Date('2025-09-27T00:00:00'), // Saturday - OUTSIDE visible range
end: new Date('2025-09-29T00:00:00'), // Monday - OUTSIDE visible range
type: 'personal',
allDay: true,
syncStatus: 'synced'
}
];
const layouts = engine.calculateLayout(events);
expect(layouts.length).toBe(1); // Only Monday event should be included - weekend event should be filtered out
expect(layouts.some(layout => layout.calenderEvent.id === '116')).toBe(true); // Monday event should be included
expect(layouts.some(layout => layout.calenderEvent.id === '117')).toBe(false); // Weekend event should be filtered out
const mondayLayout = layouts.find(layout => layout.calenderEvent.id === '116')!;
expect(mondayLayout.startColumn).toBe(1); // Monday = column 1
expect(mondayLayout.endColumn).toBe(2); // Now ends on Tuesday due to 2025-09-23T00:00:00
expect(mondayLayout.row).toBe(1);
expect(mondayLayout.gridArea).toBe('1 / 1 / 2 / 3');
});
});
});