Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { CollectedStatsSeasonTile } from "@/components/user/collected/stats/subcomponents/CollectedStatsSeasonTile";
import type { DisplaySeason } from "@/components/user/collected/stats/types";
import { render } from "@testing-library/react";
import { fireEvent, render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";

let shouldReduceMotion = false;

Expand All @@ -16,6 +17,7 @@ const buildSeason = (
): DisplaySeason => ({
id: "Season 2",
label: "SZN2",
seasonNumber: 2,
totalCards: 39,
setsHeld: 1,
nextSetCards: 26,
Expand All @@ -34,11 +36,11 @@ const renderTile = (
render(
<CollectedStatsSeasonTile
season={season}
isActive={false}
isSelected={false}
showDetailText={false}
hasTouchScreen={false}
shouldAnimateProgressOnMount={shouldAnimateProgressOnMount}
onActivate={jest.fn()}
onPreview={jest.fn()}
/>
);

Expand Down Expand Up @@ -116,4 +118,94 @@ describe("CollectedStatsSeasonTile", () => {
);
expect(progressCircle?.querySelector("animate")).not.toBeInTheDocument();
});

it("does not trigger the selection handler on hover", async () => {
const user = userEvent.setup();
const onPreview = jest.fn();
const onSelect = jest.fn();

render(
<CollectedStatsSeasonTile
season={buildSeason()}
isSelected={false}
showDetailText={false}
hasTouchScreen={false}
shouldAnimateProgressOnMount
onPreview={onPreview}
onSelect={onSelect}
/>
);

const button = screen.getByRole("button", { name: /szn2/i });

await user.hover(button);

expect(onPreview).toHaveBeenCalled();
expect(onSelect).not.toHaveBeenCalled();
});

it("shows a subtle selected state on the whole tile", () => {
render(
<CollectedStatsSeasonTile
season={buildSeason()}
isSelected
showDetailText={false}
hasTouchScreen={false}
shouldAnimateProgressOnMount
onPreview={jest.fn()}
/>
);

expect(screen.getByRole("button", { name: /szn2/i })).toHaveAttribute(
"aria-pressed",
"true"
);
expect(screen.getByRole("button", { name: /szn2/i })).toHaveClass(
"tw-border-iron-700",
"tw-bg-iron-950/80"
);
});

it("keeps the unselected tile visually plain until it is active", () => {
render(
<CollectedStatsSeasonTile
season={buildSeason()}
isSelected={false}
showDetailText={false}
hasTouchScreen={false}
shouldAnimateProgressOnMount
onPreview={jest.fn()}
/>
);

const button = screen.getByRole("button", { name: /szn2/i });

expect(button).toHaveClass("tw-border-transparent", "tw-bg-transparent");
expect(button).not.toHaveClass(
"hover:tw-border-iron-900",
"hover:tw-bg-iron-950/60"
);
});

it("does not trigger preview when selection is activated by click", () => {
const onPreview = jest.fn();
const onSelect = jest.fn();

render(
<CollectedStatsSeasonTile
season={buildSeason()}
isSelected={false}
showDetailText={false}
hasTouchScreen
shouldAnimateProgressOnMount
onPreview={onPreview}
onSelect={onSelect}
/>
);

fireEvent.click(screen.getByRole("button", { name: /szn2/i }));

expect(onPreview).not.toHaveBeenCalled();
expect(onSelect).toHaveBeenCalled();
});
});
182 changes: 179 additions & 3 deletions __tests__/components/user/collected/UserPageCollected.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import UserPageCollected from "@/components/user/collected/UserPageCollected";
import { CollectedCollectionType } from "@/entities/IProfile";
import { useQuery } from "@tanstack/react-query";
import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import {
useParams,
usePathname,
Expand All @@ -26,7 +27,14 @@ jest.mock(
() =>
function MockFilters(props: any) {
return (
<div data-testid="filters" data-collection={props.filters.collection} />
<div data-testid="filters" data-collection={props.filters.collection}>
<button
data-testid="filters-set-szn"
onClick={() => props.setSzn?.({ id: 2 })}
>
Set season
</button>
</div>
);
}
);
Expand Down Expand Up @@ -58,8 +66,28 @@ jest.mock(
jest.mock(
"@/components/user/collected/UserPageCollectedStats",
() =>
function MockCollectedStats() {
return <div data-testid="stats-summary" />;
function MockCollectedStats(props: any) {
return (
<div
data-testid="stats-summary"
data-active-season-number={String(props.activeSeasonNumber ?? "")}
>
<button
data-testid="stats-collection-shortcut"
onClick={() =>
props.onCollectionShortcut?.(CollectedCollectionType.NEXTGEN)
}
>
NextGen shortcut
</button>
<button
data-testid="stats-season-shortcut"
onClick={() => props.onSeasonShortcut?.(2)}
>
SZN2 shortcut
</button>
</div>
);
}
);

Expand Down Expand Up @@ -93,12 +121,18 @@ describe("UserPageCollected", () => {
toString: jest.fn(() => ""),
entries: jest.fn(() => [].values()),
};
const getLastReplaceParams = () => {
const path = routerReplace.mock.calls.at(-1)?.[0] as string | undefined;
const query = path?.split("?")[1] ?? "";
return new URLSearchParams(query);
};

beforeEach(() => {
useParamsMock.mockReturnValue({ user: "testuser" });
usePathnameMock.mockReturnValue("/testuser/collected");
useSearchParamsMock.mockReturnValue(mockSearchParams);
mockSearchParams.get.mockReturnValue(null);
mockSearchParams.toString.mockReturnValue("");

// Provide router with replace/push so component calls during mount don't crash
useRouterMock.mockReturnValue({
Expand Down Expand Up @@ -308,4 +342,146 @@ describe("UserPageCollected", () => {
})
);
});

it("applies collection shortcuts through the existing url filter flow", async () => {
const user = userEvent.setup();

mockSearchParams.get.mockImplementation((key: string) => {
if (key === "collection") return "network";
if (key === "sort-by") return "xtdh";
if (key === "sort-direction") return "desc";
return null;
});
mockSearchParams.toString.mockReturnValue(
"collection=network&sort-by=xtdh&sort-direction=desc"
);

renderWithTransferProvider(<UserPageCollected profile={mockProfile} />);

await user.click(screen.getByTestId("stats-collection-shortcut"));

const params = getLastReplaceParams();

expect(params.get("collection")).toBe("nextgen");
expect(params.get("page")).toBeNull();
expect(params.get("seized")).toBeNull();
expect(params.get("szn")).toBeNull();
expect(params.get("subcollection")).toBeNull();
expect(params.get("sort-by")).toBeNull();
expect(params.get("sort-direction")).toBeNull();
});

it("applies season shortcuts through the existing url filter flow", async () => {
const user = userEvent.setup();

renderWithTransferProvider(<UserPageCollected profile={mockProfile} />);

await user.click(screen.getByTestId("stats-season-shortcut"));

const params = getLastReplaceParams();

expect(params.get("collection")).toBe("memes");
expect(params.get("page")).toBeNull();
expect(params.get("seized")).toBe("seized");
expect(params.get("szn")).toBe("2");
expect(params.get("subcollection")).toBeNull();
expect(params.get("sort-by")).toBeNull();
expect(params.get("sort-direction")).toBeNull();
});

it("clears the collection shortcut when the same metric is clicked again", async () => {
const user = userEvent.setup();

mockSearchParams.get.mockImplementation((key: string) => {
if (key === "collection") return "nextgen";
if (key === "seized") return "seized";
return null;
});
mockSearchParams.toString.mockReturnValue(
"collection=nextgen&seized=seized"
);

renderWithTransferProvider(<UserPageCollected profile={mockProfile} />);

await user.click(screen.getByTestId("stats-collection-shortcut"));

const params = getLastReplaceParams();

expect(params.get("collection")).toBeNull();
expect(params.get("szn")).toBeNull();
expect(params.get("page")).toBeNull();
expect(routerReplace).toHaveBeenLastCalledWith("/testuser/collected", {
scroll: false,
});
});

it("clears the season shortcut when the same season is clicked again", async () => {
const user = userEvent.setup();

mockSearchParams.get.mockImplementation((key: string) => {
if (key === "collection") return "memes";
if (key === "szn") return "2";
if (key === "seized") return "seized";
return null;
});
mockSearchParams.toString.mockReturnValue(
"collection=memes&szn=2&seized=seized"
);

renderWithTransferProvider(<UserPageCollected profile={mockProfile} />);

await user.click(screen.getByTestId("stats-season-shortcut"));

const params = getLastReplaceParams();

expect(params.get("collection")).toBeNull();
expect(params.get("szn")).toBeNull();
expect(params.get("page")).toBeNull();
expect(routerReplace).toHaveBeenLastCalledWith("/testuser/collected", {
scroll: false,
});
});

it("uses network as a standalone canonical url without explicit default sort params", () => {
mockSearchParams.get.mockImplementation((key: string) => {
if (key === "collection") return "network";
return null;
});
mockSearchParams.toString.mockReturnValue("collection=network");

renderWithTransferProvider(<UserPageCollected profile={mockProfile} />);

expect(useQueryMock).toHaveBeenCalledWith(
expect.objectContaining({
queryKey: expect.arrayContaining([
"XTDH_TOKENS",
"testuser",
1,
24,
"XTDH",
"DESC",
]),
})
);
});

it("passes the optimistic season selection to the stats summary", async () => {
const user = userEvent.setup();

renderWithTransferProvider(<UserPageCollected profile={mockProfile} />);

expect(screen.getByTestId("stats-summary")).toHaveAttribute(
"data-active-season-number",
""
);

await user.click(screen.getByTestId("filters-set-szn"));

await waitFor(() =>
expect(screen.getByTestId("stats-summary")).toHaveAttribute(
"data-active-season-number",
"2"
)
);
});
});
Loading
Loading