Skip to content

Commit 55cde55

Browse files
authored
feat: create generic error page (#113)
1 parent ff56447 commit 55cde55

File tree

18 files changed

+219
-43
lines changed

18 files changed

+219
-43
lines changed

AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,7 @@ describe("Component", () => {
352352
it("does something", async () => {
353353
render(<Component />);
354354
await waitFor(() => {
355-
expect(screen.getByText("Expected")).toBeInTheDocument();
355+
expect(screen.getByText("Expected")).toBeVisible();
356356
});
357357
});
358358
});

CLAUDE.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,10 @@ pnpm generate-client:nofetch # Regenerate without fetching
202202
- Loading states and skeleton screens
203203
- Accessibility (keyboard navigation, screen readers)
204204

205+
### Testing Best Practices
206+
207+
- **Prefer `toBeVisible()` over `toBeInTheDocument()`** - `toBeVisible()` checks that an element is actually visible to the user (not hidden via CSS, `aria-hidden`, etc.), while `toBeInTheDocument()` only checks DOM presence. Use `toBeVisible()` for positive assertions and `.not.toBeInTheDocument()` for absence checks.
208+
205209
### Mocking & Testing
206210

207211
- **MSW Auto-Mocker**
@@ -230,13 +234,13 @@ describe("ServerList", () => {
230234
render(<ServerList />);
231235

232236
await waitFor(() => {
233-
expect(screen.getByText("Server 1")).toBeInTheDocument();
237+
expect(screen.getByText("Server 1")).toBeVisible();
234238
});
235239

236240
const copyButton = screen.getByRole("button", { name: /copy url/i });
237241
await userEvent.click(copyButton);
238242

239-
expect(screen.getByText(/copied/i)).toBeInTheDocument();
243+
expect(screen.getByText(/copied/i)).toBeVisible();
240244
});
241245
});
242246
```

src/app/catalog/[repoName]/[serverName]/[version]/not-found.test.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,15 @@ describe("NotFound", () => {
88

99
expect(
1010
screen.getByRole("heading", { name: /server not found/i }),
11-
).toBeInTheDocument();
11+
).toBeVisible();
1212
});
1313

1414
it("displays a descriptive message", () => {
1515
render(<NotFound />);
1616

1717
expect(
1818
screen.getByText(/doesn't exist or has been removed/i),
19-
).toBeInTheDocument();
19+
).toBeVisible();
2020
});
2121

2222
it("has a link to browse the catalog", () => {
@@ -29,13 +29,13 @@ describe("NotFound", () => {
2929
it("has a back button", () => {
3030
render(<NotFound />);
3131

32-
expect(screen.getByRole("button", { name: /back/i })).toBeInTheDocument();
32+
expect(screen.getByRole("button", { name: /back/i })).toBeVisible();
3333
});
3434

3535
it("displays a decorative illustration", () => {
3636
const { container } = render(<NotFound />);
3737

3838
const svg = container.querySelector("svg[aria-hidden='true']");
39-
expect(svg).toBeInTheDocument();
39+
expect(svg).toBeVisible();
4040
});
4141
});

src/app/catalog/[repoName]/[serverName]/[version]/not-found.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import Link from "next/link";
2-
import { ErrorPage } from "@/components/error-page";
2+
import { ErrorPageLayout } from "@/components/error-page/error-page";
33
import { NavigateBackButton } from "@/components/navigate-back-button";
44
import { Button } from "@/components/ui/button";
55

@@ -13,7 +13,7 @@ export default function NotFound() {
1313
className="w-fit"
1414
/>
1515

16-
<ErrorPage
16+
<ErrorPageLayout
1717
title="Server Not Found"
1818
actions={
1919
<Button asChild variant="default">
@@ -23,7 +23,7 @@ export default function NotFound() {
2323
>
2424
The MCP server you're looking for doesn't exist or has been removed from
2525
the catalog.
26-
</ErrorPage>
26+
</ErrorPageLayout>
2727
</div>
2828
);
2929
}

src/app/catalog/actions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export async function getServers(): Promise<V0ServerJson[]> {
1111

1212
if (resp.error) {
1313
console.error("[catalog] Failed to fetch servers:", resp.error);
14-
return [];
14+
throw resp.error;
1515
}
1616

1717
if (!resp.data) {

src/app/catalog/components/__tests__/servers-wrapper.test.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,6 @@ describe("ServersWrapper", () => {
161161
const awsCanvas = screen.queryByText("aws-nova-canvas");
162162
const googleApps = screen.queryByText("google-applications");
163163

164-
expect(awsCanvas).toBeInTheDocument();
165164
expect(awsCanvas).toBeVisible();
166165
expect(googleApps).not.toBeInTheDocument();
167166
});

src/app/catalog/components/__tests__/servers.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ describe("Servers", () => {
224224
/>,
225225
);
226226

227-
expect(container.querySelector("svg")).toBeInTheDocument();
227+
expect(container.querySelector("svg")).toBeVisible();
228228
});
229229

230230
it("does not show clear search button when no servers and no search", () => {

src/app/error.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
"use client";
2+
3+
import { ErrorPage } from "@/components/error-page/error-page-client";
4+
5+
interface ErrorProps {
6+
error: Error & { digest?: string };
7+
reset: () => void;
8+
}
9+
10+
export default function RootErrorPage({ error, reset }: ErrorProps) {
11+
return <ErrorPage error={error} reset={reset} />;
12+
}

src/app/not-found.test.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,15 @@ describe("NotFound (root)", () => {
1212

1313
expect(
1414
screen.getByRole("heading", { name: /page not found/i }),
15-
).toBeInTheDocument();
15+
).toBeVisible();
1616
});
1717

1818
it("displays a generic error message", async () => {
1919
render(await NotFound());
2020

2121
expect(
2222
screen.getByText(/the page you're looking for doesn't exist/i),
23-
).toBeInTheDocument();
23+
).toBeVisible();
2424
});
2525

2626
it("has a link to browse the catalog", async () => {
@@ -42,12 +42,12 @@ describe("NotFound (root)", () => {
4242
const { container } = render(await NotFound());
4343

4444
const svg = container.querySelector("svg[aria-hidden='true']");
45-
expect(svg).toBeInTheDocument();
45+
expect(svg).toBeVisible();
4646
});
4747

4848
it("displays the navbar", async () => {
4949
render(await NotFound());
5050

51-
expect(screen.getByTestId("navbar")).toBeInTheDocument();
51+
expect(screen.getByTestId("navbar")).toBeVisible();
5252
});
5353
});

src/app/not-found.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import Link from "next/link";
2-
import { ErrorPage } from "@/components/error-page";
2+
import { ErrorPageLayout } from "@/components/error-page/error-page";
33
import { Navbar } from "@/components/navbar";
44
import { Button } from "@/components/ui/button";
55

@@ -8,7 +8,7 @@ export default async function NotFound() {
88
<div className="flex flex-col h-screen">
99
<Navbar />
1010
<main className="flex flex-col flex-1 overflow-hidden px-4 py-5">
11-
<ErrorPage
11+
<ErrorPageLayout
1212
title="Page Not Found"
1313
actions={
1414
<Button asChild variant="default">
@@ -17,7 +17,7 @@ export default async function NotFound() {
1717
}
1818
>
1919
The page you're looking for doesn't exist or has been moved.
20-
</ErrorPage>
20+
</ErrorPageLayout>
2121
</main>
2222
</div>
2323
);

0 commit comments

Comments
 (0)