openpencil/apps/desktop/git/__tests__/git-ipc.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

60 lines
2.4 KiB
TypeScript

// apps/desktop/git/__tests__/git-ipc.test.ts
import { describe, it, expect, vi } from 'vitest';
// The `electron` npm package's `index.js` resolves the binary path at import
// time and throws when its postinstall didn't run (path.txt missing). This
// file only exercises pure helpers, so we stub electron's ipc surface.
vi.mock('electron', () => ({ ipcMain: { handle: () => undefined } }));
import { GitError } from '../error';
import { serializeGitError, GIT_ERROR_MARKER } from '../ipc-handlers';
describe('serializeGitError', () => {
it('produces an Error whose message starts with the marker and round-trips fields', () => {
const original = new GitError('commit-empty', 'No changes', { recoverable: true });
const serialized = serializeGitError(original);
expect(serialized.message.startsWith(GIT_ERROR_MARKER)).toBe(true);
const payload = JSON.parse(serialized.message.slice(GIT_ERROR_MARKER.length));
expect(payload.code).toBe('commit-empty');
expect(payload.message).toBe('No changes');
expect(payload.recoverable).toBe(true);
});
it('preserves recoverable=false from the GitError', () => {
const original = new GitError('engine-crash', 'something blew up', { recoverable: false });
const serialized = serializeGitError(original);
const payload = JSON.parse(serialized.message.slice(GIT_ERROR_MARKER.length));
expect(payload.recoverable).toBe(false);
});
it('the marker is a stable, unique string the renderer can pattern-match', () => {
expect(GIT_ERROR_MARKER).toBe('__GIT_ERROR__');
});
it('serializes auth-related and network error codes', () => {
const codes = [
'auth-failed',
'auth-required',
'clone-failed',
'network',
'pull-non-fast-forward',
] as const;
for (const code of codes) {
const original = new GitError(code, `${code} test`);
const serialized = serializeGitError(original);
const payload = JSON.parse(serialized.message.slice(GIT_ERROR_MARKER.length));
expect(payload.code).toBe(code);
}
});
it('serializes merge-related error codes', () => {
const codes = ['merge-conflict', 'merge-still-conflicted', 'merge-abort-failed'] as const;
for (const code of codes) {
const original = new GitError(code, `${code} test`);
const serialized = serializeGitError(original);
const payload = JSON.parse(serialized.message.slice(GIT_ERROR_MARKER.length));
expect(payload.code).toBe(code);
}
});
});