From bfd0041c6a1a14a427543f32ca80c632783004b4 Mon Sep 17 00:00:00 2001 From: Marco Pasqualetti <24919330+marcalexiei@users.noreply.github.com> Date: Tue, 11 Mar 2025 06:46:58 +0100 Subject: [PATCH] test(packages/tuono-router): improve test using mocks (#641) --- .../tuono-router/src/components/Link.spec.tsx | 5 +- .../src/components/NotFound.spec.tsx | 129 ++++++++---------- .../tuono-router/src/components/NotFound.tsx | 5 +- .../src/components/NotFoundDefaultContent.tsx | 12 ++ .../src/components/RouteMatch.spec.tsx | 56 ++++---- .../tuono-router/src/hooks/useRoute.spec.ts | 17 ++- 6 files changed, 102 insertions(+), 122 deletions(-) create mode 100644 packages/tuono-router/src/components/NotFoundDefaultContent.tsx diff --git a/packages/tuono-router/src/components/Link.spec.tsx b/packages/tuono-router/src/components/Link.spec.tsx index 5898d17e..0e14e684 100644 --- a/packages/tuono-router/src/components/Link.spec.tsx +++ b/packages/tuono-router/src/components/Link.spec.tsx @@ -1,5 +1,5 @@ import { describe, it, expect, vi, beforeEach } from 'vitest' -import { render, fireEvent, screen } from '@testing-library/react' +import { render, fireEvent, screen, cleanup } from '@testing-library/react' import { Link } from './Link' @@ -33,8 +33,9 @@ vi.mock('react-intersection-observer', () => ({ }, })) -describe('Link component', () => { +describe('', () => { beforeEach(() => { + cleanup() pushMock.mockReset() preloadMock.mockReset() intersectionObserverCallback = undefined diff --git a/packages/tuono-router/src/components/NotFound.spec.tsx b/packages/tuono-router/src/components/NotFound.spec.tsx index 1f82d23d..e504d7a4 100644 --- a/packages/tuono-router/src/components/NotFound.spec.tsx +++ b/packages/tuono-router/src/components/NotFound.spec.tsx @@ -1,114 +1,93 @@ -import type { HTMLAttributes, JSX } from 'react' +import type { JSX, ReactNode } from 'react' import { afterEach, describe, expect, it, vi } from 'vitest' - -import { cleanup, render, screen } from '@testing-library/react' +import { cleanup, render } from '@testing-library/react' import { Route } from '../route' -import type { RouteComponent, RouteProps } from '../types' +import type { RouteComponent } from '../types' +import type { RouterInstanceType } from '../router' import { NotFound } from './NotFound' +import { RouteMatch } from './RouteMatch' +import { useRouterContext } from './RouterContext' +import { NotFoundDefaultContent } from './NotFoundDefaultContent' -function createRouteComponent( - routeType: string, - RouteComponentFn: (props: RouteProps) => JSX.Element, -): RouteComponent { - const RootComponent = RouteComponentFn as RouteComponent - RootComponent.preload = vi.fn() - RootComponent.displayName = routeType - return RootComponent +vi.mock('../components/RouterContext', () => ({ + useRouterContext: vi.fn(), +})) +vi.mock('./RouteMatch', () => ({ + RouteMatch: vi.fn(), +})) +vi.mock('./NotFoundDefaultContent', () => ({ + NotFoundDefaultContent: vi.fn(), +})) + +interface RouterMock { + router: Pick } +const useRouterContextMock = vi.mocked(useRouterContext as () => RouterMock) +const RouteMatchMock = vi.mocked(RouteMatch) +const NotFoundDefaultContentMock = vi.mocked(NotFoundDefaultContent) + +const rootRouteComponentMock = vi + .fn<(props: { children: ReactNode }) => JSX.Element>() + .mockImplementation(({ children }) =>
{children}
) const root = new Route({ isRoot: true, - component: createRouteComponent('root', ({ children }) => ( -
{children}
- )), + component: rootRouteComponentMock as unknown as RouteComponent, }) -vi.mock('./Link', () => ({ - Link: (props: HTMLAttributes): JSX.Element => ( - - ), -})) - -vi.mock('../hooks/useServerPayloadData.ts', () => ({ - useServerPayloadData: (): { data: unknown; isLoading: boolean } => { - return { - data: undefined, - isLoading: false, - } - }, -})) - -const { useRouterContext } = vi.hoisted(() => { - return { useRouterContext: vi.fn() } -}) - -vi.mock('../components/RouterContext', () => ({ - useRouterContext, -})) - describe('', () => { - afterEach(cleanup) + afterEach(() => { + cleanup() + useRouterContextMock.mockReset() + RouteMatchMock.mockReset() + NotFoundDefaultContentMock.mockReset() + rootRouteComponentMock.mockClear() + }) describe('when a custom 404 page exists', () => { it('should render the custom 404 page', () => { - useRouterContext.mockReturnValue({ + const customRoute404 = new Route({ + getParentRoute: (): Route => root, + component: vi.fn() as unknown as RouteComponent, + }) + + useRouterContextMock.mockReturnValue({ router: { routesById: { - '/404': new Route({ - getParentRoute: (): Route => root, - component: createRouteComponent('404', () => ( -
custom 404
- )), - }), + '/404': customRoute404, __root__: root, }, }, }) + render() - expect(screen.getByTestId('root')).toMatchInlineSnapshot( - ` -
-
- custom 404 -
-
- `, + + expect(RouteMatchMock).toHaveBeenCalledExactlyOnceWith( + { route: customRoute404, serverInitialData: {} }, + undefined, // deprecated react context parameter ) + expect(rootRouteComponentMock).not.toHaveBeenCalled() + expect(NotFoundDefaultContentMock).not.toHaveBeenCalled() }) }) describe('when a custom 404 page does not exist', () => { it('should render the default 404 page, wrapped by the root __layout', () => { - useRouterContext.mockReturnValue({ + useRouterContextMock.mockReturnValue({ router: { routesById: { __root__: root, }, }, }) + render() - expect(screen.getByTestId('root')).toMatchInlineSnapshot( - ` -
-

- 404 Not found -

-
- Return home - -
- `, - ) + + expect(RouteMatchMock).not.toHaveBeenCalled() + expect(rootRouteComponentMock).toHaveBeenCalled() + expect(NotFoundDefaultContentMock).toHaveBeenCalledOnce() }) }) }) diff --git a/packages/tuono-router/src/components/NotFound.tsx b/packages/tuono-router/src/components/NotFound.tsx index 7ad4e186..cd59677f 100644 --- a/packages/tuono-router/src/components/NotFound.tsx +++ b/packages/tuono-router/src/components/NotFound.tsx @@ -4,7 +4,7 @@ import { useRouterContext } from '../components/RouterContext' import { ROOT_ROUTE_ID } from '../route' import { RouteMatch } from './RouteMatch' -import { Link } from './Link' +import { NotFoundDefaultContent } from './NotFoundDefaultContent' export function NotFound(): JSX.Element | null { const { router } = useRouterContext() @@ -22,8 +22,7 @@ export function NotFound(): JSX.Element | null { return ( -

404 Not found

- Return home +
) } diff --git a/packages/tuono-router/src/components/NotFoundDefaultContent.tsx b/packages/tuono-router/src/components/NotFoundDefaultContent.tsx new file mode 100644 index 00000000..59da6754 --- /dev/null +++ b/packages/tuono-router/src/components/NotFoundDefaultContent.tsx @@ -0,0 +1,12 @@ +import type { JSX } from 'react' + +import { Link } from './Link' + +export function NotFoundDefaultContent(): JSX.Element { + return ( + <> +

Page Not Found

+ Return to Homepage + + ) +} diff --git a/packages/tuono-router/src/components/RouteMatch.spec.tsx b/packages/tuono-router/src/components/RouteMatch.spec.tsx index ec09869b..ce0e2b20 100644 --- a/packages/tuono-router/src/components/RouteMatch.spec.tsx +++ b/packages/tuono-router/src/components/RouteMatch.spec.tsx @@ -1,18 +1,22 @@ -import type { JSX } from 'react' import { afterEach, describe, expect, it, vi } from 'vitest' - import { cleanup, render, screen } from '@testing-library/react' import { Route } from '../route' import type { RouteComponent, RouteProps } from '../types' +import { useServerPayloadData } from '../hooks/useServerPayloadData' import { RouteMatch } from './RouteMatch' function createRouteComponent( routeType: string, - RouteComponentFn: (props: RouteProps) => JSX.Element, + includeChildren: boolean, ): RouteComponent { - const RootComponent = RouteComponentFn as RouteComponent + const RootComponent = (({ children }: RouteProps) => ( +
+ {`${routeType} route`} + {includeChildren ? children : null} +
+ )) as RouteComponent RootComponent.preload = vi.fn() RootComponent.displayName = routeType return RootComponent @@ -20,35 +24,28 @@ function createRouteComponent( const root = new Route({ isRoot: true, - component: createRouteComponent('root', ({ children }) => ( -
root route {children}
- )), + component: createRouteComponent('root', true), }) const parent = new Route({ - component: createRouteComponent('parent', ({ children }) => ( -
parent route {children}
- )), + component: createRouteComponent('parent', true), getParentRoute: (): Route => root, }) const route = new Route({ - component: createRouteComponent('route', () => ( -

current route

- )), + component: createRouteComponent('current', false), getParentRoute: (): Route => parent, }) -vi.mock('../hooks/useServerPayloadData.ts', () => ({ - useServerPayloadData: (): { data: unknown; isLoading: boolean } => { - return { - data: undefined, - isLoading: false, - } - }, +vi.mock('../hooks/useServerPayloadData', () => ({ + useServerPayloadData: vi.fn(), })) +vi.mocked(useServerPayloadData).mockReturnValue({ + data: undefined, + isLoading: false, +}) -describe('Test RouteMatch component', () => { +describe('', () => { afterEach(cleanup) it('should correctly render nested routes', () => { @@ -59,26 +56,19 @@ describe('Test RouteMatch component', () => {
- root route + root route
- parent route -

current route -

+
`, ) - expect(screen.getByTestId('route')).toMatchInlineSnapshot(` -

- current route -

- `) }) }) diff --git a/packages/tuono-router/src/hooks/useRoute.spec.ts b/packages/tuono-router/src/hooks/useRoute.spec.ts index f5f3679e..061ee84a 100644 --- a/packages/tuono-router/src/hooks/useRoute.spec.ts +++ b/packages/tuono-router/src/hooks/useRoute.spec.ts @@ -1,19 +1,18 @@ import { afterEach, describe, expect, it, vi } from 'vitest' import { cleanup } from '@testing-library/react' +import { useRouterContext } from '../components/RouterContext' + import { useRoute } from './useRoute' -const { useRouterContextMock } = vi.hoisted(() => ({ - useRouterContextMock: vi.fn< - () => { - router: { routesById: Record } - } - >(), +vi.mock('../components/RouterContext.tsx', () => ({ + useRouterContext: vi.fn(), })) -vi.mock('../components/RouterContext.tsx', () => ({ - useRouterContext: useRouterContextMock, -})) +interface RouterMock { + router: { routesById: Record } +} +const useRouterContextMock = vi.mocked(useRouterContext as () => RouterMock) describe('useRoute', () => { afterEach(() => {