mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-04 14:39:42 +02:00
95 lines
3.5 KiB
TypeScript
95 lines
3.5 KiB
TypeScript
/** @fileoverview Real Chromium + real WebSocket coverage for SplitTerminalPane (Task 4 of the split-pane-sessions plan). */
|
|
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
import { chromium, type Browser, type Page } from 'playwright';
|
|
import { WebServer } from '../src/web/server.js';
|
|
|
|
const PORT = 3175;
|
|
const BASE_URL = `http://localhost:${PORT}`;
|
|
|
|
describe('SplitTerminalPane in a real browser', () => {
|
|
let server: WebServer;
|
|
let browser: Browser;
|
|
let page: Page;
|
|
|
|
beforeAll(async () => {
|
|
server = new WebServer(PORT, false, true);
|
|
await server.start();
|
|
browser = await chromium.launch({ headless: true });
|
|
page = await browser.newPage();
|
|
await page.goto(BASE_URL, { waitUntil: 'domcontentloaded' });
|
|
await page.waitForFunction(() => (window as any).app?.terminal, null, { timeout: 30000 });
|
|
}, 90000);
|
|
|
|
afterAll(async () => {
|
|
if (browser) await browser.close();
|
|
if (server) await server.stop();
|
|
}, 60000);
|
|
|
|
it('connects, echoes real PTY output, and cleans up on destroy', async () => {
|
|
const sessionId = await page.evaluate(async () => {
|
|
const res = await fetch('/api/sessions', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ workingDir: '/tmp', mode: 'shell' }),
|
|
});
|
|
const id = (await res.json()).data.session.id;
|
|
// Session creation alone leaves pid:null and no pane (per CLAUDE.md's
|
|
// Testing section) — the shell PTY only spawns once this is called, and
|
|
// without it the WS opens but no bytes ever flow, and the echo assertion
|
|
// below would hang until its own timeout for reasons unrelated to
|
|
// SplitTerminalPane.
|
|
await fetch(`/api/sessions/${id}/shell`, { method: 'POST' });
|
|
return id;
|
|
});
|
|
|
|
const result = await page.evaluate(async (id) => {
|
|
const mount = document.createElement('div');
|
|
mount.style.width = '400px';
|
|
mount.style.height = '300px';
|
|
document.body.appendChild(mount);
|
|
|
|
const pane = new (window as any).SplitTerminalPane(id, mount);
|
|
pane.connect();
|
|
|
|
// Wait for the WS to open, then send a real input frame — testMode's
|
|
// echo PTY (TEST_PTY_SCRIPT) echoes each byte back exactly once, which
|
|
// is what proves the WS round-trip actually reaches a real PTY and back,
|
|
// not just that xterm can render locally-written text.
|
|
await new Promise((resolve) => {
|
|
const check = () => (pane._wsReady ? resolve(undefined) : setTimeout(check, 100));
|
|
check();
|
|
});
|
|
pane.ws.send(JSON.stringify({ t: 'i', d: 'SPLITPANE_MARKER\r' }));
|
|
|
|
const hasEcho = await new Promise((resolve) => {
|
|
const deadline = Date.now() + 5000;
|
|
const poll = () => {
|
|
const buf = pane.terminal.buffer.active;
|
|
for (let i = 0; i < buf.length; i++) {
|
|
if (buf.getLine(i)?.translateToString(true).includes('SPLITPANE_MARKER')) {
|
|
resolve(true);
|
|
return;
|
|
}
|
|
}
|
|
if (Date.now() > deadline) resolve(false);
|
|
else setTimeout(poll, 100);
|
|
};
|
|
poll();
|
|
});
|
|
|
|
pane.destroy();
|
|
const cleanedUp = mount.querySelector('.xterm') === null;
|
|
document.body.removeChild(mount);
|
|
|
|
return { hasEcho, cleanedUp };
|
|
}, sessionId);
|
|
|
|
expect(result.hasEcho).toBe(true);
|
|
expect(result.cleanedUp).toBe(true);
|
|
|
|
await page.evaluate(async (id) => {
|
|
await fetch(`/api/sessions/${id}`, { method: 'DELETE' });
|
|
}, sessionId);
|
|
});
|
|
});
|