test: improve test infrastructure and fix test assertions

- 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>
This commit is contained in:
2025-11-27 08:59:23 +01:00
parent bc756fa0cd
commit cf86b45b93
9 changed files with 414 additions and 37 deletions

View File

@@ -1,6 +1,6 @@
import { describe, it, expect, beforeEach, vi } from 'vitest'
import { setupBasicDOM, createMockElement } from './setup.js'
import { createSampleProject, createSampleCSV, mockFetch, expectElementToHaveText } from './testHelpers.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
@@ -56,6 +56,7 @@ describe('Timeline Engine', () => {
mockFetch('<svg></svg>')
mockFetch(createSampleCSV())
mockPapaParse()
await timelineEngine.loadProjectConfigObject(config)
@@ -76,9 +77,16 @@ describe('Timeline Engine', () => {
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)
expect(document.getElementById('dynamicCss').href).toBe('')
// 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 () => {
@@ -86,6 +94,9 @@ describe('Timeline Engine', () => {
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()
@@ -156,10 +167,12 @@ describe('Timeline Engine', () => {
})
describe('autoLoadDefaultProject', () => {
it('should try binect/project.json first, then example/project.json', async () => {
it('should try binect/project.json first, then my-project, then example', async () => {
// First call (binect) fails
global.fetch.mockResolvedValueOnce({ ok: false })
// Second call (example) succeeds
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())
@@ -167,6 +180,7 @@ describe('Timeline Engine', () => {
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')
})

View File

@@ -48,7 +48,7 @@ describe('Timeline Generator', () => {
const template = createSampleTemplate()
const result = timelineGenerator.generate(items, config, template)
expect(result).toContain('<svg xmlns="http://www.w3.org/2000/svg">')
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}}')
@@ -147,8 +147,8 @@ describe('Timeline Generator', () => {
const result = timelineGenerator.generate(itemsWithEarlyDate, config, null)
// Should start from June 2024 (first day of month)
expect(result).toContain('Jun 24')
// 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', () => {

View File

@@ -1,6 +1,6 @@
import { describe, it, expect, beforeEach, vi } from 'vitest'
import { setupBasicDOM } from './setup.js'
import { createSampleProject, createSampleCSV, createSampleTemplate, mockFetch } from './testHelpers.js'
import { createSampleProject, createSampleCSV, createSampleTemplate, mockFetch, mockPapaParse } from './testHelpers.js'
// Import both engine and generator
const fs = await import('fs/promises')
@@ -33,21 +33,7 @@ describe('Timeline Integration', () => {
// Mock fetch calls in order: template, CSV
mockFetch(template)
mockFetch(csvData)
// Mock Papa.parse to process the CSV
global.Papa.parse.mockImplementation((text, options) => {
const lines = text.trim().split('\n')
const headers = lines[0].split(',')
const data = lines.slice(1).map(line => {
const values = line.split(',')
const obj = {}
headers.forEach((header, i) => {
obj[header] = values[i]
})
return obj
})
options.complete({ data })
})
mockPapaParse()
await timelineEngine.loadProjectConfigObject(config)
@@ -124,11 +110,11 @@ describe('Timeline Integration', () => {
// Mock fetch failures
global.fetch.mockRejectedValue(new Error('Network error'))
const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
await timelineEngine.autoLoadDefaultProject()
// Should not crash, just log warnings
// Should not crash, just log messages
expect(consoleSpy).toHaveBeenCalled()
consoleSpy.mockRestore()
@@ -142,6 +128,9 @@ describe('Timeline Integration', () => {
projectInput.id = 'projectInput'
document.body.appendChild(projectInput)
// Setup event handlers
window.setupEventHandlers()
// Mock file reading
const mockFile = new File([JSON.stringify(config)], 'project.json', { type: 'application/json' })
mockFile.text = vi.fn().mockResolvedValue(JSON.stringify(config))
@@ -149,10 +138,7 @@ describe('Timeline Integration', () => {
// Mock the fetch calls that loadProjectConfigObject will make
mockFetch(createSampleTemplate())
mockFetch(createSampleCSV())
global.Papa.parse.mockImplementation((text, options) => {
options.complete({ data: [] })
})
mockPapaParse()
// Simulate file selection
Object.defineProperty(projectInput, 'files', {
@@ -178,6 +164,9 @@ describe('Timeline Integration', () => {
csvInput.id = 'csvInput'
document.body.appendChild(csvInput)
// Setup event handlers
window.setupEventHandlers()
const csvContent = createSampleCSV()
const mockFile = new File([csvContent], 'data.csv', { type: 'text/csv' })
mockFile.text = vi.fn().mockResolvedValue(csvContent)

View File

@@ -35,5 +35,9 @@ export const setupBasicDOM = () => {
<div id="viewer"></div>
<link id="dynamicCss" rel="stylesheet" href="">
<button id="downloadSvg"></button>
<span id="projectFile"></span>
<span id="csvFile"></span>
<span id="svgFile"></span>
<span id="cssFile"></span>
`
}

View File

@@ -52,11 +52,30 @@ export const createSampleTemplate = () => `<svg xmlns="http://www.w3.org/2000/sv
export const mockFetch = (data, ok = true) => {
global.fetch.mockResolvedValueOnce({
ok,
status: ok ? 200 : 404,
statusText: ok ? 'OK' : 'Not Found',
json: () => Promise.resolve(data),
text: () => Promise.resolve(typeof data === 'string' ? data : JSON.stringify(data))
})
}
// Helper to mock Papa.parse with proper CSV parsing
export const mockPapaParse = () => {
global.Papa.parse.mockImplementation((text, options) => {
const lines = text.trim().split('\n')
const headers = lines[0].split(',')
const data = lines.slice(1).map(line => {
const values = line.split(',')
const obj = {}
headers.forEach((header, i) => {
obj[header] = values[i]
})
return obj
})
options.complete({ data })
})
}
export const expectElementToHaveText = (selector, text) => {
const element = document.querySelector(selector)
expect(element).toBeTruthy()