Skip to content

Commit 229c7e4

Browse files
committed
Add terminal page tests
1 parent cb5ae98 commit 229c7e4

File tree

11 files changed

+358
-83
lines changed

11 files changed

+358
-83
lines changed

agent/agent.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -486,7 +486,7 @@ func (a *agent) handleReconnectingPTY(ctx context.Context, rawID string, conn ne
486486
go func() {
487487
// When the context has been completed either:
488488
// 1. The timeout completed.
489-
// 2. The parent context was cancelled.
489+
// 2. The parent context was canceled.
490490
<-ctx.Done()
491491
_ = process.Kill()
492492
}()

site/jest.config.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ module.exports = {
2828
testRegex: "(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$",
2929
testPathIgnorePatterns: ["/node_modules/", "/__tests__/fakes", "/e2e/"],
3030
moduleDirectories: ["node_modules", "<rootDir>"],
31+
moduleNameMapper: {
32+
"\\.css$": "<rootDir>/src/testHelpers/styleMock.ts",
33+
},
3134
},
3235
{
3336
displayName: "lint",

site/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
"react-router-dom": "6.3.0",
4343
"swr": "1.2.2",
4444
"xstate": "4.31.0",
45+
"xterm": "^4.18.0",
4546
"xterm-addon-fit": "^0.5.0",
4647
"xterm-addon-web-links": "^0.5.1",
4748
"xterm-addon-webgl": "^0.11.4",
@@ -84,8 +85,10 @@
8485
"eslint-plugin-react-hooks": "4.4.0",
8586
"html-webpack-plugin": "5.5.0",
8687
"jest": "27.5.1",
88+
"jest-canvas-mock": "^2.4.0",
8789
"jest-junit": "13.1.0",
8890
"jest-runner-eslint": "1.0.0",
91+
"jest-websocket-mock": "^2.3.0",
8992
"mini-css-extract-plugin": "2.6.0",
9093
"msw": "0.39.2",
9194
"prettier": "2.6.2",

site/src/api/index.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,13 @@ export const getOrganizations = async (): Promise<Types.Organization[]> => {
9090
return response.data
9191
}
9292

93-
export const getWorkspace = async (organizationID: string, workspaceName: string): Promise<Types.Workspace> => {
93+
export const getWorkspace = async (
94+
organizationID: string,
95+
username = "me",
96+
workspaceName: string,
97+
): Promise<Types.Workspace> => {
9498
const response = await axios.get<Types.Workspace>(
95-
`/api/v2/organizations/${organizationID}/workspaces/me/${workspaceName}`,
99+
`/api/v2/organizations/${organizationID}/workspaces/${username}/${workspaceName}`,
96100
)
97101
return response.data
98102
}
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
import { waitFor } from "@testing-library/react"
2+
import crypto from "crypto"
3+
import "jest-canvas-mock"
4+
import WS from "jest-websocket-mock"
5+
import { rest } from "msw"
6+
import React from "react"
7+
import { Route, Routes } from "react-router-dom"
8+
import { TextDecoder, TextEncoder } from "util"
9+
import { ReconnectingPTYRequest } from "../../api/types"
10+
import { history, MockWorkspaceAgent, render } from "../../testHelpers"
11+
import { server } from "../../testHelpers/server"
12+
import { Language, TerminalPage } from "./TerminalPage"
13+
14+
Object.defineProperty(window, "matchMedia", {
15+
writable: true,
16+
value: jest.fn().mockImplementation((query) => ({
17+
matches: false,
18+
media: query,
19+
onchange: null,
20+
addListener: jest.fn(), // deprecated
21+
removeListener: jest.fn(), // deprecated
22+
addEventListener: jest.fn(),
23+
removeEventListener: jest.fn(),
24+
dispatchEvent: jest.fn(),
25+
})),
26+
})
27+
28+
Object.defineProperty(window, "crypto", {
29+
value: {
30+
randomUUID: () => crypto.randomUUID(),
31+
},
32+
})
33+
34+
Object.defineProperty(window, "TextEncoder", {
35+
value: TextEncoder,
36+
})
37+
38+
const renderTerminal = () => {
39+
return render(
40+
<Routes>
41+
<Route path="/:username/:workspace/terminal" element={<TerminalPage renderer="dom" />} />
42+
</Routes>,
43+
)
44+
}
45+
46+
const expectTerminalText = (container: HTMLElement, text: string) => {
47+
return waitFor(() => {
48+
const elements = container.getElementsByClassName("xterm-rows")
49+
if (elements.length < 1) {
50+
throw new Error("no xterm-rows")
51+
}
52+
const row = elements[0] as HTMLDivElement
53+
if (!row.textContent) {
54+
throw new Error("no text content")
55+
}
56+
expect(row.textContent).toContain(text)
57+
})
58+
}
59+
60+
describe("TerminalPage", () => {
61+
beforeEach(() => {
62+
history.push("/some-user/my-workspace/terminal")
63+
})
64+
65+
it("shows an error if fetching organizations fails", async () => {
66+
// Given
67+
server.use(
68+
rest.get("/api/v2/users/me/organizations", async (req, res, ctx) => {
69+
return res(ctx.status(500), ctx.json({ message: "nope" }))
70+
}),
71+
)
72+
73+
// When
74+
const { container } = renderTerminal()
75+
76+
// Then
77+
await expectTerminalText(container, Language.organizationsErrorMessagePrefix)
78+
})
79+
80+
it("shows an error if fetching workspace fails", async () => {
81+
// Given
82+
server.use(
83+
rest.get("/api/v2/organizations/:organizationId/workspaces/:userName/:workspaceName", (req, res, ctx) => {
84+
return res(ctx.status(500), ctx.json({ id: "workspace-id" }))
85+
}),
86+
)
87+
88+
// When
89+
const { container } = renderTerminal()
90+
91+
// Then
92+
await expectTerminalText(container, Language.workspaceErrorMessagePrefix)
93+
})
94+
95+
it("shows an error if fetching workspace agent fails", async () => {
96+
// Given
97+
server.use(
98+
rest.get("/api/v2/workspacebuilds/:workspaceId/resources", (req, res, ctx) => {
99+
return res(ctx.status(500), ctx.json({ message: "nope" }))
100+
}),
101+
)
102+
103+
// When
104+
const { container } = renderTerminal()
105+
106+
// Then
107+
await expectTerminalText(container, Language.workspaceAgentErrorMessagePrefix)
108+
})
109+
110+
it("shows an error if the websocket fails", async () => {
111+
// Given
112+
server.use(
113+
rest.get("/api/v2/workspaceagents/:agentId/pty", (req, res, ctx) => {
114+
return res(ctx.status(500), ctx.json({}))
115+
}),
116+
)
117+
118+
// When
119+
const { container } = renderTerminal()
120+
121+
// Then
122+
await expectTerminalText(container, Language.websocketErrorMessagePrefix)
123+
})
124+
125+
it("renders data from the backend", async () => {
126+
// Given
127+
const server = new WS("ws://localhost/api/v2/workspaceagents/" + MockWorkspaceAgent.id + "/pty")
128+
const text = "something to render"
129+
130+
// When
131+
const { container } = renderTerminal()
132+
133+
// Then
134+
await server.connected
135+
server.send(text)
136+
await expectTerminalText(container, text)
137+
server.close()
138+
})
139+
140+
it("resizes on connect", async () => {
141+
// Given
142+
const server = new WS("ws://localhost/api/v2/workspaceagents/" + MockWorkspaceAgent.id + "/pty")
143+
144+
// When
145+
renderTerminal()
146+
147+
// Then
148+
await server.connected
149+
const msg = await server.nextMessage
150+
const req: ReconnectingPTYRequest = JSON.parse(new TextDecoder().decode(msg as Uint8Array))
151+
152+
expect(req.height).toBeGreaterThan(0)
153+
expect(req.width).toBeGreaterThan(0)
154+
server.close()
155+
})
156+
})

0 commit comments

Comments
 (0)