import { expect, test } from "bun:test"; import { isBrowserNavigationAvailabilityPayload, isBrowserNavigationAvailabilityResult, OPEN_URL_ACTION_DEFINITION, toBrowserNavigationOpenParams, } from "./navigation-rpc"; const navigationSeed = { flowId: "flow-1", flowVersionId: "flow-version-1", flowVersionNumber: 3, flowRunId: "flow-run-1", actionPlacementId: "link-1", actionKind: "navigation-action" as const, }; test("parses trusted browser navigation parameters", () => { expect( toBrowserNavigationOpenParams({ viewId: "view_1", generation: 2, url: "https://example.com/settings", target: "self", }), ).toEqual({ viewId: "view_1", generation: 2, url: "https://example.com/settings", target: "self", }); }); test("copies complete navigation analytics metadata without changing the request", () => { const seed = { ...navigationSeed }; const parsed = toBrowserNavigationOpenParams({ viewId: "view_1", generation: 2, url: "https://example.com/settings", invocationId: "invocation-1", definition: OPEN_URL_ACTION_DEFINITION, flowActionSucceededSeed: seed, }); seed.flowId = "spoofed-flow"; expect(parsed).toMatchObject({ invocationId: "invocation-1", definition: OPEN_URL_ACTION_DEFINITION, flowActionSucceededSeed: navigationSeed, }); expect(parsed?.flowActionSucceededSeed).not.toBe(seed); expect(Object.isFrozen(parsed?.flowActionSucceededSeed)).toBe(true); }); test.each([ ["missing invocation", { definition: OPEN_URL_ACTION_DEFINITION }], ["missing definition", { invocationId: "invocation-1" }], [ "wrong definition", { invocationId: "invocation-1", definition: { ...OPEN_URL_ACTION_DEFINITION, version: 1 }, flowActionSucceededSeed: navigationSeed, }, ], [ "expanded definition", { invocationId: "invocation-1", definition: { ...OPEN_URL_ACTION_DEFINITION, responseId: "response-1" }, flowActionSucceededSeed: navigationSeed, }, ], [ "runtime seed", { invocationId: "invocation-1", definition: OPEN_URL_ACTION_DEFINITION, flowActionSucceededSeed: { ...navigationSeed, actionKind: "runtime-action", }, }, ], ] as const)("drops %s analytics metadata only", (_label, analytics) => { expect( toBrowserNavigationOpenParams({ viewId: "view_1", generation: 2, url: "https://example.com/settings", flowActionSucceededSeed: navigationSeed, ...analytics, }), ).toEqual({ viewId: "view_1", generation: 2, url: "https://example.com/settings", }); }); test("recognizes browser navigation availability RPC results", () => { expect(isBrowserNavigationAvailabilityResult({ available: true })).toBe(true); expect(isBrowserNavigationAvailabilityResult({ available: "yes" })).toBe( false, ); }); test("rejects unsafe browser navigation parameters", () => { expect( toBrowserNavigationOpenParams({ viewId: "view_1", generation: 2, url: "javascript:alert(1)", }), ).toBeNull(); expect( toBrowserNavigationOpenParams({ viewId: "", generation: 2, url: "https://example.com", }), ).toBeNull(); expect( toBrowserNavigationOpenParams({ viewId: "view_1", generation: 2, url: "https://example.com", target: "_blank", }), ).toBeNull(); }); test("recognizes browser navigation availability events", () => { expect( isBrowserNavigationAvailabilityPayload({ type: "browser-navigation-availability", available: true, hostActionLinkAvailable: true, }), ).toBe(true); expect( isBrowserNavigationAvailabilityPayload({ type: "browser-navigation-availability", available: "yes", }), ).toBe(false); expect( isBrowserNavigationAvailabilityPayload({ type: "browser-navigation-availability", available: true, hostActionLinkAvailable: "yes", }), ).toBe(false); });