openpencil/apps/desktop/git/__tests__/git-sys.test.ts
Kayshen-X a7d73ebb62 feat(ai): pencil-style agentic design tool-loop, multi-chat tabs, #27 panel restyle
Built-in design generation now runs as an agentic MCP tool-loop (reusing the
agent-rs BuiltInProvider), gated behind OPENPENCIL_DESIGN_AGENT_LOOP / the
Settings experimental toggle; the orchestrator stays the default.

- design-agent system prompt + in-process design toolset (parity-locked with
  the MCP surface) + flag-gated Intent::Design routing
- spawn_agents execution as sequential sub-loops + live creation-mode badges
  (per-agent glow + 'N/M designing...' header)
- new MCP tools: get_guidelines, ToolSearch, get_screenshot, get_editor_state,
  export_nodes, spawn_agents; style-guide local audit
- #27 AI panel restyle: rounded tool cards + green check-rings, gray user
  bubbles, model-pill bottom toolbar, header, empty-state pills, the
  PARALLEL AGENTS (agent_team_size) 1x-6x chip dropdown
- multi-chat tabs: ChatSessions model (Deref-to-active) + tab row UI
  (switch / close / + / Cmd+T) with each run bound to its tab

Large checkpoint commit spanning the working tree (Rust shell crates).
2026-07-02 21:21:06 +08:00

73 lines
2.7 KiB
TypeScript

// apps/desktop/git/__tests__/git-sys.test.ts
import { describe, it, expect, beforeEach } from 'vitest';
import { isSystemGitAvailable, __resetSystemGitCache, getSystemAuthor } from '../git-sys';
describe('git-sys', () => {
beforeEach(() => {
__resetSystemGitCache();
});
it('isSystemGitAvailable returns a boolean and caches the result', async () => {
const first = await isSystemGitAvailable();
expect(typeof first).toBe('boolean');
// Second call should hit the cache and return the same value.
const second = await isSystemGitAvailable();
expect(second).toBe(first);
});
});
describe('getSystemAuthor (injected exec)', () => {
// These tests use the injected-exec seam to stay deterministic and avoid
// depending on whatever user.name/user.email happen to be configured on the
// host running the suite. The seam short-circuits isSystemGitAvailable and
// runGit entirely, so we exercise only the parse/validate/catch logic.
it('returns parsed name/email on success', async () => {
const calls: string[][] = [];
const fakeExec = async (args: string[]) => {
calls.push(args);
if (args[2] === 'user.name') return { stdout: 'Alice\n', stderr: '' };
if (args[2] === 'user.email') return { stdout: 'alice@example.com\n', stderr: '' };
return { stdout: '', stderr: '' };
};
const result = await getSystemAuthor(fakeExec);
expect(result).toEqual({ name: 'Alice', email: 'alice@example.com' });
expect(calls).toHaveLength(2);
expect(calls[0]).toEqual(['config', '--get', 'user.name']);
expect(calls[1]).toEqual(['config', '--get', 'user.email']);
});
it('returns null when git throws (e.g. key not set)', async () => {
const calls: string[][] = [];
const fakeExec = async (args: string[]) => {
calls.push(args);
// Simulate `git config --get user.name` exiting non-zero when unset.
throw new Error('git config --get user.name failed: exit code 1');
};
const result = await getSystemAuthor(fakeExec);
expect(result).toBeNull();
// First call throws, so the second call never happens.
expect(calls).toHaveLength(1);
});
it('returns null when either value is empty/whitespace', async () => {
const calls: string[][] = [];
const fakeExec = async (args: string[]) => {
calls.push(args);
if (args[2] === 'user.name') return { stdout: 'Bob\n', stderr: '' };
if (args[2] === 'user.email') return { stdout: ' \n', stderr: '' };
return { stdout: '', stderr: '' };
};
const result = await getSystemAuthor(fakeExec);
expect(result).toBeNull();
// Both calls happen because validation is post-fetch.
expect(calls).toHaveLength(2);
});
});