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).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) - German month name expect(result).toContain('Juni 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(' { let items, config beforeEach(() => { items = createSampleItems() config = createSampleProject() }) describe('hasTemplateElements', () => { it('should detect template elements', () => { const templateWithElements = ` ` expect(timelineGenerator.hasTemplateElements(templateWithElements)).toBe(true) }) it('should return false when template elements are missing', () => { const templateWithoutElements = '{{MONTHS}}' expect(timelineGenerator.hasTemplateElements(templateWithoutElements)).toBe(false) }) it('should return false when only some template elements exist', () => { const partialTemplate = '' expect(timelineGenerator.hasTemplateElements(partialTemplate)).toBe(false) }) }) describe('extractTemplate', () => { it('should extract template element by id', () => { const svg = ` {{LABEL}} ` const result = timelineGenerator.extractTemplate(svg, 'month-template') expect(result).toContain('id="month-template"') expect(result).toContain('{{X}}') expect(result).toContain('{{LABEL}}') }) it('should return null when template not found', () => { const svg = '' const result = timelineGenerator.extractTemplate(svg, 'month-template') expect(result).toBeNull() }) }) describe('replacePlaceholders', () => { it('should replace all placeholders with values', () => { const template = '{{LABEL}}' const values = { X: 100, Y: 200, LABEL: 'Test' } const result = timelineGenerator.replacePlaceholders(template, values) expect(result).toBe('Test') }) it('should handle multiple occurrences of same placeholder', () => { const template = '' const values = { X: 50 } const result = timelineGenerator.replacePlaceholders(template, values) expect(result).toBe('') }) it('should leave unmatched placeholders unchanged', () => { const template = '{{LABEL}} {{OTHER}}' const values = { LABEL: 'Test' } const result = timelineGenerator.replacePlaceholders(template, values) expect(result).toContain('Test') expect(result).toContain('{{OTHER}}') }) }) describe('generateFromTemplates', () => { it('should generate SVG using template elements', async () => { // Read actual template-v2.svg const fs = await import('fs/promises') const templateV2 = await fs.readFile('./example/template-v2.svg', 'utf-8') const result = timelineGenerator.generate(items, config, templateV2) // Should contain SVG structure expect(result).toContain(' { const incompleteTemplate = ` {{MONTHS}} {{LANES}} ` const result = timelineGenerator.generate(items, config, incompleteTemplate) // Should still generate valid SVG expect(result).toContain(' { const fs = await import('fs/promises') const templateV2 = await fs.readFile('./example/template-v2.svg', 'utf-8') const result = timelineGenerator.generate(items, config, templateV2) // Should preserve gradient and filter definitions expect(result).toContain('monthHeaderGrad') expect(result).toContain('textShadow') expect(result).toContain('bgGrid') }) }) }) })