open-design/apps/web/tests/components/DesignSystemFlow.test.tsx
Eli-tangerine ce95266586
Some checks failed
visual-baseline / Capture visual baselines (push) Waiting to run
ci / Detect CI change scopes (push) Successful in 1s
nix-check / build (push) Failing after 3s
ci / Preflight (push) Failing after 2s
ci / Core package tests (push) Failing after 1s
ci / Tools workspace tests (push) Failing after 1s
ci / Daemon workspace tests (1/2) (push) Failing after 1s
ci / Daemon workspace tests (2/2) (push) Failing after 1s
ci / Web workspace tests (push) Failing after 1s
ci / E2E vitest (push) Failing after 1s
ci / Playwright critical (starters) (push) Failing after 1s
ci / Playwright critical (core) (push) Failing after 1s
ci / Build workspaces (push) Failing after 1s
ci / App workspace tests (push) Failing after 0s
ci / Validate workspace (push) Failing after 0s
ci / Runtime trace (push) Has been skipped
[codex] Polish home composer working-directory controls (#2468)
* Polish design system home flows

* Polish home prompt presets

* Polish home working directory controls

* test: align home hero chrome smoke

* fix: stabilize home composer ci checks

---------

Co-authored-by: qiongyu1999 <2694684348@qq.com>
2026-05-21 00:22:46 +08:00

1706 lines
62 KiB
TypeScript

// @vitest-environment jsdom
import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import type { ConnectorDetail } from '@open-design/contracts';
import {
buildDesignSystemPackageAuditRepairPrompt,
summarizeDesignSystemPackageAudit,
} from '../../src/runtime/design-system-package-audit';
import {
DesignSystemCreationFlow,
} from '../../src/components/DesignSystemFlow';
import type { AppConfig, DesignSystemDetail, Project } from '../../src/types';
const mocks = vi.hoisted(() => ({
connectConnector: vi.fn(),
createDesignSystemDraft: vi.fn(),
disconnectConnector: vi.fn(),
ensureDesignSystemWorkspace: vi.fn(),
fetchConnectorStatuses: vi.fn(),
openFolderDialog: vi.fn(),
patchProject: vi.fn(),
uploadProjectFile: vi.fn(),
writeProjectTextFile: vi.fn(),
}));
vi.mock('../../src/providers/registry', async () => {
const actual = await vi.importActual<typeof import('../../src/providers/registry')>(
'../../src/providers/registry',
);
return {
...actual,
connectConnector: mocks.connectConnector,
createDesignSystemDraft: mocks.createDesignSystemDraft,
disconnectConnector: mocks.disconnectConnector,
ensureDesignSystemWorkspace: mocks.ensureDesignSystemWorkspace,
fetchConnectorStatuses: mocks.fetchConnectorStatuses,
openFolderDialog: mocks.openFolderDialog,
uploadProjectFile: mocks.uploadProjectFile,
writeProjectTextFile: mocks.writeProjectTextFile,
};
});
vi.mock('../../src/state/projects', async () => {
const actual = await vi.importActual<typeof import('../../src/state/projects')>(
'../../src/state/projects',
);
return {
...actual,
patchProject: mocks.patchProject,
};
});
afterEach(() => {
cleanup();
vi.clearAllMocks();
window.sessionStorage.clear();
});
beforeEach(() => {
mocks.connectConnector.mockResolvedValue({ connector: null });
mocks.disconnectConnector.mockResolvedValue(null);
mocks.fetchConnectorStatuses.mockResolvedValue({});
mocks.openFolderDialog.mockResolvedValue(null);
mocks.uploadProjectFile.mockImplementation(async (_projectId: string, file: File, desiredName?: string) => ({
name: desiredName ?? file.name,
size: file.size,
mtime: 1,
kind: 'code',
mime: file.type || 'text/plain',
}));
mocks.writeProjectTextFile.mockImplementation(async (_projectId: string, name: string) => ({
name,
size: 1,
mtime: 1,
kind: 'document',
mime: 'text/markdown',
}));
});
function continueToGeneration() {
fireEvent.click(screen.getByRole('button', { name: /^(continue to generation|generate)$/i }));
}
describe('design system package audit helpers', () => {
it('summarizes passing audits and builds repair prompts for findings', () => {
expect(summarizeDesignSystemPackageAudit({
ok: true,
projectPath: '/tmp/ds',
filesInspected: 12,
errors: [],
warnings: [],
})).toBe('Package audit passed (12 files inspected).');
const failingAudit = {
ok: false,
projectPath: '/tmp/ds',
filesInspected: 8,
errors: [{
severity: 'error' as const,
code: 'ui_kit_index_missing_runtime_bootstrap',
message: 'ui_kits/app/index.html must mount the kit.',
path: 'ui_kits/app/index.html',
}],
warnings: [{
severity: 'warning' as const,
code: 'missing_source_component_examples',
message: 'Copy source examples into source_examples/.',
path: 'source_examples/',
}, {
severity: 'warning' as const,
code: 'missing_build_assets',
message: 'Preserve runtime icons under build/.',
path: 'build/',
}, {
severity: 'warning' as const,
code: 'readme_missing_package_reuse_guide',
message: 'README.md should work as a Claude Design package guide.',
path: 'README.md',
}, {
severity: 'warning' as const,
code: 'readme_missing_preview_manifest',
message: 'README.md should list preview cards.',
path: 'README.md',
}, {
severity: 'warning' as const,
code: 'missing_skill_frontmatter',
message: 'SKILL.md should include YAML frontmatter.',
path: 'SKILL.md',
}],
};
expect(summarizeDesignSystemPackageAudit(failingAudit)).toContain(
'Package audit found 1 error and 5 warnings',
);
expect(buildDesignSystemPackageAuditRepairPrompt(failingAudit)).toContain(
'tools connectors design-system-package-audit --path . --fail-on-warnings',
);
expect(buildDesignSystemPackageAuditRepairPrompt(failingAudit)).toContain(
'Treat every error and warning as blocking',
);
expect(buildDesignSystemPackageAuditRepairPrompt(failingAudit)).toContain(
'preserve representative originals under root `build/`',
);
expect(buildDesignSystemPackageAuditRepairPrompt(failingAudit)).toContain(
'copy them byte-for-byte from captured context snapshots',
);
expect(buildDesignSystemPackageAuditRepairPrompt(failingAudit)).toContain(
'copy substantive original component snapshots into `source_examples/`',
);
expect(buildDesignSystemPackageAuditRepairPrompt(failingAudit)).toContain(
'Targeted repair actions:',
);
expect(buildDesignSystemPackageAuditRepairPrompt(failingAudit)).toContain(
'Rebuild `ui_kits/app/index.html` as a runnable UI-kit entry',
);
expect(buildDesignSystemPackageAuditRepairPrompt(failingAudit)).toContain(
'Rewrite `SKILL.md` as a discoverable skill package',
);
expect(buildDesignSystemPackageAuditRepairPrompt(failingAudit)).toContain(
'Rewrite `README.md` as a Claude Design package guide',
);
expect(buildDesignSystemPackageAuditRepairPrompt(failingAudit)).toContain(
'Add a `## Preview Manifest` section to `README.md`',
);
expect(buildDesignSystemPackageAuditRepairPrompt(failingAudit)).toContain(
'copying originals from `context/.../files/build/...` into root `build/` byte-for-byte',
);
expect(buildDesignSystemPackageAuditRepairPrompt(failingAudit)).toContain(
'[warning] missing_source_component_examples source_examples/',
);
});
});
describe('DesignSystemCreationFlow', () => {
it('opens the project as soon as the workspace exists and prepares the first chat task afterward', async () => {
const system: DesignSystemDetail = {
id: 'user:acme-design-system',
title: 'Acme Design System',
category: 'Custom',
summary: 'Acme product workspace.',
swatches: [],
surface: 'web',
body: '# Acme Design System\n',
source: 'user',
status: 'draft',
isEditable: true,
projectId: 'ds-acme-design-system',
};
const project: Project = {
id: 'ds-acme-design-system',
name: 'Acme Design System',
skillId: null,
designSystemId: system.id,
createdAt: 1,
updatedAt: 1,
metadata: {
kind: 'other',
importedFrom: 'design-system',
entryFile: 'DESIGN.md',
sourceFileName: system.id,
},
};
let resolveManifestWrite: (file: {
name: string;
size: number;
mtime: number;
kind: 'document';
mime: string;
}) => void = () => {};
mocks.writeProjectTextFile.mockReturnValueOnce(new Promise((resolve) => {
resolveManifestWrite = resolve;
}));
mocks.createDesignSystemDraft.mockResolvedValue(system);
mocks.ensureDesignSystemWorkspace.mockResolvedValue({ project, files: [] });
mocks.patchProject.mockResolvedValue({ ...project, pendingPrompt: 'Create this project as a design system.' });
const onCreated = vi.fn();
const onProjectPrepared = vi.fn();
render(
<DesignSystemCreationFlow
onBack={() => {}}
onCreated={onCreated}
onProjectPrepared={onProjectPrepared}
/>,
);
fireEvent.change(screen.getByPlaceholderText(/Mission Impastabowl/i), {
target: {
value: 'Acme: analytics workspace for operations teams',
},
});
continueToGeneration();
continueToGeneration();
await waitFor(() => expect(screen.getByText('Opening project...')).toBeTruthy());
await waitFor(() => expect(onCreated).toHaveBeenCalledWith(project.id, project));
expect(screen.queryByText('Creating your design system...')).toBeNull();
expect(screen.queryByText('Opening project chat...')).toBeNull();
expect(screen.queryByText('Updated todos')).toBeNull();
expect(mocks.patchProject).not.toHaveBeenCalled();
expect(onProjectPrepared).not.toHaveBeenCalled();
resolveManifestWrite({
name: 'context/source-context.md',
size: 1,
mtime: 1,
kind: 'document',
mime: 'text/markdown',
});
await waitFor(() => expect(mocks.patchProject).toHaveBeenCalledWith(
project.id,
expect.objectContaining({
pendingPrompt: expect.stringContaining('Create this project as a complete Open Design design system workspace.'),
}),
));
await waitFor(() => expect(onProjectPrepared).toHaveBeenCalledWith(
expect.objectContaining({
id: project.id,
pendingPrompt: 'Create this project as a design system.',
}),
));
});
it('creates a project-backed design system and hands the first task to the normal project chat', async () => {
const system: DesignSystemDetail = {
id: 'user:acme-design-system',
title: 'Acme Design System',
category: 'Custom',
summary: 'Acme product workspace.',
swatches: [],
surface: 'web',
body: '# Acme Design System\n',
source: 'user',
status: 'draft',
isEditable: true,
projectId: 'ds-acme-design-system',
};
const project: Project = {
id: 'ds-acme-design-system',
name: 'Acme Design System',
skillId: null,
designSystemId: system.id,
createdAt: 1,
updatedAt: 1,
metadata: {
kind: 'other',
importedFrom: 'design-system',
entryFile: 'DESIGN.md',
sourceFileName: system.id,
},
};
mocks.createDesignSystemDraft.mockResolvedValue(system);
mocks.ensureDesignSystemWorkspace.mockResolvedValue({ project, files: [] });
mocks.patchProject.mockResolvedValue({ ...project, pendingPrompt: 'Create this project as a design system.' });
const onCreated = vi.fn();
const onSystemsRefresh = vi.fn();
render(
<DesignSystemCreationFlow
onBack={() => {}}
onCreated={onCreated}
onSystemsRefresh={onSystemsRefresh}
/>,
);
fireEvent.change(screen.getByPlaceholderText(/Mission Impastabowl/i), {
target: {
value: 'Acme: analytics workspace for operations teams',
},
});
continueToGeneration();
continueToGeneration();
await waitFor(() => expect(onCreated).toHaveBeenCalledWith(project.id, project));
expect(mocks.createDesignSystemDraft).toHaveBeenCalledWith(
expect.objectContaining({
title: 'Acme Design System',
status: 'draft',
surface: 'web',
artifactMode: 'agent-managed',
}),
);
expect(mocks.ensureDesignSystemWorkspace).toHaveBeenCalledWith(system.id);
await waitFor(() => expect(mocks.writeProjectTextFile).toHaveBeenCalled());
expect(mocks.writeProjectTextFile).toHaveBeenCalledWith(
project.id,
'context/source-context.md',
expect.stringContaining('## Review Contract'),
);
expect(mocks.writeProjectTextFile).toHaveBeenCalledWith(
project.id,
'context/source-context.md',
expect.stringContaining('Canonical design-system title: Acme Design System'),
);
expect(mocks.patchProject).toHaveBeenCalledWith(
project.id,
expect.objectContaining({
pendingPrompt: expect.stringContaining('Create this project as a complete Open Design design system workspace.'),
}),
);
expect(mocks.patchProject).toHaveBeenCalledWith(
project.id,
expect.objectContaining({
pendingPrompt: expect.stringContaining('Design system workspace title:\nAcme Design System'),
}),
);
expect(mocks.patchProject).toHaveBeenCalledWith(
project.id,
expect.objectContaining({
pendingPrompt: expect.stringContaining('Do not derive the title from URL protocol text such as `https`.'),
}),
);
expect(mocks.patchProject).toHaveBeenCalledWith(
project.id,
expect.objectContaining({
pendingPrompt: expect.stringContaining('Read `context/source-context.md` before drafting'),
}),
);
expect(mocks.patchProject).toHaveBeenCalledWith(
project.id,
expect.objectContaining({
pendingPrompt: expect.stringContaining('Do not ask setup or clarification questions during design-system generation.'),
}),
);
expect(mocks.patchProject).toHaveBeenCalledWith(
project.id,
expect.objectContaining({
pendingPrompt: expect.stringContaining('Do not emit `<question-form>`, "Quick brief — 30 seconds", `AskUserQuestion`'),
}),
);
expect(mocks.patchProject).toHaveBeenCalledWith(
project.id,
expect.objectContaining({
pendingPrompt: expect.stringContaining('All GitHub extraction, local evidence intake, source reading, design-system construction, package audit, and final artifact writes must happen inside this project workspace and this project chat run.'),
}),
);
expect(mocks.patchProject).toHaveBeenCalledWith(
project.id,
expect.objectContaining({
pendingPrompt: expect.stringContaining('A Claude Design-quality package'),
}),
);
expect(mocks.patchProject).toHaveBeenCalledWith(
project.id,
expect.objectContaining({
pendingPrompt: expect.stringContaining('preview/colors-primary.html'),
}),
);
expect(mocks.patchProject).toHaveBeenCalledWith(
project.id,
expect.objectContaining({
pendingPrompt: expect.stringContaining('ui_kits/app/'),
}),
);
expect(mocks.patchProject).toHaveBeenCalledWith(
project.id,
expect.objectContaining({
pendingPrompt: expect.stringContaining('ui_kits/app/components/'),
}),
);
expect(mocks.patchProject).toHaveBeenCalledWith(
project.id,
expect.objectContaining({
pendingPrompt: expect.stringContaining('AssistantsList.jsx'),
}),
);
expect(mocks.patchProject).toHaveBeenCalledWith(
project.id,
expect.objectContaining({
pendingPrompt: expect.stringContaining('MessageBubble.jsx'),
}),
);
expect(mocks.patchProject).toHaveBeenCalledWith(
project.id,
expect.objectContaining({
pendingPrompt: expect.stringContaining('do not write one-line placeholder components'),
}),
);
expect(mocks.patchProject).toHaveBeenCalledWith(
project.id,
expect.objectContaining({
pendingPrompt: expect.stringContaining('must load `../../colors_and_type.css`'),
}),
);
expect(mocks.patchProject).toHaveBeenCalledWith(
project.id,
expect.objectContaining({
pendingPrompt: expect.stringContaining('must load/import/compose the modular component files'),
}),
);
expect(mocks.patchProject).toHaveBeenCalledWith(
project.id,
expect.objectContaining({
pendingPrompt: expect.stringContaining('must mount/render the composed interface'),
}),
);
expect(mocks.patchProject).toHaveBeenCalledWith(
project.id,
expect.objectContaining({
pendingPrompt: expect.stringContaining('app shell component must compose the role components'),
}),
);
expect(mocks.patchProject).toHaveBeenCalledWith(
project.id,
expect.objectContaining({
pendingPrompt: expect.stringContaining('include React, ReactDOM, and Babel standalone scripts'),
}),
);
expect(mocks.patchProject).toHaveBeenCalledWith(
project.id,
expect.objectContaining({
pendingPrompt: expect.stringContaining('Claude-style UI-kit entry contract:'),
}),
);
expect(mocks.patchProject).toHaveBeenCalledWith(
project.id,
expect.objectContaining({
pendingPrompt: expect.stringContaining('<script type="text/babel" src="components/App.jsx"></script>'),
}),
);
expect(mocks.patchProject).toHaveBeenCalledWith(
project.id,
expect.objectContaining({
pendingPrompt: expect.stringContaining("const root = ReactDOM.createRoot(document.getElementById('root'));"),
}),
);
expect(mocks.patchProject).toHaveBeenCalledWith(
project.id,
expect.objectContaining({
pendingPrompt: expect.stringContaining('root.render(<App />);'),
}),
);
expect(mocks.patchProject).toHaveBeenCalledWith(
project.id,
expect.objectContaining({
pendingPrompt: expect.stringContaining('Write `README.md` as a reusable package guide'),
}),
);
expect(mocks.patchProject).toHaveBeenCalledWith(
project.id,
expect.objectContaining({
pendingPrompt: expect.stringContaining('Include a source-backed Product Overview/Product Context section'),
}),
);
expect(mocks.patchProject).toHaveBeenCalledWith(
project.id,
expect.objectContaining({
pendingPrompt: expect.stringContaining('expose each loaded component as `window.ComponentName`'),
}),
);
expect(mocks.patchProject).toHaveBeenCalledWith(
project.id,
expect.objectContaining({
pendingPrompt: expect.stringContaining('do not leave manifest text pointing to older preview names'),
}),
);
expect(mocks.patchProject).toHaveBeenCalledWith(
project.id,
expect.objectContaining({
pendingPrompt: expect.stringContaining('representative set instead of collapsing everything into one generic logo or font'),
}),
);
expect(mocks.patchProject).toHaveBeenCalledWith(
project.id,
expect.objectContaining({
pendingPrompt: expect.stringContaining('preserve representative files under `build/` as Claude Design does'),
}),
);
expect(mocks.patchProject).toHaveBeenCalledWith(
project.id,
expect.objectContaining({
pendingPrompt: expect.stringContaining('Claude-style build asset contract:'),
}),
);
expect(mocks.patchProject).toHaveBeenCalledWith(
project.id,
expect.objectContaining({
pendingPrompt: expect.stringContaining('copy representative runtime assets there with their original filenames'),
}),
);
expect(mocks.patchProject).toHaveBeenCalledWith(
project.id,
expect.objectContaining({
pendingPrompt: expect.stringContaining('Copy those runtime assets byte-for-byte from the captured `context/.../files/...` snapshots.'),
}),
);
expect(mocks.patchProject).toHaveBeenCalledWith(
project.id,
expect.objectContaining({
pendingPrompt: expect.stringContaining('Do not satisfy build/runtime icon evidence by only renaming those files into `assets/`'),
}),
);
expect(mocks.patchProject).toHaveBeenCalledWith(
project.id,
expect.objectContaining({
pendingPrompt: expect.stringContaining('ui_kits/app/README.md` should document the kit structure'),
}),
);
expect(mocks.patchProject).toHaveBeenCalledWith(
project.id,
expect.objectContaining({
pendingPrompt: expect.stringContaining('preview/brand-assets.html` must visibly load the preserved files'),
}),
);
expect(mocks.patchProject).toHaveBeenCalledWith(
project.id,
expect.objectContaining({
pendingPrompt: expect.stringContaining('bind them in `colors_and_type.css` with `@font-face`'),
}),
);
expect(mocks.patchProject).toHaveBeenCalledWith(
project.id,
expect.objectContaining({
pendingPrompt: expect.stringContaining('Preserve high-signal source component examples'),
}),
);
expect(mocks.patchProject).toHaveBeenCalledWith(
project.id,
expect.objectContaining({
pendingPrompt: expect.stringContaining('concrete `## Preview Manifest` section'),
}),
);
expect(mocks.patchProject).toHaveBeenCalledWith(
project.id,
expect.objectContaining({
pendingPrompt: expect.stringContaining('lists each generated `preview/*.html` card by exact path'),
}),
);
expect(mocks.patchProject).toHaveBeenCalledWith(
project.id,
expect.objectContaining({
pendingPrompt: expect.stringContaining('Do not replace captured source examples with tiny filename-only stubs'),
}),
);
expect(mocks.patchProject).toHaveBeenCalledWith(
project.id,
expect.objectContaining({
pendingPrompt: expect.stringContaining('source_examples/SelectModelButton.tsx'),
}),
);
expect(mocks.patchProject).toHaveBeenCalledWith(
project.id,
expect.objectContaining({
pendingPrompt: expect.stringContaining('Include YAML frontmatter with `name`, `description`, and `user-invocable`'),
}),
);
expect(mocks.patchProject).toHaveBeenCalledWith(
project.id,
expect.objectContaining({
pendingPrompt: expect.stringContaining('reusable sections for `What is inside`, `Source context`, `When to use this skill`, `How to use`, and `Design system highlights`'),
}),
);
expect(mocks.patchProject).toHaveBeenCalledWith(
project.id,
expect.objectContaining({
pendingPrompt: expect.stringContaining('read README.md, DESIGN.md, colors_and_type.css, preview/, assets/, build/, fonts/, source_examples/, and ui_kits/app/'),
}),
);
expect(mocks.patchProject).toHaveBeenCalledWith(
project.id,
expect.objectContaining({
pendingPrompt: expect.stringContaining('name or model high-signal source components from the evidence'),
}),
);
expect(mocks.patchProject).toHaveBeenCalledWith(
project.id,
expect.objectContaining({
pendingPrompt: expect.stringContaining('tools connectors design-system-package-audit --path . --fail-on-warnings'),
}),
);
expect(mocks.patchProject).toHaveBeenCalledWith(
project.id,
expect.objectContaining({
pendingPrompt: expect.stringContaining('Fix every audit error and design-quality warning'),
}),
);
expect(mocks.writeProjectTextFile).toHaveBeenCalledWith(
project.id,
'context/source-context.md',
expect.stringContaining('tools connectors design-system-package-audit --path . --fail-on-warnings'),
);
expect(mocks.writeProjectTextFile).toHaveBeenCalledWith(
project.id,
'context/source-context.md',
expect.stringContaining('fix every reported error or warning'),
);
expect(mocks.writeProjectTextFile).toHaveBeenCalledWith(
project.id,
'context/source-context.md',
expect.stringContaining('All GitHub extraction, local evidence intake, source reading, design-system construction, package audit, and artifact writes should happen inside this project workspace.'),
);
expect(mocks.writeProjectTextFile).toHaveBeenCalledWith(
project.id,
'context/source-context.md',
expect.stringContaining('ui_kits/app/components/'),
);
expect(mocks.writeProjectTextFile).toHaveBeenCalledWith(
project.id,
'context/source-context.md',
expect.stringContaining('must load `../../colors_and_type.css`'),
);
expect(mocks.writeProjectTextFile).toHaveBeenCalledWith(
project.id,
'context/source-context.md',
expect.stringContaining('must load/import/compose the modular component files'),
);
expect(mocks.writeProjectTextFile).toHaveBeenCalledWith(
project.id,
'context/source-context.md',
expect.stringContaining('must mount/render the composed interface'),
);
expect(mocks.writeProjectTextFile).toHaveBeenCalledWith(
project.id,
'context/source-context.md',
expect.stringContaining('app shell component must compose those roles'),
);
expect(mocks.writeProjectTextFile).toHaveBeenCalledWith(
project.id,
'context/source-context.md',
expect.stringContaining('ui_kits/app/README.md` should explain structure'),
);
expect(mocks.writeProjectTextFile).toHaveBeenCalledWith(
project.id,
'context/source-context.md',
expect.stringContaining('include React, ReactDOM, and Babel standalone scripts'),
);
expect(mocks.writeProjectTextFile).toHaveBeenCalledWith(
project.id,
'context/source-context.md',
expect.stringContaining('Claude-style UI-kit entry contract:'),
);
expect(mocks.writeProjectTextFile).toHaveBeenCalledWith(
project.id,
'context/source-context.md',
expect.stringContaining('<script type="text/babel" src="components/App.jsx"></script>'),
);
expect(mocks.writeProjectTextFile).toHaveBeenCalledWith(
project.id,
'context/source-context.md',
expect.stringContaining("const root = ReactDOM.createRoot(document.getElementById('root'));"),
);
expect(mocks.writeProjectTextFile).toHaveBeenCalledWith(
project.id,
'context/source-context.md',
expect.stringContaining('root.render(<App />);'),
);
expect(mocks.writeProjectTextFile).toHaveBeenCalledWith(
project.id,
'context/source-context.md',
expect.stringContaining('expose each loaded component as `window.ComponentName`'),
);
expect(mocks.writeProjectTextFile).toHaveBeenCalledWith(
project.id,
'context/source-context.md',
expect.stringContaining('assistant/list rail'),
);
expect(mocks.writeProjectTextFile).toHaveBeenCalledWith(
project.id,
'context/source-context.md',
expect.stringContaining('message bubble/comment'),
);
expect(mocks.writeProjectTextFile).toHaveBeenCalledWith(
project.id,
'context/source-context.md',
expect.stringContaining('must describe the final focused preview cards'),
);
expect(mocks.writeProjectTextFile).toHaveBeenCalledWith(
project.id,
'context/source-context.md',
expect.stringContaining('README.md should include a source-backed Product Overview/Product Context section'),
);
expect(mocks.writeProjectTextFile).toHaveBeenCalledWith(
project.id,
'context/source-context.md',
expect.stringContaining('colors_and_type.css must bind those files with @font-face'),
);
expect(mocks.writeProjectTextFile).toHaveBeenCalledWith(
project.id,
'context/source-context.md',
expect.stringContaining('concrete `## Preview Manifest` listing every generated `preview/*.html` card'),
);
expect(mocks.writeProjectTextFile).toHaveBeenCalledWith(
project.id,
'context/source-context.md',
expect.stringContaining('SKILL.md should include YAML frontmatter'),
);
expect(mocks.writeProjectTextFile).toHaveBeenCalledWith(
project.id,
'context/source-context.md',
expect.stringContaining('Claude-style reusable skill sections: What is inside, Source context, When to use this skill, How to use, and Design system highlights'),
);
expect(mocks.writeProjectTextFile).toHaveBeenCalledWith(
project.id,
'context/source-context.md',
expect.stringContaining('README.md, DESIGN.md, colors_and_type.css, preview/, assets/, build/, fonts/, source_examples/, and ui_kits/app/'),
);
expect(mocks.writeProjectTextFile).toHaveBeenCalledWith(
project.id,
'context/source-context.md',
expect.stringContaining('source_examples/ or equivalent root/nested source files'),
);
expect(mocks.writeProjectTextFile).toHaveBeenCalledWith(
project.id,
'context/source-context.md',
expect.stringContaining('not tiny stubs that only share the component name'),
);
expect(mocks.writeProjectTextFile).toHaveBeenCalledWith(
project.id,
'context/source-context.md',
expect.stringContaining('explicitly label or model source-backed modules'),
);
expect(mocks.writeProjectTextFile).toHaveBeenCalledWith(
project.id,
'context/source-context.md',
expect.stringContaining('Placeholder component shells are not sufficient'),
);
expect(window.sessionStorage.getItem(`od:auto-send-first:${project.id}`)).toBe('1');
expect(onCreated).toHaveBeenCalledWith(project.id, project);
expect(onSystemsRefresh).toHaveBeenCalled();
});
it('links a local code folder into the design-system project so the agent can read it', async () => {
const system: DesignSystemDetail = {
id: 'user:folder-design-system',
title: 'Folder Design System',
category: 'Custom',
summary: 'Folder product workspace.',
swatches: [],
surface: 'web',
body: '# Folder Design System\n',
source: 'user',
status: 'draft',
isEditable: true,
projectId: 'ds-folder-design-system',
};
const project: Project = {
id: 'ds-folder-design-system',
name: 'Folder Design System',
skillId: null,
designSystemId: system.id,
createdAt: 1,
updatedAt: 1,
metadata: {
kind: 'other',
importedFrom: 'design-system',
entryFile: 'DESIGN.md',
sourceFileName: system.id,
},
};
mocks.createDesignSystemDraft.mockResolvedValue(system);
mocks.ensureDesignSystemWorkspace.mockResolvedValue({ project, files: [] });
mocks.patchProject.mockResolvedValue({ ...project, pendingPrompt: 'Create this project as a design system.' });
mocks.openFolderDialog.mockResolvedValue('/Users/qingyu/work/comfyui');
render(
<DesignSystemCreationFlow
onBack={() => {}}
onCreated={() => {}}
/>,
);
fireEvent.change(screen.getByPlaceholderText(/Mission Impastabowl/i), {
target: { value: 'ComfyUI: node-based image workflow editor' },
});
fireEvent.click(screen.getByRole('button', { name: 'Browse folder' }));
await waitFor(() => expect(screen.getByText('/Users/qingyu/work/comfyui')).toBeTruthy());
continueToGeneration();
continueToGeneration();
await waitFor(() => expect(mocks.patchProject).toHaveBeenCalled());
expect(mocks.patchProject).toHaveBeenCalledWith(
project.id,
expect.objectContaining({
metadata: expect.objectContaining({
linkedDirs: ['/Users/qingyu/work/comfyui'],
}),
pendingPrompt: expect.stringContaining('Read the linked local code folders'),
}),
);
expect(mocks.patchProject).toHaveBeenCalledWith(
project.id,
expect.objectContaining({
pendingPrompt: expect.stringContaining('tools connectors local-design-context --path'),
}),
);
expect(mocks.writeProjectTextFile).toHaveBeenCalledWith(
project.id,
'context/source-context.md',
expect.stringContaining('/Users/qingyu/work/comfyui'),
);
expect(mocks.writeProjectTextFile).toHaveBeenCalledWith(
project.id,
'context/source-context.md',
expect.stringContaining('## Local Folder Intake Runbook'),
);
expect(mocks.writeProjectTextFile).toHaveBeenCalledWith(
project.id,
'context/source-context.md',
expect.stringContaining('tools connectors local-design-context --path'),
);
});
it('copies browser-selected local code folder files into the design-system project context', async () => {
const system: DesignSystemDetail = {
id: 'user:snapshot-design-system',
title: 'Snapshot Design System',
category: 'Custom',
summary: 'Snapshot product workspace.',
swatches: [],
surface: 'web',
body: '# Snapshot Design System\n',
source: 'user',
status: 'draft',
isEditable: true,
projectId: 'ds-snapshot-design-system',
};
const project: Project = {
id: 'ds-snapshot-design-system',
name: 'Snapshot Design System',
skillId: null,
designSystemId: system.id,
createdAt: 1,
updatedAt: 1,
metadata: {
kind: 'other',
importedFrom: 'design-system',
entryFile: 'DESIGN.md',
sourceFileName: system.id,
},
};
mocks.createDesignSystemDraft.mockResolvedValue(system);
mocks.ensureDesignSystemWorkspace.mockResolvedValue({ project, files: [] });
mocks.patchProject.mockResolvedValue({ ...project, pendingPrompt: 'Create this project as a design system.' });
const onCreated = vi.fn();
const { container } = render(
<DesignSystemCreationFlow
onBack={() => {}}
onCreated={onCreated}
/>,
);
const localCodeInput = container.querySelector('input[webkitdirectory]') as HTMLInputElement | null;
const tokenFile = new File([':root { --brand: #d86a4a; }'], 'tokens.css', { type: 'text/css' });
Object.defineProperty(tokenFile, 'webkitRelativePath', { value: 'comfyui/src/tokens.css' });
fireEvent.change(screen.getByPlaceholderText(/Mission Impastabowl/i), {
target: { value: 'Snapshot: product UI with tokens' },
});
fireEvent.change(localCodeInput!, { target: { files: [tokenFile] } });
expect(screen.getByText('1 local code files selected')).toBeTruthy();
continueToGeneration();
continueToGeneration();
await waitFor(() => expect(mocks.uploadProjectFile).toHaveBeenCalled());
expect(mocks.uploadProjectFile).toHaveBeenCalledWith(
project.id,
tokenFile,
'context/local-code/comfyui/src/tokens.css',
);
expect(mocks.patchProject).toHaveBeenCalledWith(
project.id,
expect.objectContaining({
pendingPrompt: expect.stringContaining('context/local-code/comfyui/src/tokens.css'),
}),
);
expect(mocks.writeProjectTextFile).toHaveBeenCalledWith(
project.id,
'context/source-context.md',
expect.stringContaining('context/local-code/comfyui/src/tokens.css'),
);
expect(window.sessionStorage.getItem(`od:auto-send-first:${project.id}`)).toBe('1');
expect(onCreated).toHaveBeenCalledWith(project.id, project);
});
it('recursively reads a dragged local code folder into the design-system project context', async () => {
const system: DesignSystemDetail = {
id: 'user:dragged-folder-design-system',
title: 'Dragged Folder Design System',
category: 'Custom',
summary: 'Dragged folder workspace.',
swatches: [],
surface: 'web',
body: '# Dragged Folder Design System\n',
source: 'user',
status: 'draft',
isEditable: true,
projectId: 'ds-dragged-folder-design-system',
};
const project: Project = {
id: 'ds-dragged-folder-design-system',
name: 'Dragged Folder Design System',
skillId: null,
designSystemId: system.id,
createdAt: 1,
updatedAt: 1,
metadata: {
kind: 'other',
importedFrom: 'design-system',
entryFile: 'DESIGN.md',
sourceFileName: system.id,
},
};
mocks.createDesignSystemDraft.mockResolvedValue(system);
mocks.ensureDesignSystemWorkspace.mockResolvedValue({ project, files: [] });
mocks.patchProject.mockResolvedValue({ ...project, pendingPrompt: 'Create this project as a design system.' });
const { container } = render(
<DesignSystemCreationFlow
onBack={() => {}}
onCreated={() => {}}
/>,
);
const dropZone = container.querySelector('input[webkitdirectory]')?.closest('.ds-drop-zone') as HTMLElement | null;
const tokenFile = new File([':root { --brand: #d86a4a; }'], 'tokens.css', { type: 'text/css' });
const buttonFile = new File(['export function Button() {}'], 'Button.tsx', { type: 'text/typescript' });
const srcEntries = [
{ isFile: true, isDirectory: false, name: 'tokens.css', file: (done: (file: File) => void) => done(tokenFile) },
{ isFile: true, isDirectory: false, name: 'Button.tsx', file: (done: (file: File) => void) => done(buttonFile) },
];
const srcDirectory = {
isFile: false,
isDirectory: true,
name: 'src',
createReader: () => {
let read = false;
return {
readEntries: (done: (entries: typeof srcEntries) => void) => {
const entries = read ? [] : srcEntries;
read = true;
done(entries);
},
};
},
};
const rootDirectory = {
isFile: false,
isDirectory: true,
name: 'comfyui',
createReader: () => {
let read = false;
return {
readEntries: (done: (entries: [typeof srcDirectory] | []) => void) => {
const entries: [typeof srcDirectory] | [] = read ? [] : [srcDirectory];
read = true;
done(entries);
},
};
},
};
fireEvent.change(screen.getByPlaceholderText(/Mission Impastabowl/i), {
target: { value: 'Dragged: product UI with tokens and components' },
});
fireEvent.drop(dropZone!, {
dataTransfer: {
files: [],
items: [
{
webkitGetAsEntry: () => rootDirectory,
},
],
},
});
await waitFor(() => expect(screen.getByText('2 local code files selected')).toBeTruthy());
continueToGeneration();
continueToGeneration();
await waitFor(() => expect(mocks.uploadProjectFile).toHaveBeenCalledTimes(2));
expect(mocks.uploadProjectFile).toHaveBeenCalledWith(
project.id,
tokenFile,
'context/local-code/comfyui/src/tokens.css',
);
expect(mocks.uploadProjectFile).toHaveBeenCalledWith(
project.id,
buttonFile,
'context/local-code/comfyui/src/Button.tsx',
);
expect(mocks.writeProjectTextFile).toHaveBeenCalledWith(
project.id,
'context/source-context.md',
expect.stringContaining('context/local-code/comfyui/src/Button.tsx'),
);
});
it('parses .fig files locally into project context summaries without uploading the source file', async () => {
const system: DesignSystemDetail = {
id: 'user:figma-design-system',
title: 'Figma Design System',
category: 'Custom',
summary: 'Figma-backed workspace.',
swatches: [],
surface: 'web',
body: '# Figma Design System\n',
source: 'user',
status: 'draft',
isEditable: true,
projectId: 'ds-figma-design-system',
};
const project: Project = {
id: 'ds-figma-design-system',
name: 'Figma Design System',
skillId: null,
designSystemId: system.id,
createdAt: 1,
updatedAt: 1,
metadata: {
kind: 'other',
importedFrom: 'design-system',
entryFile: 'DESIGN.md',
sourceFileName: system.id,
},
};
mocks.createDesignSystemDraft.mockResolvedValue(system);
mocks.ensureDesignSystemWorkspace.mockResolvedValue({ project, files: [] });
mocks.patchProject.mockResolvedValue({ ...project, pendingPrompt: 'Create this project as a design system.' });
render(
<DesignSystemCreationFlow
onBack={() => {}}
onCreated={() => {}}
/>,
);
const figInput = screen
.getByText('Drop .fig here or browse')
.closest('label')
?.querySelector('input') as HTMLInputElement | null;
const figFile = new File([
'{"name":"Primary Button","fontFamily":"Inter","name":"Dashboard Card","fill":"#FF6A3D"}',
], 'product-design.fig', { type: 'application/octet-stream' });
fireEvent.change(screen.getByPlaceholderText(/Mission Impastabowl/i), {
target: { value: 'Figma: product UI with button and dashboard components' },
});
fireEvent.change(figInput!, { target: { files: [figFile] } });
expect(screen.getByText('product-design.fig')).toBeTruthy();
continueToGeneration();
continueToGeneration();
await waitFor(() => expect(mocks.writeProjectTextFile).toHaveBeenCalledWith(
project.id,
'context/figma/product-design.md',
expect.stringContaining('Primary Button'),
));
expect(mocks.writeProjectTextFile).toHaveBeenCalledWith(
project.id,
'context/figma/product-design.md',
expect.stringContaining('#FF6A3D'),
);
expect(mocks.writeProjectTextFile).toHaveBeenCalledWith(
project.id,
'context/source-context.md',
expect.stringContaining('context/figma/product-design.md'),
);
expect(mocks.patchProject).toHaveBeenCalledWith(
project.id,
expect.objectContaining({
pendingPrompt: expect.stringContaining('Use the locally parsed Figma summaries'),
}),
);
expect(mocks.uploadProjectFile).not.toHaveBeenCalled();
});
it('uploads brand assets into the design-system project context', async () => {
const system: DesignSystemDetail = {
id: 'user:asset-design-system',
title: 'Asset Design System',
category: 'Custom',
summary: 'Asset-backed workspace.',
swatches: [],
surface: 'web',
body: '# Asset Design System\n',
source: 'user',
status: 'draft',
isEditable: true,
projectId: 'ds-asset-design-system',
};
const project: Project = {
id: 'ds-asset-design-system',
name: 'Asset Design System',
skillId: null,
designSystemId: system.id,
createdAt: 1,
updatedAt: 1,
metadata: {
kind: 'other',
importedFrom: 'design-system',
entryFile: 'DESIGN.md',
sourceFileName: system.id,
},
};
mocks.createDesignSystemDraft.mockResolvedValue(system);
mocks.ensureDesignSystemWorkspace.mockResolvedValue({ project, files: [] });
mocks.patchProject.mockResolvedValue({ ...project, pendingPrompt: 'Create this project as a design system.' });
render(
<DesignSystemCreationFlow
onBack={() => {}}
onCreated={() => {}}
/>,
);
const assetInput = screen
.getByText('Drag files here or browse')
.closest('label')
?.querySelector('input') as HTMLInputElement | null;
const logoFile = new File(['<svg />'], 'logo.svg', { type: 'image/svg+xml' });
const fontFile = new File(['font-data'], 'brand.woff2', { type: 'font/woff2' });
fireEvent.change(screen.getByPlaceholderText(/Mission Impastabowl/i), {
target: { value: 'Assets: product brand with custom logo and font' },
});
fireEvent.change(assetInput!, { target: { files: [logoFile, fontFile] } });
expect(screen.getByText('logo.svg, brand.woff2')).toBeTruthy();
continueToGeneration();
continueToGeneration();
await waitFor(() => expect(mocks.uploadProjectFile).toHaveBeenCalledTimes(2));
expect(mocks.uploadProjectFile).toHaveBeenCalledWith(project.id, logoFile, 'assets/logo.svg');
expect(mocks.uploadProjectFile).toHaveBeenCalledWith(project.id, fontFile, 'assets/brand.woff2');
expect(mocks.writeProjectTextFile).toHaveBeenCalledWith(
project.id,
'context/source-context.md',
expect.stringContaining('assets/logo.svg'),
);
expect(mocks.patchProject).toHaveBeenCalledWith(
project.id,
expect.objectContaining({
pendingPrompt: expect.stringContaining('Use uploaded brand assets in `assets/`'),
}),
);
});
it('infers a product title from a GitHub URL instead of the URL protocol', async () => {
const system: DesignSystemDetail = {
id: 'user:cherry-studio-design-system',
title: 'Cherry Studio Design System',
category: 'Custom',
summary: 'https://github.com/cherryhq/cherry-studio',
swatches: [],
surface: 'web',
body: '# Cherry Studio Design System\n',
source: 'user',
status: 'draft',
isEditable: true,
projectId: 'ds-cherry-studio-design-system',
};
const project: Project = {
id: 'ds-cherry-studio-design-system',
name: 'Cherry Studio Design System',
skillId: null,
designSystemId: system.id,
createdAt: 1,
updatedAt: 1,
metadata: {
kind: 'other',
importedFrom: 'design-system',
entryFile: 'DESIGN.md',
sourceFileName: system.id,
},
};
mocks.createDesignSystemDraft.mockResolvedValue(system);
mocks.ensureDesignSystemWorkspace.mockResolvedValue({ project, files: [] });
mocks.patchProject.mockResolvedValue({ ...project, pendingPrompt: 'Create this project as a design system.' });
render(
<DesignSystemCreationFlow
onBack={() => {}}
onCreated={() => {}}
onSystemsRefresh={() => {}}
/>,
);
fireEvent.change(screen.getByPlaceholderText(/Mission Impastabowl/i), {
target: { value: 'https://github.com/cherryhq/cherry-studio' },
});
continueToGeneration();
continueToGeneration();
await waitFor(() => expect(mocks.createDesignSystemDraft).toHaveBeenCalled());
expect(mocks.createDesignSystemDraft).toHaveBeenCalledWith(
expect.objectContaining({
title: 'Cherry Studio Design System',
}),
);
expect(mocks.createDesignSystemDraft).not.toHaveBeenCalledWith(
expect.objectContaining({
title: 'https Design System',
}),
);
await waitFor(() => expect(mocks.writeProjectTextFile).toHaveBeenCalled());
expect(mocks.writeProjectTextFile).toHaveBeenCalledWith(
project.id,
'context/source-context.md',
expect.stringContaining('Canonical design-system title: Cherry Studio Design System'),
);
expect(mocks.patchProject).toHaveBeenCalledWith(
project.id,
expect.objectContaining({
pendingPrompt: expect.stringContaining('Design system workspace title:\nCherry Studio Design System'),
}),
);
});
it('allows GitHub repo links without Composio by using local GitHub intake', () => {
const onOpenConnectorsTab = vi.fn();
const config = {
composio: { apiKeyConfigured: false },
} as AppConfig;
render(
<DesignSystemCreationFlow
onBack={() => {}}
onCreated={() => {}}
config={config}
onOpenConnectorsTab={onOpenConnectorsTab}
/>,
);
const input = screen.getByPlaceholderText('https://github.com/owner/repo') as HTMLInputElement;
expect(input.disabled).toBe(false);
expect(screen.getByText('Repository access: Auto')).toBeTruthy();
expect(screen.getByRole('button', { name: 'Show access methods' })).toBeTruthy();
expect(screen.queryByRole('button', { name: 'Configure Composio' })).toBeNull();
fireEvent.click(screen.getByRole('button', { name: 'Show access methods' }));
expect(screen.getByText('This device')).toBeTruthy();
expect(screen.getByText('Open Design account')).toBeTruthy();
expect(screen.getByText('Connector platform')).toBeTruthy();
expect(screen.getByText('Coming soon')).toBeTruthy();
expect(screen.getByText('Not configured')).toBeTruthy();
fireEvent.change(input, { target: { value: 'https://github.com/nexu-io/open-design/' } });
fireEvent.click(screen.getByRole('button', { name: 'Add' }));
expect(screen.getByText('nexu-io/open-design')).toBeTruthy();
fireEvent.click(screen.getByRole('button', { name: 'Configure Composio' }));
expect(onOpenConnectorsTab).toHaveBeenCalledTimes(1);
expect(mocks.fetchConnectorStatuses).not.toHaveBeenCalled();
});
it('stops checking GitHub connector if the status request hangs', async () => {
const originalSetTimeout = window.setTimeout;
type WindowSetTimeout = typeof window.setTimeout;
const timeoutSpy = vi.spyOn(window, 'setTimeout').mockImplementation(((...params: Parameters<WindowSetTimeout>) => {
const [handler, timeout, ...args] = params;
if (timeout === 5000 && typeof handler === 'function') {
handler(...args);
return 1;
}
return originalSetTimeout(...params);
}) as typeof window.setTimeout);
mocks.fetchConnectorStatuses.mockReturnValue(new Promise(() => {}));
const config = {
composio: { apiKeyConfigured: true, apiKeyTail: 'uQEg' },
} as AppConfig;
try {
render(
<DesignSystemCreationFlow
onBack={() => {}}
onCreated={() => {}}
config={config}
/>,
);
expect(screen.getByText('Repository access: Auto')).toBeTruthy();
fireEvent.click(screen.getByRole('button', { name: 'Show access methods' }));
expect(screen.queryByText('Checking GitHub connector')).toBeNull();
await waitFor(() => expect(screen.getByText('Needs attention')).toBeTruthy());
expect(screen.queryByText('Checking GitHub connector')).toBeNull();
expect(screen.getByText(/Could not finish checking GitHub connector/i)).toBeTruthy();
expect(screen.getByRole('button', { name: 'Connect via Composio' })).toBeTruthy();
expect(mocks.fetchConnectorStatuses.mock.calls[0]?.[0]?.signal?.aborted).toBe(true);
} finally {
timeoutSpy.mockRestore();
}
});
it('surfaces GitHub connector status errors from the connector status endpoint', async () => {
mocks.fetchConnectorStatuses.mockResolvedValue({
github: {
status: 'error',
lastError: 'GitHub authorization expired. Reconnect GitHub.',
},
});
const config = {
composio: { apiKeyConfigured: true, apiKeyTail: 'uQEg' },
} as AppConfig;
render(
<DesignSystemCreationFlow
onBack={() => {}}
onCreated={() => {}}
config={config}
/>,
);
fireEvent.click(screen.getByRole('button', { name: 'Show access methods' }));
await waitFor(() => expect(screen.getByText('Needs attention')).toBeTruthy());
expect(screen.getByText('GitHub authorization expired. Reconnect GitHub.')).toBeTruthy();
expect(screen.getByRole('button', { name: 'Connect via Composio' })).toBeTruthy();
});
it('keeps GitHub repo links available and shows connected connector status', async () => {
const connectedConnector: ConnectorDetail = {
id: 'github',
name: 'GitHub',
provider: 'Composio',
category: 'Code',
status: 'connected',
tools: [],
accountLabel: 'qiongyu1999',
};
mocks.fetchConnectorStatuses.mockResolvedValue({ github: { status: 'available' } });
mocks.connectConnector.mockResolvedValue({
connector: connectedConnector,
auth: { kind: 'connected' },
});
const config = {
composio: { apiKeyConfigured: true, apiKeyTail: 'uQEg' },
} as AppConfig;
render(
<DesignSystemCreationFlow
onBack={() => {}}
onCreated={() => {}}
config={config}
/>,
);
fireEvent.click(screen.getByRole('button', { name: 'Show access methods' }));
await waitFor(() => expect(screen.getByRole('button', { name: 'Connect via Composio' })).toBeTruthy());
expect((screen.getByPlaceholderText('https://github.com/owner/repo') as HTMLInputElement).disabled).toBe(false);
fireEvent.click(screen.getByRole('button', { name: 'Connect via Composio' }));
await waitFor(() => expect(mocks.connectConnector).toHaveBeenCalledWith('github'));
await waitFor(() => expect(screen.getByText(/Connected as qiongyu1999/i)).toBeTruthy());
expect(screen.queryByRole('button', { name: 'Configure' })).toBeNull();
expect(screen.getByRole('button', { name: 'Disconnect' })).toBeTruthy();
const input = screen.getByPlaceholderText('https://github.com/owner/repo') as HTMLInputElement;
expect(input.disabled).toBe(false);
fireEvent.change(input, { target: { value: 'https://github.com/nexu-io/open-design/' } });
fireEvent.click(screen.getByRole('button', { name: 'Add' }));
expect(screen.getByText('nexu-io/open-design')).toBeTruthy();
expect(input.value).toBe('');
});
it('hides Composio connection ids in the connected GitHub label', async () => {
mocks.fetchConnectorStatuses.mockResolvedValue({
github: { status: 'connected', accountLabel: 'ca_6U6mv_8IzMVR' },
});
const config = {
composio: { apiKeyConfigured: true, apiKeyTail: 'uQEg' },
} as AppConfig;
render(
<DesignSystemCreationFlow
onBack={() => {}}
onCreated={() => {}}
config={config}
/>,
);
fireEvent.click(screen.getByRole('button', { name: 'Show access methods' }));
await waitFor(() => expect(screen.getByText('Connected')).toBeTruthy());
expect(screen.queryByText(/ca_6U6mv_8IzMVR/)).toBeNull();
expect(screen.queryByRole('button', { name: 'Configure' })).toBeNull();
expect(screen.getByRole('button', { name: 'Disconnect' })).toBeTruthy();
});
it('keeps a manual GitHub authorization action when the automatic popup is blocked', async () => {
const availableConnector: ConnectorDetail = {
id: 'github',
name: 'GitHub',
provider: 'Composio',
category: 'Code',
status: 'available',
tools: [],
};
mocks.fetchConnectorStatuses.mockResolvedValue({ github: { status: 'available' } });
mocks.connectConnector.mockResolvedValue({
connector: availableConnector,
auth: {
kind: 'redirect_required',
redirectUrl: 'https://example.com/oauth',
expiresAt: '2099-05-08T10:00:00.000Z',
},
error: 'Popup blocked. Allow popups for Open Design and try again.',
});
const openSpy = vi.spyOn(window, 'open').mockImplementation(() => ({ closed: false } as Window));
const config = {
composio: { apiKeyConfigured: true, apiKeyTail: 'uQEg' },
} as AppConfig;
try {
render(
<DesignSystemCreationFlow
onBack={() => {}}
onCreated={() => {}}
config={config}
/>,
);
fireEvent.click(screen.getByRole('button', { name: 'Show access methods' }));
await waitFor(() => expect(screen.getByRole('button', { name: 'Connect via Composio' })).toBeTruthy());
fireEvent.click(screen.getByRole('button', { name: 'Connect via Composio' }));
await waitFor(() => expect(screen.getByText('Pending')).toBeTruthy());
expect(screen.getByText('Popup blocked. Allow popups for Open Design and try again.')).toBeTruthy();
fireEvent.click(screen.getByRole('button', { name: 'Open authorization' }));
expect(openSpy).toHaveBeenCalledWith('https://example.com/oauth', '_blank');
} finally {
openSpy.mockRestore();
}
});
it('records connected GitHub repository sources in the project source manifest', async () => {
const availableConnector: ConnectorDetail = {
id: 'github',
name: 'GitHub',
provider: 'Composio',
category: 'Code',
status: 'connected',
accountLabel: 'qiongyu1999',
tools: [],
};
const system: DesignSystemDetail = {
id: 'user:github-design-system',
title: 'Github Design System',
category: 'Custom',
summary: 'GitHub-backed workspace.',
swatches: [],
surface: 'web',
body: '# Github Design System\n',
source: 'user',
status: 'draft',
isEditable: true,
projectId: 'ds-github-design-system',
};
const project: Project = {
id: 'ds-github-design-system',
name: 'Github Design System',
skillId: null,
designSystemId: system.id,
createdAt: 1,
updatedAt: 1,
metadata: {
kind: 'other',
importedFrom: 'design-system',
entryFile: 'DESIGN.md',
sourceFileName: system.id,
},
};
mocks.fetchConnectorStatuses.mockResolvedValue({
github: { status: 'connected', accountLabel: 'qiongyu1999' },
});
mocks.createDesignSystemDraft.mockResolvedValue(system);
mocks.ensureDesignSystemWorkspace.mockResolvedValue({ project, files: [] });
mocks.patchProject.mockResolvedValue({ ...project, pendingPrompt: 'Create this project as a design system.' });
const config = {
composio: { apiKeyConfigured: true, apiKeyTail: 'uQEg' },
} as AppConfig;
render(
<DesignSystemCreationFlow
onBack={() => {}}
onCreated={() => {}}
config={config}
/>,
);
fireEvent.click(screen.getByRole('button', { name: 'Show access methods' }));
await waitFor(() => expect(screen.getByText(/Connected as qiongyu1999/i)).toBeTruthy());
fireEvent.change(screen.getByPlaceholderText(/Mission Impastabowl/i), {
target: { value: 'GitHub: product workspace' },
});
const input = screen.getByPlaceholderText('https://github.com/owner/repo') as HTMLInputElement;
fireEvent.change(input, { target: { value: 'https://github.com/nexu-io/open-design' } });
fireEvent.click(screen.getByRole('button', { name: 'Add' }));
continueToGeneration();
continueToGeneration();
await waitFor(() => expect(mocks.writeProjectTextFile).toHaveBeenCalled());
expect(mocks.writeProjectTextFile).toHaveBeenCalledWith(
project.id,
'context/source-context.md',
expect.stringContaining('Connector status: connected as qiongyu1999.'),
);
expect(mocks.writeProjectTextFile).toHaveBeenCalledWith(
project.id,
'context/source-context.md',
expect.stringContaining('https://github.com/nexu-io/open-design'),
);
expect(mocks.writeProjectTextFile).toHaveBeenCalledWith(
project.id,
'context/source-context.md',
expect.stringContaining('GitHub Connector Intake Runbook'),
);
expect(mocks.writeProjectTextFile).toHaveBeenCalledWith(
project.id,
'context/source-context.md',
expect.stringContaining('"$OD_NODE_BIN" "$OD_BIN" tools connectors github-design-context --repo \'https://github.com/nexu-io/open-design\' --output context/github/nexu-io-open-design.md'),
);
expect(mocks.writeProjectTextFile).not.toHaveBeenCalledWith(
project.id,
'context/source-context.md',
expect.stringContaining('--require-connector'),
);
expect(mocks.patchProject).toHaveBeenCalledWith(
project.id,
expect.objectContaining({
pendingPrompt: expect.stringContaining('GitHub repository intake is required before drafting the design system'),
}),
);
expect(mocks.patchProject).toHaveBeenCalledWith(
project.id,
expect.objectContaining({
pendingPrompt: expect.stringContaining('Do not call GitHub connector tree/content/raw tools directly from the agent.'),
}),
);
expect(mocks.patchProject).toHaveBeenCalledWith(
project.id,
expect.objectContaining({
pendingPrompt: expect.stringContaining('The command tries this-device access first'),
}),
);
expect(mocks.writeProjectTextFile).toHaveBeenCalledWith(
project.id,
'context/source-context.md',
expect.stringContaining('GitHub evidence must come from the bounded `github-design-context` command'),
);
expect(mocks.patchProject).toHaveBeenCalledWith(
project.id,
expect.objectContaining({
pendingPrompt: expect.stringContaining('Do not call GitHub connector tree/content/raw tools directly from the agent.'),
}),
);
expect(mocks.patchProject).toHaveBeenCalledWith(
project.id,
expect.objectContaining({
pendingPrompt: expect.stringContaining('Treat `Read method: git-clone` as the preferred this-device path.'),
}),
);
expect(mocks.patchProject).toHaveBeenCalledWith(
project.id,
expect.objectContaining({
pendingPrompt: expect.stringContaining('selects design-system-relevant source files plus available logos/icons/fonts'),
}),
);
expect(mocks.writeProjectTextFile).toHaveBeenCalledWith(
project.id,
'context/source-context.md',
expect.stringContaining('assets/, build/, fonts/, and context/ should preserve logos'),
);
expect(mocks.writeProjectTextFile).toHaveBeenCalledWith(
project.id,
'context/source-context.md',
expect.stringContaining('Claude-style build asset contract:'),
);
expect(mocks.writeProjectTextFile).toHaveBeenCalledWith(
project.id,
'context/source-context.md',
expect.stringContaining('copy representative runtime assets there with their original filenames'),
);
expect(mocks.writeProjectTextFile).toHaveBeenCalledWith(
project.id,
'context/source-context.md',
expect.stringContaining('Copy those runtime assets byte-for-byte from the captured `context/.../files/...` snapshots.'),
);
expect(mocks.writeProjectTextFile).toHaveBeenCalledWith(
project.id,
'context/source-context.md',
expect.stringContaining('Do not satisfy build/runtime icon evidence by only renaming those files into `assets/`'),
);
expect(mocks.writeProjectTextFile).toHaveBeenCalledWith(
project.id,
'context/source-context.md',
expect.stringContaining('preview/brand-assets.html should visibly reference preserved files'),
);
});
it('does not leak Composio connected-account ids into the project source manifest', async () => {
const system: DesignSystemDetail = {
id: 'user:github-internal-account-design-system',
title: 'GitHub Internal Account Design System',
category: 'Custom',
summary: 'GitHub-backed workspace.',
swatches: [],
surface: 'web',
body: '# GitHub Internal Account Design System\n',
source: 'user',
status: 'draft',
isEditable: true,
projectId: 'ds-github-internal-account-design-system',
};
const project: Project = {
id: 'ds-github-internal-account-design-system',
name: 'GitHub Internal Account Design System',
skillId: null,
designSystemId: system.id,
createdAt: 1,
updatedAt: 1,
metadata: {
kind: 'other',
importedFrom: 'design-system',
entryFile: 'DESIGN.md',
sourceFileName: system.id,
},
};
mocks.fetchConnectorStatuses.mockResolvedValue({
github: { status: 'connected', accountLabel: 'ca_6U6mv_8IzMVR' },
});
mocks.createDesignSystemDraft.mockResolvedValue(system);
mocks.ensureDesignSystemWorkspace.mockResolvedValue({ project, files: [] });
mocks.patchProject.mockResolvedValue({ ...project, pendingPrompt: 'Create this project as a design system.' });
const config = {
composio: { apiKeyConfigured: true, apiKeyTail: 'uQEg' },
} as AppConfig;
render(
<DesignSystemCreationFlow
onBack={() => {}}
onCreated={() => {}}
config={config}
/>,
);
fireEvent.click(screen.getByRole('button', { name: 'Show access methods' }));
await waitFor(() => expect(screen.getByText('Connected')).toBeTruthy());
fireEvent.change(screen.getByPlaceholderText(/Mission Impastabowl/i), {
target: { value: 'GitHub: product workspace' },
});
const input = screen.getByPlaceholderText('https://github.com/owner/repo') as HTMLInputElement;
fireEvent.change(input, { target: { value: 'https://github.com/nexu-io/open-design' } });
fireEvent.click(screen.getByRole('button', { name: 'Add' }));
continueToGeneration();
continueToGeneration();
await waitFor(() => expect(mocks.writeProjectTextFile).toHaveBeenCalled());
const sourceManifestCall = mocks.writeProjectTextFile.mock.calls.find(
(call) => call[0] === project.id && call[1] === 'context/source-context.md',
);
expect(sourceManifestCall?.[2]).toEqual(expect.stringContaining('Connector status: connected.'));
expect(sourceManifestCall?.[2]).not.toEqual(expect.stringContaining('ca_6U6mv_8IzMVR'));
});
});