Add integration tests for reconnect scenarios
Test that initial_state provides full terminal state, multiple sessions are independent, and the CSI 'T' artifact bug is fixed.
This commit is contained in:
parent
baa6ef9d70
commit
4502be8d06
1 changed files with 101 additions and 0 deletions
|
|
@ -164,4 +164,105 @@ describe("terminal", () => {
|
||||||
|
|
||||||
disposeTerminal(term);
|
disposeTerminal(term);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("reconnect scenarios", () => {
|
||||||
|
test("initial_state provides full terminal state for reconnecting clients", async () => {
|
||||||
|
const term = createTerminal(80, 24);
|
||||||
|
|
||||||
|
// Simulate some output that a client missed
|
||||||
|
await writeSync(term, "Line 1\r\n");
|
||||||
|
await writeSync(term, "Line 2\r\n");
|
||||||
|
await writeSync(term, "\x1b[31mRed line\x1b[0m\r\n");
|
||||||
|
|
||||||
|
// Serialize the full state (as sent in initial_state event)
|
||||||
|
const html = serializeAsHTML(term);
|
||||||
|
|
||||||
|
// Verify all content is present
|
||||||
|
expect(html).toContain("Line 1");
|
||||||
|
expect(html).toContain("Line 2");
|
||||||
|
expect(html).toContain("Red line");
|
||||||
|
|
||||||
|
// Verify ANSI styling is preserved
|
||||||
|
expect(html).toMatch(/color|red|#[a-f0-9]{6}/i);
|
||||||
|
|
||||||
|
disposeTerminal(term);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("multiple sessions have independent terminal emulators", async () => {
|
||||||
|
const term1 = createTerminal(80, 24);
|
||||||
|
const term2 = createTerminal(80, 24);
|
||||||
|
|
||||||
|
// Write different content to each
|
||||||
|
await writeSync(term1, "Session 1 output");
|
||||||
|
await writeSync(term2, "Session 2 output");
|
||||||
|
|
||||||
|
const html1 = serializeAsHTML(term1);
|
||||||
|
const html2 = serializeAsHTML(term2);
|
||||||
|
|
||||||
|
// Each should have only its own content
|
||||||
|
expect(html1).toContain("Session 1 output");
|
||||||
|
expect(html1).not.toContain("Session 2 output");
|
||||||
|
expect(html2).toContain("Session 2 output");
|
||||||
|
expect(html2).not.toContain("Session 1 output");
|
||||||
|
|
||||||
|
disposeTerminal(term1);
|
||||||
|
disposeTerminal(term2);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("T artifact bug is fixed (CSI sequences with T final byte)", async () => {
|
||||||
|
const term = createTerminal(80, 24);
|
||||||
|
|
||||||
|
// CSI sequences ending in 'T' (scroll down) were leaking through as literal 'T'
|
||||||
|
// This was a bug in our old ANSI parser
|
||||||
|
await writeSync(term, "Before\x1b[1T"); // Scroll down 1 line
|
||||||
|
await writeSync(term, "After");
|
||||||
|
|
||||||
|
const html = serializeAsHTML(term);
|
||||||
|
|
||||||
|
// Should not contain literal 'T' from the escape sequence
|
||||||
|
// Only 'Before' and 'After' should be visible
|
||||||
|
expect(html).toContain("Before");
|
||||||
|
expect(html).toContain("After");
|
||||||
|
|
||||||
|
// Extract text content without HTML tags
|
||||||
|
const textContent = html
|
||||||
|
.replace(/<[^>]+>/g, "")
|
||||||
|
.replace(/ /g, " ")
|
||||||
|
.trim();
|
||||||
|
|
||||||
|
// Should not have standalone 'T' characters
|
||||||
|
expect(textContent).not.toMatch(/\bT\b/);
|
||||||
|
|
||||||
|
disposeTerminal(term);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("complex ANSI sequences don't leak artifacts", async () => {
|
||||||
|
const term = createTerminal(80, 24);
|
||||||
|
|
||||||
|
// Test various CSI final bytes that could leak as literals
|
||||||
|
await writeSync(term, "Start\r\n");
|
||||||
|
await writeSync(term, "\x1b[2J"); // Clear screen (J)
|
||||||
|
await writeSync(term, "\x1b[H"); // Cursor home (H)
|
||||||
|
await writeSync(term, "\x1b[K"); // Clear to end of line (K)
|
||||||
|
await writeSync(term, "\x1b[1T"); // Scroll down (T)
|
||||||
|
await writeSync(term, "\x1b[1S"); // Scroll up (S)
|
||||||
|
await writeSync(term, "Clean output");
|
||||||
|
|
||||||
|
const html = serializeAsHTML(term);
|
||||||
|
|
||||||
|
// Should only contain the actual text
|
||||||
|
expect(html).toContain("Clean output");
|
||||||
|
|
||||||
|
// Extract text without HTML
|
||||||
|
const textContent = html
|
||||||
|
.replace(/<[^>]+>/g, "")
|
||||||
|
.replace(/ /g, " ")
|
||||||
|
.trim();
|
||||||
|
|
||||||
|
// Should not contain literal CSI final bytes
|
||||||
|
expect(textContent).not.toMatch(/[HJKTS](?!.*Clean)/);
|
||||||
|
|
||||||
|
disposeTerminal(term);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue