Skip to content

Commit a8df9ec

Browse files
committed
Add unit tests
(generated by Claude Opus 4.6)
1 parent fd5c98a commit a8df9ec

61 files changed

Lines changed: 8460 additions & 124 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/model_catalog/package-lock.json

Lines changed: 1662 additions & 122 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

apps/model_catalog/package.json

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,19 +36,28 @@
3636
"preview": "vite preview",
3737
"serve:e2e": "vite --port 5173",
3838
"e2e": "npx cypress run --browser chrome",
39-
"e2e:ci": "npx start-server-and-test serve:e2e http://localhost:5173 e2e"
39+
"e2e:ci": "npx start-server-and-test serve:e2e http://localhost:5173 e2e",
40+
"test": "vitest run",
41+
"test:watch": "vitest",
42+
"test:coverage": "vitest run --coverage"
4043
},
4144
"devDependencies": {
4245
"@eslint/js": "^9.19.0",
46+
"@testing-library/jest-dom": "^5.17.0",
47+
"@testing-library/react": "^12.1.5",
48+
"@testing-library/user-event": "^13.5.0",
4349
"@types/react": "^17.0.2",
4450
"@types/react-dom": "^17.0.2",
4551
"@vitejs/plugin-react": "^4.3.4",
52+
"@vitest/coverage-v8": "^4.1.2",
4653
"cypress": "^13.17.0",
4754
"globals": "^15.14.0",
55+
"jsdom": "^29.0.1",
4856
"prettier": "^2.3.0",
4957
"start-server-and-test": "^1.12.2",
5058
"vite": "^6.1.0",
5159
"vite-plugin-commonjs": "^0.10.4",
52-
"vite-plugin-node-polyfills": "^0.23.0"
60+
"vite-plugin-node-polyfills": "^0.23.0",
61+
"vitest": "^4.1.2"
5362
}
5463
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import React from "react";
2+
import { screen, fireEvent } from "@testing-library/react";
3+
import { renderWithProviders } from "./helpers/renderWithProviders";
4+
import AuthWidget from "../AuthWidget";
5+
6+
describe("AuthWidget", () => {
7+
it("renders a Login button when no user is logged in", () => {
8+
renderWithProviders(<AuthWidget currentUser={null} />);
9+
expect(screen.getByRole("button", { name: /login/i })).toBeInTheDocument();
10+
});
11+
12+
it("renders a person icon button when user is logged in", () => {
13+
renderWithProviders(<AuthWidget currentUser="John Doe" />);
14+
// The icon button should be present instead of a Login text button
15+
expect(screen.queryByRole("button", { name: /login/i })).not.toBeInTheDocument();
16+
expect(screen.getByRole("button")).toBeInTheDocument();
17+
});
18+
19+
it("shows the username in a tooltip when user is logged in", () => {
20+
renderWithProviders(<AuthWidget currentUser="John Doe" />);
21+
// The tooltip title is set to the currentUser prop
22+
expect(screen.getByRole("button")).toBeInTheDocument();
23+
});
24+
});
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import React from "react";
2+
import { render, screen } from "@testing-library/react";
3+
import ColoredCircularProgress from "../ColoredCircularProgress";
4+
5+
describe("ColoredCircularProgress", () => {
6+
it("renders a progress indicator", () => {
7+
render(<ColoredCircularProgress />);
8+
expect(screen.getByRole("progressbar")).toBeInTheDocument();
9+
});
10+
});
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import React from "react";
2+
import { render } from "@testing-library/react";
3+
import CompareMultiGraphs from "../CompareMultiGraphs";
4+
5+
vi.mock("react-plotly.js", () => ({
6+
default: () => <div data-testid="plotly-mock" />,
7+
}));
8+
9+
vi.mock("react-plotly.js/factory", () => ({
10+
default: () => () => <div data-testid="plotly-mock" />,
11+
}));
12+
13+
vi.mock("plotly.js", () => ({}));
14+
15+
describe("CompareMultiGraphs (smoke test)", () => {
16+
it("renders without crashing with empty results", () => {
17+
render(
18+
<CompareMultiGraphs
19+
results={[]}
20+
model_ids={[]}
21+
test_ids={[]}
22+
/>
23+
);
24+
});
25+
});
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import React from "react";
2+
import { renderWithProviders } from "./helpers/renderWithProviders";
3+
import { createMockDatastore } from "./helpers/mockDatastore";
4+
import CompareMultiResults from "../CompareMultiResults";
5+
6+
vi.mock("../datastore", () => ({
7+
datastore: createMockDatastore(),
8+
}));
9+
10+
vi.mock("react-plotly.js", () => ({
11+
default: () => <div data-testid="plotly-mock" />,
12+
}));
13+
14+
vi.mock("react-plotly.js/factory", () => ({
15+
default: () => () => <div data-testid="plotly-mock" />,
16+
}));
17+
18+
describe("CompareMultiResults (smoke test)", () => {
19+
it("renders without crashing", () => {
20+
renderWithProviders(
21+
<CompareMultiResults
22+
open={true}
23+
onClose={vi.fn()}
24+
/>
25+
);
26+
});
27+
});
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import React from "react";
2+
import { render, screen } from "@testing-library/react";
3+
import ConfigDisplaySimple from "../ConfigDisplaySimple";
4+
5+
describe("ConfigDisplaySimple", () => {
6+
const defaultFilters = {
7+
species: ["Rattus norvegicus"],
8+
brain_region: ["CA1 field of hippocampus"],
9+
cell_type: [],
10+
model_scope: [],
11+
abstraction_level: [],
12+
test_type: [],
13+
score_type: [],
14+
recording_modality: [],
15+
implementation_status: [],
16+
project_id: [],
17+
};
18+
19+
it("renders filter chips for active filters", () => {
20+
render(
21+
<ConfigDisplaySimple display="Models and Tests" filters={defaultFilters} />
22+
);
23+
expect(screen.getByText("Rattus norvegicus")).toBeInTheDocument();
24+
expect(screen.getByText("CA1 field of hippocampus")).toBeInTheDocument();
25+
});
26+
27+
it("renders no chips when all filters are empty", () => {
28+
const emptyFilters = {
29+
species: [],
30+
brain_region: [],
31+
cell_type: [],
32+
model_scope: [],
33+
abstraction_level: [],
34+
test_type: [],
35+
score_type: [],
36+
recording_modality: [],
37+
implementation_status: [],
38+
project_id: [],
39+
};
40+
render(
41+
<ConfigDisplaySimple display="Models and Tests" filters={emptyFilters} />
42+
);
43+
expect(screen.queryByRole("button")).not.toBeInTheDocument();
44+
});
45+
46+
it("shows only model filters when display is Only Models", () => {
47+
const filters = {
48+
...defaultFilters,
49+
test_type: ["single cell"],
50+
};
51+
render(
52+
<ConfigDisplaySimple display="Only Models" filters={filters} />
53+
);
54+
// Model filters should be shown
55+
expect(screen.getByText("Rattus norvegicus")).toBeInTheDocument();
56+
// Test-only filters should not be shown
57+
expect(screen.queryByText("single cell")).not.toBeInTheDocument();
58+
});
59+
60+
it("shows only test filters when display is Only Tests", () => {
61+
const filters = {
62+
...defaultFilters,
63+
model_scope: ["network"],
64+
test_type: ["single cell"],
65+
};
66+
render(
67+
<ConfigDisplaySimple display="Only Tests" filters={filters} />
68+
);
69+
// Test filters should be shown
70+
expect(screen.getByText("single cell")).toBeInTheDocument();
71+
// Model-only filters should not be shown
72+
expect(screen.queryByText("network")).not.toBeInTheDocument();
73+
});
74+
});
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import React from "react";
2+
import { render, screen } from "@testing-library/react";
3+
import ConfigDisplayTop from "../ConfigDisplayTop";
4+
5+
describe("ConfigDisplayTop", () => {
6+
const defaultFilters = {
7+
species: ["Rattus norvegicus"],
8+
brain_region: [],
9+
cell_type: [],
10+
model_scope: [],
11+
abstraction_level: [],
12+
test_type: [],
13+
score_type: [],
14+
recording_modality: [],
15+
implementation_status: [],
16+
project_id: [],
17+
};
18+
19+
it("renders the accordion with header text", () => {
20+
render(
21+
<ConfigDisplayTop display="Models and Tests" filters={defaultFilters} />
22+
);
23+
expect(screen.getByText("View Current Configuration")).toBeInTheDocument();
24+
});
25+
26+
it("renders the display chip", () => {
27+
render(
28+
<ConfigDisplayTop display="Models and Tests" filters={defaultFilters} />
29+
);
30+
expect(screen.getByText("Models and Tests")).toBeInTheDocument();
31+
});
32+
33+
it("renders active filter values as chips", () => {
34+
render(
35+
<ConfigDisplayTop display="Models and Tests" filters={defaultFilters} />
36+
);
37+
expect(screen.getByText("Rattus norvegicus")).toBeInTheDocument();
38+
});
39+
40+
it("renders '<< all >>' for empty filter values", () => {
41+
render(
42+
<ConfigDisplayTop display="Models and Tests" filters={defaultFilters} />
43+
);
44+
// brain_region and others are empty, so they should show "<< all >>"
45+
const allChips = screen.getAllByText("<< all >>");
46+
expect(allChips.length).toBeGreaterThan(0);
47+
});
48+
49+
it("renders configure instruction text", () => {
50+
render(
51+
<ConfigDisplayTop display="Models and Tests" filters={defaultFilters} />
52+
);
53+
expect(
54+
screen.getByText(/To re-configure the app, click on the configure icon/)
55+
).toBeInTheDocument();
56+
});
57+
58+
it("shows only model filters when display is Only Models", () => {
59+
const filters = {
60+
...defaultFilters,
61+
test_type: ["single cell"],
62+
};
63+
render(
64+
<ConfigDisplayTop display="Only Models" filters={filters} />
65+
);
66+
expect(screen.getByText("Rattus norvegicus")).toBeInTheDocument();
67+
// test_type is test-only, should not appear
68+
expect(screen.queryByText("single cell")).not.toBeInTheDocument();
69+
});
70+
});
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import React from "react";
2+
import { screen, fireEvent } from "@testing-library/react";
3+
import { renderWithContext } from "./helpers/renderWithProviders";
4+
import { mockVocab } from "./helpers/fixtures";
5+
import ConfigForm from "../ConfigForm";
6+
7+
describe("ConfigForm", () => {
8+
const defaultConfig = {
9+
species: [],
10+
brain_region: [],
11+
cell_type: [],
12+
model_scope: [],
13+
abstraction_level: [],
14+
test_type: [],
15+
score_type: [],
16+
recording_modality: [],
17+
implementation_status: [],
18+
project_id: [],
19+
};
20+
21+
const defaultProps = {
22+
open: true,
23+
onClose: vi.fn(),
24+
config: defaultConfig,
25+
display: "Models and Tests",
26+
};
27+
28+
const contextOverrides = {
29+
validFilterValues: [mockVocab, vi.fn()],
30+
};
31+
32+
afterEach(() => {
33+
vi.clearAllMocks();
34+
});
35+
36+
it("renders the dialog with title", () => {
37+
renderWithContext(<ConfigForm {...defaultProps} />, contextOverrides);
38+
expect(screen.getByText("Configure App")).toBeInTheDocument();
39+
});
40+
41+
it("renders Cancel and Ok buttons", () => {
42+
renderWithContext(<ConfigForm {...defaultProps} />, contextOverrides);
43+
expect(screen.getByRole("button", { name: "Cancel" })).toBeInTheDocument();
44+
expect(screen.getByRole("button", { name: "Ok" })).toBeInTheDocument();
45+
});
46+
47+
it("calls onClose with cancel flag when Cancel is clicked", () => {
48+
renderWithContext(<ConfigForm {...defaultProps} />, contextOverrides);
49+
fireEvent.click(screen.getByRole("button", { name: "Cancel" }));
50+
expect(defaultProps.onClose).toHaveBeenCalledWith(
51+
"Models and Tests",
52+
defaultConfig,
53+
true
54+
);
55+
});
56+
57+
it("calls onClose without cancel flag when Ok is clicked", () => {
58+
renderWithContext(<ConfigForm {...defaultProps} />, contextOverrides);
59+
fireEvent.click(screen.getByRole("button", { name: "Ok" }));
60+
expect(defaultProps.onClose).toHaveBeenCalledWith(
61+
"Models and Tests",
62+
defaultConfig
63+
);
64+
});
65+
66+
it("does not render when open is false", () => {
67+
renderWithContext(<ConfigForm {...defaultProps} open={false} />, contextOverrides);
68+
expect(screen.queryByText("Configure App")).not.toBeInTheDocument();
69+
});
70+
71+
it("renders the display switch options", () => {
72+
renderWithContext(<ConfigForm {...defaultProps} />, contextOverrides);
73+
expect(screen.getByText("Only Models")).toBeInTheDocument();
74+
expect(screen.getByText("Only Tests")).toBeInTheDocument();
75+
});
76+
});

0 commit comments

Comments
 (0)