Files
portifolio-evandrodevbr/__mocks__/next/navigation.ts
T

110 lines
4.0 KiB
TypeScript

/**
* 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;`);
},
);