Refactors all-day event rendering to use a layout engine for overlap detection and positioning, ensuring events are placed in available rows and columns. Removes deprecated method and adds unit tests.
138 lines
No EOL
7.4 KiB
TypeScript
138 lines
No EOL
7.4 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: '2 / 1 / 3 / 3' }, // row 2, columns 1-2 (2 dage, processed second)
|
|
{ id: 'workshop', gridArea: '1 / 2 / 2 / 6' } // row 1, columns 2-5 (4 dage, processed first)
|
|
]
|
|
},
|
|
|
|
{
|
|
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: '3 / 2 / 4 / 4' }, // row 3, columns 2-3 (2 dage, processed last)
|
|
{ id: '3', gridArea: '2 / 3 / 3 / 6' } // row 2, columns 3-5 (3 dage, processed second)
|
|
]
|
|
},
|
|
|
|
{
|
|
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: '4 / 1 / 5 / 3' }, // Autumn Equinox: row 4, columns 1-2 (2 dage, processed last)
|
|
{ id: '122', gridArea: '1 / 1 / 2 / 4' }, // Multi-Day Conference: row 1, columns 1-3 (4 dage, starts 21/9, processed first)
|
|
{ id: '123', gridArea: '2 / 1 / 3 / 5' }, // Project Sprint: row 2, columns 1-4 (4 dage, starts 22/9, processed second)
|
|
{ id: '143', gridArea: '1 / 5 / 2 / 8' }, // Weekend Hackathon: row 1, columns 5-7 (3 dage, no overlap, reuse row 1)
|
|
{ id: '161', gridArea: '3 / 2 / 4 / 6' } // Teknisk Workshop: row 3, columns 2-5 (4 dage, starts 23/9, processed third)
|
|
]
|
|
}
|
|
];
|
|
|
|
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.size).toBe(testCase.events.length);
|
|
|
|
// Check each expected result
|
|
testCase.expected.forEach(expected => {
|
|
const actualLayout = layouts.get(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);
|
|
});
|
|
}); |