Skip to content

Commit be08785

Browse files
NathanFlurryclaude
andcommitted
feat: US-028 - Claude Code interactive tests (PTY mode)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 927f049 commit be08785

1 file changed

Lines changed: 88 additions & 6 deletions

File tree

packages/secure-exec/tests/cli-tools/claude-interactive.test.ts

Lines changed: 88 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,18 +29,18 @@ import {
2929
createKernel,
3030
allowAllChildProcess,
3131
allowAllEnv,
32-
} from '../../../kernel/src/index.ts';
32+
} from '../../../secure-exec-core/src/kernel/index.ts';
3333
import type {
3434
Kernel,
3535
RuntimeDriver,
3636
KernelInterface,
3737
DriverProcess,
3838
ProcessContext,
39-
} from '../../../kernel/src/index.ts';
40-
import type { VirtualFileSystem } from '../../../kernel/src/vfs.ts';
41-
import { TerminalHarness } from '../../../kernel/test/terminal-harness.ts';
42-
import { InMemoryFileSystem } from '../../../os/browser/src/index.ts';
43-
import { createNodeRuntime } from '../../../runtime/node/src/index.ts';
39+
} from '../../../secure-exec-core/src/kernel/index.ts';
40+
import type { VirtualFileSystem } from '../../../secure-exec-core/src/kernel/vfs.ts';
41+
import { TerminalHarness } from '../../../secure-exec-core/test/kernel/terminal-harness.ts';
42+
import { InMemoryFileSystem } from '../../../secure-exec-browser/src/os-filesystem.ts';
43+
import { createNodeRuntime } from '../../../secure-exec-nodejs/src/kernel-runtime.ts';
4444
import {
4545
createMockLlmServer,
4646
type MockLlmServerHandle,
@@ -685,6 +685,88 @@ describe.skipIf(skipReason)('Claude Code interactive PTY E2E (sandbox)', () => {
685685
45_000,
686686
);
687687

688+
it(
689+
'Tool use UI — tool execution renders on screen when LLM requests a tool',
690+
async ({ skip }) => {
691+
if (sandboxSkip) skip();
692+
693+
// Queue: tool_use (Bash) → text result after tool execution
694+
mockServer.reset([
695+
{ type: 'tool_use', name: 'Bash', input: { command: 'echo TOOL_CANARY_42' } },
696+
{ type: 'text', text: 'The tool ran successfully.' },
697+
{ type: 'text', text: 'The tool ran successfully.' },
698+
]);
699+
700+
harness = createClaudeHarness();
701+
await waitForClaudeBoot(harness);
702+
await harness.waitFor('❯', 1, 5_000);
703+
704+
// Submit a prompt that will trigger a tool use
705+
await harness.type('run echo\r');
706+
707+
// Wait for tool-related UI to appear — Claude shows tool name or output
708+
// With --dangerously-skip-permissions, tools auto-execute and show results
709+
await harness.waitFor('Bash', 1, 30_000);
710+
711+
const screen = harness.screenshotTrimmed();
712+
// Tool name should appear in the TUI output
713+
expect(screen).toMatch(/Bash|echo|TOOL_CANARY/i);
714+
},
715+
60_000,
716+
);
717+
718+
it(
719+
'PTY resize — Ink re-renders for new dimensions',
720+
async ({ skip }) => {
721+
if (sandboxSkip) skip();
722+
723+
mockServer.reset([{ type: 'text', text: 'resize placeholder' }]);
724+
725+
harness = createClaudeHarness();
726+
await waitForClaudeBoot(harness);
727+
await harness.waitFor('❯', 1, 5_000);
728+
729+
// Resize PTY to wider terminal and resize xterm to match
730+
harness.shell.resize(120, 40);
731+
harness.term.resize(120, 40);
732+
733+
// Wait for Claude's Ink TUI to process SIGWINCH and re-render
734+
await new Promise((r) => setTimeout(r, 1_500));
735+
736+
const screenAfter = harness.screenshotTrimmed();
737+
// Claude TUI should still show its UI elements after resize
738+
expect(screenAfter).toMatch(/|Haiku|Welcome/);
739+
// Screen should not be blank/garbled
740+
expect(screenAfter.length).toBeGreaterThan(0);
741+
},
742+
45_000,
743+
);
744+
745+
it(
746+
'/help renders help text — help command output visible on screen',
747+
async ({ skip }) => {
748+
if (sandboxSkip) skip();
749+
750+
mockServer.reset([]);
751+
752+
harness = createClaudeHarness();
753+
await waitForClaudeBoot(harness);
754+
await harness.waitFor('❯', 1, 5_000);
755+
756+
// Type /help and submit
757+
await harness.type('/help\r');
758+
759+
// Wait for help content to render — Claude shows slash commands list
760+
// Help output includes common commands like /exit, /clear, /help
761+
await harness.waitFor('exit', 1, 15_000);
762+
763+
const screen = harness.screenshotTrimmed();
764+
// Help text should list available commands
765+
expect(screen).toMatch(/exit|clear|help/i);
766+
},
767+
45_000,
768+
);
769+
688770
it(
689771
'Exit cleanly — /exit causes Claude to exit',
690772
async ({ skip }) => {

0 commit comments

Comments
 (0)