generated from coulomb/repo-seed
- Add mockPapaParse helper to centralize CSV mocking - Fix test assertions to match actual output (German month names) - Add missing DOM elements to test setup (projectFile, csvFile, etc.) - Update vitest to v4.0.14 for improved testing capabilities - Make setupEventHandlers globally accessible for testing - Use textContent instead of innerText for better consistency - Fix autoLoadDefaultProject test to check all three fallback paths - Add proper event handler setup in integration tests - Improve error handling test assertions 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
194 lines
6.7 KiB
JavaScript
194 lines
6.7 KiB
JavaScript
import { describe, it, expect, beforeEach, vi } from 'vitest'
|
|
import { setupBasicDOM, createMockElement } from './setup.js'
|
|
import { createSampleProject, createSampleCSV, mockFetch, mockPapaParse, expectElementToHaveText } from './testHelpers.js'
|
|
|
|
// Import the engine by loading it as text and evaluating
|
|
// This is needed because engine.js creates a global object
|
|
const fs = await import('fs/promises')
|
|
const engineCode = await fs.readFile('./engine.js', 'utf-8')
|
|
|
|
describe('Timeline Engine', () => {
|
|
let timelineEngine
|
|
|
|
beforeEach(async () => {
|
|
setupBasicDOM()
|
|
|
|
// Reset global window object
|
|
global.window = global
|
|
global.timelineGenerator = { generate: vi.fn() }
|
|
|
|
// Execute engine code to create timelineEngine
|
|
eval(engineCode)
|
|
timelineEngine = global.window.timelineEngine
|
|
})
|
|
|
|
describe('parseDate', () => {
|
|
it('should parse YYYY-MM-DD format', () => {
|
|
const result = timelineEngine.parseDate('2025-12-15')
|
|
expect(result).toEqual(new Date(2025, 11, 15))
|
|
})
|
|
|
|
it('should parse YYYY/MM/DD format', () => {
|
|
const result = timelineEngine.parseDate('2025/12/15')
|
|
expect(result).toEqual(new Date(2025, 11, 15))
|
|
})
|
|
|
|
it('should parse DD.MM.YYYY format', () => {
|
|
const result = timelineEngine.parseDate('15.12.2025')
|
|
expect(result).toEqual(new Date(2025, 11, 15))
|
|
})
|
|
|
|
it('should return null for invalid dates', () => {
|
|
expect(timelineEngine.parseDate('invalid')).toBeNull()
|
|
expect(timelineEngine.parseDate('')).toBeNull()
|
|
expect(timelineEngine.parseDate(null)).toBeNull()
|
|
})
|
|
|
|
it('should handle single digit months and days', () => {
|
|
const result = timelineEngine.parseDate('2025-1-5')
|
|
expect(result).toEqual(new Date(2025, 0, 5))
|
|
})
|
|
})
|
|
|
|
describe('loadProjectConfigObject', () => {
|
|
it('should load project configuration and update DOM', async () => {
|
|
const config = createSampleProject()
|
|
|
|
mockFetch('<svg></svg>')
|
|
mockFetch(createSampleCSV())
|
|
mockPapaParse()
|
|
|
|
await timelineEngine.loadProjectConfigObject(config)
|
|
|
|
expect(timelineEngine.config).toEqual(config)
|
|
expectElementToHaveText('#projectName', 'Test Project')
|
|
expectElementToHaveText('#projectSubtitle', 'A test project for unit testing')
|
|
})
|
|
|
|
it('should handle missing stylesheet gracefully', async () => {
|
|
const config = { name: 'Test', description: 'Test desc' }
|
|
|
|
await timelineEngine.loadProjectConfigObject(config)
|
|
|
|
expect(timelineEngine.config).toEqual(config)
|
|
})
|
|
|
|
it('should not override CSS when cssOverride is true', async () => {
|
|
timelineEngine.cssOverride = true
|
|
const config = createSampleProject()
|
|
|
|
// Still need to mock SVG template and CSV fetches
|
|
mockFetch('<svg></svg>')
|
|
mockFetch(createSampleCSV())
|
|
mockPapaParse()
|
|
|
|
await timelineEngine.loadProjectConfigObject(config)
|
|
|
|
// Should not load the stylesheet from config
|
|
const href = document.getElementById('dynamicCss').href
|
|
expect(href).not.toContain(config.stylesheet)
|
|
})
|
|
|
|
it('should not load CSV when csvOverride is true', async () => {
|
|
timelineEngine.csvOverride = true
|
|
const config = createSampleProject()
|
|
const processCsvSpy = vi.spyOn(timelineEngine, 'processCsv')
|
|
|
|
// Still need to mock SVG template fetch
|
|
mockFetch('<svg></svg>')
|
|
|
|
await timelineEngine.loadProjectConfigObject(config)
|
|
|
|
expect(processCsvSpy).not.toHaveBeenCalled()
|
|
})
|
|
})
|
|
|
|
describe('processCsv', () => {
|
|
beforeEach(() => {
|
|
timelineEngine.config = createSampleProject()
|
|
global.Papa.parse.mockImplementation((text, options) => {
|
|
const mockData = [
|
|
{ ID: 'T-1', Title: 'Task 1', Lane: 'Dev', Due: '2025-01-15' },
|
|
{ ID: 'T-2', Title: 'Task 2', Lane: 'Test', Due: '2025-02-20' },
|
|
{ ID: '', Title: '', Lane: '', Due: '' } // Empty row should be filtered
|
|
]
|
|
options.complete({ data: mockData })
|
|
})
|
|
})
|
|
|
|
it('should process CSV and generate timeline', () => {
|
|
global.timelineGenerator.generate.mockReturnValue('<svg>test</svg>')
|
|
|
|
timelineEngine.processCsv(createSampleCSV())
|
|
|
|
expect(global.Papa.parse).toHaveBeenCalled()
|
|
expect(global.timelineGenerator.generate).toHaveBeenCalled()
|
|
expect(document.getElementById('viewer').innerHTML).toBe('<svg>test</svg>')
|
|
})
|
|
|
|
it('should filter out items without title or due date', () => {
|
|
global.timelineGenerator.generate.mockImplementation((items) => {
|
|
expect(items).toHaveLength(2) // Empty row should be filtered out
|
|
return '<svg>test</svg>'
|
|
})
|
|
|
|
timelineEngine.processCsv(createSampleCSV())
|
|
})
|
|
|
|
it('should handle missing config gracefully', () => {
|
|
timelineEngine.config = null
|
|
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
|
|
|
timelineEngine.processCsv(createSampleCSV())
|
|
|
|
expect(consoleSpy).toHaveBeenCalledWith('No config or fieldMapping found.')
|
|
consoleSpy.mockRestore()
|
|
})
|
|
|
|
it('should show message when no valid items found', () => {
|
|
global.Papa.parse.mockImplementation((text, options) => {
|
|
options.complete({ data: [] })
|
|
})
|
|
|
|
timelineEngine.processCsv('')
|
|
|
|
expect(document.getElementById('viewer').innerHTML).toContain('Keine gültigen Items gefunden')
|
|
})
|
|
|
|
it('should enable download button after successful generation', () => {
|
|
global.timelineGenerator.generate.mockReturnValue('<svg>test</svg>')
|
|
const downloadBtn = document.getElementById('downloadSvg')
|
|
|
|
timelineEngine.processCsv(createSampleCSV())
|
|
|
|
expect(downloadBtn.disabled).toBe(false)
|
|
expect(downloadBtn.style.opacity).toBe('1')
|
|
})
|
|
})
|
|
|
|
describe('autoLoadDefaultProject', () => {
|
|
it('should try binect/project.json first, then my-project, then example', async () => {
|
|
// First call (binect) fails
|
|
global.fetch.mockResolvedValueOnce({ ok: false, status: 404, statusText: 'Not Found' })
|
|
// Second call (my-project) fails
|
|
global.fetch.mockResolvedValueOnce({ ok: false, status: 404, statusText: 'Not Found' })
|
|
// Third call (example) succeeds
|
|
mockFetch(createSampleProject())
|
|
mockFetch('<svg></svg>')
|
|
mockFetch(createSampleCSV())
|
|
|
|
await timelineEngine.autoLoadDefaultProject()
|
|
|
|
expect(global.fetch).toHaveBeenCalledWith('binect/project.json')
|
|
expect(global.fetch).toHaveBeenCalledWith('my-project/project.json')
|
|
expect(global.fetch).toHaveBeenCalledWith('example/project.json')
|
|
})
|
|
|
|
it('should handle all fetch failures gracefully', async () => {
|
|
global.fetch.mockRejectedValue(new Error('Network error'))
|
|
|
|
// Should not throw
|
|
await expect(timelineEngine.autoLoadDefaultProject()).resolves.toBeUndefined()
|
|
})
|
|
})
|
|
}) |