// @vitest-environment jsdom import { act } from 'react'; import { createRoot, type Root } from 'react-dom/client'; import { renderToStaticMarkup } from 'react-dom/server'; import { afterEach, describe, expect, it, vi } from 'vitest'; import { FileWorkspace } from '../../src/components/FileWorkspace'; import type { AgentEvent, DesignSystemSummary, ProjectFile } from '../../src/types'; const registryMocks = vi.hoisted(() => ({ updateDesignSystemDraft: vi.fn(), })); vi.mock('../../src/providers/registry', async () => { const actual = await vi.importActual( '../../src/providers/registry', ); return { ...actual, updateDesignSystemDraft: registryMocks.updateDesignSystemDraft, }; }); let root: Root | null = null; let host: HTMLDivElement | null = null; (globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true; afterEach(() => { if (root) { act(() => root?.unmount()); root = null; } host?.remove(); host = null; vi.clearAllMocks(); vi.unstubAllGlobals(); }); function workspaceFile(name: string): ProjectFile { return { name, path: name, type: 'file', size: 100, mtime: Date.parse('2026-05-14T00:00:00.000Z'), kind: name.endsWith('.html') ? 'html' : name.endsWith('.svg') ? 'image' : 'text', mime: name.endsWith('.html') ? 'text/html' : name.endsWith('.svg') ? 'image/svg+xml' : 'text/plain', }; } function designSystem(overrides: Partial = {}): DesignSystemSummary { return { id: 'user:acme', title: 'Acme Design System', category: 'Custom', summary: 'Context project for Acme.', swatches: [], surface: 'web', source: 'user', status: 'draft', isEditable: true, ...overrides, }; } function renderWorkspace(element: React.ReactElement) { host = document.createElement('div'); document.body.appendChild(host); act(() => { root = createRoot(host!); root.render(element); }); return host; } type ToolUseEvent = Extract; type ToolResultEvent = Extract; function toolUse(name: string, input: unknown, id: string): ToolUseEvent { return { kind: 'tool_use', id, name, input }; } function toolOk(id: string): ToolResultEvent { return { kind: 'tool_result', toolUseId: id, content: '', isError: false }; } function todoWrite( todos: Array<{ content: string; status: 'pending' | 'in_progress' | 'completed'; activeForm?: string }>, ): ToolUseEvent { return toolUse('TodoWrite', { todos }, 'todo-write'); } describe('FileWorkspace design-system project surface', () => { it('keeps project-backed design systems inside the normal workspace tabs with inline preview cards', () => { const markup = renderToStaticMarkup( , ); expect(markup).toContain('data-testid="design-system-project-tab"'); expect(markup).toContain('data-testid="design-files-tab"'); expect(markup).toContain('Review Acme design system'); expect(markup).not.toContain('

Needs review

'); expect(markup).toContain('Type'); expect(markup).toContain('Colors'); expect(markup).toContain('Spacing'); expect(markup).toContain('Components'); expect(markup).toContain('Brand'); expect(markup).toContain('typography-specimens'); expect(markup).toContain('colors-primary'); expect(markup).toContain('spacing-tokens'); expect(markup).toContain('app'); expect(markup).toContain('brand-assets'); expect(markup).toContain(' { const markup = renderToStaticMarkup( , ); expect(markup).toContain('Creating your design system...'); expect(markup).toContain('Keep this tab open. You can come back in a few minutes.'); expect(markup).toContain('role="progressbar"'); expect(markup).not.toContain('Review Acme design system'); }); it('keeps generated preview cards hidden until the initial run finishes', () => { const markup = renderToStaticMarkup( , ); expect(markup).toContain('Creating your design system...'); expect(markup).not.toContain('Review Acme design system'); expect(markup).not.toContain('typography-specimens'); expect(markup).not.toContain(' { const container = renderWorkspace( , ); expect(container.textContent).toContain('Brand'); expect(container.textContent).toContain('brand-assets'); expect(container.textContent).not.toContain('context/github/acme-product.md'); expect(container.textContent).not.toContain('GitHub metadata: React UI library with token CSS.'); }); it('marks a section for review after the latest agent run edits it', () => { const markup = renderToStaticMarkup( , ); expect(markup).toContain('This section changed during the latest run. Review it before publishing.'); }); it('blocks publishing GitHub-backed design systems until connector evidence snapshots exist', async () => { const container = renderWorkspace( , ); const publishButton = container.querySelector( '[data-testid="design-system-publish"]', ); expect(container.textContent).toContain('Connect your repo to pull aspects of your design system'); expect(publishButton?.disabled).toBe(true); await act(async () => { publishButton?.click(); await Promise.resolve(); }); expect(registryMocks.updateDesignSystemDraft).not.toHaveBeenCalled(); }); it('keeps the disabled-publish guidance on a non-disabled wrapper so it stays reachable', () => { const container = renderWorkspace( , ); const publishButton = container.querySelector( '[data-testid="design-system-publish"]', ); expect(publishButton?.disabled).toBe(true); // A disabled button never fires the hover or focus that surfaces a `title`, so // the guidance has to live on a wrapper instead of on the button itself. const guidance = 'Finish importing your GitHub repo before you can publish.'; const carrier = container.querySelector(`[title="${guidance}"]`); expect(carrier).toBeTruthy(); expect(carrier?.tagName).not.toBe('BUTTON'); expect(carrier?.contains(publishButton ?? null)).toBe(true); }); it('publishes project-backed design systems and refreshes the registry state', async () => { registryMocks.updateDesignSystemDraft.mockResolvedValue( designSystem({ status: 'published' }), ); const onRefresh = vi.fn(); const container = renderWorkspace( , ); const publishButton = container.querySelector( '[data-testid="design-system-publish"]', ); await act(async () => { publishButton?.click(); await Promise.resolve(); }); expect(registryMocks.updateDesignSystemDraft).toHaveBeenCalledWith('user:acme', { status: 'published', }); expect(onRefresh).toHaveBeenCalledOnce(); expect(container.textContent).toContain('Acme design system'); }); it('offers a Connect GitHub action that routes to Connectors when repo evidence is missing', async () => { const onConnectRepo = vi.fn(); const container = renderWorkspace( , ); const connectButton = Array.from(container.querySelectorAll('button')).find((button) => button.textContent?.includes('Connect GitHub'), ); expect(connectButton).toBeTruthy(); await act(async () => { connectButton?.click(); await Promise.resolve(); }); expect(onConnectRepo).toHaveBeenCalledTimes(1); }); it('keeps the Connect GitHub action when evidence notes exist but file snapshots are still missing', () => { const container = renderWorkspace( , ); expect(container.textContent).toContain('Connect your repo to pull aspects of your design system'); const connectButton = Array.from(container.querySelectorAll('button')).find((button) => button.textContent?.includes('Connect GitHub'), ); expect(connectButton).toBeTruthy(); }); it('shows re-import guidance instead of Connect when GitHub is already connected', () => { const container = renderWorkspace( , ); expect(container.textContent).toContain('GitHub is connected'); expect(container.textContent).not.toContain('Connect your repo to pull aspects of your design system'); const importButton = Array.from(container.querySelectorAll('button')).find((button) => button.textContent?.includes('Import repo'), ); expect(importButton).toBeTruthy(); }); it('collapses a section once it is marked looks-good', () => { const container = renderWorkspace( , ); const items = Array.from(container.querySelectorAll('.ds-project-review-item')); const titleOf = (el: Element) => el.querySelector('.ds-project-section-title strong')?.textContent ?? ''; const reviewed = items.find((el) => titleOf(el) === 'typography-specimens'); const unreviewed = items.find((el) => titleOf(el) === 'colors-primary'); // A validated section collapses and reads as "Looks good". expect(reviewed?.classList.contains('is-collapsed')).toBe(true); expect(reviewed?.querySelector('.ds-project-section-state')?.textContent).toContain('Looks good'); // An unreviewed section stays expanded for review. expect(unreviewed?.classList.contains('is-expanded')).toBe(true); }); it('reopens a looks-good section after it is regenerated so the review-again prompt stays visible', () => { const container = renderWorkspace( , ); const items = Array.from(container.querySelectorAll('.ds-project-review-item')); const titleOf = (el: Element) => el.querySelector('.ds-project-section-title strong')?.textContent ?? ''; const colors = items.find((el) => titleOf(el) === 'colors'); // The stored decision is still "looks-good", but the regenerated files moved // the section back to "updated". It has to reopen instead of staying collapsed // behind the stale decision, so the review-again notice and the review buttons // are visible again. expect(colors?.classList.contains('is-expanded')).toBe(true); expect(colors?.classList.contains('is-collapsed')).toBe(false); expect(colors?.querySelector('.ds-project-review-actions')).toBeTruthy(); expect(colors?.textContent).toContain('before publishing'); }); it('routes the default checkbox to the selected design system id', async () => { const onSetDefault = vi.fn(); const container = renderWorkspace( , ); const defaultToggle = container.querySelector( '.ds-project-publish-card__toggles input[type="checkbox"]', ); await act(async () => { defaultToggle?.click(); await Promise.resolve(); }); expect(onSetDefault).toHaveBeenCalledWith('user:acme'); }); it('clears the default design system when the selected default checkbox is unchecked', async () => { const onSetDefault = vi.fn(); const container = renderWorkspace( , ); const defaultToggle = container.querySelector( '.ds-project-publish-card__toggles input[type="checkbox"]', ); expect(defaultToggle?.checked).toBe(true); await act(async () => { defaultToggle?.click(); await Promise.resolve(); }); expect(onSetDefault).toHaveBeenCalledWith(null); }); });