Moves complex layout determination logic (grid grouping, stack levels, positioning) from `EventRenderer` to a new `EventLayoutCoordinator` class. Delegates layout responsibilities to the coordinator, significantly simplifying the `EventRenderer`'s `renderColumnEvents` method. Refines `EventStackManager` by removing deprecated layout methods, consolidating its role to event grouping and core stack level management. Improves modularity and separation of concerns within the rendering pipeline.
656 lines
20 KiB
TypeScript
656 lines
20 KiB
TypeScript
/**
|
|
* TDD Test Suite for EventStackManager
|
|
*
|
|
* This test suite follows Test-Driven Development principles:
|
|
* 1. Write a failing test (RED)
|
|
* 2. Write minimal code to make it pass (GREEN)
|
|
* 3. Refactor if needed (REFACTOR)
|
|
*
|
|
* @see STACKING_CONCEPT.md for concept documentation
|
|
*
|
|
* NOTE: This test file is SKIPPED as it tests removed methods (createStackLinks, findOverlappingEvents)
|
|
* See EventStackManager.flexbox.test.ts for current implementation tests
|
|
*/
|
|
|
|
import { describe, it, expect, beforeEach } from 'vitest';
|
|
import { EventStackManager, StackLink } from '../../src/managers/EventStackManager';
|
|
|
|
describe.skip('EventStackManager - TDD Suite (DEPRECATED - uses removed methods)', () => {
|
|
let manager: EventStackManager;
|
|
|
|
beforeEach(() => {
|
|
manager = new EventStackManager();
|
|
});
|
|
|
|
describe('Overlap Detection', () => {
|
|
it('should detect overlap when event A starts before event B ends and event A ends after event B starts', () => {
|
|
// RED - This test will fail initially
|
|
const eventA = {
|
|
id: 'event-a',
|
|
start: new Date('2025-01-01T09:00:00'),
|
|
end: new Date('2025-01-01T11:00:00')
|
|
};
|
|
|
|
const eventB = {
|
|
id: 'event-b',
|
|
start: new Date('2025-01-01T10:00:00'),
|
|
end: new Date('2025-01-01T12:00:00')
|
|
};
|
|
|
|
// Expected: true (events overlap from 10:00 to 11:00)
|
|
expect(manager.doEventsOverlap(eventA, eventB)).toBe(true);
|
|
});
|
|
|
|
it('should return false when events do not overlap', () => {
|
|
const eventA = {
|
|
id: 'event-a',
|
|
start: new Date('2025-01-01T09:00:00'),
|
|
end: new Date('2025-01-01T10:00:00')
|
|
};
|
|
|
|
const eventB = {
|
|
id: 'event-b',
|
|
start: new Date('2025-01-01T11:00:00'),
|
|
end: new Date('2025-01-01T12:00:00')
|
|
};
|
|
|
|
expect(manager.doEventsOverlap(eventA, eventB)).toBe(false);
|
|
});
|
|
|
|
it('should detect overlap when one event completely contains another', () => {
|
|
const eventA = {
|
|
id: 'event-a',
|
|
start: new Date('2025-01-01T09:00:00'),
|
|
end: new Date('2025-01-01T13:00:00')
|
|
};
|
|
|
|
const eventB = {
|
|
id: 'event-b',
|
|
start: new Date('2025-01-01T10:00:00'),
|
|
end: new Date('2025-01-01T11:00:00')
|
|
};
|
|
|
|
expect(manager.doEventsOverlap(eventA, eventB)).toBe(true);
|
|
});
|
|
|
|
it('should return false when events touch but do not overlap', () => {
|
|
const eventA = {
|
|
id: 'event-a',
|
|
start: new Date('2025-01-01T09:00:00'),
|
|
end: new Date('2025-01-01T10:00:00')
|
|
};
|
|
|
|
const eventB = {
|
|
id: 'event-b',
|
|
start: new Date('2025-01-01T10:00:00'), // Exactly when A ends
|
|
end: new Date('2025-01-01T11:00:00')
|
|
};
|
|
|
|
expect(manager.doEventsOverlap(eventA, eventB)).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('Find Overlapping Events', () => {
|
|
it('should find all events that overlap with a given event', () => {
|
|
const targetEvent = {
|
|
id: 'target',
|
|
start: new Date('2025-01-01T10:00:00'),
|
|
end: new Date('2025-01-01T11:00:00')
|
|
};
|
|
|
|
const columnEvents = [
|
|
{
|
|
id: 'event-a',
|
|
start: new Date('2025-01-01T09:00:00'),
|
|
end: new Date('2025-01-01T10:30:00') // Overlaps
|
|
},
|
|
{
|
|
id: 'event-b',
|
|
start: new Date('2025-01-01T12:00:00'),
|
|
end: new Date('2025-01-01T13:00:00') // Does not overlap
|
|
},
|
|
{
|
|
id: 'event-c',
|
|
start: new Date('2025-01-01T10:30:00'),
|
|
end: new Date('2025-01-01T11:30:00') // Overlaps
|
|
}
|
|
];
|
|
|
|
const overlapping = manager.findOverlappingEvents(targetEvent, columnEvents);
|
|
|
|
expect(overlapping).toHaveLength(2);
|
|
expect(overlapping.map(e => e.id)).toContain('event-a');
|
|
expect(overlapping.map(e => e.id)).toContain('event-c');
|
|
expect(overlapping.map(e => e.id)).not.toContain('event-b');
|
|
});
|
|
|
|
it('should return empty array when no events overlap', () => {
|
|
const targetEvent = {
|
|
id: 'target',
|
|
start: new Date('2025-01-01T10:00:00'),
|
|
end: new Date('2025-01-01T11:00:00')
|
|
};
|
|
|
|
const columnEvents = [
|
|
{
|
|
id: 'event-a',
|
|
start: new Date('2025-01-01T09:00:00'),
|
|
end: new Date('2025-01-01T09:30:00')
|
|
},
|
|
{
|
|
id: 'event-b',
|
|
start: new Date('2025-01-01T12:00:00'),
|
|
end: new Date('2025-01-01T13:00:00')
|
|
}
|
|
];
|
|
|
|
const overlapping = manager.findOverlappingEvents(targetEvent, columnEvents);
|
|
|
|
expect(overlapping).toHaveLength(0);
|
|
});
|
|
});
|
|
|
|
describe('Create Stack Links', () => {
|
|
it('should create stack links for overlapping events sorted by start time', () => {
|
|
const events = [
|
|
{
|
|
id: 'event-b',
|
|
start: new Date('2025-01-01T10:00:00'),
|
|
end: new Date('2025-01-01T12:00:00')
|
|
},
|
|
{
|
|
id: 'event-a',
|
|
start: new Date('2025-01-01T09:00:00'),
|
|
end: new Date('2025-01-01T11:00:00')
|
|
},
|
|
{
|
|
id: 'event-c',
|
|
start: new Date('2025-01-01T11:00:00'),
|
|
end: new Date('2025-01-01T13:00:00')
|
|
}
|
|
];
|
|
|
|
const stackLinks = manager.createStackLinks(events);
|
|
|
|
// Should be sorted by start time: event-a, event-b, event-c
|
|
expect(stackLinks.size).toBe(3);
|
|
|
|
const linkA = stackLinks.get('event-a');
|
|
expect(linkA).toEqual({
|
|
stackLevel: 0,
|
|
next: 'event-b'
|
|
// no prev
|
|
});
|
|
|
|
const linkB = stackLinks.get('event-b');
|
|
expect(linkB).toEqual({
|
|
stackLevel: 1,
|
|
prev: 'event-a',
|
|
next: 'event-c'
|
|
});
|
|
|
|
const linkC = stackLinks.get('event-c');
|
|
expect(linkC).toEqual({
|
|
stackLevel: 2,
|
|
prev: 'event-b'
|
|
// no next
|
|
});
|
|
});
|
|
|
|
it('should return empty map for empty event array', () => {
|
|
const stackLinks = manager.createStackLinks([]);
|
|
|
|
expect(stackLinks.size).toBe(0);
|
|
});
|
|
|
|
it('should create single stack link for single event', () => {
|
|
const events = [
|
|
{
|
|
id: 'event-a',
|
|
start: new Date('2025-01-01T09:00:00'),
|
|
end: new Date('2025-01-01T10:00:00')
|
|
}
|
|
];
|
|
|
|
const stackLinks = manager.createStackLinks(events);
|
|
|
|
expect(stackLinks.size).toBe(1);
|
|
|
|
const link = stackLinks.get('event-a');
|
|
expect(link).toEqual({
|
|
stackLevel: 0
|
|
// no prev, no next
|
|
});
|
|
});
|
|
|
|
it('should handle events with same start time by sorting by end time', () => {
|
|
const events = [
|
|
{
|
|
id: 'event-b',
|
|
start: new Date('2025-01-01T10:00:00'),
|
|
end: new Date('2025-01-01T12:00:00') // Longer event
|
|
},
|
|
{
|
|
id: 'event-a',
|
|
start: new Date('2025-01-01T10:00:00'),
|
|
end: new Date('2025-01-01T11:00:00') // Shorter event (should come first)
|
|
}
|
|
];
|
|
|
|
const stackLinks = manager.createStackLinks(events);
|
|
|
|
// Shorter event should have lower stack level
|
|
expect(stackLinks.get('event-a')?.stackLevel).toBe(0);
|
|
expect(stackLinks.get('event-b')?.stackLevel).toBe(1);
|
|
});
|
|
});
|
|
|
|
describe('Calculate Visual Styling', () => {
|
|
it('should calculate marginLeft based on stack level', () => {
|
|
const stackLevel = 0;
|
|
expect(manager.calculateMarginLeft(stackLevel)).toBe(0);
|
|
|
|
const stackLevel1 = 1;
|
|
expect(manager.calculateMarginLeft(stackLevel1)).toBe(15);
|
|
|
|
const stackLevel2 = 2;
|
|
expect(manager.calculateMarginLeft(stackLevel2)).toBe(30);
|
|
|
|
const stackLevel5 = 5;
|
|
expect(manager.calculateMarginLeft(stackLevel5)).toBe(75);
|
|
});
|
|
|
|
it('should calculate zIndex based on stack level', () => {
|
|
const stackLevel = 0;
|
|
expect(manager.calculateZIndex(stackLevel)).toBe(100);
|
|
|
|
const stackLevel1 = 1;
|
|
expect(manager.calculateZIndex(stackLevel1)).toBe(101);
|
|
|
|
const stackLevel2 = 2;
|
|
expect(manager.calculateZIndex(stackLevel2)).toBe(102);
|
|
});
|
|
});
|
|
|
|
describe('Stack Link Serialization', () => {
|
|
it('should serialize stack link to JSON string', () => {
|
|
const stackLink: StackLink = {
|
|
stackLevel: 1,
|
|
prev: 'event-a',
|
|
next: 'event-c'
|
|
};
|
|
|
|
const serialized = manager.serializeStackLink(stackLink);
|
|
|
|
expect(serialized).toBe('{"stackLevel":1,"prev":"event-a","next":"event-c"}');
|
|
});
|
|
|
|
it('should deserialize JSON string to stack link', () => {
|
|
const json = '{"stackLevel":1,"prev":"event-a","next":"event-c"}';
|
|
|
|
const stackLink = manager.deserializeStackLink(json);
|
|
|
|
expect(stackLink).toEqual({
|
|
stackLevel: 1,
|
|
prev: 'event-a',
|
|
next: 'event-c'
|
|
});
|
|
});
|
|
|
|
it('should handle stack link without prev/next', () => {
|
|
const stackLink: StackLink = {
|
|
stackLevel: 0
|
|
};
|
|
|
|
const serialized = manager.serializeStackLink(stackLink);
|
|
const deserialized = manager.deserializeStackLink(serialized);
|
|
|
|
expect(deserialized).toEqual({
|
|
stackLevel: 0
|
|
});
|
|
});
|
|
|
|
it('should return null when deserializing invalid JSON', () => {
|
|
const invalid = 'not-valid-json';
|
|
|
|
const result = manager.deserializeStackLink(invalid);
|
|
|
|
expect(result).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('DOM Integration', () => {
|
|
it('should apply stack link to DOM element', () => {
|
|
const element = document.createElement('div');
|
|
element.dataset.eventId = 'event-a';
|
|
|
|
const stackLink: StackLink = {
|
|
stackLevel: 1,
|
|
prev: 'event-b',
|
|
next: 'event-c'
|
|
};
|
|
|
|
manager.applyStackLinkToElement(element, stackLink);
|
|
|
|
expect(element.dataset.stackLink).toBe('{"stackLevel":1,"prev":"event-b","next":"event-c"}');
|
|
});
|
|
|
|
it('should read stack link from DOM element', () => {
|
|
const element = document.createElement('div');
|
|
element.dataset.stackLink = '{"stackLevel":2,"prev":"event-a"}';
|
|
|
|
const stackLink = manager.getStackLinkFromElement(element);
|
|
|
|
expect(stackLink).toEqual({
|
|
stackLevel: 2,
|
|
prev: 'event-a'
|
|
});
|
|
});
|
|
|
|
it('should return null when element has no stack link', () => {
|
|
const element = document.createElement('div');
|
|
|
|
const stackLink = manager.getStackLinkFromElement(element);
|
|
|
|
expect(stackLink).toBeNull();
|
|
});
|
|
|
|
it('should apply visual styling to element based on stack level', () => {
|
|
const element = document.createElement('div');
|
|
|
|
manager.applyVisualStyling(element, 2);
|
|
|
|
expect(element.style.marginLeft).toBe('30px');
|
|
expect(element.style.zIndex).toBe('102');
|
|
});
|
|
|
|
it('should clear stack link from element', () => {
|
|
const element = document.createElement('div');
|
|
element.dataset.stackLink = '{"stackLevel":1}';
|
|
|
|
manager.clearStackLinkFromElement(element);
|
|
|
|
expect(element.dataset.stackLink).toBeUndefined();
|
|
});
|
|
|
|
it('should clear visual styling from element', () => {
|
|
const element = document.createElement('div');
|
|
element.style.marginLeft = '30px';
|
|
element.style.zIndex = '102';
|
|
|
|
manager.clearVisualStyling(element);
|
|
|
|
expect(element.style.marginLeft).toBe('');
|
|
expect(element.style.zIndex).toBe('');
|
|
});
|
|
});
|
|
|
|
describe('Edge Cases', () => {
|
|
it('should optimize stack levels when events do not overlap each other but both overlap a parent event', () => {
|
|
// Visual representation:
|
|
// Event A: 09:00 ════════════════════════════ 14:00
|
|
// Event B: 10:00 ═════ 12:00
|
|
// Event C: 12:30 ═══ 13:00
|
|
//
|
|
// Expected stacking:
|
|
// Event A: stackLevel 0 (base)
|
|
// Event B: stackLevel 1 (conflicts with A)
|
|
// Event C: stackLevel 1 (conflicts with A, but NOT with B - can share same level!)
|
|
|
|
const eventA = {
|
|
id: 'event-a',
|
|
start: new Date('2025-01-01T09:00:00'),
|
|
end: new Date('2025-01-01T14:00:00')
|
|
};
|
|
|
|
const eventB = {
|
|
id: 'event-b',
|
|
start: new Date('2025-01-01T10:00:00'),
|
|
end: new Date('2025-01-01T12:00:00')
|
|
};
|
|
|
|
const eventC = {
|
|
id: 'event-c',
|
|
start: new Date('2025-01-01T12:30:00'),
|
|
end: new Date('2025-01-01T13:00:00')
|
|
};
|
|
|
|
const stackLinks = manager.createOptimizedStackLinks([eventA, eventB, eventC]);
|
|
|
|
expect(stackLinks.size).toBe(3);
|
|
|
|
// Event A is the base (contains both B and C)
|
|
expect(stackLinks.get('event-a')?.stackLevel).toBe(0);
|
|
|
|
// Event B and C should both be at stackLevel 1 (they don't overlap each other)
|
|
expect(stackLinks.get('event-b')?.stackLevel).toBe(1);
|
|
expect(stackLinks.get('event-c')?.stackLevel).toBe(1);
|
|
|
|
// Verify they are NOT linked to each other (no prev/next between B and C)
|
|
expect(stackLinks.get('event-b')?.next).toBeUndefined();
|
|
expect(stackLinks.get('event-c')?.prev).toBeUndefined();
|
|
});
|
|
|
|
it('should create multiple parallel tracks when events at same level do not overlap', () => {
|
|
// Complex scenario with multiple parallel tracks:
|
|
// Event A: 09:00 ════════════════════════════════════ 15:00
|
|
// Event B: 10:00 ═══ 11:00
|
|
// Event C: 11:30 ═══ 12:30
|
|
// Event D: 13:00 ═══ 14:00
|
|
//
|
|
// Expected:
|
|
// - A at level 0 (base)
|
|
// - B, C, D all at level 1 (they don't overlap each other, only with A)
|
|
|
|
const eventA = {
|
|
id: 'event-a',
|
|
start: new Date('2025-01-01T09:00:00'),
|
|
end: new Date('2025-01-01T15:00:00')
|
|
};
|
|
|
|
const eventB = {
|
|
id: 'event-b',
|
|
start: new Date('2025-01-01T10:00:00'),
|
|
end: new Date('2025-01-01T11:00:00')
|
|
};
|
|
|
|
const eventC = {
|
|
id: 'event-c',
|
|
start: new Date('2025-01-01T11:30:00'),
|
|
end: new Date('2025-01-01T12:30:00')
|
|
};
|
|
|
|
const eventD = {
|
|
id: 'event-d',
|
|
start: new Date('2025-01-01T13:00:00'),
|
|
end: new Date('2025-01-01T14:00:00')
|
|
};
|
|
|
|
const stackLinks = manager.createOptimizedStackLinks([eventA, eventB, eventC, eventD]);
|
|
|
|
expect(stackLinks.size).toBe(4);
|
|
expect(stackLinks.get('event-a')?.stackLevel).toBe(0);
|
|
expect(stackLinks.get('event-b')?.stackLevel).toBe(1);
|
|
expect(stackLinks.get('event-c')?.stackLevel).toBe(1);
|
|
expect(stackLinks.get('event-d')?.stackLevel).toBe(1);
|
|
});
|
|
|
|
it('should handle nested overlaps with optimal stacking', () => {
|
|
// Scenario:
|
|
// Event A: 09:00 ════════════════════════════════════ 15:00
|
|
// Event B: 10:00 ════════════════════ 13:00
|
|
// Event C: 11:00 ═══ 12:00
|
|
// Event D: 12:30 ═══ 13:30
|
|
//
|
|
// Expected:
|
|
// - A at level 0 (base, contains all)
|
|
// - B at level 1 (overlaps with A)
|
|
// - C at level 2 (overlaps with A and B)
|
|
// - D at level 2 (overlaps with A and B, but NOT with C - can share level with C)
|
|
|
|
const eventA = {
|
|
id: 'event-a',
|
|
start: new Date('2025-01-01T09:00:00'),
|
|
end: new Date('2025-01-01T15:00:00')
|
|
};
|
|
|
|
const eventB = {
|
|
id: 'event-b',
|
|
start: new Date('2025-01-01T10:00:00'),
|
|
end: new Date('2025-01-01T13:00:00')
|
|
};
|
|
|
|
const eventC = {
|
|
id: 'event-c',
|
|
start: new Date('2025-01-01T11:00:00'),
|
|
end: new Date('2025-01-01T12:00:00')
|
|
};
|
|
|
|
const eventD = {
|
|
id: 'event-d',
|
|
start: new Date('2025-01-01T12:30:00'),
|
|
end: new Date('2025-01-01T13:30:00')
|
|
};
|
|
|
|
const stackLinks = manager.createOptimizedStackLinks([eventA, eventB, eventC, eventD]);
|
|
|
|
expect(stackLinks.size).toBe(4);
|
|
expect(stackLinks.get('event-a')?.stackLevel).toBe(0);
|
|
expect(stackLinks.get('event-b')?.stackLevel).toBe(1);
|
|
expect(stackLinks.get('event-c')?.stackLevel).toBe(2);
|
|
expect(stackLinks.get('event-d')?.stackLevel).toBe(2); // Can share level with C
|
|
});
|
|
|
|
it('should handle events with identical start and end times', () => {
|
|
const eventA = {
|
|
id: 'event-a',
|
|
start: new Date('2025-01-01T10:00:00'),
|
|
end: new Date('2025-01-01T11:00:00')
|
|
};
|
|
|
|
const eventB = {
|
|
id: 'event-b',
|
|
start: new Date('2025-01-01T10:00:00'),
|
|
end: new Date('2025-01-01T11:00:00')
|
|
};
|
|
|
|
expect(manager.doEventsOverlap(eventA, eventB)).toBe(true);
|
|
|
|
const stackLinks = manager.createStackLinks([eventA, eventB]);
|
|
expect(stackLinks.size).toBe(2);
|
|
});
|
|
|
|
it('should handle events with zero duration', () => {
|
|
const eventA = {
|
|
id: 'event-a',
|
|
start: new Date('2025-01-01T10:00:00'),
|
|
end: new Date('2025-01-01T10:00:00') // Zero duration
|
|
};
|
|
|
|
const eventB = {
|
|
id: 'event-b',
|
|
start: new Date('2025-01-01T10:00:00'),
|
|
end: new Date('2025-01-01T11:00:00')
|
|
};
|
|
|
|
// Zero-duration event should not overlap
|
|
expect(manager.doEventsOverlap(eventA, eventB)).toBe(false);
|
|
});
|
|
|
|
it('should handle large number of overlapping events', () => {
|
|
const events = Array.from({ length: 100 }, (_, i) => ({
|
|
id: `event-${i}`,
|
|
start: new Date('2025-01-01T09:00:00'),
|
|
end: new Date(`2025-01-01T${10 + i}:00:00`)
|
|
}));
|
|
|
|
const stackLinks = manager.createStackLinks(events);
|
|
|
|
expect(stackLinks.size).toBe(100);
|
|
expect(stackLinks.get('event-0')?.stackLevel).toBe(0);
|
|
expect(stackLinks.get('event-99')?.stackLevel).toBe(99);
|
|
});
|
|
});
|
|
|
|
describe('Integration Tests', () => {
|
|
it('should create complete stack for new event with overlapping events', () => {
|
|
// Scenario: Adding new event that overlaps with existing events
|
|
const newEvent = {
|
|
id: 'new-event',
|
|
start: new Date('2025-01-01T10:00:00'),
|
|
end: new Date('2025-01-01T11:00:00')
|
|
};
|
|
|
|
const existingEvents = [
|
|
{
|
|
id: 'existing-a',
|
|
start: new Date('2025-01-01T09:00:00'),
|
|
end: new Date('2025-01-01T10:30:00')
|
|
},
|
|
{
|
|
id: 'existing-b',
|
|
start: new Date('2025-01-01T10:30:00'),
|
|
end: new Date('2025-01-01T12:00:00')
|
|
}
|
|
];
|
|
|
|
// Find overlapping
|
|
const overlapping = manager.findOverlappingEvents(newEvent, existingEvents);
|
|
|
|
// Create stack links for all events
|
|
const allEvents = [...overlapping, newEvent];
|
|
const stackLinks = manager.createStackLinks(allEvents);
|
|
|
|
// Verify complete stack
|
|
expect(stackLinks.size).toBe(3);
|
|
expect(stackLinks.get('existing-a')?.stackLevel).toBe(0);
|
|
expect(stackLinks.get('new-event')?.stackLevel).toBe(1);
|
|
expect(stackLinks.get('existing-b')?.stackLevel).toBe(2);
|
|
});
|
|
|
|
it('should handle complete workflow: detect, create, apply to DOM', () => {
|
|
const newEvent = {
|
|
id: 'new-event',
|
|
start: new Date('2025-01-01T10:00:00'),
|
|
end: new Date('2025-01-01T11:00:00')
|
|
};
|
|
|
|
const existingEvents = [
|
|
{
|
|
id: 'existing-a',
|
|
start: new Date('2025-01-01T09:00:00'),
|
|
end: new Date('2025-01-01T10:30:00')
|
|
}
|
|
];
|
|
|
|
// Step 1: Find overlapping
|
|
const overlapping = manager.findOverlappingEvents(newEvent, existingEvents);
|
|
expect(overlapping).toHaveLength(1);
|
|
|
|
// Step 2: Create stack links
|
|
const allEvents = [...overlapping, newEvent];
|
|
const stackLinks = manager.createStackLinks(allEvents);
|
|
expect(stackLinks.size).toBe(2);
|
|
|
|
// Step 3: Apply to DOM
|
|
const elementA = document.createElement('div');
|
|
elementA.dataset.eventId = 'existing-a';
|
|
|
|
const elementNew = document.createElement('div');
|
|
elementNew.dataset.eventId = 'new-event';
|
|
|
|
manager.applyStackLinkToElement(elementA, stackLinks.get('existing-a')!);
|
|
manager.applyStackLinkToElement(elementNew, stackLinks.get('new-event')!);
|
|
|
|
manager.applyVisualStyling(elementA, stackLinks.get('existing-a')!.stackLevel);
|
|
manager.applyVisualStyling(elementNew, stackLinks.get('new-event')!.stackLevel);
|
|
|
|
// Verify DOM state
|
|
expect(elementA.dataset.stackLink).toContain('"stackLevel":0');
|
|
expect(elementA.style.marginLeft).toBe('0px');
|
|
|
|
expect(elementNew.dataset.stackLink).toContain('"stackLevel":1');
|
|
expect(elementNew.style.marginLeft).toBe('15px');
|
|
});
|
|
});
|
|
});
|