diff --git a/.github/workflows/coder.yaml b/.github/workflows/coder.yaml index 020de286445e2..a39f30c840a71 100644 --- a/.github/workflows/coder.yaml +++ b/.github/workflows/coder.yaml @@ -136,7 +136,7 @@ jobs: with: token: ${{ secrets.CODECOV_TOKEN }} files: ./gotests.coverage - flags: ${{ matrix.os }} + flags: unittest-go-${{ matrix.os }} fail_ci_if_error: true test-js: @@ -161,3 +161,12 @@ jobs: - run: yarn install - run: yarn build + + - run: yarn test:coverage + + - uses: codecov/codecov-action@v2 + with: + token: ${{ secrets.CODECOV_TOKEN }} + files: ./coverage/lcov.info + flags: unittest-js + fail_ci_if_error: true diff --git a/.gitignore b/.gitignore index 3c8f4804384bb..7bdc617bd2381 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,5 @@ yarn-error.log # Front-end ignore .next/ -site/.next/ \ No newline at end of file +site/.next/ +coverage/ \ No newline at end of file diff --git a/.prettierignore b/.prettierignore index d827b4937f7b5..9989ddd066534 100644 --- a/.prettierignore +++ b/.prettierignore @@ -12,4 +12,5 @@ yarn-error.log # Front-end ignore .next/ -site/.next/ \ No newline at end of file +site/.next/ +coverage/ \ No newline at end of file diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 0000000000000..101676e3f66d0 --- /dev/null +++ b/jest.config.js @@ -0,0 +1,26 @@ +module.exports = { + projects: [ + { + coverageReporters: ["text", "lcov"], + displayName: "test", + preset: "ts-jest", + roots: ["/site"], + transform: { + "^.+\\.tsx?$": "ts-jest", + }, + testEnvironment: "jsdom", + testRegex: "(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$", + testPathIgnorePatterns: ["/node_modules/", "/__tests__/fakes"], + moduleDirectories: ["node_modules", ""], + }, + ], + collectCoverageFrom: [ + "/site/**/*.js", + "/site/**/*.ts", + "/site/**/*.tsx", + "!/site/**/*.stories.tsx", + "!/site/.next/**/*.*", + "!/site/next-env.d.ts", + "!/site/next.config.js", + ], +} diff --git a/package.json b/package.json index 4d9a609d9ecce..e604c74a70b86 100644 --- a/package.json +++ b/package.json @@ -8,20 +8,26 @@ "build:dev": "next build site", "dev": "next dev site", "format:check": "prettier --check '**/*.{css,html,js,json,jsx,md,ts,tsx,yaml,yml}'", - "format:write": "prettier --write '**/*.{css,html,js,json,jsx,md,ts,tsx,yaml,yml}'" + "format:write": "prettier --write '**/*.{css,html,js,json,jsx,md,ts,tsx,yaml,yml}'", + "test": "jest --selectProjects test", + "test:coverage": "jest --selectProjects test --collectCoverage" }, "devDependencies": { "@material-ui/core": "4.9.4", "@material-ui/icons": "4.5.1", "@material-ui/lab": "4.0.0-alpha.42", + "@testing-library/react": "12.1.2", + "@types/jest": "27.4.0", "@types/node": "14.18.4", "@types/react": "17.0.38", "@types/react-dom": "17.0.11", "@types/superagent": "4.1.14", + "jest": "27.4.7", "next": "12.0.7", "prettier": "2.5.1", "react": "17.0.2", "react-dom": "17.0.2", + "ts-jest": "27.1.2", "ts-loader": "9.2.6", "typescript": "4.5.4" } diff --git a/site/components/Button/SplitButton.test.tsx b/site/components/Button/SplitButton.test.tsx new file mode 100644 index 0000000000000..67bfbe25676d1 --- /dev/null +++ b/site/components/Button/SplitButton.test.tsx @@ -0,0 +1,58 @@ +import { fireEvent, render, screen } from "@testing-library/react" +import React from "react" +import { SplitButton, SplitButtonProps } from "./SplitButton" + +namespace Helpers { + export type SplitButtonOptions = "a" | "b" | "c" + + // eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars + export const callback = (selectedOption: SplitButtonOptions): void => {} + + export const options: SplitButtonProps["options"] = [ + { + label: "test a", + value: "a", + }, + { + label: "test b", + value: "b", + }, + { + label: "test c", + value: "c", + }, + ] +} + +describe("SplitButton", () => { + describe("onClick", () => { + it("is called when primary action is clicked", () => { + // Given + const mockedAndSpyedCallback = jest.fn(Helpers.callback) + + // When + render() + fireEvent.click(screen.getByText("test a")) + + // Then + expect(mockedAndSpyedCallback.mock.calls.length).toBe(1) + expect(mockedAndSpyedCallback.mock.calls[0][0]).toBe("a") + }) + + it("is called when clicking option in pop-up", () => { + // Given + const mockedAndSpyedCallback = jest.fn(Helpers.callback) + + // When + render() + const buttons = screen.getAllByRole("button") + const dropdownButton = buttons[1] + fireEvent.click(dropdownButton) + fireEvent.click(screen.getByText("test c")) + + // Then + expect(mockedAndSpyedCallback.mock.calls.length).toBe(1) + expect(mockedAndSpyedCallback.mock.calls[0][0]).toBe("c") + }) + }) +}) diff --git a/site/components/EmptyState/index.test.tsx b/site/components/EmptyState/index.test.tsx new file mode 100644 index 0000000000000..d128a194d0eff --- /dev/null +++ b/site/components/EmptyState/index.test.tsx @@ -0,0 +1,14 @@ +import { screen } from "@testing-library/react" +import { render } from "../../test_helpers" +import React from "react" +import { EmptyState, EmptyStateProps } from "./index" + +describe("EmptyState", () => { + it("renders (smoke test)", async () => { + // When + render() + + // Then + await screen.findByText("Hello, world") + }) +}) diff --git a/site/components/Icons/index.test.tsx b/site/components/Icons/index.test.tsx new file mode 100644 index 0000000000000..e0c6f18481fe1 --- /dev/null +++ b/site/components/Icons/index.test.tsx @@ -0,0 +1,22 @@ +import React from "react" +import { SvgIcon } from "@material-ui/core" +import { render } from "./../../test_helpers" + +import * as Icons from "./index" + +const getAllIcons = (): [string, typeof SvgIcon][] => { + let k: keyof typeof Icons + let ret: [string, typeof SvgIcon][] = [] + for (k in Icons) { + ret.push([k, Icons[k]]) + } + return ret +} + +describe("Icons", () => { + const allIcons = getAllIcons() + + it.each(allIcons)(`rendering icon %p`, (_name, Icon) => { + render() + }) +}) diff --git a/site/components/Navbar/NavMenuEntry.tsx b/site/components/Navbar/NavMenuEntry.tsx deleted file mode 100644 index fbf719239d180..0000000000000 --- a/site/components/Navbar/NavMenuEntry.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import ListItemIcon from "@material-ui/core/ListItemIcon" -import MenuItem from "@material-ui/core/MenuItem" -import { SvgIcon, Typography } from "@material-ui/core" -import { makeStyles } from "@material-ui/core/styles" -import React from "react" - -export interface NavMenuEntryProps { - icon: typeof SvgIcon - path: string - label?: string - selected: boolean - className?: string - onClick?: () => void -} - -export const NavMenuEntry: React.FC = ({ - className, - icon, - path, - label = path, - selected, - onClick, -}) => { - const styles = useStyles() - const Icon = icon - return ( - -
- {icon && ( - - - - )} - {label} -
-
- ) -} - -const useStyles = makeStyles((theme) => ({ - root: { - padding: "2em", - }, - icon: { - color: theme.palette.text.primary, - - "& path": { - fill: theme.palette.text.primary, - }, - }, -})) diff --git a/site/components/Navbar/index.test.tsx b/site/components/Navbar/index.test.tsx new file mode 100644 index 0000000000000..9a729fd86e8f8 --- /dev/null +++ b/site/components/Navbar/index.test.tsx @@ -0,0 +1,15 @@ +import React from "react" +import { screen } from "@testing-library/react" + +import { render } from "../../test_helpers" +import { Navbar } from "./index" + +describe("Navbar", () => { + it("renders content", async () => { + // When + render() + + // Then + await screen.findAllByText("Coder", { exact: false }) + }) +}) diff --git a/site/components/Navbar/index.tsx b/site/components/Navbar/index.tsx index 6afc68c89f14f..3e3ae6bf8428b 100644 --- a/site/components/Navbar/index.tsx +++ b/site/components/Navbar/index.tsx @@ -18,7 +18,7 @@ export const Navbar: React.FC = () => {
-
Hello, World - Coder v2
+
Coder v2
diff --git a/site/components/Page/Footer.test.tsx b/site/components/Page/Footer.test.tsx new file mode 100644 index 0000000000000..32d710d1ad564 --- /dev/null +++ b/site/components/Page/Footer.test.tsx @@ -0,0 +1,15 @@ +import React from "react" +import { screen } from "@testing-library/react" + +import { render } from "../../test_helpers" +import { Footer } from "./Footer" + +describe("Footer", () => { + it("renders content", async () => { + // When + render(