Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/PullRequestOpenAll.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ jobs:
- name: Copy env
run: cp .env.example .env
- name: Test
run: npm test
run: npm run test:coverage
codeql:
name: CodeQL
runs-on: ubuntu-latest
Expand Down
8 changes: 8 additions & 0 deletions jest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ const createJestConfig = nextJest({

const config: Config = {
testEnvironment: "jsdom",
collectCoverageFrom: [
"src/**/*.{ts,tsx}",
"!src/**/*.d.ts",
"!src/**/__tests__/**",
],
coverageThreshold: {
global: { branches: 90 },
},
setupFilesAfterEnv: ["<rootDir>/jest.setup.ts"],
testPathIgnorePatterns: ["<rootDir>/.next/", "<rootDir>/node_modules/"],
modulePathIgnorePatterns: ["<rootDir>/.next/"],
Expand Down
8 changes: 2 additions & 6 deletions src/components/ProfileButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,7 @@ const ProfileButton: React.FC = () => {
}
};

if (isLoggedIn) {
fetchAvatar();
} else {
setAvatarUrl(GUEST_AVATAR_PATH);
}
if (isLoggedIn) fetchAvatar();
}, [isLoggedIn]);

// Close dropdown when clicking outside
Expand Down Expand Up @@ -138,7 +134,7 @@ const ProfileButton: React.FC = () => {
aria-label={isLoggedIn ? "Account menu" : "Login"}
style={{
border: "1px solid var(--color-line)",
background: "var(--color-surface)",
backgroundColor: "var(--color-surface)",
...(showAvatar && { backgroundImage: `url(${avatarUrl})` }),
}}
>
Expand Down
44 changes: 44 additions & 0 deletions src/components/__tests__/App.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { render, screen } from "@testing-library/react";
import type { AppProps } from "next/app";
import App from "@/pages/_app";
import { useDrugs } from "@/utils/drugs";

const originalFetch = global.fetch;

afterEach(() => {
global.fetch = originalFetch;
});

function FactsheetNames() {
const { data } = useDrugs();
return <p>{data?.map((drug) => drug.pretty_name).join(", ")}</p>;
}

test("reuses the downloaded drug database across page changes", async () => {
const fetchMock = jest.fn().mockResolvedValue({
ok: true,
json: async () => ({
example: { name: "example", pretty_name: "Example compound" },
}),
});
global.fetch = fetchMock;
const first = {
Component: FactsheetNames,
pageProps: {},
router: {} as AppProps["router"],
};
const second = {
...first,
Component: () => (
<main>
<FactsheetNames />
</main>
),
};
const { rerender } = render(<App {...first} />);
expect(await screen.findByText("Example compound")).toBeVisible();

rerender(<App {...second} />);
expect(screen.getByRole("main")).toHaveTextContent("Example compound");
expect(fetchMock).toHaveBeenCalledTimes(1);
});
57 changes: 57 additions & 0 deletions src/components/__tests__/AppealForm.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { fireEvent, render, screen } from "@testing-library/react";
import AppealForm from "../AppealForm";

test("rejects blank answers and submits all completed fields", () => {
const onSubmit = jest.fn();
render(<AppealForm onSubmit={onSubmit} submitting={false} />);
const submit = screen.getByRole("button", { name: "Submit Appeal" });
const reason = screen.getByRole("textbox", { name: /why you were banned/i });
fireEvent.change(reason, { target: { value: " " } });
const form = submit.closest("form");
if (!form) throw new Error("Expected an appeal form");
fireEvent.submit(form);
expect(onSubmit).not.toHaveBeenCalled();

fireEvent.change(reason, { target: { value: "I broke a rule." } });
fireEvent.change(screen.getByRole("textbox", { name: /make amends/i }), {
target: { value: "I apologized." },
});
fireEvent.change(screen.getByRole("textbox", { name: /avoid repeating/i }), {
target: { value: "I will review the rules." },
});
fireEvent.change(screen.getByRole("textbox", { name: /anything else/i }), {
target: { value: "Thank you." },
});
fireEvent.click(submit);
expect(onSubmit).toHaveBeenCalledWith({
reason: "I broke a rule.",
solution: "I apologized.",
future: "I will review the rules.",
extra: "Thank you.",
});
});

test("warns near the shared limit and blocks submissions over it", () => {
const onSubmit = jest.fn();
const { rerender } = render(
<AppealForm onSubmit={onSubmit} submitting={false} />,
);
const [reason, solution, future, extra] = screen.getAllByRole("textbox");
for (const field of [reason, solution, future]) {
fireEvent.change(field, { target: { value: "x" } });
}
fireEvent.change(extra, { target: { value: "x".repeat(3597) } });
expect(screen.getByText(/3600\/3600.*getting close/i)).toBeVisible();
const submit = screen.getByRole("button", { name: "Submit Appeal" });
expect(submit).toBeEnabled();
fireEvent.change(extra, { target: { value: "x".repeat(3598) } });
expect(screen.getByText(/3601\/3600.*too long/i)).toBeVisible();
expect(submit).toBeDisabled();
const form = submit.closest("form");
if (!form) throw new Error("Expected the submit button to be inside a form");
fireEvent.submit(form);
expect(onSubmit).not.toHaveBeenCalled();

rerender(<AppealForm onSubmit={onSubmit} submitting />);
expect(screen.getByRole("button", { name: /submitting/i })).toBeDisabled();
});
Loading
Loading