128 lines
5.1 KiB
TypeScript
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("/");
|
|
});
|
|
});
|