next.js/packages/next/src/server/route-modules/app-page/helpers/prerender-manifest-matcher.test.ts
prerender-manifest-matcher.test.ts191 lines5.3 KB
import { PrerenderManifestMatcher } from './prerender-manifest-matcher'
import type {
  PrerenderManifest,
  DynamicPrerenderManifestRoute,
} from '../../../../build'
import { RenderingMode } from '../../../../build/rendering-mode'

// Helper function to create a mock PrerenderManifest
function createMockPrerenderManifest(
  dynamicRoutes: Record<string, DynamicPrerenderManifestRoute> = {}
): PrerenderManifest {
  return {
    version: 4,
    routes: {},
    dynamicRoutes,
    notFoundRoutes: [],
    preview: {
      previewModeId: 'test-preview-id',
      previewModeEncryptionKey: 'test-encryption-key',
      previewModeSigningKey: 'test-signing-key',
    },
  }
}

// Helper function to create a mock DynamicPrerenderManifestRoute
function createMockDynamicRoute(
  overrides: Partial<DynamicPrerenderManifestRoute> = {}
): DynamicPrerenderManifestRoute {
  return {
    dataRoute: null,
    dataRouteRegex: null,
    fallback: null,
    fallbackRevalidate: false,
    fallbackExpire: undefined,
    fallbackHeaders: undefined,
    fallbackStatus: undefined,
    fallbackRouteParams: undefined,
    fallbackRootParams: undefined,
    fallbackSourceRoute: undefined,
    prefetchDataRoute: undefined,
    prefetchDataRouteRegex: undefined,
    routeRegex: '^/[^/]+(?:/[^/]+)?/?$',
    experimentalPPR: undefined,
    renderingMode: RenderingMode.STATIC,
    allowHeader: ['host'],
    ...overrides,
  }
}

describe('PrerenderManifestMatcher', () => {
  describe('match', () => {
    describe('successful matches', () => {
      it('should respect route specificity order', () => {
        const specificRoute = createMockDynamicRoute({
          fallbackSourceRoute: '/[category]/[id]',
        })

        const catchAllRoute = createMockDynamicRoute({
          fallbackSourceRoute: '/[category]/[id]',
        })

        // Order matters - more specific routes should come first
        const manifest = createMockPrerenderManifest({
          '/products/[id]': specificRoute,
          '/[category]/[id]': catchAllRoute,
        })

        const matcher = new PrerenderManifestMatcher(
          '/[category]/[id]',
          manifest
        )

        const result = matcher.match('/products/123')

        expect(result).toEqual({
          source: '/products/[id]',
          route: specificRoute,
        })
      })

      it('should handle when the fallbackSourceRoute is not set', () => {
        const route = createMockDynamicRoute({
          fallbackSourceRoute: undefined,
        })

        const manifest = createMockPrerenderManifest({
          '/products/[id]': route,
        })

        const matcher = new PrerenderManifestMatcher('/products/[id]', manifest)

        const result = matcher.match('/products/123')

        expect(result).toEqual({
          source: '/products/[id]',
          route,
        })
      })

      it('should match unknown root branches against the generic source shell', () => {
        const generatedRootRoute = createMockDynamicRoute({
          fallbackSourceRoute: '/root-gsp/[lang]/[slug]',
          routeRegex: '^/root-gsp/en/([^/]+?)(?:/)?$',
          fallbackRootParams: [],
          fallbackRouteParams: [
            {
              paramName: 'slug',
              paramType: 'dynamic',
            },
          ],
        })

        const genericRootRoute = createMockDynamicRoute({
          fallbackSourceRoute: '/root-gsp/[lang]/[slug]',
          routeRegex: '^/root-gsp/([^/]+?)/([^/]+?)(?:/)?$',
          fallbackRootParams: ['lang'],
          fallbackRouteParams: [
            {
              paramName: 'lang',
              paramType: 'dynamic',
            },
            {
              paramName: 'slug',
              paramType: 'dynamic',
            },
          ],
        })

        const manifest = createMockPrerenderManifest({
          '/root-gsp/en/[slug]': generatedRootRoute,
          '/root-gsp/[lang]/[slug]': genericRootRoute,
        })

        const matcher = new PrerenderManifestMatcher(
          '/root-gsp/[lang]/[slug]',
          manifest
        )

        expect(matcher.match('/root-gsp/en/two')).toEqual({
          source: '/root-gsp/en/[slug]',
          route: generatedRootRoute,
        })
        expect(matcher.match('/root-gsp/fr/two')).toEqual({
          source: '/root-gsp/[lang]/[slug]',
          route: genericRootRoute,
        })
      })
    })

    describe('no match scenarios', () => {
      it('should return null when no matching route is found', () => {
        const route = createMockDynamicRoute({
          fallbackSourceRoute: '/[category]/[id]',
        })

        const manifest = createMockPrerenderManifest({
          '/products/[id]': route,
        })

        const matcher = new PrerenderManifestMatcher(
          '/[category]/[id]',
          manifest
        )

        const result = matcher.match('/non-matching-path')

        expect(result).toBe(null)
      })

      it('should return null when no routes match the fallback source route', () => {
        const route = createMockDynamicRoute({
          fallbackSourceRoute: '/products/[id]',
        })

        const manifest = createMockPrerenderManifest({
          '/products/[id]': route,
        })

        const matcher = new PrerenderManifestMatcher(
          '/[category]/[id]',
          manifest
        )

        const result = matcher.match('/products/123')

        expect(result).toBe(null)
      })
    })
  })
})
Quest for Codev2.0.0
/
SIGN IN