Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 423611b

Browse files
authored
chore: Add initial jest tests + code coverage (#13)
- Adds initial infra for running front-end tests (`jest`, `ts-jest`, `jest.config.js`, etc) - Adds codecov integration front-end code
1 parent afc2fa3 commit 423611b

20 files changed

+2512
-170
lines changed

.github/workflows/coder.yaml

+10-1
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ jobs:
136136
with:
137137
token: ${{ secrets.CODECOV_TOKEN }}
138138
files: ./gotests.coverage
139-
flags: ${{ matrix.os }}
139+
flags: unittest-go-${{ matrix.os }}
140140
fail_ci_if_error: true
141141

142142
test-js:
@@ -161,3 +161,12 @@ jobs:
161161
- run: yarn install
162162

163163
- run: yarn build
164+
165+
- run: yarn test:coverage
166+
167+
- uses: codecov/codecov-action@v2
168+
with:
169+
token: ${{ secrets.CODECOV_TOKEN }}
170+
files: ./coverage/lcov.info
171+
flags: unittest-js
172+
fail_ci_if_error: true

.gitignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,5 @@ yarn-error.log
1616

1717
# Front-end ignore
1818
.next/
19-
site/.next/
19+
site/.next/
20+
coverage/

.prettierignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,5 @@ yarn-error.log
1212

1313
# Front-end ignore
1414
.next/
15-
site/.next/
15+
site/.next/
16+
coverage/

jest.config.js

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
module.exports = {
2+
projects: [
3+
{
4+
coverageReporters: ["text", "lcov"],
5+
displayName: "test",
6+
preset: "ts-jest",
7+
roots: ["<rootDir>/site"],
8+
transform: {
9+
"^.+\\.tsx?$": "ts-jest",
10+
},
11+
testEnvironment: "jsdom",
12+
testRegex: "(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$",
13+
testPathIgnorePatterns: ["/node_modules/", "/__tests__/fakes"],
14+
moduleDirectories: ["node_modules", "<rootDir>"],
15+
},
16+
],
17+
collectCoverageFrom: [
18+
"<rootDir>/site/**/*.js",
19+
"<rootDir>/site/**/*.ts",
20+
"<rootDir>/site/**/*.tsx",
21+
"!<rootDir>/site/**/*.stories.tsx",
22+
"!<rootDir>/site/.next/**/*.*",
23+
"!<rootDir>/site/next-env.d.ts",
24+
"!<rootDir>/site/next.config.js",
25+
],
26+
}

package.json

+7-1
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,26 @@
88
"build:dev": "next build site",
99
"dev": "next dev site",
1010
"format:check": "prettier --check '**/*.{css,html,js,json,jsx,md,ts,tsx,yaml,yml}'",
11-
"format:write": "prettier --write '**/*.{css,html,js,json,jsx,md,ts,tsx,yaml,yml}'"
11+
"format:write": "prettier --write '**/*.{css,html,js,json,jsx,md,ts,tsx,yaml,yml}'",
12+
"test": "jest --selectProjects test",
13+
"test:coverage": "jest --selectProjects test --collectCoverage"
1214
},
1315
"devDependencies": {
1416
"@material-ui/core": "4.9.4",
1517
"@material-ui/icons": "4.5.1",
1618
"@material-ui/lab": "4.0.0-alpha.42",
19+
"@testing-library/react": "12.1.2",
20+
"@types/jest": "27.4.0",
1721
"@types/node": "14.18.4",
1822
"@types/react": "17.0.38",
1923
"@types/react-dom": "17.0.11",
2024
"@types/superagent": "4.1.14",
25+
"jest": "27.4.7",
2126
"next": "12.0.7",
2227
"prettier": "2.5.1",
2328
"react": "17.0.2",
2429
"react-dom": "17.0.2",
30+
"ts-jest": "27.1.2",
2531
"ts-loader": "9.2.6",
2632
"typescript": "4.5.4"
2733
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { fireEvent, render, screen } from "@testing-library/react"
2+
import React from "react"
3+
import { SplitButton, SplitButtonProps } from "./SplitButton"
4+
5+
namespace Helpers {
6+
export type SplitButtonOptions = "a" | "b" | "c"
7+
8+
// eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars
9+
export const callback = (selectedOption: SplitButtonOptions): void => {}
10+
11+
export const options: SplitButtonProps<SplitButtonOptions>["options"] = [
12+
{
13+
label: "test a",
14+
value: "a",
15+
},
16+
{
17+
label: "test b",
18+
value: "b",
19+
},
20+
{
21+
label: "test c",
22+
value: "c",
23+
},
24+
]
25+
}
26+
27+
describe("SplitButton", () => {
28+
describe("onClick", () => {
29+
it("is called when primary action is clicked", () => {
30+
// Given
31+
const mockedAndSpyedCallback = jest.fn(Helpers.callback)
32+
33+
// When
34+
render(<SplitButton onClick={mockedAndSpyedCallback} options={Helpers.options} />)
35+
fireEvent.click(screen.getByText("test a"))
36+
37+
// Then
38+
expect(mockedAndSpyedCallback.mock.calls.length).toBe(1)
39+
expect(mockedAndSpyedCallback.mock.calls[0][0]).toBe("a")
40+
})
41+
42+
it("is called when clicking option in pop-up", () => {
43+
// Given
44+
const mockedAndSpyedCallback = jest.fn(Helpers.callback)
45+
46+
// When
47+
render(<SplitButton onClick={mockedAndSpyedCallback} options={Helpers.options} />)
48+
const buttons = screen.getAllByRole("button")
49+
const dropdownButton = buttons[1]
50+
fireEvent.click(dropdownButton)
51+
fireEvent.click(screen.getByText("test c"))
52+
53+
// Then
54+
expect(mockedAndSpyedCallback.mock.calls.length).toBe(1)
55+
expect(mockedAndSpyedCallback.mock.calls[0][0]).toBe("c")
56+
})
57+
})
58+
})
+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { screen } from "@testing-library/react"
2+
import { render } from "../../test_helpers"
3+
import React from "react"
4+
import { EmptyState, EmptyStateProps } from "./index"
5+
6+
describe("EmptyState", () => {
7+
it("renders (smoke test)", async () => {
8+
// When
9+
render(<EmptyState message="Hello, world" />)
10+
11+
// Then
12+
await screen.findByText("Hello, world")
13+
})
14+
})

site/components/Icons/index.test.tsx

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import React from "react"
2+
import { SvgIcon } from "@material-ui/core"
3+
import { render } from "./../../test_helpers"
4+
5+
import * as Icons from "./index"
6+
7+
const getAllIcons = (): [string, typeof SvgIcon][] => {
8+
let k: keyof typeof Icons
9+
let ret: [string, typeof SvgIcon][] = []
10+
for (k in Icons) {
11+
ret.push([k, Icons[k]])
12+
}
13+
return ret
14+
}
15+
16+
describe("Icons", () => {
17+
const allIcons = getAllIcons()
18+
19+
it.each(allIcons)(`rendering icon %p`, (_name, Icon) => {
20+
render(<Icon />)
21+
})
22+
})

site/components/Navbar/NavMenuEntry.tsx

-51
This file was deleted.

site/components/Navbar/index.test.tsx

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import React from "react"
2+
import { screen } from "@testing-library/react"
3+
4+
import { render } from "../../test_helpers"
5+
import { Navbar } from "./index"
6+
7+
describe("Navbar", () => {
8+
it("renders content", async () => {
9+
// When
10+
render(<Navbar />)
11+
12+
// Then
13+
await screen.findAllByText("Coder", { exact: false })
14+
})
15+
})

site/components/Navbar/index.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export const Navbar: React.FC = () => {
1818
</Link>
1919
</div>
2020
<div className={styles.fullWidth}>
21-
<div className={styles.title}>Hello, World - Coder v2</div>
21+
<div className={styles.title}>Coder v2</div>
2222
</div>
2323
<div className={styles.fixed}>
2424
<List>

site/components/Page/Footer.test.tsx

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import React from "react"
2+
import { screen } from "@testing-library/react"
3+
4+
import { render } from "../../test_helpers"
5+
import { Footer } from "./Footer"
6+
7+
describe("Footer", () => {
8+
it("renders content", async () => {
9+
// When
10+
render(<Footer />)
11+
12+
// Then
13+
await screen.findByText("Copyright", { exact: false })
14+
})
15+
})

site/components/Page/index.tsx

+1-50
Original file line numberDiff line numberDiff line change
@@ -1,50 +1 @@
1-
import React from "react"
2-
import { makeStyles } from "@material-ui/core/styles"
3-
4-
import { Footer } from "./Footer"
5-
import { Navbar } from "../Navbar"
6-
7-
export const Page: React.FC<{ children: React.ReactNode }> = ({ children }) => {
8-
// TODO: More interesting styling here!
9-
10-
const styles = useStyles()
11-
12-
const header = (
13-
<div className={styles.header}>
14-
<Navbar />
15-
</div>
16-
)
17-
18-
const footer = (
19-
<div className={styles.footer}>
20-
<Footer />
21-
</div>
22-
)
23-
24-
const body = <div className={styles.body}> {children}</div>
25-
26-
return (
27-
<div className={styles.root}>
28-
{header}
29-
{body}
30-
{footer}
31-
</div>
32-
)
33-
}
34-
35-
const useStyles = makeStyles((theme) => ({
36-
root: {
37-
display: "flex",
38-
flexDirection: "column",
39-
},
40-
header: {
41-
flex: 0,
42-
},
43-
body: {
44-
height: "100%",
45-
flex: 1,
46-
},
47-
footer: {
48-
flex: 0,
49-
},
50-
}))
1+
export * from "./Footer"

site/next.config.js

-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
/* eslint-disable @typescript-eslint/no-unsafe-call */
44
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
55
/* eslint-disable @typescript-eslint/no-var-requires */
6-
const path = require("path")
76

87
module.exports = {
98
env: {},

0 commit comments

Comments
 (0)