open-design/apps/web/tests/components/design-system-github-evidence.test.ts
Chris Seifert fb5d4bbd16
Surface GitHub repo connect/import CTA in design system projects (#2820)
* Surface GitHub repo connect/import CTA in design system projects

Design systems built from a GitHub repo used to show a dead-end banner
("Waiting for GitHub connector evidence") when the repo files were never
pulled in. It said nothing actionable and blocked publishing with no way
forward.

That state now reads as a CTA, in both the Design System review banner and
the project's Start-a-conversation empty state. When GitHub is not
connected, both say "Connect your repo to pull aspects of your design
system" with a Connect GitHub button that opens Connectors. Once GitHub is
connected the copy switches to "GitHub is connected" with an Import repo
button that drops the bounded github-design-context intake instruction into
the chat composer, so you review it and send. The agent runs the pull, the
snapshots land, and the prompt clears itself.

The copy lives in one helper so the banner and the chat card never drift,
and ProjectView reads connector status (refreshing on window focus) only
while the CTA is showing.

* Build the import prompt from linked repo URLs, not the manifest

The Import repo prompt hard-coded "read context/source-context.md first,"
but the CTA shows for any incomplete GitHub-backed system, and that manifest
is not always written yet. So clicking Import repo could start the recovery
on a file that does not exist.

buildRepoImportPrompt now builds the instruction from the design system's
provenance.githubUrls, which is always present when the CTA shows (the CTA
gates on a non-empty repo list). It points at context/source-context.md only
when that file is actually present, and otherwise tells the agent to run the
bounded github-design-context intake for the linked repos directly.

* Hold the connect-repo CTA neutral until the status resolves

githubConnected seeded as false, so on first paint every GitHub-backed
project rendered the CTA as Connect GitHub and routed clicks to Connectors
until /api/connectors/status came back. An already-connected user who
clicked fast got bounced to Connectors instead of the Import repo handoff.

githubConnected is now tri-state (undefined while loading). repoConnectCopy
returns a neutral "Checking GitHub..." label for that window, the CTA button
is disabled until the status resolves, and handleConnectRepo guards the
undefined case. Once the fetch lands the button flips to Connect or Import.
Tests cover the pending label, the disabled button, and the no-op click.
2026-05-24 14:20:35 +00:00

150 lines
5.5 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import {
buildRepoImportPrompt,
designSystemGithubEvidenceState,
designSystemNeedsRepoConnect,
repoConnectCopy,
} from '../../src/components/design-system-github-evidence';
import type { DesignSystemSummary } from '../../src/types';
function designSystem(overrides: Partial<DesignSystemSummary> = {}): 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,
};
}
const githubBacked = designSystem({
provenance: { githubUrls: ['https://github.com/acme/product'] },
});
describe('designSystemGithubEvidenceState', () => {
it('treats systems without GitHub repos as ready and never required', () => {
const state = designSystemGithubEvidenceState(designSystem(), ['DESIGN.md', 'preview/colors.html']);
expect(state.required).toBe(false);
expect(state.ready).toBe(true);
});
it('detects the source manifest for non-GitHub systems', () => {
const state = designSystemGithubEvidenceState(designSystem(), ['context/source-context.md']);
expect(state.hasSourceManifest).toBe(true);
});
it('is not ready when a GitHub repo is declared but no evidence has landed', () => {
const state = designSystemGithubEvidenceState(githubBacked, ['DESIGN.md']);
expect(state.required).toBe(true);
expect(state.noteCount).toBe(0);
expect(state.snapshotCount).toBe(0);
expect(state.ready).toBe(false);
});
it('stays not-ready when notes exist but file snapshots are still missing', () => {
const state = designSystemGithubEvidenceState(githubBacked, [
'DESIGN.md',
'context/github/acme-product.md',
]);
expect(state.noteCount).toBe(1);
expect(state.snapshotCount).toBe(0);
expect(state.ready).toBe(false);
});
it('becomes ready once notes cover every repo and at least one snapshot exists', () => {
const state = designSystemGithubEvidenceState(githubBacked, [
'context/github/acme-product.md',
'context/github/acme-product/files/App.tsx',
]);
expect(state.noteCount).toBe(1);
expect(state.snapshotCount).toBe(1);
expect(state.ready).toBe(true);
});
it('normalizes leading ./ and backslash paths case-insensitively', () => {
const state = designSystemGithubEvidenceState(githubBacked, [
'./context\\github\\Acme-Product.md',
'context\\github\\Acme-Product\\files\\Button.tsx',
]);
expect(state.noteCount).toBe(1);
expect(state.snapshotCount).toBe(1);
expect(state.ready).toBe(true);
});
});
describe('designSystemNeedsRepoConnect', () => {
it('returns false for a missing system', () => {
expect(designSystemNeedsRepoConnect(null, ['DESIGN.md'])).toBe(false);
expect(designSystemNeedsRepoConnect(undefined, ['DESIGN.md'])).toBe(false);
});
it('returns false for systems without GitHub repos', () => {
expect(designSystemNeedsRepoConnect(designSystem(), ['DESIGN.md'])).toBe(false);
});
it('returns true while a GitHub-backed system is missing evidence', () => {
expect(designSystemNeedsRepoConnect(githubBacked, ['context/github/acme-product.md'])).toBe(true);
});
it('returns false once evidence is fully captured', () => {
expect(
designSystemNeedsRepoConnect(githubBacked, [
'context/github/acme-product.md',
'context/github/acme-product/files/App.tsx',
]),
).toBe(false);
});
});
describe('repoConnectCopy', () => {
it('asks the user to connect when GitHub is not connected', () => {
const copy = repoConnectCopy(false);
expect(copy.buttonLabel).toBe('Connect GitHub');
expect(copy.bannerTitle).toBe('Connect your repo to pull aspects of your design system');
expect(copy.cardTitle).toBe('Connect your repo');
});
it('switches to re-import guidance when GitHub is already connected', () => {
const copy = repoConnectCopy(true);
expect(copy.buttonLabel).toBe('Import repo');
expect(copy.bannerTitle).toBe('GitHub is connected');
expect(copy.cardTitle).toBe('GitHub is connected');
expect(copy.bannerBody).toContain('Re-import');
expect(copy.cardBody).toContain('Re-import');
});
it('shows a neutral pending label while the status is still loading', () => {
const copy = repoConnectCopy(undefined);
expect(copy.buttonLabel).toBe('Checking GitHub...');
expect(copy.bannerBody).toContain('Checking');
expect(copy.cardBody).toContain('Checking');
// Never advertise connect or import before the status is known.
expect(copy.buttonLabel).not.toBe('Connect GitHub');
expect(copy.buttonLabel).not.toBe('Import repo');
});
});
describe('buildRepoImportPrompt', () => {
it('runs the bounded intake from the linked repo URLs', () => {
const prompt = buildRepoImportPrompt(githubBacked, ['DESIGN.md']);
expect(prompt).toContain('github-design-context');
expect(prompt).toContain('context/github/');
expect(prompt).toContain('https://github.com/acme/product');
});
it('does not require source-context.md when the manifest is absent', () => {
const prompt = buildRepoImportPrompt(githubBacked, ['DESIGN.md']);
expect(prompt).not.toContain('context/source-context.md');
});
it('points at source-context.md only once the manifest exists', () => {
const prompt = buildRepoImportPrompt(githubBacked, ['DESIGN.md', 'context/source-context.md']);
expect(prompt).toContain('context/source-context.md');
});
});