Header name/command were baked into innerHTML at connection creation and never revisited. ConnectionPool.mount() reused the cached connection on later calls but dropped the fresh info, so a header never picked up haiku autoname's rename.
129 lines
3.7 KiB
TypeScript
129 lines
3.7 KiB
TypeScript
// @vitest-environment happy-dom
|
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
|
|
import { ConnectionPool } from "./connections.js";
|
|
|
|
// The pane's own bytes-out path, stubbed: the escape hatch only needs to know
|
|
// the pool reaches the focused connection with them.
|
|
const sent: { id: string; data: string }[] = [];
|
|
vi.mock("./attach.js", () => ({
|
|
createAttachConnection: (id: string) => ({
|
|
sessionId: id,
|
|
el: document.createElement("div"),
|
|
onMount() {},
|
|
onUnmount() {},
|
|
focusTerminal() {},
|
|
sendInput(data: string) {
|
|
sent.push({ id, data });
|
|
},
|
|
updateInfo() {},
|
|
dispose() {},
|
|
}),
|
|
}));
|
|
|
|
afterEach(() => {
|
|
vi.unstubAllGlobals();
|
|
});
|
|
|
|
const A = "aaaaaaaa-0000-0000-0000-0000000000ff";
|
|
const B = "bbbbbbbb-0000-0000-0000-0000000000ff";
|
|
|
|
function mockFetch() {
|
|
const fetchMock = vi.fn(() => Promise.resolve(new Response(null, { status: 204 })));
|
|
vi.stubGlobal("fetch", fetchMock);
|
|
return fetchMock;
|
|
}
|
|
|
|
describe("ConnectionPool.setFocus", () => {
|
|
it("reports the new session focused, none unfocused, the first time", () => {
|
|
const fetchMock = mockFetch();
|
|
const pool = new ConnectionPool();
|
|
|
|
pool.setFocus(A);
|
|
|
|
expect(fetchMock).toHaveBeenCalledOnce();
|
|
const [url, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit];
|
|
expect(url).toBe(`/api/sessions/${A}/focus`);
|
|
expect(init.method).toBe("POST");
|
|
expect(JSON.parse(init.body as string)).toEqual({ focused: true });
|
|
});
|
|
|
|
it("unfocuses the previous session and focuses the new one on a switch", () => {
|
|
const fetchMock = mockFetch();
|
|
const pool = new ConnectionPool();
|
|
pool.setFocus(A);
|
|
fetchMock.mockClear();
|
|
|
|
pool.setFocus(B);
|
|
|
|
expect(fetchMock).toHaveBeenCalledTimes(2);
|
|
const bodies = fetchMock.mock.calls.map(([url, init]: unknown[]) => [
|
|
url,
|
|
JSON.parse((init as RequestInit).body as string),
|
|
]);
|
|
expect(bodies).toContainEqual([`/api/sessions/${A}/focus`, { focused: false }]);
|
|
expect(bodies).toContainEqual([`/api/sessions/${B}/focus`, { focused: true }]);
|
|
});
|
|
|
|
it("reports unfocused when focus clears to null", () => {
|
|
const fetchMock = mockFetch();
|
|
const pool = new ConnectionPool();
|
|
pool.setFocus(A);
|
|
fetchMock.mockClear();
|
|
|
|
pool.setFocus(null);
|
|
|
|
expect(fetchMock).toHaveBeenCalledOnce();
|
|
const [url, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit];
|
|
expect(url).toBe(`/api/sessions/${A}/focus`);
|
|
expect(JSON.parse(init.body as string)).toEqual({ focused: false });
|
|
});
|
|
|
|
it("is a no-op when the focused session doesn't change", () => {
|
|
const fetchMock = mockFetch();
|
|
const pool = new ConnectionPool();
|
|
pool.setFocus(A);
|
|
fetchMock.mockClear();
|
|
|
|
pool.setFocus(A);
|
|
|
|
expect(fetchMock).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("does not let a fetch rejection break the pool", async () => {
|
|
vi.stubGlobal(
|
|
"fetch",
|
|
vi.fn(() => Promise.reject(new Error("offline"))),
|
|
);
|
|
const pool = new ConnectionPool();
|
|
|
|
expect(() => pool.setFocus(A)).not.toThrow();
|
|
// Let the rejected promise's `.catch` settle before the test ends.
|
|
await Promise.resolve();
|
|
await Promise.resolve();
|
|
});
|
|
});
|
|
|
|
describe("ConnectionPool.sendToFocused", () => {
|
|
it("types the bytes into the focused session's PTY", () => {
|
|
mockFetch();
|
|
sent.length = 0;
|
|
const pool = new ConnectionPool();
|
|
const pane = document.createElement("div");
|
|
pool.mount(A, pane);
|
|
pool.setFocus(A);
|
|
|
|
expect(pool.sendToFocused("\x0b")).toBe(true);
|
|
expect(sent).toEqual([{ id: A, data: "\x0b" }]);
|
|
});
|
|
|
|
it("is a no-op with nothing focused", () => {
|
|
mockFetch();
|
|
sent.length = 0;
|
|
const pool = new ConnectionPool();
|
|
|
|
expect(pool.sendToFocused("\x0b")).toBe(false);
|
|
expect(sent).toEqual([]);
|
|
});
|
|
});
|