From 6d23551abd147d64ba8d6efc40e604509083fa82 Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Fri, 27 Mar 2026 08:37:15 -0600 Subject: [PATCH] =?UTF-8?q?test:=20gallery=20generation=20=E2=80=94=20sess?= =?UTF-8?q?ions,=20dates,=20corruption,=20empty=20state?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 7 tests: empty dir, nonexistent dir, single session with approved variant, multiple sessions sorted newest-first, corrupted approved.json handled gracefully, session without approved.json, self-contained HTML (no external dependencies). Co-Authored-By: Claude Opus 4.6 (1M context) --- design/test/gallery.test.ts | 139 ++++++++++++++++++++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 design/test/gallery.test.ts diff --git a/design/test/gallery.test.ts b/design/test/gallery.test.ts new file mode 100644 index 00000000..7eaebc61 --- /dev/null +++ b/design/test/gallery.test.ts @@ -0,0 +1,139 @@ +/** + * Tests for the $D gallery command — design history timeline generation. + */ + +import { describe, test, expect, beforeAll, afterAll } from 'bun:test'; +import { generateGalleryHtml } from '../src/gallery'; +import * as fs from 'fs'; +import * as path from 'path'; + +let tmpDir: string; + +function createTestPng(filePath: string): void { + const png = Buffer.from( + 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/58BAwAI/AL+hc2rNAAAAABJRU5ErkJggg==', + 'base64' + ); + fs.writeFileSync(filePath, png); +} + +beforeAll(() => { + tmpDir = '/tmp/gallery-test-' + Date.now(); + fs.mkdirSync(tmpDir, { recursive: true }); +}); + +afterAll(() => { + fs.rmSync(tmpDir, { recursive: true, force: true }); +}); + +describe('Gallery generation', () => { + test('empty directory returns "No history" page', () => { + const emptyDir = path.join(tmpDir, 'empty'); + fs.mkdirSync(emptyDir, { recursive: true }); + + const html = generateGalleryHtml(emptyDir); + expect(html).toContain('No design history yet'); + expect(html).toContain('/design-shotgun'); + }); + + test('nonexistent directory returns "No history" page', () => { + const html = generateGalleryHtml('/nonexistent/path'); + expect(html).toContain('No design history yet'); + }); + + test('single session with approved variant', () => { + const sessionDir = path.join(tmpDir, 'designs', 'homepage-20260327'); + fs.mkdirSync(sessionDir, { recursive: true }); + + createTestPng(path.join(sessionDir, 'variant-A.png')); + createTestPng(path.join(sessionDir, 'variant-B.png')); + createTestPng(path.join(sessionDir, 'variant-C.png')); + + fs.writeFileSync(path.join(sessionDir, 'approved.json'), JSON.stringify({ + approved_variant: 'B', + feedback: 'Great spacing and colors', + date: '2026-03-27T12:00:00Z', + screen: 'homepage', + })); + + const html = generateGalleryHtml(path.join(tmpDir, 'designs')); + expect(html).toContain('Design History'); + expect(html).toContain('1 exploration'); + expect(html).toContain('homepage'); + expect(html).toContain('2026-03-27'); + expect(html).toContain('approved'); + expect(html).toContain('Great spacing and colors'); + // Should have 3 variant images (base64) + expect(html).toContain('data:image/png;base64,'); + }); + + test('multiple sessions sorted by date (newest first)', () => { + const dir = path.join(tmpDir, 'multi'); + const session1 = path.join(dir, 'settings-20260301'); + const session2 = path.join(dir, 'dashboard-20260315'); + fs.mkdirSync(session1, { recursive: true }); + fs.mkdirSync(session2, { recursive: true }); + + createTestPng(path.join(session1, 'variant-A.png')); + createTestPng(path.join(session2, 'variant-A.png')); + + fs.writeFileSync(path.join(session1, 'approved.json'), JSON.stringify({ + approved_variant: 'A', date: '2026-03-01T12:00:00Z', + })); + fs.writeFileSync(path.join(session2, 'approved.json'), JSON.stringify({ + approved_variant: 'A', date: '2026-03-15T12:00:00Z', + })); + + const html = generateGalleryHtml(dir); + expect(html).toContain('2 explorations'); + // Dashboard (Mar 15) should appear before settings (Mar 1) + const dashIdx = html.indexOf('dashboard'); + const settingsIdx = html.indexOf('settings'); + expect(dashIdx).toBeLessThan(settingsIdx); + }); + + test('corrupted approved.json is handled gracefully', () => { + const dir = path.join(tmpDir, 'corrupt'); + const session = path.join(dir, 'broken-20260327'); + fs.mkdirSync(session, { recursive: true }); + + createTestPng(path.join(session, 'variant-A.png')); + fs.writeFileSync(path.join(session, 'approved.json'), 'NOT VALID JSON {{{'); + + const html = generateGalleryHtml(dir); + // Should still render the session, just without any variant marked as approved + expect(html).toContain('Design History'); + expect(html).toContain('broken'); + // The class "approved" should not appear on any variant div (only in CSS definition) + expect(html).not.toContain('class="gallery-variant approved"'); + }); + + test('session without approved.json still renders', () => { + const dir = path.join(tmpDir, 'no-approved'); + const session = path.join(dir, 'draft-20260327'); + fs.mkdirSync(session, { recursive: true }); + + createTestPng(path.join(session, 'variant-A.png')); + createTestPng(path.join(session, 'variant-B.png')); + + const html = generateGalleryHtml(dir); + expect(html).toContain('draft'); + // No variant should be marked as approved + expect(html).not.toContain('class="gallery-variant approved"'); + }); + + test('HTML is self-contained (no external dependencies)', () => { + const dir = path.join(tmpDir, 'self-contained'); + const session = path.join(dir, 'test-20260327'); + fs.mkdirSync(session, { recursive: true }); + createTestPng(path.join(session, 'variant-A.png')); + + const html = generateGalleryHtml(dir); + // No external CSS/JS/image links + expect(html).not.toContain('href="http'); + expect(html).not.toContain('src="http'); + expect(html).not.toContain('