Files
gerbeur/src/test/components/layout/AppHeader.test.tsx

128 lines
5.1 KiB
TypeScript

import { afterEach, describe, expect, test } from "vitest";
import { render, screen, waitFor } from "@testing-library/react";
import { userEvent } from "@testing-library/user-event";
import { I18nProvider } from "@lingui/react";
import { i18n } from "@lingui/core";
import { BrowserRouter, Route, Routes, useLocation } from "react-router";
import { useState } from "react";
import { AppHeader } from "../../../components/layout/AppHeader.tsx";
import { AuthContext } from "../../../contexts/auth/AuthContext.ts";
import { defaultWSValue, WSContext } from "../../../contexts/ws/WSContext.ts";
import type { Session } from "../../../utils/auth/storage.ts";
import { makeSession } from "../../fixtures.ts";
import { currentPath, renderWithContexts } from "../../render.tsx";
describe("AppHeader", () => {
test("guests get a login button", async () => {
const user = userEvent.setup();
renderWithContexts(<AppHeader />);
await user.click(screen.getByRole("button", { name: "Log in" }));
expect(currentPath()).toBe("/login");
});
test("members get their links, chat, bell and the new-dump button", () => {
renderWithContexts(<AppHeader disableNew />, {
session: makeSession({ username: "ann" }),
});
expect(screen.getByRole("link", { name: "Playlists" })).toHaveAttribute(
"href",
"/@ann/playlists",
);
expect(screen.getByRole("button", { name: "User menu" }))
.toBeInTheDocument();
expect(screen.getByRole("button", { name: "Chat" })).toBeInTheDocument();
expect(screen.getByRole("button", { name: "Notifications" }))
.toBeInTheDocument();
const dump = screen.getByRole("button", { name: /Dump/ });
expect(dump).toBeDisabled();
expect(dump).toHaveAttribute("title", "Server unreachable");
});
test("shows the live-updates banner when disconnected", () => {
renderWithContexts(<AppHeader />, {
ws: { wsStatus: "disconnected", wsErrorMessage: "Trying to reconnect…" },
});
expect(screen.getByRole("alert")).toHaveTextContent(
"Live updates unavailable. Trying to reconnect…",
);
});
test("the search toggle opens and closes, giving focus back", async () => {
const user = userEvent.setup();
renderWithContexts(<AppHeader />);
const toggle = screen.getByRole("button", { name: "Search" });
await user.click(toggle);
expect(screen.getByRole("button", { name: "Cancel search" }))
.toHaveAttribute(
"aria-expanded",
"true",
);
const [input] = screen.getAllByRole("searchbox");
await user.type(input, "x");
await user.keyboard("{Escape}");
expect(screen.getByRole("button", { name: "Search" })).toHaveFocus();
expect(screen.getAllByRole("searchbox")[0]).toHaveValue("");
});
// Leaving search needs the real history stack (React Router keeps its
// index in history.state), so these use a BrowserRouter.
function Browser({ session }: { session?: Session }) {
const [authResponse, setAuthResponse] = useState(session ?? null);
function Loc() {
const l = useLocation();
return <output data-testid="loc">{l.pathname + l.search}</output>;
}
return (
<I18nProvider i18n={i18n}>
<AuthContext.Provider value={{ authResponse, setAuthResponse }}>
<WSContext.Provider value={defaultWSValue}>
<BrowserRouter>
<Routes>
<Route path="*" element={<AppHeader />} />
</Routes>
<Loc />
</BrowserRouter>
</WSContext.Provider>
</AuthContext.Provider>
</I18nProvider>
);
}
const loc = () => screen.getByTestId("loc").textContent;
afterEach(() => globalThis.history.replaceState(null, "", "/"));
test("several searches, then ✕, return to where search began", async () => {
globalThis.history.replaceState(null, "", "/start");
const user = userEvent.setup();
render(<Browser />);
await user.click(screen.getByRole("button", { name: "Search" }));
await user.type(screen.getAllByRole("searchbox")[0], "cats{Enter}");
expect(loc()).toBe("/search?q=cats");
const bar = () => screen.getAllByRole("searchbox")[0];
expect(bar()).toHaveValue("cats");
await user.clear(bar());
await user.type(bar(), "dogs{Enter}");
expect(loc()).toBe("/search?q=dogs");
await user.click(screen.getByRole("button", { name: "Cancel search" }));
await waitFor(() => expect(loc()).toBe("/start"));
});
test("Escape on the results page reverts the query instead of leaving", async () => {
globalThis.history.replaceState(null, "", "/search?q=cats");
const user = userEvent.setup();
render(<Browser />);
const bar = screen.getAllByRole("searchbox")[0];
expect(bar).toHaveValue("cats");
await user.type(bar, "zzz{Escape}");
expect(bar).toHaveValue("cats");
expect(loc()).toBe("/search?q=cats");
});
test("✕ on results that opened the session goes home", async () => {
globalThis.history.replaceState(null, "", "/search?q=cats");
const user = userEvent.setup();
render(<Browser />);
await user.click(screen.getByRole("button", { name: "Cancel search" }));
expect(loc()).toBe("/");
});
});