v4: /follow/playlists opens the followed playlists' feed, and a page's test opens it through the app's routes — the path was a 404 since the two views of the followed feed became pages: the route table held each feed tab as an exact path, /follow, so /follow/playlists fell to /:handle/*, which read "follow" as a category and "playlists" as a tab it hasn't. /music/follow/playlists, which goes through that route, worked. The tabs' routes now take what follows them (/${tab}/*), which Feed already reads and refuses when it names no view.
No test saw it because each page's test mounted the page on a route pattern of its own, a copy of App's, with stand-in pages where a redirect lands: Index's parsed the path itself, and passed on one the app doesn't serve. renderPage (src/test/render.tsx) now opens a URL through AppRoutes, which App.tsx exports for it, inside the real providers, and every page's test uses it: the page is the one the app routes that URL to, behind its guard, and a redirect is checked by the path it lands on. An entry may be a location, to arrive with the state a link hands over. Every page being loaded with its route, the first query after opening one waits. src/test/App.test.tsx tests the table itself: the paths that are a feed, those that are not found, those that send a guest to sign in and back, and those that send a signed-in user home. The profile's test held /@ann/dumps/x to be a path the profile answers as not found, which only its own pattern made true: in the app it is a dump's page. It now asks for /@ann/dumps/x/y. The tests of the two guards still mount them around a stand-in child, being tests of the guard alone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -2,7 +2,13 @@ import { type ReactElement, type ReactNode, useState } from "react";
|
||||
import { render, type RenderOptions } from "@testing-library/react";
|
||||
import { I18nProvider } from "@lingui/react";
|
||||
import { i18n } from "@lingui/core";
|
||||
import { MemoryRouter, Route, Routes, useLocation } from "react-router";
|
||||
import {
|
||||
type InitialEntry,
|
||||
MemoryRouter,
|
||||
Route,
|
||||
Routes,
|
||||
useLocation,
|
||||
} from "react-router";
|
||||
|
||||
import type { Session } from "../utils/auth/storage.ts";
|
||||
import type { Category } from "../../shared/category.ts";
|
||||
@@ -46,6 +52,7 @@ import {
|
||||
import { PlayerProvider } from "../contexts/player/PlayerProvider.tsx";
|
||||
import { ThemeProvider } from "../contexts/theme/ThemeProvider.tsx";
|
||||
import { ChatProvider } from "../contexts/chat/ChatProvider.tsx";
|
||||
import { AppRoutes } from "../App.tsx";
|
||||
import { route } from "./fetch.ts";
|
||||
import { storeSession } from "./fixtures.ts";
|
||||
|
||||
@@ -69,8 +76,11 @@ export function currentPath(): string {
|
||||
}
|
||||
|
||||
interface RouterOptions {
|
||||
/** Initial URL, or several for a history stack (the last is current). */
|
||||
route?: string | string[];
|
||||
/**
|
||||
* Initial URL, or several for a history stack (the last is current). An
|
||||
* entry may be a location, to arrive with the `state` a link hands over.
|
||||
*/
|
||||
route?: InitialEntry | InitialEntry[];
|
||||
/** Route pattern the element is mounted on, for useParams. */
|
||||
path?: string;
|
||||
/** Extra routes to mount next to it, e.g. a redirect target. */
|
||||
@@ -277,3 +287,18 @@ export function renderApp(ui: ReactElement, opts: AppOptions = {}) {
|
||||
);
|
||||
return render(ui, { wrapper: Wrapper });
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens `url` as a visit does: `App`'s own route table inside its providers
|
||||
* (`renderApp`), so the page under test is whichever one the app routes that
|
||||
* URL to, behind its guard, and a redirect lands on the real page. A page's
|
||||
* test opens it this way; mounting the page on a pattern typed in the test
|
||||
* says nothing of whether the app serves that path. Every page is loaded with
|
||||
* its route, so the first query after it waits (`findBy`, `waitFor`).
|
||||
*/
|
||||
export function renderPage(
|
||||
url: NonNullable<AppOptions["route"]>,
|
||||
opts: Pick<AppOptions, "session" | "categories"> = {},
|
||||
) {
|
||||
return renderApp(<AppRoutes />, { ...opts, route: url });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user