refactor(06-02): migrate test imports to new module locations

This commit is contained in:
Christopher Mühl 2026-04-09 09:47:18 +02:00
parent aab3915681
commit 881718e3f0
No known key found for this signature in database
GPG key ID: 925AC7D69955293F
3 changed files with 62 additions and 21 deletions

View file

@ -1,6 +1,20 @@
import { describe, it, expect, beforeEach } from 'vitest'; import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { renderHeatmap } from '../src/heatmap'; import { YearModeRenderer } from '../src/renderers/year';
import type { RenderContext } from '../src/renderers/types';
import type { HeatmapData } from '../src/types'; import type { HeatmapData } from '../src/types';
import { createInitialState } from '../src/state';
const DEFAULT_CONFIG = { cellSize: 13, cellGap: 2, marginTop: 20, marginLeft: 30, marginBottom: 4 };
function makeCtx(container: HTMLElement, data: HeatmapData, overrides?: Partial<RenderContext>): RenderContext {
return {
container,
data,
state: createInitialState('monday'),
config: DEFAULT_CONFIG,
...overrides,
};
}
function makeMockData(overrides: Partial<HeatmapData> = {}): HeatmapData { function makeMockData(overrides: Partial<HeatmapData> = {}): HeatmapData {
return { return {
@ -21,34 +35,41 @@ function makeMockData(overrides: Partial<HeatmapData> = {}): HeatmapData {
describe('renderHeatmap', () => { describe('renderHeatmap', () => {
let container: HTMLDivElement; let container: HTMLDivElement;
let renderer: YearModeRenderer;
beforeEach(() => { beforeEach(() => {
container = document.createElement('div'); container = document.createElement('div');
document.body.appendChild(container); document.body.appendChild(container);
renderer = new YearModeRenderer();
});
afterEach(() => {
renderer.destroy();
document.querySelectorAll('.heatmap-tooltip').forEach(el => el.remove());
}); });
it('renders an SVG element', () => { it('renders an SVG element', () => {
renderHeatmap(container, makeMockData()); renderer.render(makeCtx(container, makeMockData()));
const svg = container.querySelector('svg'); const svg = container.querySelector('svg');
expect(svg).not.toBeNull(); expect(svg).not.toBeNull();
}); });
it('renders correct number of rect elements for date range', () => { it('renders correct number of rect elements for date range', () => {
renderHeatmap(container, makeMockData()); renderer.render(makeCtx(container, makeMockData()));
const rects = container.querySelectorAll('rect.heatmap-cell'); const rects = container.querySelectorAll('rect.heatmap-cell');
// Jan 1 to Jan 31 = 31 days // Jan 1 to Jan 31 = 31 days
expect(rects.length).toBe(31); expect(rects.length).toBe(31);
}); });
it('applies heatmap-empty class to cells with no data', () => { it('applies heatmap-empty class to cells with no data', () => {
renderHeatmap(container, makeMockData()); renderer.render(makeCtx(container, makeMockData()));
const emptyRects = container.querySelectorAll('rect.heatmap-empty'); const emptyRects = container.querySelectorAll('rect.heatmap-empty');
// 31 days total, 5 with data = 26 empty // 31 days total, 5 with data = 26 empty
expect(emptyRects.length).toBe(26); expect(emptyRects.length).toBe(26);
}); });
it('applies fill attribute to cells with data', () => { it('applies fill attribute to cells with data', () => {
renderHeatmap(container, makeMockData()); renderer.render(makeCtx(container, makeMockData()));
const allRects = container.querySelectorAll('rect.heatmap-cell:not(.heatmap-empty)'); const allRects = container.querySelectorAll('rect.heatmap-cell:not(.heatmap-empty)');
expect(allRects.length).toBe(5); expect(allRects.length).toBe(5);
allRects.forEach((rect) => { allRects.forEach((rect) => {
@ -57,7 +78,7 @@ describe('renderHeatmap', () => {
}); });
it('renders day labels (Mon, Wed, Fri)', () => { it('renders day labels (Mon, Wed, Fri)', () => {
renderHeatmap(container, makeMockData()); renderer.render(makeCtx(container, makeMockData()));
const dayLabels = container.querySelectorAll('text.day-label'); const dayLabels = container.querySelectorAll('text.day-label');
expect(dayLabels.length).toBe(7); // all 7 slots rendered expect(dayLabels.length).toBe(7); // all 7 slots rendered
const texts = Array.from(dayLabels).map((el) => el.textContent); const texts = Array.from(dayLabels).map((el) => el.textContent);
@ -75,7 +96,7 @@ describe('renderHeatmap', () => {
{ date: '2025-02-10', hours: 4.0, count: 1 }, { date: '2025-02-10', hours: 4.0, count: 1 },
], ],
}); });
renderHeatmap(container, data); renderer.render(makeCtx(container, data));
const monthLabels = container.querySelectorAll('text.month-label'); const monthLabels = container.querySelectorAll('text.month-label');
expect(monthLabels.length).toBeGreaterThan(0); expect(monthLabels.length).toBeGreaterThan(0);
const texts = Array.from(monthLabels).map((el) => el.textContent); const texts = Array.from(monthLabels).map((el) => el.textContent);
@ -83,7 +104,7 @@ describe('renderHeatmap', () => {
}); });
it('creates tooltip on mouseenter', () => { it('creates tooltip on mouseenter', () => {
renderHeatmap(container, makeMockData()); renderer.render(makeCtx(container, makeMockData()));
const rect = container.querySelector( const rect = container.querySelector(
'rect.heatmap-cell:not(.heatmap-empty)', 'rect.heatmap-cell:not(.heatmap-empty)',
); );
@ -102,7 +123,7 @@ describe('renderHeatmap', () => {
}); });
it('hides tooltip on mouseleave', () => { it('hides tooltip on mouseleave', () => {
renderHeatmap(container, makeMockData()); renderer.render(makeCtx(container, makeMockData()));
const rect = container.querySelector( const rect = container.querySelector(
'rect.heatmap-cell:not(.heatmap-empty)', 'rect.heatmap-cell:not(.heatmap-empty)',
); );
@ -115,7 +136,7 @@ describe('renderHeatmap', () => {
}); });
it('handles empty days array gracefully', () => { it('handles empty days array gracefully', () => {
renderHeatmap(container, makeMockData({ days: [] })); renderer.render(makeCtx(container, makeMockData({ days: [] })));
const svg = container.querySelector('svg'); const svg = container.querySelector('svg');
expect(svg).toBeNull(); expect(svg).toBeNull();
expect(container.textContent).toContain('No tracking data available'); expect(container.textContent).toContain('No tracking data available');
@ -129,7 +150,7 @@ describe('renderHeatmap', () => {
], ],
range: { begin: '2025-03-01', end: '2025-03-07' }, range: { begin: '2025-03-01', end: '2025-03-07' },
}; };
renderHeatmap(container, data); renderer.render(makeCtx(container, data));
const rects = container.querySelectorAll('rect.heatmap-cell'); const rects = container.querySelectorAll('rect.heatmap-cell');
expect(rects.length).toBe(7); expect(rects.length).toBe(7);
}); });
@ -142,7 +163,7 @@ describe('renderHeatmap', () => {
], ],
range: { begin: '2025-01-01', end: '2025-01-07' }, range: { begin: '2025-01-01', end: '2025-01-07' },
}; };
renderHeatmap(container, data); renderer.render(makeCtx(container, data));
const weekendRects = container.querySelectorAll('rect.heatmap-weekend'); const weekendRects = container.querySelectorAll('rect.heatmap-weekend');
// Jan 1 (Wed) through Jan 7 (Tue): Sat Jan 4 + Sun Jan 5 = 2 weekend cells // Jan 1 (Wed) through Jan 7 (Tue): Sat Jan 4 + Sun Jan 5 = 2 weekend cells
expect(weekendRects.length).toBe(2); expect(weekendRects.length).toBe(2);
@ -155,14 +176,14 @@ describe('renderHeatmap', () => {
], ],
range: { begin: '2025-01-06', end: '2025-01-10' }, range: { begin: '2025-01-06', end: '2025-01-10' },
}; };
renderHeatmap(container, data); renderer.render(makeCtx(container, data));
const weekendRects = container.querySelectorAll('rect.heatmap-weekend'); const weekendRects = container.querySelectorAll('rect.heatmap-weekend');
// Mon-Fri, no weekends // Mon-Fri, no weekends
expect(weekendRects.length).toBe(0); expect(weekendRects.length).toBe(0);
}); });
it('renders day labels for Sunday week start', () => { it('renders day labels for Sunday week start', () => {
renderHeatmap(container, makeMockData(), undefined, undefined, undefined, 'sunday'); renderer.render(makeCtx(container, makeMockData(), { state: createInitialState('sunday') }));
const dayLabels = container.querySelectorAll('text.day-label'); const dayLabels = container.querySelectorAll('text.day-label');
const texts = Array.from(dayLabels).map((el) => el.textContent); const texts = Array.from(dayLabels).map((el) => el.textContent);
expect(texts).toContain('Sun'); expect(texts).toContain('Sun');

View file

@ -1,5 +1,7 @@
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { renderHeatmap, init } from '../src/heatmap'; import { init } from '../src/heatmap';
import { YearModeRenderer } from '../src/renderers/year';
import { createInitialState } from '../src/state';
import type { HeatmapData } from '../src/types'; import type { HeatmapData } from '../src/types';
const MOCK_DATA: HeatmapData = { const MOCK_DATA: HeatmapData = {
@ -10,22 +12,31 @@ const MOCK_DATA: HeatmapData = {
range: { begin: '2025-01-01', end: '2025-01-14' }, range: { begin: '2025-01-01', end: '2025-01-14' },
}; };
const DEFAULT_CONFIG = { cellSize: 13, cellGap: 2, marginTop: 20, marginLeft: 30, marginBottom: 4 };
describe('click navigation', () => { describe('click navigation', () => {
let container: HTMLDivElement; let container: HTMLDivElement;
let renderer: YearModeRenderer;
beforeEach(() => { beforeEach(() => {
container = document.createElement('div'); container = document.createElement('div');
document.body.appendChild(container); document.body.appendChild(container);
renderer = new YearModeRenderer();
}); });
afterEach(() => { afterEach(() => {
renderer.destroy();
document.querySelectorAll('.heatmap-tooltip').forEach(el => el.remove());
document.body.removeChild(container); document.body.removeChild(container);
vi.restoreAllMocks(); vi.restoreAllMocks();
}); });
it('calls onCellClick with dateStr when a data cell is clicked', () => { it('calls onCellClick with dateStr when a data cell is clicked', () => {
const onClick = vi.fn(); const onClick = vi.fn();
renderHeatmap(container, MOCK_DATA, undefined, onClick); renderer.render({
container, data: MOCK_DATA, state: createInitialState('monday'),
config: DEFAULT_CONFIG, onCellClick: onClick,
});
const cell = container.querySelector('rect.heatmap-cell:not(.heatmap-empty)') as SVGRectElement; const cell = container.querySelector('rect.heatmap-cell:not(.heatmap-empty)') as SVGRectElement;
cell.dispatchEvent(new MouseEvent('click', { bubbles: true })); cell.dispatchEvent(new MouseEvent('click', { bubbles: true }));
expect(onClick).toHaveBeenCalledOnce(); expect(onClick).toHaveBeenCalledOnce();
@ -34,20 +45,29 @@ describe('click navigation', () => {
it('calls onCellClick when an empty cell is clicked', () => { it('calls onCellClick when an empty cell is clicked', () => {
const onClick = vi.fn(); const onClick = vi.fn();
renderHeatmap(container, MOCK_DATA, undefined, onClick); renderer.render({
container, data: MOCK_DATA, state: createInitialState('monday'),
config: DEFAULT_CONFIG, onCellClick: onClick,
});
const emptyCell = container.querySelector('rect.heatmap-empty') as SVGRectElement; const emptyCell = container.querySelector('rect.heatmap-empty') as SVGRectElement;
emptyCell.dispatchEvent(new MouseEvent('click', { bubbles: true })); emptyCell.dispatchEvent(new MouseEvent('click', { bubbles: true }));
expect(onClick).toHaveBeenCalledOnce(); expect(onClick).toHaveBeenCalledOnce();
}); });
it('does not throw when no onCellClick provided', () => { it('does not throw when no onCellClick provided', () => {
renderHeatmap(container, MOCK_DATA); renderer.render({
container, data: MOCK_DATA, state: createInitialState('monday'),
config: DEFAULT_CONFIG,
});
const cell = container.querySelector('rect.heatmap-cell') as SVGRectElement; const cell = container.querySelector('rect.heatmap-cell') as SVGRectElement;
expect(() => cell.dispatchEvent(new MouseEvent('click', { bubbles: true }))).not.toThrow(); expect(() => cell.dispatchEvent(new MouseEvent('click', { bubbles: true }))).not.toThrow();
}); });
it('all cells have heatmap-cell class for cursor styling', () => { it('all cells have heatmap-cell class for cursor styling', () => {
renderHeatmap(container, MOCK_DATA); renderer.render({
container, data: MOCK_DATA, state: createInitialState('monday'),
config: DEFAULT_CONFIG,
});
const allRects = container.querySelectorAll('rect.heatmap-cell'); const allRects = container.querySelectorAll('rect.heatmap-cell');
expect(allRects.length).toBeGreaterThan(0); expect(allRects.length).toBeGreaterThan(0);
allRects.forEach((rect) => { allRects.forEach((rect) => {

View file

@ -1,7 +1,7 @@
import { describe, it, expect } from 'vitest'; import { describe, it, expect } from 'vitest';
import { timeFormat } from 'd3-time-format'; import { timeFormat } from 'd3-time-format';
import { timeDay } from 'd3-time'; import { timeDay } from 'd3-time';
import { calculateStreak, calculateStats } from '../src/heatmap'; import { calculateStreak, calculateStats } from '../src/shared/stats';
import type { DayEntry } from '../src/types'; import type { DayEntry } from '../src/types';
const DATE_FORMAT = timeFormat('%Y-%m-%d'); const DATE_FORMAT = timeFormat('%Y-%m-%d');