Skip to content

Commit 813b549

Browse files
chore(site): Make FE tests faster (#6543)
1 parent 9b2abf0 commit 813b549

File tree

19 files changed

+867
-838
lines changed

19 files changed

+867
-838
lines changed

.github/workflows/ci.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -512,7 +512,7 @@ jobs:
512512
- name: Install node_modules
513513
run: ./scripts/yarn_install.sh
514514

515-
- run: yarn test:ci
515+
- run: yarn test:ci --max-workers ${{ steps.cpu-cores.outputs.count }}
516516
working-directory: site
517517

518518
- uses: codecov/codecov-action@v3

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ site/test-results/*
2727
site/e2e/test-results/*
2828
site/e2e/states/*.json
2929
site/playwright-report/*
30+
site/.swc
3031

3132
# Make target for updating golden files.
3233
cli/testdata/.gen-golden

.prettierignore

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ site/test-results/*
3030
site/e2e/test-results/*
3131
site/e2e/states/*.json
3232
site/playwright-report/*
33+
site/.swc
3334

3435
# Make target for updating golden files.
3536
cli/testdata/.gen-golden

site/.eslintignore

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ test-results/*
3030
e2e/test-results/*
3131
e2e/states/*.json
3232
playwright-report/*
33+
.swc
3334

3435
# Make target for updating golden files.
3536
../cli/testdata/.gen-golden

site/.prettierignore

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ test-results/*
3030
e2e/test-results/*
3131
e2e/states/*.json
3232
playwright-report/*
33+
.swc
3334

3435
# Make target for updating golden files.
3536
../cli/testdata/.gen-golden

site/jest.config.js

+18-18
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,28 @@
1-
// REMARK: Jest is supposed to never exceed 50% maxWorkers by default. However,
2-
// there seems to be an issue with this in our Ubuntu-based workspaces.
3-
// If we don't limit it, then 100% CPU and high MEM usage is hit
4-
// unexpectedly, leading to OOM kills.
5-
//
6-
// SEE thread: https://github.com/coder/coder/pull/483#discussion_r829636583
7-
const maxWorkers = 2
8-
91
module.exports = {
10-
maxWorkers,
2+
testTimeout: 10_000,
3+
maxWorkers: 8,
114
projects: [
125
{
13-
globals: {
14-
"ts-jest": {
15-
tsconfig: "./tsconfig.test.json",
16-
},
17-
},
18-
coverageReporters: ["text", "lcov"],
196
displayName: "test",
20-
preset: "ts-jest",
217
roots: ["<rootDir>"],
228
setupFilesAfterEnv: ["./jest.setup.ts"],
9+
extensionsToTreatAsEsm: [".ts"],
2310
transform: {
24-
"^.+\\.tsx?$": "ts-jest",
25-
"\\.m?jsx?$": "jest-esm-transformer",
11+
"^.+\\.(t|j)sx?$": [
12+
"@swc/jest",
13+
{
14+
jsc: {
15+
transform: {
16+
react: {
17+
runtime: "automatic",
18+
},
19+
},
20+
experimental: {
21+
plugins: [["jest_workaround", {}]],
22+
},
23+
},
24+
},
25+
],
2626
},
2727
testEnvironment: "jsdom",
2828
testRegex: "(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$",

site/jest.setup.ts

+3-17
Original file line numberDiff line numberDiff line change
@@ -5,30 +5,16 @@ import { server } from "./src/testHelpers/server"
55
import "jest-location-mock"
66
import { TextEncoder, TextDecoder } from "util"
77
import { Blob } from "buffer"
8-
import { fetch, Request, Response, Headers } from "@remix-run/web-fetch"
8+
import jestFetchMock from "jest-fetch-mock"
9+
10+
jestFetchMock.enableMocks()
911

1012
global.TextEncoder = TextEncoder
1113
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Polyfill for jsdom
1214
global.TextDecoder = TextDecoder as any
1315
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Polyfill for jsdom
1416
global.Blob = Blob as any
1517

16-
// From REMIX https://github.com/remix-run/react-router/blob/main/packages/react-router-dom/__tests__/setup.ts
17-
if (!global.fetch) {
18-
// Built-in lib.dom.d.ts expects `fetch(Request | string, ...)` but the web
19-
// fetch API allows a URL so @remix-run/web-fetch defines
20-
// `fetch(string | URL | Request, ...)`
21-
// @ts-expect-error -- Polyfill for jsdom
22-
global.fetch = fetch
23-
// Same as above, lib.dom.d.ts doesn't allow a URL to the Request constructor
24-
// @ts-expect-error -- Polyfill for jsdom
25-
global.Request = Request
26-
// web-std/fetch Response does not currently implement Response.error()
27-
// @ts-expect-error -- Polyfill for jsdom
28-
global.Response = Response
29-
global.Headers = Headers
30-
}
31-
3218
// Polyfill the getRandomValues that is used on utils/random.ts
3319
Object.defineProperty(global.self, "crypto", {
3420
value: {

site/package.json

+8-6
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@
3636
"@material-ui/icons": "4.5.1",
3737
"@material-ui/lab": "4.0.0-alpha.42",
3838
"@monaco-editor/react": "4.4.6",
39-
"@remix-run/web-fetch": "4.3.2",
4039
"@tanstack/react-query": "4.22.4",
4140
"@testing-library/react-hooks": "8.0.1",
4241
"@types/color-convert": "2.0.0",
@@ -59,6 +58,7 @@
5958
"front-matter": "4.0.2",
6059
"history": "5.3.0",
6160
"i18next": "21.9.1",
61+
"jest-environment-jsdom": "29.5.0",
6262
"jest-location-mock": "1.0.9",
6363
"just-debounce-it": "3.1.1",
6464
"lodash": "4.17.21",
@@ -93,10 +93,12 @@
9393
"@storybook/addon-essentials": "6.5.12",
9494
"@storybook/addon-links": "6.5.9",
9595
"@storybook/react": "6.5.12",
96+
"@swc/core": "1.3.38",
97+
"@swc/jest": "0.2.24",
9698
"@testing-library/jest-dom": "5.16.4",
9799
"@testing-library/react": "13.4.0",
98100
"@testing-library/user-event": "14.4.3",
99-
"@types/jest": "27.4.1",
101+
"@types/jest": "29.4.0",
100102
"@types/node": "14.18.22",
101103
"@types/react": "18.0.15",
102104
"@types/react-dom": "18.0.6",
@@ -120,17 +122,17 @@
120122
"eslint-plugin-react": "7.31.1",
121123
"eslint-plugin-react-hooks": "4.6.0",
122124
"eslint-plugin-unicorn": "44.0.0",
123-
"jest": "27.5.1",
125+
"jest": "29.5.0",
124126
"jest-canvas-mock": "2.4.0",
125-
"jest-esm-transformer": "1.0.0",
127+
"jest-fetch-mock": "3.0.3",
126128
"jest-runner-eslint": "1.1.0",
127129
"jest-websocket-mock": "2.4.0",
130+
"jest_workaround": "0.1.14",
128131
"monaco-editor": "0.34.1",
129-
"msw": "0.47.0",
132+
"msw": "1.1.0",
130133
"prettier": "2.8.1",
131134
"resize-observer": "1.0.4",
132135
"semver": "7.3.7",
133-
"ts-jest": "27.1.4",
134136
"typescript": "4.8.2"
135137
},
136138
"browserslist": [

site/src/components/DeploymentBanner/DeploymentBannerView.tsx

-1
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,6 @@ export const DeploymentBannerView: FC<DeploymentBannerViewProps> = ({
192192
<Tooltip title="A countdown until stats are fetched again. Click to refresh!">
193193
<Button
194194
className={`${styles.value} ${styles.refreshButton}`}
195-
title="Refresh"
196195
onClick={() => {
197196
if (fetchStats) {
198197
fetchStats()

site/src/components/Dialogs/ConfirmDialog/ConfirmDialog.tsx

+6-1
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,12 @@ export const ConfirmDialog: FC<PropsWithChildren<ConfirmDialogProps>> = ({
123123
}
124124

125125
return (
126-
<Dialog className={styles.dialogWrapper} onClose={onClose} open={open}>
126+
<Dialog
127+
className={styles.dialogWrapper}
128+
onClose={onClose}
129+
open={open}
130+
data-testid="dialog"
131+
>
127132
<div className={styles.dialogContent}>
128133
<h3 className={styles.dialogTitle}>{title}</h3>
129134
{description && (

site/src/components/DropdownButton/DropdownButton.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export const DropdownButton: FC<DropdownButtonProps> = ({
3030
const canOpen = secondaryActions.length > 0
3131

3232
return (
33-
<span className={styles.buttonContainer}>
33+
<span className={styles.buttonContainer} data-testid="workspace-actions">
3434
{/* primary workspace CTA */}
3535
<span data-testid="primary-cta" className={styles.primaryCta}>
3636
{primaryAction}

site/src/components/Loader/FullScreenLoader.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export const FullScreenLoader: FC = () => {
2020
const styles = useStyles()
2121

2222
return (
23-
<div className={styles.root}>
23+
<div className={styles.root} data-testid="loader">
2424
<CircularProgress />
2525
</div>
2626
)

site/src/components/Loader/Loader.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export const Loader: FC<React.PropsWithChildren<{ size?: number }>> = ({
1212
display="flex"
1313
alignItems="center"
1414
justifyContent="center"
15+
data-testid="loader"
1516
>
1617
<CircularProgress size={size} />
1718
</Box>

site/src/pages/CreateTemplatePage/CreateTemplatePage.test.tsx

+6-23
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,10 @@ import {
77
MockTemplateVersionVariable1,
88
MockTemplateVersionVariable2,
99
MockTemplateVersionVariable3,
10-
MockTemplateVersionVariable4,
11-
MockTemplateVersionVariable5,
1210
renderWithAuth,
1311
} from "testHelpers/renderHelpers"
1412
import CreateTemplatePage from "./CreateTemplatePage"
15-
import { screen, waitFor } from "@testing-library/react"
13+
import { screen, waitFor, within } from "@testing-library/react"
1614
import userEvent from "@testing-library/user-event"
1715
import * as API from "api/api"
1816

@@ -55,19 +53,19 @@ test("Create template with variables", async () => {
5553
MockTemplateVersionVariable1,
5654
MockTemplateVersionVariable2,
5755
MockTemplateVersionVariable3,
58-
MockTemplateVersionVariable4,
59-
MockTemplateVersionVariable5,
6056
])
6157

6258
// Render page, fill the name and submit
63-
const { router } = await renderPage()
59+
const { router, container } = await renderPage()
60+
const form = container.querySelector("form") as HTMLFormElement
6461
await userEvent.type(screen.getByLabelText(/Name/), "my-template")
6562
await userEvent.click(
66-
screen.getByRole("button", { name: /create template/i }),
63+
within(form).getByRole("button", { name: /create template/i }),
6764
)
6865

6966
// Wait for the variables form to be rendered and fill it
7067
await screen.findByText(/Variables/)
68+
7169
// Type first variable
7270
await userEvent.clear(screen.getByLabelText(/var.first_variable/))
7371
await userEvent.type(
@@ -79,28 +77,15 @@ test("Create template with variables", async () => {
7977
await userEvent.type(screen.getByLabelText(/var.second_variable/), "2")
8078
// Select third variable on radio
8179
await userEvent.click(screen.getByLabelText(/True/))
82-
// Type fourth variable
83-
await userEvent.clear(screen.getByLabelText(/var.fourth_variable/))
84-
await userEvent.type(
85-
screen.getByLabelText(/var.fourth_variable/),
86-
"Fourth value",
87-
)
88-
// Type fifth variable
89-
await userEvent.clear(screen.getByLabelText(/var.fifth_variable/))
90-
await userEvent.type(
91-
screen.getByLabelText(/var.fifth_variable/),
92-
"Fifth value",
93-
)
9480
// Setup the mock for the second template version creation before submit the form
9581
jest.clearAllMocks()
9682
jest
9783
.spyOn(API, "createTemplateVersion")
9884
.mockResolvedValue(MockTemplateVersion)
9985
jest.spyOn(API, "createTemplate").mockResolvedValue(MockTemplate)
10086
await userEvent.click(
101-
screen.getByRole("button", { name: /create template/i }),
87+
within(form).getByRole("button", { name: /create template/i }),
10288
)
103-
10489
await waitFor(() => expect(API.createTemplate).toBeCalledTimes(1))
10590
expect(router.state.location.pathname).toEqual(
10691
`/templates/${MockTemplate.name}`,
@@ -115,8 +100,6 @@ test("Create template with variables", async () => {
115100
{ name: "first_variable", value: "First value" },
116101
{ name: "second_variable", value: "2" },
117102
{ name: "third_variable", value: "true" },
118-
{ name: "fourth_variable", value: "Fourth value" },
119-
{ name: "fifth_variable", value: "Fifth value" },
120103
],
121104
})
122105
})

site/src/pages/WorkspacePage/WorkspacePage.test.tsx

+17-11
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ const renderWorkspacePage = async () => {
3535
route: `/@${MockWorkspace.owner_name}/${MockWorkspace.name}`,
3636
path: "/@:username/:workspace",
3737
})
38+
3839
await waitForLoaderToBeRemoved()
3940
}
4041

@@ -46,9 +47,9 @@ const renderWorkspacePage = async () => {
4647
*/
4748
const testButton = async (label: string, actionMock: jest.SpyInstance) => {
4849
const user = userEvent.setup()
49-
5050
await renderWorkspacePage()
51-
const button = await screen.findByRole("button", { name: label })
51+
const workspaceActions = screen.getByTestId("workspace-actions")
52+
const button = within(workspaceActions).getByRole("button", { name: label })
5253
await user.click(button)
5354
expect(actionMock).toBeCalled()
5455
}
@@ -86,32 +87,36 @@ afterAll(() => {
8687

8788
describe("WorkspacePage", () => {
8889
it("requests a delete job when the user presses Delete and confirms", async () => {
89-
const user = userEvent.setup()
90-
90+
const user = userEvent.setup({ delay: 0 })
9191
const deleteWorkspaceMock = jest
9292
.spyOn(api, "deleteWorkspace")
9393
.mockResolvedValueOnce(MockWorkspaceBuild)
9494
await renderWorkspacePage()
9595

9696
// open the workspace action popover so we have access to all available ctas
97-
const trigger = await screen.findByTestId("workspace-actions-button")
97+
const trigger = screen.getByTestId("workspace-actions-button")
9898
await user.click(trigger)
99-
10099
const buttonText = t("actionButton.delete", { ns: "workspacePage" })
100+
101+
// Click on delete
101102
const button = await screen.findByText(buttonText)
102103
await user.click(button)
103104

105+
// Get dialog and confirm
106+
const dialog = await screen.findByTestId("dialog")
104107
const labelText = t("deleteDialog.confirmLabel", {
105108
ns: "common",
106109
entity: "workspace",
107110
})
108-
const textField = await screen.findByLabelText(labelText)
111+
const textField = within(dialog).getByLabelText(labelText)
109112
await user.type(textField, MockWorkspace.name)
110-
const confirmButton = await screen.findByRole("button", { name: "Delete" })
113+
const confirmButton = within(dialog).getByRole("button", {
114+
name: "Delete",
115+
hidden: false,
116+
})
111117
await user.click(confirmButton)
112118
expect(deleteWorkspaceMock).toBeCalled()
113-
// This test takes long to finish
114-
}, 20_000)
119+
})
115120

116121
it("requests a start job when the user presses Start", async () => {
117122
server.use(
@@ -157,7 +162,8 @@ describe("WorkspacePage", () => {
157162

158163
await renderWorkspacePage()
159164

160-
const cancelButton = await screen.findByRole("button", {
165+
const workspaceActions = screen.getByTestId("workspace-actions")
166+
const cancelButton = within(workspaceActions).getByRole("button", {
161167
name: "cancel action",
162168
})
163169

0 commit comments

Comments
 (0)