This commit is contained in:
Janus C. H. Knudsen 2025-10-06 21:39:57 +02:00
parent faa59f6a3c
commit 69495ce00f
9 changed files with 337 additions and 1306 deletions

View file

@ -1,287 +0,0 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { OverlapDetector } from '../../src/utils/OverlapDetector';
import { CalendarEvent } from '../../src/types/CalendarTypes';
describe('OverlapDetector', () => {
let detector: OverlapDetector;
beforeEach(() => {
detector = new OverlapDetector();
});
// Helper function to create test events
const createEvent = (id: string, startHour: number, startMin: number, endHour: number, endMin: number): CalendarEvent => {
const start = new Date(2024, 0, 1, startHour, startMin);
const end = new Date(2024, 0, 1, endHour, endMin);
return {
id,
title: `Event ${id}`,
start,
end,
type: 'meeting',
allDay: false,
syncStatus: 'synced'
};
};
describe('resolveOverlap', () => {
it('should detect no overlap when events do not overlap', () => {
const event1 = createEvent('1', 9, 0, 10, 0); // 09:00-10:00
const event2 = createEvent('2', 10, 0, 11, 0); // 10:00-11:00
const overlaps = detector.resolveOverlap(event1, [event2]);
expect(overlaps).toHaveLength(0);
});
it('should detect overlap when events partially overlap', () => {
const event1 = createEvent('1', 9, 0, 10, 30); // 09:00-10:30
const event2 = createEvent('2', 10, 0, 11, 0); // 10:00-11:00
const overlaps = detector.resolveOverlap(event1, [event2]);
expect(overlaps).toHaveLength(1);
expect(overlaps[0].id).toBe('2');
});
it('should detect overlap when one event contains another', () => {
const event1 = createEvent('1', 9, 0, 12, 0); // 09:00-12:00
const event2 = createEvent('2', 10, 0, 11, 0); // 10:00-11:00
const overlaps = detector.resolveOverlap(event1, [event2]);
expect(overlaps).toHaveLength(1);
expect(overlaps[0].id).toBe('2');
});
it('should detect overlap when events have same start time', () => {
const event1 = createEvent('1', 9, 0, 10, 0); // 09:00-10:00
const event2 = createEvent('2', 9, 0, 10, 30); // 09:00-10:30
const overlaps = detector.resolveOverlap(event1, [event2]);
expect(overlaps).toHaveLength(1);
expect(overlaps[0].id).toBe('2');
});
it('should detect overlap when events have same end time', () => {
const event1 = createEvent('1', 9, 0, 10, 0); // 09:00-10:00
const event2 = createEvent('2', 9, 30, 10, 0); // 09:30-10:00
const overlaps = detector.resolveOverlap(event1, [event2]);
expect(overlaps).toHaveLength(1);
expect(overlaps[0].id).toBe('2');
});
it('should detect multiple overlapping events', () => {
const event1 = createEvent('1', 9, 0, 11, 0); // 09:00-11:00
const event2 = createEvent('2', 9, 30, 10, 30); // 09:30-10:30
const event3 = createEvent('3', 10, 0, 11, 30); // 10:00-11:30
const event4 = createEvent('4', 12, 0, 13, 0); // 12:00-13:00 (no overlap)
const overlaps = detector.resolveOverlap(event1, [event2, event3, event4]);
expect(overlaps).toHaveLength(2);
expect(overlaps.map(e => e.id)).toEqual(['2', '3']);
});
it('should handle edge case where event ends exactly when another starts', () => {
const event1 = createEvent('1', 9, 0, 10, 0); // 09:00-10:00
const event2 = createEvent('2', 10, 0, 11, 0); // 10:00-11:00
const overlaps = detector.resolveOverlap(event1, [event2]);
// Events that touch at boundaries should NOT overlap
expect(overlaps).toHaveLength(0);
});
it('should handle events with 1-minute overlap', () => {
const event1 = createEvent('1', 9, 0, 10, 1); // 09:00-10:01
const event2 = createEvent('2', 10, 0, 11, 0); // 10:00-11:00
const overlaps = detector.resolveOverlap(event1, [event2]);
expect(overlaps).toHaveLength(1);
});
});
describe('decorateWithStackLinks', () => {
it('should return empty result when no overlapping events', () => {
const event1 = createEvent('1', 9, 0, 10, 0);
const result = detector.decorateWithStackLinks(event1, []);
expect(result.overlappingEvents).toHaveLength(0);
expect(result.stackLinks.size).toBe(0);
});
it('should assign stack levels based on start time order', () => {
const event1 = createEvent('1', 9, 0, 10, 30); // 09:00-10:30
const event2 = createEvent('2', 9, 30, 11, 0); // 09:30-11:00
const result = detector.decorateWithStackLinks(event1, [event2]);
expect(result.stackLinks.size).toBe(2);
const link1 = result.stackLinks.get('1' as any);
const link2 = result.stackLinks.get('2' as any);
expect(link1?.stackLevel).toBe(0);
expect(link1?.prev).toBeUndefined();
expect(link1?.next).toBe('2');
expect(link2?.stackLevel).toBe(1);
expect(link2?.prev).toBe('1');
expect(link2?.next).toBeUndefined();
});
it('should create linked chain for multiple overlapping events', () => {
const event1 = createEvent('1', 9, 0, 11, 0); // 09:00-11:00
const event2 = createEvent('2', 9, 30, 10, 30); // 09:30-10:30
const event3 = createEvent('3', 10, 0, 11, 30); // 10:00-11:30
const result = detector.decorateWithStackLinks(event1, [event2, event3]);
expect(result.stackLinks.size).toBe(3);
const link1 = result.stackLinks.get('1' as any);
const link2 = result.stackLinks.get('2' as any);
const link3 = result.stackLinks.get('3' as any);
// Check chain: 1 -> 2 -> 3
expect(link1?.stackLevel).toBe(0);
expect(link1?.prev).toBeUndefined();
expect(link1?.next).toBe('2');
expect(link2?.stackLevel).toBe(1);
expect(link2?.prev).toBe('1');
expect(link2?.next).toBe('3');
expect(link3?.stackLevel).toBe(2);
expect(link3?.prev).toBe('2');
expect(link3?.next).toBeUndefined();
});
it('should handle events with same start time', () => {
const event1 = createEvent('1', 9, 0, 10, 0); // 09:00-10:00
const event2 = createEvent('2', 9, 0, 10, 30); // 09:00-10:30
const result = detector.decorateWithStackLinks(event1, [event2]);
const link1 = result.stackLinks.get('1' as any);
const link2 = result.stackLinks.get('2' as any);
// Both start at same time - order may vary but levels should be 0 and 1
const levels = [link1?.stackLevel, link2?.stackLevel].sort();
expect(levels).toEqual([0, 1]);
// Verify they are linked together
expect(result.stackLinks.size).toBe(2);
});
it('KNOWN ISSUE: should NOT stack events that do not overlap', () => {
// This test documents the current bug
const event1 = createEvent('1', 9, 0, 10, 0); // 09:00-10:00
const event2 = createEvent('2', 9, 30, 10, 30); // 09:30-10:30 (overlaps with 1)
const event3 = createEvent('3', 11, 0, 12, 0); // 11:00-12:00 (NO overlap with 1 or 2)
const result = detector.decorateWithStackLinks(event1, [event2, event3]);
const link3 = result.stackLinks.get('3' as any);
// CURRENT BEHAVIOR (BUG): Event 3 gets stackLevel 2
expect(link3?.stackLevel).toBe(2);
// EXPECTED BEHAVIOR: Event 3 should get stackLevel 0 since it doesn't overlap
// expect(link3?.stackLevel).toBe(0);
// expect(link3?.prev).toBeUndefined();
// expect(link3?.next).toBeUndefined();
});
it('KNOWN ISSUE: should reuse stack levels when possible', () => {
// This test documents another aspect of the bug
const event1 = createEvent('1', 9, 0, 10, 0); // 09:00-10:00
const event2 = createEvent('2', 10, 30, 11, 30); // 10:30-11:30 (NO overlap)
const event3 = createEvent('3', 12, 0, 13, 0); // 12:00-13:00 (NO overlap)
const result = detector.decorateWithStackLinks(event1, [event2, event3]);
const link1 = result.stackLinks.get('1' as any);
const link2 = result.stackLinks.get('2' as any);
const link3 = result.stackLinks.get('3' as any);
// CURRENT BEHAVIOR (BUG): All get different stack levels
expect(link1?.stackLevel).toBe(0);
expect(link2?.stackLevel).toBe(1);
expect(link3?.stackLevel).toBe(2);
// EXPECTED BEHAVIOR: All should reuse level 0 since none overlap
// expect(link1?.stackLevel).toBe(0);
// expect(link2?.stackLevel).toBe(0);
// expect(link3?.stackLevel).toBe(0);
});
it('should handle complex overlapping pattern correctly', () => {
// Event 1: 09:00-11:00 (base)
// Event 2: 09:30-10:30 (overlaps with 1)
// Event 3: 10:00-11:30 (overlaps with 1 and 2)
// Event 4: 11:00-12:00 (overlaps with 3 only)
const event1 = createEvent('1', 9, 0, 11, 0);
const event2 = createEvent('2', 9, 30, 10, 30);
const event3 = createEvent('3', 10, 0, 11, 30);
const event4 = createEvent('4', 11, 0, 12, 0);
const result = detector.decorateWithStackLinks(event1, [event2, event3, event4]);
expect(result.stackLinks.size).toBe(4);
// All events are linked in one chain (current behavior)
const link1 = result.stackLinks.get('1' as any);
const link2 = result.stackLinks.get('2' as any);
const link3 = result.stackLinks.get('3' as any);
const link4 = result.stackLinks.get('4' as any);
expect(link1?.stackLevel).toBe(0);
expect(link2?.stackLevel).toBe(1);
expect(link3?.stackLevel).toBe(2);
expect(link4?.stackLevel).toBe(3);
});
});
describe('Edge Cases', () => {
it('should handle zero-duration events', () => {
const event1 = createEvent('1', 9, 0, 9, 0); // 09:00-09:00
const event2 = createEvent('2', 9, 0, 10, 0); // 09:00-10:00
const overlaps = detector.resolveOverlap(event1, [event2]);
// Zero-duration event at start of another should not overlap
expect(overlaps).toHaveLength(0);
});
it('should handle events spanning multiple hours', () => {
const event1 = createEvent('1', 8, 0, 17, 0); // 08:00-17:00 (9 hours)
const event2 = createEvent('2', 12, 0, 13, 0); // 12:00-13:00
const overlaps = detector.resolveOverlap(event1, [event2]);
expect(overlaps).toHaveLength(1);
});
it('should handle many events in same time slot', () => {
const event1 = createEvent('1', 9, 0, 10, 0);
const events = [
createEvent('2', 9, 0, 10, 0),
createEvent('3', 9, 0, 10, 0),
createEvent('4', 9, 0, 10, 0),
createEvent('5', 9, 0, 10, 0)
];
const overlaps = detector.resolveOverlap(event1, events);
expect(overlaps).toHaveLength(4);
});
});
});