Skip to content

Commit 62e68fa

Browse files
authored
chore: Add tests to brand store snaps (#5028)
1 parent 5a58a8b commit 62e68fa

7 files changed

Lines changed: 339 additions & 1 deletion

File tree

static/js/publisher/pages/Snaps/SnapsFilter.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ function SnapsFilter({
2929
}: Props): ReactNode {
3030
return (
3131
<div className="p-search-box">
32+
<label htmlFor="search-snaps" className="u-off-screen">
33+
Search snaps
34+
</label>
3235
<input
3336
type="search"
3437
className="p-search-box__input"
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import { RecoilRoot, MutableSnapshot } from "recoil";
2+
import { QueryClient, QueryClientProvider } from "react-query";
3+
import { BrowserRouter } from "react-router-dom";
4+
import { setupServer } from "msw/node";
5+
import { render, screen, waitFor } from "@testing-library/react";
6+
import userEvent from "@testing-library/user-event";
7+
import "@testing-library/jest-dom";
8+
9+
import {
10+
RecoilObserver,
11+
brandStoreRequests,
12+
storesResponse,
13+
} from "../../../test-utils";
14+
15+
import Snaps from "../Snaps";
16+
17+
import { brandStoresState } from "../../../state/brandStoreState";
18+
19+
jest.mock("react-router-dom", () => {
20+
return {
21+
...jest.requireActual("react-router-dom"),
22+
useParams: () => ({
23+
id: "test-store-id",
24+
}),
25+
};
26+
});
27+
28+
const queryClient = new QueryClient();
29+
30+
function renderComponent() {
31+
return render(
32+
<RecoilRoot
33+
initializeState={(snapshot: MutableSnapshot) => {
34+
return snapshot.set(brandStoresState, storesResponse);
35+
}}
36+
>
37+
<QueryClientProvider client={queryClient}>
38+
<BrowserRouter>
39+
<RecoilObserver node={brandStoresState} event={jest.fn()} />
40+
<Snaps />
41+
</BrowserRouter>
42+
</QueryClientProvider>
43+
</RecoilRoot>,
44+
);
45+
}
46+
47+
const server = setupServer();
48+
49+
beforeAll(() => {
50+
server.listen();
51+
});
52+
53+
beforeEach(() => {
54+
brandStoreRequests(server);
55+
});
56+
57+
afterEach(() => {
58+
server.resetHandlers();
59+
});
60+
61+
afterAll(() => {
62+
server.close();
63+
});
64+
65+
describe("Snaps", () => {
66+
test("shows correct store name in title", async () => {
67+
renderComponent();
68+
69+
await waitFor(() => {
70+
expect(
71+
screen.getByRole("heading", {
72+
level: 1,
73+
name: "Test store / Store snaps",
74+
}),
75+
).toBeInTheDocument();
76+
});
77+
});
78+
79+
test("populates snaps in store table", async () => {
80+
renderComponent();
81+
82+
await waitFor(() => {
83+
expect(
84+
screen.getByRole("gridcell", { name: "test-snap-name" }),
85+
).toBeInTheDocument();
86+
});
87+
});
88+
89+
test("populates included snaps table", async () => {
90+
renderComponent();
91+
92+
await waitFor(() => {
93+
expect(
94+
screen.getByRole("gridcell", { name: "test-snap-name-included" }),
95+
).toBeInTheDocument();
96+
});
97+
});
98+
99+
test("search filters snaps table", async () => {
100+
renderComponent();
101+
102+
const user = userEvent.setup();
103+
104+
await waitFor(() => {
105+
user.type(screen.getByLabelText("Search snaps"), "e-2");
106+
});
107+
108+
await waitFor(() => {
109+
expect(
110+
screen.getByRole("gridcell", { name: "test-snap-name-2" }),
111+
).toBeInTheDocument();
112+
expect(
113+
screen.queryByRole("gridcell", { name: "test-snap-name" }),
114+
).not.toBeInTheDocument();
115+
});
116+
});
117+
});
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { useEffect } from "react";
2+
import { RecoilState, useRecoilValue } from "recoil";
3+
4+
import type { EventFunction } from "../types/shared";
5+
6+
const RecoilObserver = <T,>({
7+
node,
8+
event,
9+
}: {
10+
node: RecoilState<T>;
11+
event: EventFunction<T>;
12+
}) => {
13+
const value = useRecoilValue(node);
14+
15+
useEffect(() => {
16+
event(value);
17+
}, [event, value]);
18+
19+
return null;
20+
};
21+
22+
export default RecoilObserver;
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { http, HttpResponse } from "msw";
2+
3+
import {
4+
accountResponse,
5+
snapsResponse,
6+
membersResponse,
7+
storesResponse,
8+
searchResponse,
9+
} from "./brand-store-responses";
10+
11+
// @ts-expect-error - Unknown type
12+
function brandStoreRequests(server) {
13+
server.use(
14+
http.get("/account.json", () => {
15+
return HttpResponse.json(accountResponse);
16+
}),
17+
);
18+
19+
server.use(
20+
http.get("/api/store/test-store-id/snaps", () => {
21+
return HttpResponse.json(snapsResponse);
22+
}),
23+
);
24+
25+
server.use(
26+
http.get("/api/store/test-store-id/members", () => {
27+
return HttpResponse.json(membersResponse);
28+
}),
29+
);
30+
31+
server.use(
32+
http.get("/api/stores", () => {
33+
return HttpResponse.json({
34+
success: true,
35+
data: storesResponse,
36+
});
37+
}),
38+
);
39+
40+
server.use(
41+
http.get(
42+
"/api/test-store-id/snaps/search?q=te&allowed_for_inclusion=test-store-id",
43+
() => {
44+
return HttpResponse.json(searchResponse);
45+
},
46+
),
47+
);
48+
}
49+
50+
export default brandStoreRequests;
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import type { Snap, Member, Store } from "../types/shared";
2+
3+
export const accountResponse = {
4+
publisher: {
5+
email: "john.doe@canonical.com",
6+
fullname: "John Doe",
7+
has_stores: true,
8+
indentity_url: "https://login.ubuntu.com/+id/test-user-id",
9+
image: null,
10+
is_canonical: true,
11+
nickname: "johndoe",
12+
},
13+
};
14+
15+
export const snapsResponse = [
16+
{
17+
essential: true,
18+
id: "test-snap-id",
19+
"latest-release": {
20+
channel: "stable",
21+
revision: 2,
22+
timestamp: "2018-04-01T12:37:37.926251+00:00",
23+
version: "1.0.2",
24+
},
25+
name: "test-snap-name",
26+
"other-stores": ["test-other-store-1", "test-other-store-2"],
27+
private: false,
28+
store: "test-store-id",
29+
users: [
30+
{
31+
displayname: "John Doe",
32+
roles: ["owner"],
33+
username: "johndoe",
34+
},
35+
],
36+
},
37+
{
38+
essential: true,
39+
id: "test-snap-id-2",
40+
"latest-release": {
41+
channel: "stable",
42+
revision: 2,
43+
timestamp: "2018-04-01T12:37:37.926251+00:00",
44+
version: "1.0.2",
45+
},
46+
name: "test-snap-name-2",
47+
"other-stores": ["test-other-store-1", "test-other-store-2"],
48+
private: false,
49+
store: "test-store-id",
50+
users: [
51+
{
52+
displayname: "John Doe",
53+
roles: ["owner"],
54+
username: "johndoe",
55+
},
56+
],
57+
},
58+
{
59+
essential: false,
60+
id: "test-snap-id-included",
61+
"latest-release": {
62+
channel: "edge",
63+
revision: 806,
64+
timestamp: "2025-02-14T10:47:54.153305+00:00",
65+
version: "0+git.d2171a4",
66+
},
67+
name: "test-snap-name-included",
68+
"other-stores": ["test-other-store-3"],
69+
private: false,
70+
store: "ubuntu",
71+
users: [
72+
{
73+
displayname: "Jane Doe",
74+
roles: ["owner"],
75+
username: "janedoe",
76+
},
77+
],
78+
},
79+
] as Snap[];
80+
81+
export const membersResponse = [
82+
{
83+
displayname: "John Doe",
84+
email: "john.doe@canonical.com",
85+
id: "john-doe-id",
86+
roles: ["admin", "review", "view", "access"],
87+
username: "johndoe",
88+
},
89+
] as Member[];
90+
91+
export const storesResponse = [
92+
{
93+
id: "ubuntu",
94+
name: "Global",
95+
roles: ["admin", "review", "view", "access"],
96+
snaps: [],
97+
},
98+
{
99+
id: "test-store-id",
100+
name: "Test store",
101+
roles: ["admin", "review", "view", "access"],
102+
snaps: [],
103+
},
104+
] as Store[];
105+
106+
export const searchResponse = [
107+
{
108+
essential: false,
109+
id: "test-snap-id-search",
110+
"latest-release": {
111+
channel: "edge",
112+
revision: 806,
113+
timestamp: "2025-02-14T10:47:54.153305+00:00",
114+
version: "0+git.d2171a4",
115+
},
116+
name: "test-snap-name-search",
117+
"other-stores": ["test-other-store-3"],
118+
private: false,
119+
store: "ubuntu",
120+
users: [
121+
{
122+
displayname: "Jane Doe",
123+
roles: ["owner"],
124+
username: "janedoe",
125+
},
126+
],
127+
},
128+
] as Snap[];
Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,19 @@
11
import { mockListingData } from "./mockListingData";
2+
import RecoilObserver from "./RecoilObserver";
3+
import brandStoreRequests from "./brand-store-requests";
4+
import {
5+
accountResponse,
6+
snapsResponse,
7+
membersResponse,
8+
storesResponse,
9+
} from "./brand-store-responses";
210

3-
export { mockListingData };
11+
export {
12+
mockListingData,
13+
RecoilObserver,
14+
brandStoreRequests,
15+
accountResponse,
16+
snapsResponse,
17+
membersResponse,
18+
storesResponse,
19+
};

static/js/publisher/types/shared.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,3 +164,5 @@ export type UsePoliciesResponse = {
164164
export type ApiError = {
165165
message: string;
166166
};
167+
168+
export type EventFunction<T> = (value: T) => void;

0 commit comments

Comments
 (0)