per status with canonical emoji label', () => {
+ const html = renderBoard([], { generatedAt: now });
+
+ expect(html).toContain('data-status="open"');
+ expect(html).toContain('data-status="in_progress"');
+ expect(html).toContain('data-status="paused"');
+ expect(html).toContain('data-status="blocked"');
+ expect(html).toContain('data-status="done"');
+ expect(html).toContain('⚪');
+ expect(html).toContain('🔴');
+ expect(html).toContain('🟡');
+ expect(html).toContain('🔵');
+ expect(html).toContain('🟢');
+ });
+
+ test('renders a card per record with data-slug and data-raw-url', () => {
+ const records = [
+ rec({
+ slug: 'task-a',
+ title: 'first task',
+ raw_url: 'https://git.example/raw/task-a.md',
+ last_commit_iso: '2026-05-19T12:00:00Z',
+ }),
+ ];
+
+ const html = renderBoard(records, { generatedAt: now });
+
+ expect(html).toContain('data-slug="task-a"');
+ expect(html).toContain('data-raw-url="https://git.example/raw/task-a.md"');
+ expect(html).toContain('first task');
+ expect(html).toContain('3d');
+ });
+
+ test('truncates title longer than 80 chars with ellipsis in the card body', () => {
+ const longTitle = 'x'.repeat(100);
+ const html = renderBoard([rec({ title: longTitle })], { generatedAt: now });
+ const cardMatch = /([^<]*)<\/div>/.exec(html);
+
+ expect(cardMatch).not.toBeNull();
+ expect(cardMatch![1]).toBe('x'.repeat(80) + '…');
+ });
+
+ test('escapes HTML special chars in user-provided fields', () => {
+ const html = renderBoard(
+ [rec({ slug: 'safe', title: '' })],
+ { generatedAt: now },
+ );
+
+ expect(html).not.toContain('