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).
73 lines
2.7 KiB
TypeScript
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);
|
|
});
|
|
});
|