test: dublê central de next/navigation para todos os testes
This commit is contained in:
1 parent
2cd4a98c55
commit
cf360041bd
2 files changed
+111
No files matched your search
@@ -0,0 +1,109 @@
|
|||||||
|
/**
|
||||||
|
* Dublê central e completo de `next/navigation` para o Jest (jest.config.ts: moduleNameMapper). Todo teste que importa
|
||||||
|
* o módulo, direta ou indiretamente (HoverPrefetchLink, Header, páginas com `notFound`), recebe este arquivo: nenhum
|
||||||
|
* teste precisa montar um `jest.mock("next/navigation", ...)` parcial, e nenhum componente vê o módulo sem um export.
|
||||||
|
*
|
||||||
|
* Estado: `usePathname`, `useSearchParams`, `useParams` e `useSelectedLayoutSegment(s)` leem `navigationState`.
|
||||||
|
* Troque-o com `setNavigation({ pathname: "/cases" })` (ou `jest.mocked(usePathname).mockReturnValue("/cases")`) e
|
||||||
|
* volte ao padrão ("/", sem parâmetros) com `resetNavigation()` num `beforeEach`.
|
||||||
|
* Erros: `notFound` e `redirect` lançam como os do Next (com `digest`), e `notFound` lança com a mensagem
|
||||||
|
* "NEXT_NOT_FOUND", que os testes das páginas conferem.
|
||||||
|
*/
|
||||||
|
|
||||||
|
type Params = Record<string, string | string[]>;
|
||||||
|
|
||||||
|
interface NavigationState {
|
||||||
|
pathname: string | null;
|
||||||
|
searchParams: URLSearchParams;
|
||||||
|
params: Params;
|
||||||
|
segments: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialState = (): NavigationState => ({
|
||||||
|
pathname: "/",
|
||||||
|
searchParams: new URLSearchParams(),
|
||||||
|
params: {},
|
||||||
|
segments: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
export const navigationState: NavigationState = initialState();
|
||||||
|
|
||||||
|
/** Cada `jest.fn` com a implementação padrão, para o `resetNavigation` devolvê-la depois de um `mockReset`. */
|
||||||
|
const defaults: Array<[jest.Mock, (...args: never[]) => unknown]> = [];
|
||||||
|
function mockFn<Args extends unknown[], Result>(
|
||||||
|
implementation: (...args: Args) => Result,
|
||||||
|
) {
|
||||||
|
const fn = jest.fn(implementation);
|
||||||
|
defaults.push([fn as jest.Mock, implementation as never]);
|
||||||
|
return fn;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setNavigation(
|
||||||
|
next: Partial<Omit<NavigationState, "searchParams">> & {
|
||||||
|
searchParams?: string | URLSearchParams;
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
const { searchParams, ...rest } = next;
|
||||||
|
Object.assign(navigationState, rest);
|
||||||
|
if (searchParams !== undefined)
|
||||||
|
navigationState.searchParams = new URLSearchParams(searchParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resetNavigation() {
|
||||||
|
Object.assign(navigationState, initialState());
|
||||||
|
for (const [fn, implementation] of defaults) {
|
||||||
|
fn.mockReset();
|
||||||
|
fn.mockImplementation(implementation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const router = {
|
||||||
|
push: mockFn(() => undefined),
|
||||||
|
replace: mockFn(() => undefined),
|
||||||
|
back: mockFn(() => undefined),
|
||||||
|
forward: mockFn(() => undefined),
|
||||||
|
refresh: mockFn(() => undefined),
|
||||||
|
prefetch: mockFn(() => undefined),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const usePathname = mockFn(() => navigationState.pathname);
|
||||||
|
export const useRouter = mockFn(() => router);
|
||||||
|
export const useSearchParams = mockFn(() => navigationState.searchParams);
|
||||||
|
export const useParams = mockFn(() => navigationState.params);
|
||||||
|
export const useSelectedLayoutSegments = mockFn(() => navigationState.segments);
|
||||||
|
export const useSelectedLayoutSegment = mockFn(
|
||||||
|
() => navigationState.segments[0] ?? null,
|
||||||
|
);
|
||||||
|
export const useServerInsertedHTML = mockFn(() => undefined);
|
||||||
|
export const unstable_isUnrecognizedActionError = mockFn(() => false);
|
||||||
|
export const unstable_rethrow = mockFn((error: unknown): void => {
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
|
||||||
|
export const RedirectType = { push: "push", replace: "replace" } as const;
|
||||||
|
export const ReadonlyURLSearchParams = URLSearchParams;
|
||||||
|
|
||||||
|
/** Erro com `digest`, como os que o Next lança para interromper a renderização. */
|
||||||
|
function nextError(message: string, digest: string): never {
|
||||||
|
throw Object.assign(new Error(message), { digest });
|
||||||
|
}
|
||||||
|
|
||||||
|
export const notFound = mockFn((): never => {
|
||||||
|
return nextError("NEXT_NOT_FOUND", "NEXT_HTTP_ERROR_FALLBACK;404");
|
||||||
|
});
|
||||||
|
export const forbidden = mockFn((): never => {
|
||||||
|
return nextError("NEXT_FORBIDDEN", "NEXT_HTTP_ERROR_FALLBACK;403");
|
||||||
|
});
|
||||||
|
export const unauthorized = mockFn((): never => {
|
||||||
|
return nextError("NEXT_UNAUTHORIZED", "NEXT_HTTP_ERROR_FALLBACK;401");
|
||||||
|
});
|
||||||
|
export const redirect = mockFn(
|
||||||
|
(url: string, type: "push" | "replace" = "replace"): never => {
|
||||||
|
return nextError("NEXT_REDIRECT", `NEXT_REDIRECT;${type};${url};307;`);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
export const permanentRedirect = mockFn(
|
||||||
|
(url: string, type: "push" | "replace" = "replace"): never => {
|
||||||
|
return nextError("NEXT_REDIRECT", `NEXT_REDIRECT;${type};${url};308;`);
|
||||||
|
},
|
||||||
|
);
|
||||||
@@ -12,6 +12,8 @@ const config: Config = {
|
|||||||
"^@/(.*)$": "<rootDir>/$1",
|
"^@/(.*)$": "<rootDir>/$1",
|
||||||
"^.+\\.(css|sass|scss)$": "identity-obj-proxy",
|
"^.+\\.(css|sass|scss)$": "identity-obj-proxy",
|
||||||
"^server-only$": "<rootDir>/lib/__mocks__/server-only.ts",
|
"^server-only$": "<rootDir>/lib/__mocks__/server-only.ts",
|
||||||
|
// dublê completo e central: nenhum teste monta um mock parcial de next/navigation
|
||||||
|
"^next/navigation$": "<rootDir>/__mocks__/next/navigation.ts",
|
||||||
},
|
},
|
||||||
testMatch: ["**/__tests__/**/*.[jt]s?(x)", "**/?(*.)+(spec|test).[jt]s?(x)"],
|
testMatch: ["**/__tests__/**/*.[jt]s?(x)", "**/?(*.)+(spec|test).[jt]s?(x)"],
|
||||||
transform: {
|
transform: {
|
||||||
|
|||||||
Reference in new issue
Block a user