Files
timeline-svg/test/generator.test.js
tegwick 2090e372bd add: comprehensive TDD test infrastructure
- Add Vitest + jsdom testing framework
- Create unit tests for engine.js and generator.js
- Add integration tests for end-to-end workflows
- Include test utilities and setup helpers
- Document testing approach in TESTING.md
- Document all dependencies in DEPENDS.md
- Add Makefile with test targets and dev workflow

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-18 23:36:22 +01:00

167 lines
6.0 KiB
JavaScript

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 = '<test & "quotes" & \'apostrophes\'>'
const expected = '&lt;test &amp; &quot;quotes&quot; &amp; &#39;apostrophes&#39;&gt;'
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('<svg xmlns="http://www.w3.org/2000/svg">')
expect(result).toContain('<rect width="100%" height="100%" fill="#FFFFFF"/>')
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('<svg xmlns="http://www.w3.org/2000/svg"')
expect(result).toContain('width=')
expect(result).toContain('height=')
expect(result).toContain('<rect width="100%" height="100%" fill="#FFFFFF"')
})
it('should create month labels and grid lines', () => {
const result = timelineGenerator.generate(items, config, null)
expect(result).toContain('<line')
expect(result).toContain('stroke="#E3E8EF"')
expect(result).toContain('<text')
expect(result).toContain('fill="#5C6B7A"')
})
it('should create lane backgrounds and labels', () => {
const result = timelineGenerator.generate(items, config, null)
expect(result).toContain('Development')
expect(result).toContain('Testing')
expect(result).toContain('<rect')
expect(result).toContain('fill="#FFFFFF"')
})
it('should position items correctly in lanes', () => {
const result = timelineGenerator.generate(items, config, null)
expect(result).toContain('<circle')
expect(result).toContain('fill="#0A4D8C"')
expect(result).toContain('T-1')
expect(result).toContain('First Task')
})
it('should sort items by due date within lanes', () => {
// 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(/<line/g) || []).length
expect(lineCount).toBeGreaterThan(0)
})
it('should escape special characters in item text', () => {
const itemsWithSpecialChars = [{
id: 'T&1',
title: 'Task with <special> & "characters"',
lane: 'Development',
due: new Date('2025-01-01')
}]
const result = timelineGenerator.generate(itemsWithSpecialChars, config, null)
expect(result).toContain('T&amp;1')
expect(result).toContain('&lt;special&gt; &amp; &quot;characters&quot;')
})
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('<svg')
expect(result).toContain('T-1')
expect(result).toContain('T-2')
})
})
})