Files
cloudflare__vinext/tests/navigation-planner-early-intent.test.ts
James Anderson a5f0bb44cb fix(app-router): match current navigation identity (#2876)
* fix(app-router): match soft navigation identity

* fix(app-router): refresh exact links behind basePath

* fix(app-router): preserve navigation URL spaces

* fix(app-router): preserve raw navigation identity

* fix(app-router): preserve licensed history cache reuse

* fix(app-router): replace visited route aliases

* fix(app-router): preserve exact hash cache bypass

* test(navigation): cover cache bypass runtime argument
2026-08-16 15:43:57 +01:00

262 lines
8.8 KiB
TypeScript

import { describe, expect, it } from "vite-plus/test";
import {
NAVIGATION_TRACE_SCHEMA_VERSION,
NavigationTraceReasonCodes,
} from "../packages/vinext/src/server/navigation-trace.js";
import {
navigationPlanner,
type EarlyNavigationIntentDecision,
type EarlyNavigationIntentFacts,
} from "../packages/vinext/src/server/navigation-planner.js";
function createFacts(
overrides: Partial<EarlyNavigationIntentFacts> = {},
): EarlyNavigationIntentFacts {
return {
basePath: "",
currentUrlSpace: "browser",
currentHref: "https://example.com/docs?q=1",
mode: "push",
scroll: true,
targetHref: "https://example.com/docs?q=1#section",
...overrides,
};
}
function classify(
overrides: Partial<EarlyNavigationIntentFacts> = {},
): EarlyNavigationIntentDecision {
return navigationPlanner.classifyEarlyNavigationIntent(createFacts(overrides));
}
function expectSingleTraceEntry(
decision: EarlyNavigationIntentDecision,
code: string,
fields: Record<string, string | number | boolean | null>,
): void {
expect(decision.trace).toEqual({
schemaVersion: NAVIGATION_TRACE_SCHEMA_VERSION,
entries: [{ code, fields }],
});
}
describe("navigationPlanner early navigation intent classification", () => {
it("classifies a same-page hash change as a same-document scroll", () => {
const decision = classify({
currentHref: "https://example.com/docs?q=1",
targetHref: "https://example.com/docs?q=1#section",
});
expect(decision).toEqual({
kind: "sameDocumentScroll",
hash: "#section",
mode: "push",
scroll: true,
trace: decision.trace,
});
expectSingleTraceEntry(decision, NavigationTraceReasonCodes.sameDocumentScroll, {
targetHref: "https://example.com/docs?q=1#section",
});
});
it("preserves replace mode and scroll intent on a same-document scroll", () => {
const decision = classify({
mode: "replace",
scroll: false,
currentHref: "https://example.com/docs",
targetHref: "https://example.com/docs#footer",
});
expect(decision).toMatchObject({
kind: "sameDocumentScroll",
hash: "#footer",
mode: "replace",
scroll: false,
});
});
it("resolves a relative hash target against the current href", () => {
const decision = classify({
currentHref: "https://example.com/docs?q=1",
targetHref: "#section",
});
expect(decision).toMatchObject({ kind: "sameDocumentScroll", hash: "#section" });
});
it("classifies a same-path search change as a cache-bypassing flight navigation", () => {
const decision = classify({
currentHref: "https://example.com/docs?q=1",
targetHref: "https://example.com/docs?q=2",
});
expect(decision).toEqual({
kind: "flightNavigation",
bypassNavigationCache: true,
trace: decision.trace,
});
expectSingleTraceEntry(decision, NavigationTraceReasonCodes.samePageSearch, {
targetHref: "https://example.com/docs?q=2",
});
});
it("treats a search change as a flight navigation even when the target adds a hash", () => {
const decision = classify({
currentHref: "https://example.com/docs?q=1",
targetHref: "https://example.com/docs?q=2#section",
});
expect(decision).toMatchObject({ kind: "flightNavigation", bypassNavigationCache: true });
});
it("classifies a cross-path navigation as a non-bypassing flight navigation", () => {
const decision = classify({
currentHref: "https://example.com/docs",
targetHref: "https://example.com/blog",
});
expect(decision).toEqual({
kind: "flightNavigation",
bypassNavigationCache: false,
trace: decision.trace,
});
expectSingleTraceEntry(decision, NavigationTraceReasonCodes.crossDocumentFlight, {
targetHref: "https://example.com/blog",
});
});
it("treats search params that differ only by encoding as the same page", () => {
// "+" and "%20" both decode to a space, so this is not a search change and
// must not bypass the navigation cache. Guards the choice of comparing
// serialised params over raw search strings.
const decision = classify({
currentHref: "https://example.com/docs?a=+",
targetHref: "https://example.com/docs?a=%20",
});
expect(decision).toMatchObject({ kind: "flightNavigation", bypassNavigationCache: false });
});
it("refreshes an identical URL without replaying the current page response", () => {
const decision = classify({
currentHref: "https://example.com/docs?q=1",
targetHref: "https://example.com/docs?q=1",
});
expect(decision).toMatchObject({ kind: "flightNavigation", bypassNavigationCache: true });
expectSingleTraceEntry(decision, NavigationTraceReasonCodes.samePageRefresh, {
targetHref: "https://example.com/docs?q=1",
});
});
it("refreshes an exact current URL when the committed snapshot is app-relative", () => {
const decision = classify({
basePath: "/app",
currentUrlSpace: "appRelativeSnapshot",
currentHref: "https://example.com/docs?q=1",
targetHref: "https://example.com/app/docs?q=1",
});
expect(decision).toMatchObject({ kind: "flightNavigation", bypassNavigationCache: true });
expectSingleTraceEntry(decision, NavigationTraceReasonCodes.samePageRefresh, {
targetHref: "https://example.com/app/docs?q=1",
});
});
it("refreshes an identical non-empty hash", () => {
const decision = classify({
basePath: "/app",
currentUrlSpace: "appRelativeSnapshot",
currentHref: "https://example.com/docs#section",
targetHref: "https://example.com/app/docs#section",
});
expect(decision).toMatchObject({ kind: "flightNavigation", bypassNavigationCache: true });
expectSingleTraceEntry(decision, NavigationTraceReasonCodes.samePageRefresh, {
targetHref: "https://example.com/app/docs#section",
});
});
it("does not strip a basePath-like first segment from an app-relative snapshot", () => {
const decision = classify({
basePath: "/docs",
currentUrlSpace: "appRelativeSnapshot",
currentHref: "https://example.com/docs/foo",
targetHref: "https://example.com/docs/docs/foo",
});
expect(decision).toMatchObject({ kind: "flightNavigation", bypassNavigationCache: true });
expectSingleTraceEntry(decision, NavigationTraceReasonCodes.samePageRefresh, {
targetHref: "https://example.com/docs/docs/foo",
});
});
it("refreshes an app-relative exact URL that preserves %20 spelling", () => {
const decision = classify({
basePath: "/docs",
currentUrlSpace: "appRelativeSnapshot",
currentHref: "https://example.com/docs/foo?q=%20",
targetHref: "https://example.com/docs/docs/foo?q=%20",
});
expect(decision).toMatchObject({ kind: "flightNavigation", bypassNavigationCache: true });
});
it("keeps %20 and + as distinct raw spellings for app-relative exact identity", () => {
const decision = classify({
basePath: "/docs",
currentUrlSpace: "appRelativeSnapshot",
currentHref: "https://example.com/docs/foo?q=%20",
targetHref: "https://example.com/docs/docs/foo?q=+",
});
expect(decision).toMatchObject({ kind: "flightNavigation", bypassNavigationCache: false });
});
it("treats hash removal as a same-document navigation", () => {
const decision = classify({
currentHref: "https://example.com/docs#section",
targetHref: "https://example.com/docs",
});
expect(decision).toMatchObject({ kind: "sameDocumentScroll", hash: "" });
});
it("does not treat a cross-origin same-path hash target as a same-document scroll", () => {
const decision = classify({
currentHref: "https://a.example/docs?q=1",
targetHref: "https://b.example/docs?q=1#section",
});
expect(decision).toMatchObject({ kind: "flightNavigation", bypassNavigationCache: false });
});
it("does not treat a cross-origin same-path search target as same-page search", () => {
const decision = classify({
currentHref: "https://a.example/docs?q=1",
targetHref: "https://b.example/docs?q=2",
});
expect(decision).toMatchObject({ kind: "flightNavigation", bypassNavigationCache: false });
});
it("strips the base path before comparing pathnames for a hash change", () => {
const decision = classify({
basePath: "/app",
currentHref: "https://example.com/app/docs",
targetHref: "https://example.com/app/docs#section",
});
expect(decision).toMatchObject({ kind: "sameDocumentScroll", hash: "#section" });
});
it("falls back to a non-bypassing flight navigation when an href cannot be parsed", () => {
const decision = classify({
currentHref: "not a url",
targetHref: "also not a url",
});
expect(decision).toMatchObject({ kind: "flightNavigation", bypassNavigationCache: false });
});
});