Skip to content

Commit 95cfe80

Browse files
committed
Fix web test mocks and add favicon wiring
1 parent ffe21e2 commit 95cfe80

6 files changed

Lines changed: 72 additions & 3 deletions

File tree

web/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
<head>
44
<meta charset="UTF-8" />
55
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<link rel="icon" type="image/png" href="/favicon.png" />
67
<title>underbyteLabs - ataxx-zero</title>
78
</head>
89
<body>

web/public/favicon.png

2.2 KB
Loading

web/src/pages/landing/LandingPage.test.tsx

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { fireEvent, screen } from "@testing-library/react";
1+
import { fireEvent, screen, waitFor } from "@testing-library/react";
22
import { beforeEach, describe, expect, it, vi } from "vitest";
33
import { LandingPage } from "@/pages/landing/LandingPage";
44
import { renderWithProviders } from "@/test/render";
@@ -12,6 +12,7 @@ const acceptMatchedQueueMock = vi.fn();
1212
const rejectMatchedQueueMock = vi.fn();
1313
const fetchPersistedGameSummaryMock = vi.fn();
1414
const fetchPublicPlayersMock = vi.fn();
15+
const prefetchPublicPlayersMock = vi.fn();
1516
const useAuthMock = vi.fn();
1617

1718
vi.mock("@/widgets/layout/AppShell", () => ({
@@ -37,6 +38,7 @@ vi.mock("@/features/match/persistence", () => ({
3738

3839
vi.mock("@/features/identity/api", () => ({
3940
fetchPublicPlayers: (...args: unknown[]) => fetchPublicPlayersMock(...args),
41+
prefetchPublicPlayers: (...args: unknown[]) => prefetchPublicPlayersMock(...args),
4042
}));
4143

4244
vi.mock("@/app/providers/useAuth", () => ({
@@ -54,6 +56,7 @@ describe("LandingPage queue", () => {
5456
rejectMatchedQueueMock.mockReset();
5557
fetchPersistedGameSummaryMock.mockReset();
5658
fetchPublicPlayersMock.mockReset();
59+
prefetchPublicPlayersMock.mockReset();
5760
openQueueSocketMock.mockReturnValue({
5861
close: vi.fn(),
5962
onmessage: null,
@@ -123,6 +126,7 @@ describe("LandingPage queue", () => {
123126
enabled: true,
124127
},
125128
]);
129+
prefetchPublicPlayersMock.mockResolvedValue([]);
126130
});
127131

128132
it("shows queue searching status after clicking Buscar partida", async () => {
@@ -192,4 +196,14 @@ describe("LandingPage queue", () => {
192196
expect(screen.getByRole("button", { name: /entrar como invitado/i })).toBeInTheDocument();
193197
});
194198

199+
it("prefetches player list when hovering advanced configuration link", async () => {
200+
renderWithProviders(<LandingPage />, { route: "/" });
201+
202+
fireEvent.mouseEnter(screen.getByRole("link", { name: /configuracion avanzada/i }));
203+
204+
await waitFor(() => {
205+
expect(prefetchPublicPlayersMock).toHaveBeenCalledWith("token-123", { limit: 200 });
206+
});
207+
});
208+
195209
});

web/src/pages/match/MatchPage.test.tsx

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const fetchPersistedGameSummaryMock = vi.fn();
1313
const fetchPersistedReplayMock = vi.fn();
1414
const openPersistedGameSocketMock = vi.fn();
1515
const fetchPublicPlayersMock = vi.fn();
16+
const readPrefetchedPublicPlayersMock = vi.fn();
1617
const createHumanInvitationMock = vi.fn();
1718
const fetchInvitationGameMock = vi.fn();
1819
const rejectInvitationMock = vi.fn();
@@ -47,7 +48,8 @@ vi.mock("@/features/match/persistence", () => ({
4748
}));
4849

4950
vi.mock("@/features/identity/api", () => ({
50-
fetchPublicPlayers: (...args: unknown[]) => fetchPublicPlayersMock(...args),
51+
prefetchPublicPlayers: (...args: unknown[]) => fetchPublicPlayersMock(...args),
52+
readPrefetchedPublicPlayers: (...args: unknown[]) => readPrefetchedPublicPlayersMock(...args),
5153
}));
5254

5355
vi.mock("@/features/matches/api", () => ({
@@ -89,6 +91,7 @@ describe("MatchPage spectator mode", () => {
8991
fetchPersistedReplayMock.mockReset();
9092
openPersistedGameSocketMock.mockReset();
9193
fetchPublicPlayersMock.mockReset();
94+
readPrefetchedPublicPlayersMock.mockReset();
9295
createHumanInvitationMock.mockReset();
9396
fetchInvitationGameMock.mockReset();
9497
rejectInvitationMock.mockReset();
@@ -152,6 +155,7 @@ describe("MatchPage spectator mode", () => {
152155
enabled: true,
153156
},
154157
]);
158+
readPrefetchedPublicPlayersMock.mockReturnValue(null);
155159
useAuthMock.mockReturnValue({
156160
user: { id: "spectator", username: "spec" },
157161
loading: false,
@@ -207,6 +211,33 @@ describe("MatchPage spectator mode", () => {
207211
expect(screen.getByLabelText("Jugador P1")).toBeDisabled();
208212
expect(screen.getByLabelText("Jugador P2")).toBeDisabled();
209213
}, 15000);
214+
215+
it("uses prefetched player catalog immediately before refreshing in background", async () => {
216+
const cachedPlayers = [
217+
{
218+
user_id: "bot-cached",
219+
is_bot: true,
220+
username: "CachedBot",
221+
bot_kind: "heuristic",
222+
agent_type: "heuristic",
223+
heuristic_level: "normal",
224+
model_mode: null,
225+
enabled: true,
226+
},
227+
];
228+
readPrefetchedPublicPlayersMock.mockReturnValueOnce(cachedPlayers);
229+
fetchPublicPlayersMock.mockResolvedValueOnce(cachedPlayers);
230+
231+
render(<MatchPage />);
232+
233+
await waitFor(() => {
234+
expect(screen.getByLabelText("Rival (jugador)")).toHaveValue("bot-cached");
235+
});
236+
expect(fetchPublicPlayersMock).toHaveBeenCalledWith("token-spec", {
237+
limit: 200,
238+
maxAgeMs: 0,
239+
});
240+
});
210241
});
211242

212243
describe("MatchPage automatic persistence", () => {
@@ -220,6 +251,7 @@ describe("MatchPage automatic persistence", () => {
220251
fetchPersistedReplayMock.mockReset();
221252
openPersistedGameSocketMock.mockReset();
222253
fetchPublicPlayersMock.mockReset();
254+
readPrefetchedPublicPlayersMock.mockReset();
223255
createHumanInvitationMock.mockReset();
224256
fetchInvitationGameMock.mockReset();
225257
rejectInvitationMock.mockReset();
@@ -283,6 +315,7 @@ describe("MatchPage automatic persistence", () => {
283315
enabled: true,
284316
},
285317
]);
318+
readPrefetchedPublicPlayersMock.mockReturnValue(null);
286319
useAuthMock.mockReturnValue({
287320
user: { id: "u1", username: "demo" },
288321
loading: false,
@@ -420,6 +453,7 @@ describe("MatchPage queued human vs human", () => {
420453
fetchPersistedReplayMock.mockReset();
421454
openPersistedGameSocketMock.mockReset();
422455
fetchPublicPlayersMock.mockReset();
456+
readPrefetchedPublicPlayersMock.mockReset();
423457
createHumanInvitationMock.mockReset();
424458
fetchInvitationGameMock.mockReset();
425459
rejectInvitationMock.mockReset();
@@ -483,6 +517,7 @@ describe("MatchPage queued human vs human", () => {
483517
enabled: true,
484518
},
485519
]);
520+
readPrefetchedPublicPlayersMock.mockReturnValue(null);
486521
useAuthMock.mockReturnValue({
487522
user: { id: "u2", username: "bravo" },
488523
loading: false,

web/src/widgets/layout/AppShell.test.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,18 @@ describe("AppShell", () => {
128128
expect(screen.getByText("contenido")).toBeInTheDocument();
129129
});
130130

131+
it("marks profile tab active on profile subroutes", () => {
132+
renderWithProviders(
133+
<AppShell>
134+
<div>detalle</div>
135+
</AppShell>,
136+
{ route: "/profile/games/game-123" },
137+
);
138+
139+
expect(screen.getByRole("link", { name: "Perfil" })).toHaveAttribute("aria-current", "page");
140+
expect(screen.getByRole("link", { name: "Inicio" })).not.toHaveAttribute("aria-current");
141+
});
142+
131143
it("renders login action when user is not authenticated", () => {
132144
authState = {
133145
isAuthenticated: false,

web/src/widgets/layout/AppShell.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,13 @@ type FlashState = {
5454
flash?: string | { message: string; tone?: FlashTone };
5555
};
5656

57+
function isNavRouteActive(currentPath: string, navPath: string): boolean {
58+
if (navPath === "/") {
59+
return currentPath === "/";
60+
}
61+
return currentPath === navPath || currentPath.startsWith(`${navPath}/`);
62+
}
63+
5764
function normalizeFlash(state: unknown): FlashMessage | null {
5865
if (typeof state !== "object" || state === null) {
5966
return null;
@@ -415,7 +422,7 @@ export function AppShell({ children, onNavigateAttempt, onLogoutAttempt }: AppSh
415422

416423
<nav className="mt-3 flex items-center gap-1 overflow-visible border-t border-zinc-800/80 pt-2">
417424
{NAV_ITEMS.map((item) => {
418-
const active = location.pathname === item.to;
425+
const active = isNavRouteActive(location.pathname, item.to);
419426
const Icon = item.icon;
420427
return (
421428
<Button

0 commit comments

Comments
 (0)