coder/site/src/pages/TerminalPage/TerminalPage.test.tsx

150 lines
4.8 KiB
TypeScript

import "jest-canvas-mock";
import { waitFor } from "@testing-library/react";
import WS from "jest-websocket-mock";
import { HttpResponse, http } from "msw";
import * as API from "api/api";
import {
MockUser,
MockWorkspace,
MockWorkspaceAgent,
} from "testHelpers/entities";
import { renderWithAuth } from "testHelpers/renderHelpers";
import { server } from "testHelpers/server";
import TerminalPage, { Language } from "./TerminalPage";
const renderTerminal = async (
route = `/${MockUser.username}/${MockWorkspace.name}/terminal`,
) => {
const utils = renderWithAuth(<TerminalPage />, {
route,
path: "/:username/:workspace/terminal",
});
await waitFor(() => {
// To avoid 'act' errors during testing, we ensure the component is
// completely rendered without any outstanding state updates. This is
// accomplished by incorporating a 'data-status' attribute into the
// component. We then observe this attribute for any changes, as we cannot
// rely on other screen elements to indicate completion.
const wrapper =
utils.container.querySelector<HTMLDivElement>("[data-status]")!;
expect(wrapper.dataset.state).not.toBe("initializing");
});
return utils;
};
const expectTerminalText = (container: HTMLElement, text: string) => {
return waitFor(
() => {
const elements = container.getElementsByClassName("xterm-rows");
if (elements.length === 0) {
throw new Error("no xterm-rows");
}
const row = elements[0] as HTMLDivElement;
if (!row.textContent) {
throw new Error("no text content");
}
expect(row.textContent).toContain(text);
},
{ timeout: 5_000 },
);
};
describe("TerminalPage", () => {
afterEach(() => {
WS.clean();
});
it("loads the right workspace data", async () => {
jest
.spyOn(API, "getWorkspaceByOwnerAndName")
.mockResolvedValue(MockWorkspace);
new WS(
`ws://localhost/api/v2/workspaceagents/${MockWorkspaceAgent.id}/pty`,
);
await renderTerminal(
`/${MockUser.username}/${MockWorkspace.name}/terminal`,
);
await waitFor(() => {
expect(API.getWorkspaceByOwnerAndName).toHaveBeenCalledWith(
MockUser.username,
MockWorkspace.name,
{ include_deleted: true },
);
});
});
it("shows an error if fetching workspace fails", async () => {
server.use(
http.get("/api/v2/users/:userId/workspace/:workspaceName", () => {
return HttpResponse.json({ id: "workspace-id" }, { status: 500 });
}),
);
const { container } = await renderTerminal();
await expectTerminalText(container, Language.workspaceErrorMessagePrefix);
});
it("shows an error if the websocket fails", async () => {
server.use(
http.get("/api/v2/workspaceagents/:agentId/pty", () => {
return HttpResponse.json({}, { status: 500 });
}),
);
const { container } = await renderTerminal();
await expectTerminalText(container, Language.websocketErrorMessagePrefix);
});
it("renders data from the backend", async () => {
const ws = new WS(
`ws://localhost/api/v2/workspaceagents/${MockWorkspaceAgent.id}/pty`,
);
const text = "something to render";
const { container } = await renderTerminal();
// Ideally we could use ws.connected but that seems to pause React updates.
// For now, wait for the initial resize message instead.
await ws.nextMessage;
ws.send(text);
await expectTerminalText(container, text);
});
// Ideally we could just pass the correct size in the web socket URL without
// having to resize separately afterward (and then we could delete also this
// test), but we need the initial resize message to have something to wait for
// in the other tests since ws.connected appears to pause React updates. So
// for now the initial resize message (and this test) are here to stay.
it("resizes on connect", async () => {
const ws = new WS(
`ws://localhost/api/v2/workspaceagents/${MockWorkspaceAgent.id}/pty`,
);
await renderTerminal();
const msg = await ws.nextMessage;
const req = JSON.parse(new TextDecoder().decode(msg as Uint8Array));
expect(req.height).toBeGreaterThan(0);
expect(req.width).toBeGreaterThan(0);
});
it("supports workspace.agent syntax", async () => {
const ws = new WS(
`ws://localhost/api/v2/workspaceagents/${MockWorkspaceAgent.id}/pty`,
);
const text = "something to render";
const { container } = await renderTerminal(
`/some-user/${MockWorkspace.name}.${MockWorkspaceAgent.name}/terminal`,
);
// Ideally we could use ws.connected but that seems to pause React updates.
// For now, wait for the initial resize message instead.
await ws.nextMessage;
ws.send(text);
await expectTerminalText(container, text);
});
});