Skip to content

Commit deaec9e

Browse files
authored
Merge pull request #775 from WHIZAB4TECH/Newmain
[CLEAN] Remove All mockApi.ts Usage from Pages
2 parents 5ead5bb + 72c8b0b commit deaec9e

13 files changed

Lines changed: 825 additions & 20 deletions

File tree

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import React from "react";
2+
import { render, screen, waitFor } from "@testing-library/react";
3+
import ErrorBoundary from "@/components/ErrorBoundary";
4+
import * as Sentry from "@sentry/nextjs";
5+
6+
jest.mock("@sentry/nextjs", () => ({
7+
captureException: jest.fn(),
8+
withScope: jest.fn((callback) => callback({ setExtras: jest.fn() })),
9+
}));
10+
11+
class ThrowError extends React.Component {
12+
componentDidMount() {
13+
throw new Error("Test error");
14+
}
15+
16+
render() {
17+
return <div>Should not render</div>;
18+
}
19+
}
20+
21+
function FailingComponent() {
22+
throw new Error("Test error");
23+
}
24+
25+
describe("ErrorBoundary", () => {
26+
beforeEach(() => {
27+
jest.clearAllMocks();
28+
});
29+
30+
it("renders children when there is no error", () => {
31+
render(
32+
<ErrorBoundary>
33+
<div>Normal content</div>
34+
</ErrorBoundary>
35+
);
36+
37+
expect(screen.getByText("Normal content")).toBeInTheDocument();
38+
});
39+
40+
it("catches errors thrown in child components and renders fallback", () => {
41+
const consoleSpy = jest.spyOn(console, "error").mockImplementation(() => {});
42+
43+
render(
44+
<ErrorBoundary>
45+
<FailingComponent />
46+
</ErrorBoundary>
47+
);
48+
49+
expect(screen.getByText("Something went wrong")).toBeInTheDocument();
50+
expect(screen.getByText("Try Again")).toBeInTheDocument();
51+
expect(Sentry.captureException).toHaveBeenCalled();
52+
53+
consoleSpy.mockRestore();
54+
});
55+
56+
it("catches errors thrown in componentDidMount and renders fallback", () => {
57+
const consoleSpy = jest.spyOn(console, "error").mockImplementation(() => {});
58+
59+
render(
60+
<ErrorBoundary>
61+
<ThrowError />
62+
</ErrorBoundary>
63+
);
64+
65+
expect(screen.getByText("Something went wrong")).toBeInTheDocument();
66+
expect(screen.getByText("Try Again")).toBeInTheDocument();
67+
expect(Sentry.captureException).toHaveBeenCalled();
68+
69+
consoleSpy.mockRestore();
70+
});
71+
72+
it("displays error ID when Sentry returns one", () => {
73+
const consoleSpy = jest.spyOn(console, "error").mockImplementation(() => {});
74+
(Sentry.captureException as jest.Mock).mockReturnValue("test-error-id-123");
75+
76+
render(
77+
<ErrorBoundary>
78+
<FailingComponent />
79+
</ErrorBoundary>
80+
);
81+
82+
expect(screen.getByText("Error ID:")).toBeInTheDocument();
83+
expect(screen.getByText("test-error-id-123")).toBeInTheDocument();
84+
85+
consoleSpy.mockRestore();
86+
});
87+
88+
it("resets error state when retry button is clicked", async () => {
89+
const consoleSpy = jest.spyOn(console, "error").mockImplementation(() => {});
90+
91+
const { rerender } = render(
92+
<ErrorBoundary>
93+
<FailingComponent />
94+
</ErrorBoundary>
95+
);
96+
97+
expect(screen.getByText("Something went wrong")).toBeInTheDocument();
98+
99+
const retryButton = screen.getByText("Try Again");
100+
retryButton.click();
101+
102+
// After reset, children should render again
103+
rerender(
104+
<ErrorBoundary>
105+
<div>Recovered content</div>
106+
</ErrorBoundary>
107+
);
108+
109+
await waitFor(() => {
110+
expect(screen.getByText("Recovered content")).toBeInTheDocument();
111+
});
112+
113+
consoleSpy.mockRestore();
114+
});
115+
116+
it("renders custom fallback when provided", () => {
117+
const consoleSpy = jest.spyOn(console, "error").mockImplementation(() => {});
118+
const customFallback = <div>Custom error UI</div>;
119+
120+
render(
121+
<ErrorBoundary fallback={customFallback}>
122+
<FailingComponent />
123+
</ErrorBoundary>
124+
);
125+
126+
expect(screen.getByText("Custom error UI")).toBeInTheDocument();
127+
expect(screen.queryByText("Something went wrong")).not.toBeInTheDocument();
128+
129+
consoleSpy.mockRestore();
130+
});
131+
});
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
"use client";
2-
export { default } from "@/components/ErrorUI";
2+
export { default } from "@/components/ui/ErrorUI";

app/(dashboard)/earnings/error.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
"use client";
2-
export { default } from "@/components/ErrorUI";
2+
export { default } from "@/components/ui/ErrorUI";

app/(dashboard)/vault/page.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import React, { useState, useEffect } from "react";
44
import VaultSidebar from "@/components/vault/VaultSidebar";
55
import NFTGrid from "@/components/vault/NFTGrid";
66
import MintConfigForm from "@/components/projects/MintConfigForm";
7-
import { MockApi } from "@/__mocks__/app/lib/mockApi";
87
import { ChevronRight } from "lucide-react";
98

109
export default function VaultPage() {

app/recovery/page.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import Link from "next/link";
55
import { useRouter } from "next/navigation";
66
import { useAuth } from "@/components/auth/AuthProvider";
77
import { useWallet } from "@/components/wallet/WalletProvider";
8-
import { MockApi } from "@/__mocks__/app/lib/mockApi";
98
import { restoreWalletFromMnemonic } from "@/app/lib/stellar";
109
import { decryptWithPassword } from "@/app/lib/cryptoUtils";
1110
import { secureStorage } from "@/app/lib/secureStorage";

components/ErrorBoundary.tsx

Lines changed: 77 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,90 @@
11
"use client";
22

33
import React from "react";
4+
import * as Sentry from "@sentry/nextjs";
5+
import { AlertCircle, RefreshCw } from "lucide-react";
6+
7+
interface ErrorBoundaryState {
8+
hasError: boolean;
9+
error: Error | null;
10+
errorId: string | null;
11+
}
12+
13+
interface ErrorBoundaryProps {
14+
children: React.ReactNode;
15+
fallback?: React.ReactNode;
16+
}
417

518
export default class ErrorBoundary extends React.Component<
6-
{ children: React.ReactNode },
7-
{ hasError: boolean }
19+
ErrorBoundaryProps,
20+
ErrorBoundaryState
821
> {
9-
state = { hasError: false };
22+
state: ErrorBoundaryState = {
23+
hasError: false,
24+
error: null,
25+
errorId: null,
26+
};
1027

11-
static getDerivedStateFromError() {
12-
return { hasError: true };
28+
static getDerivedStateFromError(error: Error): Partial<ErrorBoundaryState> {
29+
return {
30+
hasError: true,
31+
error,
32+
errorId: Sentry.captureException(error),
33+
};
1334
}
1435

36+
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
37+
// Additional context can be added here
38+
Sentry.withScope((scope) => {
39+
scope.setExtras({
40+
componentStack: errorInfo.componentStack,
41+
});
42+
Sentry.captureException(error);
43+
});
44+
}
45+
46+
handleReset = () => {
47+
this.setState({
48+
hasError: false,
49+
error: null,
50+
errorId: null,
51+
});
52+
};
53+
1554
render() {
16-
if (this.state.hasError) return null;
55+
if (this.state.hasError) {
56+
if (this.props.fallback) {
57+
return this.props.fallback;
58+
}
59+
60+
return (
61+
<div className="flex min-h-[50vh] flex-col items-center justify-center gap-6 p-8 text-center">
62+
<div className="flex h-16 w-16 items-center justify-center rounded-full bg-destructive/10">
63+
<AlertCircle className="h-8 w-8 text-destructive" />
64+
</div>
65+
<div className="space-y-2">
66+
<h2 className="text-2xl font-bold text-foreground">Something went wrong</h2>
67+
<p className="text-sm text-muted-foreground max-w-md">
68+
An unexpected error occurred. Please try again, or contact support if
69+
the problem persists.
70+
</p>
71+
</div>
72+
{this.state.errorId && (
73+
<p className="text-xs text-muted-foreground">
74+
Error ID: <code className="bg-muted px-1.5 py-0.5 rounded font-mono">{this.state.errorId}</code>
75+
</p>
76+
)}
77+
<button
78+
onClick={this.handleReset}
79+
className="flex items-center gap-2 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 transition-colors"
80+
>
81+
<RefreshCw className="h-4 w-4" />
82+
Try Again
83+
</button>
84+
</div>
85+
);
86+
}
87+
1788
return this.props.children;
1889
}
1990
}

0 commit comments

Comments
 (0)