next.js/test/production/debug-build-path/debug-build-paths.test.ts
debug-build-paths.test.ts314 lines13.5 KB
import path from 'path'
import { nextTestSetup } from 'e2e-utils'

describe('debug-build-paths', () => {
  describe('default fixture', () => {
    const { next, skipped } = nextTestSetup({
      files: path.join(__dirname, 'fixtures/default'),
      skipStart: true,
    })

    if (skipped) return

    describe('explicit path formats', () => {
      it('should build single page with pages/ prefix', async () => {
        const buildResult = await next.build({
          args: ['--debug-build-paths', 'pages/foo.tsx'],
        })
        expect(buildResult.exitCode).toBe(0)
        expect(buildResult.cliOutput).toBeDefined()

        // Should only build the specified page
        expect(buildResult.cliOutput).toContain('Route (pages)')
        expect(buildResult.cliOutput).toContain('○ /foo')
        // Should not build other pages
        expect(buildResult.cliOutput).not.toContain('○ /bar')
        // Should not build app routes
        expect(buildResult.cliOutput).not.toContain('Route (app)')
      })

      it('should build multiple pages routes', async () => {
        const buildResult = await next.build({
          args: ['--debug-build-paths', 'pages/foo.tsx,pages/bar.tsx'],
        })
        expect(buildResult.exitCode).toBe(0)
        expect(buildResult.cliOutput).toBeDefined()

        // Should build both specified pages
        expect(buildResult.cliOutput).toContain('Route (pages)')
        expect(buildResult.cliOutput).toContain('○ /foo')
        expect(buildResult.cliOutput).toContain('○ /bar')
        // Should not build app routes
        expect(buildResult.cliOutput).not.toContain('Route (app)')
      })

      it('should build dynamic route with literal [slug] path', async () => {
        // Test that literal paths with brackets work without escaping
        // The path is checked for file existence before being treated as glob
        const buildResult = await next.build({
          args: ['--debug-build-paths', 'app/blog/[slug]/page.tsx'],
        })
        expect(buildResult.exitCode).toBe(0)
        expect(buildResult.cliOutput).toBeDefined()

        // Should build only the blog/[slug] route
        expect(buildResult.cliOutput).toContain('Route (app)')
        expect(buildResult.cliOutput).toContain('/blog/[slug]')
        // Should not build other app routes
        expect(buildResult.cliOutput).not.toMatch(/○ \/\n/)
        expect(buildResult.cliOutput).not.toContain('○ /about')
        expect(buildResult.cliOutput).not.toContain('○ /dashboard')
        // Should not build pages routes
        expect(buildResult.cliOutput).not.toContain('Route (pages)')
      })
    })

    describe('glob pattern matching', () => {
      it('should match app and pages routes with glob patterns', async () => {
        const buildResult = await next.build({
          args: ['--debug-build-paths', 'pages/*.tsx,app/page.tsx'],
        })
        expect(buildResult.exitCode).toBe(0)
        expect(buildResult.cliOutput).toBeDefined()

        // Should build pages matching the glob
        expect(buildResult.cliOutput).toContain('Route (pages)')
        expect(buildResult.cliOutput).toContain('○ /foo')
        expect(buildResult.cliOutput).toContain('○ /bar')

        // Should build the specified app route
        expect(buildResult.cliOutput).toContain('Route (app)')
        expect(buildResult.cliOutput).toContain('○ /')
        // Should not build other app routes
        expect(buildResult.cliOutput).not.toContain('○ /about')
        expect(buildResult.cliOutput).not.toContain('○ /dashboard')
      })

      it('should match nested routes with app/blog/**/page.tsx pattern', async () => {
        const buildResult = await next.build({
          args: ['--debug-build-paths', 'app/blog/**/page.tsx'],
        })
        expect(buildResult.exitCode).toBe(0)
        expect(buildResult.cliOutput).toBeDefined()

        // Should build the blog route
        expect(buildResult.cliOutput).toContain('Route (app)')
        expect(buildResult.cliOutput).toContain('/blog/[slug]')
        // Should not build other app routes (check for exact route, not substring)
        expect(buildResult.cliOutput).not.toMatch(/○ \/\n/)
        expect(buildResult.cliOutput).not.toContain('○ /about')
        expect(buildResult.cliOutput).not.toContain('○ /dashboard')
        // Should not build pages routes
        expect(buildResult.cliOutput).not.toContain('Route (pages)')
      })

      it('should match dynamic routes with glob before brackets like app/**/[slug]/page.tsx', async () => {
        const buildResult = await next.build({
          args: ['--debug-build-paths', 'app/**/[slug]/page.tsx'],
        })
        expect(buildResult.exitCode).toBe(0)
        expect(buildResult.cliOutput).toBeDefined()

        // Should build the blog/[slug] route
        expect(buildResult.cliOutput).toContain('Route (app)')
        expect(buildResult.cliOutput).toContain('/blog/[slug]')
        // Should not build other app routes
        expect(buildResult.cliOutput).not.toMatch(/○ \/\n/)
        expect(buildResult.cliOutput).not.toContain('○ /about')
        expect(buildResult.cliOutput).not.toContain('○ /dashboard')
        // Should not build pages routes
        expect(buildResult.cliOutput).not.toContain('Route (pages)')
      })

      it('should match hybrid pattern with literal [slug] and glob **', async () => {
        // Test pattern: app/blog/[slug]/**/page.tsx
        // [slug] should be treated as literal directory (exists on disk)
        // ** should be treated as glob (match any depth)
        const buildResult = await next.build({
          args: ['--debug-build-paths', 'app/blog/[slug]/**/page.tsx'],
        })
        expect(buildResult.exitCode).toBe(0)
        expect(buildResult.cliOutput).toBeDefined()

        // Should build both blog/[slug] and blog/[slug]/comments routes
        expect(buildResult.cliOutput).toContain('Route (app)')
        expect(buildResult.cliOutput).toContain('/blog/[slug]')
        expect(buildResult.cliOutput).toContain('/blog/[slug]/comments')
        // Should not build other app routes
        expect(buildResult.cliOutput).not.toMatch(/○ \/\n/)
        expect(buildResult.cliOutput).not.toContain('○ /about')
        expect(buildResult.cliOutput).not.toContain('○ /dashboard')
        // Should not build pages routes
        expect(buildResult.cliOutput).not.toContain('Route (pages)')
      })

      it('should match multiple app routes with explicit patterns', async () => {
        const buildResult = await next.build({
          args: [
            '--debug-build-paths',
            'app/page.tsx,app/about/page.tsx,app/dashboard/page.tsx,app/blog/**/page.tsx',
          ],
        })
        expect(buildResult.exitCode).toBe(0)
        expect(buildResult.cliOutput).toBeDefined()

        // Should build specified app routes
        expect(buildResult.cliOutput).toContain('Route (app)')
        expect(buildResult.cliOutput).toContain('○ /')
        expect(buildResult.cliOutput).toContain('○ /about')
        expect(buildResult.cliOutput).toContain('○ /dashboard')
        expect(buildResult.cliOutput).toContain('/blog/[slug]')
        // Should not build routes not specified
        expect(buildResult.cliOutput).not.toContain('/with-type-error')
        // Should not build pages routes
        expect(buildResult.cliOutput).not.toContain('Route (pages)')
      })

      it('should exclude paths matching negation patterns', async () => {
        const buildResult = await next.build({
          args: [
            '--debug-build-paths',
            'app/**/page.tsx,!app/with-type-error/**',
          ],
        })
        expect(buildResult.exitCode).toBe(0)

        expect(buildResult.cliOutput).toContain('Route (app)')
        expect(buildResult.cliOutput).toContain('○ /')
        expect(buildResult.cliOutput).toContain('○ /about')
        expect(buildResult.cliOutput).toContain('○ /dashboard')
        expect(buildResult.cliOutput).toContain('/blog/[slug]')
        expect(buildResult.cliOutput).not.toContain('/with-type-error')
      })

      it('should exclude dynamic route paths with negation', async () => {
        const buildResult = await next.build({
          args: [
            '--debug-build-paths',
            'app/blog/**/page.tsx,!app/blog/[slug]/comments/**',
          ],
        })
        expect(buildResult.exitCode).toBe(0)

        expect(buildResult.cliOutput).toContain('Route (app)')
        expect(buildResult.cliOutput).toContain('/blog/[slug]')
        expect(buildResult.cliOutput).not.toContain('/blog/[slug]/comments')
      })

      it('should support multiple negation patterns', async () => {
        const buildResult = await next.build({
          args: [
            '--debug-build-paths',
            'app/**/page.tsx,!app/with-type-error/**,!app/dashboard/**',
          ],
        })
        expect(buildResult.exitCode).toBe(0)

        expect(buildResult.cliOutput).toContain('Route (app)')
        expect(buildResult.cliOutput).toContain('○ /')
        expect(buildResult.cliOutput).toContain('○ /about')
        expect(buildResult.cliOutput).not.toContain('/with-type-error')
        expect(buildResult.cliOutput).not.toContain('○ /dashboard')
      })

      it('should build everything except excluded paths when only negation patterns are provided', async () => {
        const buildResult = await next.build({
          args: ['--debug-build-paths', '!app/with-type-error/**'],
        })
        expect(buildResult.exitCode).toBe(0)

        expect(buildResult.cliOutput).toContain('Route (app)')
        expect(buildResult.cliOutput).toContain('Route (pages)')
        expect(buildResult.cliOutput).toContain('○ /')
        expect(buildResult.cliOutput).toContain('○ /about')
        expect(buildResult.cliOutput).toContain('○ /foo')
        expect(buildResult.cliOutput).not.toContain('/with-type-error')
      })

      it('should build routes inside route groups', async () => {
        const buildResult = await next.build({
          args: ['--debug-build-paths', 'app/(group)/**/page.tsx'],
        })
        expect(buildResult.exitCode).toBe(0)
        expect(buildResult.cliOutput).toContain('Route (app)')
        // Route groups are stripped from the path, so /nested instead of /(group)/nested
        expect(buildResult.cliOutput).toContain('/nested')
        // Should not build other routes
        expect(buildResult.cliOutput).not.toContain('○ /about')
        expect(buildResult.cliOutput).not.toContain('○ /dashboard')
      })

      it('should build routes with parallel routes', async () => {
        const buildResult = await next.build({
          args: ['--debug-build-paths', 'app/parallel-test/**/page.tsx'],
        })
        expect(buildResult.exitCode).toBe(0)
        expect(buildResult.cliOutput).toContain('Route (app)')
        // Parallel route segments (@sidebar) are stripped from the path
        expect(buildResult.cliOutput).toContain('/parallel-test')
        // Should not build other routes
        expect(buildResult.cliOutput).not.toContain('○ /about')
        expect(buildResult.cliOutput).not.toContain('○ /dashboard')
      })
    })

    describe('typechecking with debug-build-paths', () => {
      it('should skip typechecking for excluded app routes', async () => {
        // Build only pages routes, excluding app routes with type error
        const buildResult = await next.build({
          args: ['--debug-build-paths', 'pages/foo.tsx'],
        })
        // Build should succeed because the file with type error is not checked
        expect(buildResult.exitCode).toBe(0)
        expect(buildResult.cliOutput).toContain('Route (pages)')
        expect(buildResult.cliOutput).toContain('○ /foo')
        // Should not include app routes
        expect(buildResult.cliOutput).not.toContain('Route (app)')
      })

      it('should fail typechecking when route with type error is included', async () => {
        // Build all app routes including the one with type error
        const buildResult = await next.build({
          args: ['--debug-build-paths', 'app/**/page.tsx'],
        })
        // Build should fail due to type error in with-type-error/page.tsx
        expect(buildResult.exitCode).toBe(1)
        expect(buildResult.cliOutput).toContain('Type error')
        expect(buildResult.cliOutput).toContain('with-type-error/page.tsx')
      })
    })
  })

  describe('with-compile-error fixture', () => {
    const { next, skipped } = nextTestSetup({
      files: path.join(__dirname, 'fixtures/with-compile-error'),
      skipStart: true,
    })

    if (skipped) return

    it('should skip compilation of excluded routes with compile errors', async () => {
      // Build only the valid page, excluding the broken page
      const buildResult = await next.build({
        args: ['--debug-build-paths', 'app/valid/page.tsx'],
      })
      // Build should succeed because the broken page is not compiled
      expect(buildResult.exitCode).toBe(0)
      expect(buildResult.cliOutput).toContain('Route (app)')
      expect(buildResult.cliOutput).toContain('○ /valid')
      // Should not include the broken route
      expect(buildResult.cliOutput).not.toContain('/broken')
    })

    it('should fail compilation when route with compile error is included', async () => {
      // Build all app routes including the one with compile error
      const buildResult = await next.build({
        args: ['--debug-build-paths', 'app/**/page.tsx'],
      })
      // Build should fail due to compile error in broken/page.tsx
      expect(buildResult.exitCode).toBe(1)
      expect(buildResult.cliOutput).toMatch(/error|Error/)
    })
  })
})
Quest for Codev2.0.0
/
SIGN IN