Skip to content

(7/7) Add route replay miss coverage for incomplete router data#93648

Closed
feedthejim wants to merge 23 commits intofeedthejim/offline-navigations-dynamic-route-patternsfrom
feedthejim/offline-navigations-router-cache-negative-coverage
Closed

(7/7) Add route replay miss coverage for incomplete router data#93648
feedthejim wants to merge 23 commits intofeedthejim/offline-navigations-dynamic-route-patternsfrom
feedthejim/offline-navigations-router-cache-negative-coverage

Conversation

@feedthejim
Copy link
Copy Markdown
Contributor

Stack position

This is PR 7/7 in the first offlineNavigations client-router persistence stack.

It sits after #93647, which teaches the client to replay a dynamic route from the persisted known-route table when the exact target URL entry is absent. This PR is intentionally test-only: it makes the new replay path prove that incomplete persisted router-cache data turns into a visible offline miss instead of a partial or stale render.

The stack is still behind experimental.offlineNavigations, which requires Cache Components and implies the existing offline surface. The service worker continues to own document survival only; the cache-components client router owns persisted route/segment/head data, route matching, and replay eligibility.

Why this exists

Dynamic route replay is the riskiest part of the first client-side stack because the route table can know that /dynamic-prefetch/[value] is replayable even when a specific target URL has never had an exact URL record. That is useful, but only if replay refuses to continue when one of the router-cache records needed to reconstruct the target tree is missing.

This test covers that boundary directly. It creates the successful PR6 setup, deletes the required target page segment record from IndexedDB, then verifies the offline hard load misses with the expected diagnostics and user-visible fallback.

What changed

  • Refactored the dynamic-pattern prefetch setup into a shared helper so the success and failure cases assert the same persisted state before replay.
  • Added a test helper that deletes selected persisted segment-data records from the offline navigation IndexedDB database.
  • Added an e2e for the missing-segment case:
    • the source dynamic route record is persisted
    • the target exact URL record is intentionally absent
    • the target route record is intentionally absent
    • the target head and segment records are prefetched
    • the target page segment record is deleted
    • the server is stopped and the browser is offline
    • the hard load reports router-cache-reconstruction-miss with missing-segment
    • the final document shows the normal visible offline cache miss UI

Not included

This PR does not add new runtime behavior. It also does not cover the later production-hardening matrix for middleware, rewrites, redirects, interception, optional/catch-all routes, app/session invalidation, or storage quotas. Those belong in the next stack because they need their own fixtures and policy decisions.

Reviewer focus

  • The test should fail if dynamic replay accidentally renders with incomplete router-cache data.
  • The destructive IndexedDB helper should delete only the intended page segment record, leaving the route table and other target records in place.
  • The assertions should prove both internal diagnostics and the user-visible miss path.

Verification

  • NEXT_SKIP_ISOLATE=1 pnpm test-start-turbo test/production/app-dir/offline-navigations/offline-navigations.test.ts -t "misses dynamic route pattern replay when a required segment record is missing"
  • NEXT_SKIP_ISOLATE=1 pnpm test-start-turbo test/production/app-dir/offline-navigations/offline-navigations.test.ts
  • pnpm --filter=next build
  • pnpm test-start-turbo test/production/app-dir/offline-navigations/offline-navigations.test.ts -t "misses dynamic route pattern replay when a required segment record is missing"
  • pnpm test-start-webpack test/production/app-dir/offline-navigations/offline-navigations.test.ts -t "misses dynamic route pattern replay when a required segment record is missing"
  • git diff --check

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 8, 2026

Failing test suites

Commit: e72f4b4 | About building and testing Next.js

pnpm test-start test/production/app-dir/offline-navigations/offline-navigations.test.ts (job)

  • offlineNavigations build artifacts > reconstructs a fully prefetched route from persisted router records (DD)
  • offlineNavigations build artifacts > replays a dynamic route from persisted known route patterns (DD)
  • offlineNavigations build artifacts > replays request-sensitive exact URLs from browser-private storage (DD)
  • offlineNavigations build artifacts > covers exact URL shape and pass-through stress cases (DD)
Expand output

● offlineNavigations build artifacts › reconstructs a fully prefetched route from persisted router records

expect(received).toBe(expected) // Object.is equality

Expected: true
Received: false

  1066 |         expect(
  1067 |           await browser.eval(() => Boolean(navigator.serviceWorker.controller))
> 1068 |         ).toBe(true)
       |           ^
  1069 |       })
  1070 |
  1071 |       await browser.elementById('prefetch-offline-navigation').click()

  at toBe (production/app-dir/offline-navigations/offline-navigations.test.ts:1068:11)
  at retry (lib/next-test-utils.ts:862:14)
  at Object.<anonymous> (production/app-dir/offline-navigations/offline-navigations.test.ts:1065:7)

● offlineNavigations build artifacts › replays a dynamic route from persisted known route patterns

expect(received).toBe(expected) // Object.is equality

Expected: true
Received: false

  1177 |         expect(
  1178 |           await browser.eval(() => Boolean(navigator.serviceWorker.controller))
> 1179 |         ).toBe(true)
       |           ^
  1180 |       })
  1181 |
  1182 |       await prefetchDynamicPatternReplayData(browser)

  at toBe (production/app-dir/offline-navigations/offline-navigations.test.ts:1179:11)
  at retry (lib/next-test-utils.ts:862:14)
  at Object.<anonymous> (production/app-dir/offline-navigations/offline-navigations.test.ts:1176:7)

● offlineNavigations build artifacts › replays request-sensitive exact URLs from browser-private storage

expect(received).toBe(expected) // Object.is equality

Expected: true
Received: false

  1342 |         expect(
  1343 |           await browser.eval(() => Boolean(navigator.serviceWorker.controller))
> 1344 |         ).toBe(true)
       |           ^
  1345 |       })
  1346 |
  1347 |       await browser.eval(() => {

  at toBe (production/app-dir/offline-navigations/offline-navigations.test.ts:1344:11)
  at retry (lib/next-test-utils.ts:862:14)
  at Object.<anonymous> (production/app-dir/offline-navigations/offline-navigations.test.ts:1341:7)

● offlineNavigations build artifacts › covers exact URL shape and pass-through stress cases

expect(received).toBe(expected) // Object.is equality

Expected: true
Received: false

  1439 |         expect(
  1440 |           await browser.eval(() => Boolean(navigator.serviceWorker.controller))
> 1441 |         ).toBe(true)
       |           ^
  1442 |       })
  1443 |
  1444 |       const notFoundResponse = await page!.goto(

  at toBe (production/app-dir/offline-navigations/offline-navigations.test.ts:1441:11)
  at retry (lib/next-test-utils.ts:862:14)
  at Object.<anonymous> (production/app-dir/offline-navigations/offline-navigations.test.ts:1438:7)

pnpm test-start test/production/app-dir/offline-navigations/offline-navigations.test.ts (job)

  • offlineNavigations build artifacts > reconstructs a fully prefetched route from persisted router records (DD)
  • offlineNavigations build artifacts > replays a dynamic route from persisted known route patterns (DD)
  • offlineNavigations build artifacts > replays request-sensitive exact URLs from browser-private storage (DD)
  • offlineNavigations build artifacts > covers exact URL shape and pass-through stress cases (DD)
Expand output

● offlineNavigations build artifacts › reconstructs a fully prefetched route from persisted router records

expect(received).toBe(expected) // Object.is equality

Expected: true
Received: false

  1066 |         expect(
  1067 |           await browser.eval(() => Boolean(navigator.serviceWorker.controller))
> 1068 |         ).toBe(true)
       |           ^
  1069 |       })
  1070 |
  1071 |       await browser.elementById('prefetch-offline-navigation').click()

  at toBe (production/app-dir/offline-navigations/offline-navigations.test.ts:1068:11)
  at retry (lib/next-test-utils.ts:862:14)
  at Object.<anonymous> (production/app-dir/offline-navigations/offline-navigations.test.ts:1065:7)

● offlineNavigations build artifacts › replays a dynamic route from persisted known route patterns

expect(received).toBe(expected) // Object.is equality

Expected: true
Received: false

  1177 |         expect(
  1178 |           await browser.eval(() => Boolean(navigator.serviceWorker.controller))
> 1179 |         ).toBe(true)
       |           ^
  1180 |       })
  1181 |
  1182 |       await prefetchDynamicPatternReplayData(browser)

  at toBe (production/app-dir/offline-navigations/offline-navigations.test.ts:1179:11)
  at retry (lib/next-test-utils.ts:862:14)
  at Object.<anonymous> (production/app-dir/offline-navigations/offline-navigations.test.ts:1176:7)

● offlineNavigations build artifacts › replays request-sensitive exact URLs from browser-private storage

expect(received).toBe(expected) // Object.is equality

Expected: true
Received: false

  1342 |         expect(
  1343 |           await browser.eval(() => Boolean(navigator.serviceWorker.controller))
> 1344 |         ).toBe(true)
       |           ^
  1345 |       })
  1346 |
  1347 |       await browser.eval(() => {

  at toBe (production/app-dir/offline-navigations/offline-navigations.test.ts:1344:11)
  at retry (lib/next-test-utils.ts:862:14)
  at Object.<anonymous> (production/app-dir/offline-navigations/offline-navigations.test.ts:1341:7)

● offlineNavigations build artifacts › covers exact URL shape and pass-through stress cases

expect(received).toBe(expected) // Object.is equality

Expected: true
Received: false

  1439 |         expect(
  1440 |           await browser.eval(() => Boolean(navigator.serviceWorker.controller))
> 1441 |         ).toBe(true)
       |           ^
  1442 |       })
  1443 |
  1444 |       const notFoundResponse = await page!.goto(

  at toBe (production/app-dir/offline-navigations/offline-navigations.test.ts:1441:11)
  at retry (lib/next-test-utils.ts:862:14)
  at Object.<anonymous> (production/app-dir/offline-navigations/offline-navigations.test.ts:1438:7)

@feedthejim feedthejim force-pushed the feedthejim/offline-navigations-dynamic-route-patterns branch from 50d10ce to 8c40fef Compare May 8, 2026 08:18
@feedthejim feedthejim force-pushed the feedthejim/offline-navigations-router-cache-negative-coverage branch from 1ff84b6 to febc878 Compare May 8, 2026 08:18
@feedthejim feedthejim force-pushed the feedthejim/offline-navigations-dynamic-route-patterns branch from 8c40fef to eaa8ac0 Compare May 8, 2026 08:34
This was referenced May 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

created-by: Next.js team PRs by the Next.js team. tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant