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
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ If `npm run test` fails due to low coverage on a modified file, write meaningful

- Use TypeScript and React functional components with hooks.
- Follow existing code style and naming conventions.
- Adhere to clean code standards as measured by SonarQube.
- Place tests in `__tests__` directories or alongside components as `ComponentName.test.tsx`.
- Mock external dependencies and APIs in tests.

Expand Down
19 changes: 9 additions & 10 deletions __tests__/components/auth/SeizeConnectContext.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
import {
AuthenticationError,
SeizeConnectProvider,
useSeizeConnectContext,
WalletConnectionError,
WalletDisconnectionError,
} from "@/components/auth/SeizeConnectContext";
import { publicEnv } from "@/config/env";
import * as authUtils from "@/services/auth/auth.utils";
import { WalletInitializationError } from "@/src/errors/wallet";
import {
fireEvent,
render,
Expand All @@ -9,15 +18,6 @@ import {
import userEvent from "@testing-library/user-event";
import React from "react";
import { act } from "react-dom/test-utils";
import {
AuthenticationError,
SeizeConnectProvider,
useSeizeConnectContext,
WalletConnectionError,
WalletDisconnectionError,
} from "@/components/auth/SeizeConnectContext";
import * as authUtils from "@/services/auth/auth.utils";
import { WalletInitializationError } from "@/src/errors/wallet";

// Mock the Reown AppKit hooks
jest.mock("@reown/appkit/react", () => ({
Expand Down Expand Up @@ -48,7 +48,6 @@ jest.mock("viem", () => ({

// Mock auth utils
jest.mock("@/services/auth/auth.utils", () => ({
migrateCookiesToLocalStorage: jest.fn(),
getWalletAddress: jest.fn(() => null),
removeAuthJwt: jest.fn(),
}));
Expand Down
47 changes: 32 additions & 15 deletions __tests__/components/common/DateAccordion.test.tsx
Original file line number Diff line number Diff line change
@@ -1,40 +1,57 @@
import { render, screen, fireEvent } from '@testing-library/react';
import React from 'react';
import { fireEvent, render, screen } from "@testing-library/react";

jest.mock('framer-motion', () => ({
jest.mock("framer-motion", () => ({
motion: {
div: ({ children, ...props }: any) => <div {...props}>{children}</div>,
},
AnimatePresence: ({ children }: any) => <div>{children}</div>,
}));

jest.mock('@fortawesome/react-fontawesome', () => ({
jest.mock("@fortawesome/react-fontawesome", () => ({
FontAwesomeIcon: () => <span data-testid="icon" />,
}));

import DateAccordion from '@/components/common/DateAccordion';
import DateAccordion from "@/components/common/DateAccordion";

describe('DateAccordion', () => {
it('shows collapsed content when not expanded and triggers toggle', () => {
describe("DateAccordion", () => {
it("shows collapsed content when not expanded, exposes aria attributes, and triggers toggle", () => {
const toggle = jest.fn();
render(
<DateAccordion title="Title" isExpanded={false} onToggle={toggle} collapsedContent={<span>info</span>}>
<DateAccordion
title="Title"
isExpanded={false}
onToggle={toggle}
collapsedContent={<span>info</span>}>
<div data-testid="child" />
</DateAccordion>
);
expect(screen.getByText('info')).toBeInTheDocument();
expect(screen.queryByTestId('child')).not.toBeInTheDocument();
fireEvent.click(screen.getByText('Title'));
expect(screen.getByText("info")).toBeInTheDocument();
expect(screen.queryByTestId("child")).not.toBeInTheDocument();
const button = screen.getByRole("button", { name: /Title/ });
expect(button).toHaveAttribute("aria-expanded", "false");
expect(button).toHaveAttribute("aria-controls");
fireEvent.click(button);
expect(toggle).toHaveBeenCalled();
});

it('renders children when expanded', () => {
it("renders children when expanded and links aria-controls to the content region", () => {
render(
<DateAccordion title="Title" isExpanded={true} onToggle={() => {}} collapsedContent={<span>info</span>}>
<DateAccordion
title="Title"
isExpanded={true}
onToggle={() => {}}
collapsedContent={<span>info</span>}>
<div data-testid="child" />
</DateAccordion>
);
expect(screen.getByTestId('child')).toBeInTheDocument();
expect(screen.queryByText('info')).not.toBeInTheDocument();
const button = screen.getByRole("button", { name: /Title/ });
expect(button).toHaveAttribute("aria-expanded", "true");
const controls = button.getAttribute("aria-controls");
expect(controls).toBeTruthy();
expect(screen.getByTestId("child")).toBeInTheDocument();
expect(screen.queryByText("info")).not.toBeInTheDocument();
const region = controls ? document.getElementById(controls) : null;
expect(region).toBeInTheDocument();
expect(region).toContainElement(screen.getByTestId("child"));
});
});
62 changes: 62 additions & 0 deletions __tests__/components/common/FallbackImage.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { fireEvent, render, screen, waitFor } from "@testing-library/react";

import { FallbackImage } from "../../../components/common/FallbackImage";

describe("FallbackImage", () => {
afterEach(() => {
jest.restoreAllMocks();
});

it("falls back without logging to the console", async () => {
const errorSpy = jest.spyOn(console, "error").mockImplementation(() => {});
const onPrimaryError = jest.fn();

render(
<FallbackImage
primarySrc="primary.gif"
fallbackSrc="fallback.gif"
alt="fallback example"
onPrimaryError={onPrimaryError}
/>
);

const image = screen.getByRole("img", { name: "fallback example" });
expect(image).toHaveAttribute("src", "primary.gif");

fireEvent.error(image);

await waitFor(() => {
expect(image).toHaveAttribute("src", "fallback.gif");
});

expect(onPrimaryError).toHaveBeenCalledTimes(1);
expect(errorSpy).not.toHaveBeenCalled();
});
Comment thread
coderabbitai[bot] marked this conversation as resolved.

it("calls onError when both primary and fallback fail", async () => {
const onPrimaryError = jest.fn();
const onError = jest.fn();

render(
<FallbackImage
primarySrc="primary.gif"
fallbackSrc="fallback.gif"
alt="fallback example"
onPrimaryError={onPrimaryError}
onError={onError}
/>
);

const image = screen.getByRole("img", { name: "fallback example" });

fireEvent.error(image);
await waitFor(() => {
expect(image).toHaveAttribute("src", "fallback.gif");
});

fireEvent.error(image);

expect(onPrimaryError).toHaveBeenCalledTimes(1);
expect(onError).toHaveBeenCalledTimes(1);
});
});
30 changes: 22 additions & 8 deletions __tests__/components/header/HeaderSearchModal.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,13 @@ jest.mock(
(...args: any[]) =>
useLocalPreference(...args)
);
jest.mock(
"@/components/header/header-search/HeaderSearchModalItem",
() => (props: any) => <div data-testid="item">{JSON.stringify(props)}</div>
);
jest.mock("@/components/header/header-search/HeaderSearchModalItem", () => {
const MockHeaderSearchModalItem = (props: any) => (
<div data-testid="item">{JSON.stringify(props)}</div>
);
MockHeaderSearchModalItem.displayName = "MockHeaderSearchModalItem";
return MockHeaderSearchModalItem;
});

const profile = { handle: "alice", wallet: "0x1", display: "Alice", level: 1 };

Expand Down Expand Up @@ -137,6 +140,15 @@ describe("HeaderSearchModal", () => {
jest.clearAllMocks();
});

it("associates the search input with an accessible label", () => {
setup();
expect(
screen.getByRole("textbox", {
name: "Search",
})
).toBeInTheDocument();
});

it("calls onClose when escape is pressed", () => {
const { onClose } = setup();
escapeCb();
Expand All @@ -145,7 +157,7 @@ describe("HeaderSearchModal", () => {

it("renders search results when query returns items", () => {
setup();
const input = screen.getByRole("textbox");
const input = screen.getByRole("textbox", { name: "Search" });
fireEvent.change(input, { target: { value: "abc" } });
expect(screen.getByTestId("item")).toBeInTheDocument();
});
Expand All @@ -158,7 +170,7 @@ describe("HeaderSearchModal", () => {

it("navigates on enter key", () => {
const { push } = setup();
const input = screen.getByRole("textbox");
const input = screen.getByRole("textbox", { name: "Search" });
fireEvent.change(input, { target: { value: "alice" } });
enterCb();
expect(push).toHaveBeenCalled();
Expand Down Expand Up @@ -188,7 +200,7 @@ describe("HeaderSearchModal", () => {
},
});

const input = screen.getByRole("textbox");
const input = screen.getByRole("textbox", { name: "Search" });
fireEvent.change(input, { target: { value: "alice" } });

expect(
Expand All @@ -197,7 +209,9 @@ describe("HeaderSearchModal", () => {
)
).toBeInTheDocument();

const retryButton = await screen.findByRole("button", { name: /try again/i });
const retryButton = await screen.findByRole("button", {
name: /try again/i,
});
fireEvent.click(retryButton);

expect(profilesRefetch).toHaveBeenCalled();
Expand Down
3 changes: 0 additions & 3 deletions __tests__/components/header/share/HeaderShare.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,6 @@ const mockAuthUtils = {
getRefreshToken: jest.fn<string | null, []>(() => null),
getWalletAddress: jest.fn<string | null, []>(() => null),
getWalletRole: jest.fn<string | null, []>(() => null),
migrateCookiesToLocalStorage: jest.fn(),
removeAuthJwt: jest.fn(),
};

Expand All @@ -115,8 +114,6 @@ require("@/services/auth/auth.utils").getWalletAddress =
mockAuthUtils.getWalletAddress;
require("@/services/auth/auth.utils").getWalletRole =
mockAuthUtils.getWalletRole;
require("@/services/auth/auth.utils").migrateCookiesToLocalStorage =
mockAuthUtils.migrateCookiesToLocalStorage;
require("@/services/auth/auth.utils").removeAuthJwt =
mockAuthUtils.removeAuthJwt;

Expand Down
40 changes: 5 additions & 35 deletions __tests__/services/auth.utils.test.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import Cookies from "js-cookie";
import { jwtDecode } from "jwt-decode";
import { safeLocalStorage } from "@/helpers/safeLocalStorage";
import {
getAuthJwt,
getStagingAuth,
getWalletAddress,
migrateCookiesToLocalStorage,
removeAuthJwt,
setAuthJwt,
syncWalletRoleWithServer,
} from "@/services/auth/auth.utils";
import Cookies from "js-cookie";
import { jwtDecode } from "jwt-decode";

jest.mock("js-cookie", () => ({
get: jest.fn(),
Expand Down Expand Up @@ -64,9 +63,7 @@ describe("auth.utils", () => {
expect(safeLocalStorage.removeItem).toHaveBeenCalledWith(
"6529-wallet-role"
);
expect(safeLocalStorage.removeItem).toHaveBeenCalledWith(
"auth-role-addr"
);
expect(safeLocalStorage.removeItem).toHaveBeenCalledWith("auth-role-addr");
});

it("syncWalletRoleWithServer stores server role", () => {
Expand All @@ -86,9 +83,7 @@ describe("auth.utils", () => {
expect(safeLocalStorage.removeItem).toHaveBeenCalledWith(
"6529-wallet-role"
);
expect(safeLocalStorage.removeItem).toHaveBeenCalledWith(
"auth-role-0xabc"
);
expect(safeLocalStorage.removeItem).toHaveBeenCalledWith("auth-role-0xabc");
});

it("getAuthJwt prefers dev mode", () => {
Expand All @@ -110,29 +105,6 @@ describe("auth.utils", () => {
expect(getStagingAuth()).toBe("e");
});

it("migrateCookiesToLocalStorage moves and removes cookies", () => {
(Cookies.get as jest.Mock)
.mockReturnValueOnce("addr")
.mockReturnValueOnce("refresh")
.mockReturnValueOnce("role");
migrateCookiesToLocalStorage();
expect(safeLocalStorage.setItem).toHaveBeenCalledWith(
"6529-wallet-address",
"addr"
);
expect(Cookies.remove).toHaveBeenCalledWith("wallet-address");
expect(safeLocalStorage.setItem).toHaveBeenCalledWith(
"6529-wallet-refresh-token",
"refresh"
);
expect(Cookies.remove).toHaveBeenCalledWith("wallet-refresh-token");
expect(safeLocalStorage.setItem).toHaveBeenCalledWith(
"6529-wallet-role",
"role"
);
expect(Cookies.remove).toHaveBeenCalledWith("wallet-role");
});

it("getWalletAddress respects dev mode and storage", () => {
const { publicEnv } = require("@/config/env");
publicEnv.USE_DEV_AUTH = "true";
Expand All @@ -159,8 +131,6 @@ describe("auth.utils", () => {
expect(safeLocalStorage.removeItem).toHaveBeenCalledWith(
"6529-wallet-role"
);
expect(safeLocalStorage.removeItem).toHaveBeenCalledWith(
"auth-role-addr"
);
expect(safeLocalStorage.removeItem).toHaveBeenCalledWith("auth-role-addr");
});
});
Loading