import { describe, it, expect, beforeEach } from 'vitest' import { createSampleItems, createSampleProject, createSampleTemplate } from './testHelpers.js' // Import generator by loading it as text and evaluating const fs = await import('fs/promises') const generatorCode = await fs.readFile('./generator.js', 'utf-8') describe('Timeline Generator', () => { let timelineGenerator beforeEach(() => { // Reset global window object global.window = global // Execute generator code eval(generatorCode) timelineGenerator = global.window.timelineGenerator }) describe('escapeXml', () => { it('should escape XML special characters', () => { const input = '' const expected = '<test & "quotes" & 'apostrophes'>' expect(timelineGenerator.escapeXml(input)).toBe(expected) }) it('should handle empty and null values', () => { expect(timelineGenerator.escapeXml('')).toBe('') expect(timelineGenerator.escapeXml(null)).toBe('null') expect(timelineGenerator.escapeXml(undefined)).toBe('undefined') }) it('should convert non-strings to strings', () => { expect(timelineGenerator.escapeXml(123)).toBe('123') expect(timelineGenerator.escapeXml(true)).toBe('true') }) }) describe('generate', () => { let items, config beforeEach(() => { items = createSampleItems() config = createSampleProject() }) it('should generate SVG with template placeholders', () => { const template = createSampleTemplate() const result = timelineGenerator.generate(items, config, template) expect(result).toContain('') expect(result).toContain('') expect(result).not.toContain('{{MONTHS}}') expect(result).not.toContain('{{LANES}}') }) it('should generate fallback SVG when no template provided', () => { const result = timelineGenerator.generate(items, config, null) expect(result).toContain(' { const result = timelineGenerator.generate(items, config, null) expect(result).toContain(' { const result = timelineGenerator.generate(items, config, null) expect(result).toContain('Development') expect(result).toContain('Testing') expect(result).toContain(' { const result = timelineGenerator.generate(items, config, null) expect(result).toContain(' { // Add items with same lane but different dates const unsortedItems = [ { id: 'T-3', title: 'Third', lane: 'Dev', due: new Date('2025-03-01') }, { id: 'T-1', title: 'First', lane: 'Dev', due: new Date('2025-01-01') }, { id: 'T-2', title: 'Second', lane: 'Dev', due: new Date('2025-02-01') } ] const result = timelineGenerator.generate(unsortedItems, config, null) const firstIndex = result.indexOf('First') const secondIndex = result.indexOf('Second') const thirdIndex = result.indexOf('Third') expect(firstIndex).toBeLessThan(secondIndex) expect(secondIndex).toBeLessThan(thirdIndex) }) it('should handle items without lanes', () => { const itemsNoLane = [ { id: 'T-1', title: 'No Lane Task', lane: null, due: new Date('2025-01-01') } ] const result = timelineGenerator.generate(itemsNoLane, config, null) expect(result).toContain('Ohne Epic') }) it('should respect timelineMonths setting', () => { const shortConfig = { ...config, settings: { timelineMonths: 6 } } const result = timelineGenerator.generate(items, shortConfig, null) // Should create 6 months worth of grid lines const lineCount = (result.match(/ { const itemsWithSpecialChars = [{ id: 'T&1', title: 'Task with & "characters"', lane: 'Development', due: new Date('2025-01-01') }] const result = timelineGenerator.generate(itemsWithSpecialChars, config, null) expect(result).toContain('T&1') expect(result).toContain('<special> & "characters"') }) it('should determine start date from earliest item', () => { const itemsWithEarlyDate = [ { id: 'T-1', title: 'Early', lane: 'Dev', due: new Date('2024-06-15') }, { id: 'T-2', title: 'Late', lane: 'Dev', due: new Date('2025-12-01') } ] const result = timelineGenerator.generate(itemsWithEarlyDate, config, null) // Should start from June 2024 (first day of month) expect(result).toContain('Jun 24') }) it('should clamp item positions to timeline bounds', () => { const itemsOutOfRange = [ { id: 'T-1', title: 'In Range', lane: 'Dev', due: new Date('2025-01-01') }, { id: 'T-2', title: 'Way Future', lane: 'Dev', due: new Date('2030-01-01') } ] // Should not throw and should generate valid SVG const result = timelineGenerator.generate(itemsOutOfRange, config, null) expect(result).toContain('