|
1 | 1 | // HTTP canonical-key identity tests. |
2 | 2 | // |
3 | 3 | // Pins the behavior of the native CanonicalizeHttpUrlKey (the loader/registry |
4 | | -// key) via the debug-only __nsCanonicalizeHttpUrlKey diagnostic global. Pure |
5 | | -// string logic — no dev server required. Android does NOT collapse the |
6 | | -// __ns_boot__/__ns_hmr__ virtual prefixes here (that is canonicalHotKey's job), |
7 | | -// which these specs assert explicitly. |
| 4 | +// key) via the debug-only __NS_DEV__.canonicalizeHttpUrlKey diagnostic. Pure |
| 5 | +// string logic — no dev server required. |
| 6 | +// |
| 7 | +// Module identity IS the canonical URL: the dev server serves every module |
| 8 | +// under one URL and freshness is handled by __NS_DEV__.invalidateModules |
| 9 | +// (registry + prewarm-cache evict + fetch nonce), never by URL variation. |
| 10 | +// There is deliberately no path-tag vocabulary (__ns_boot__/__ns_hmr__) |
| 11 | +// to collapse, and no versioned-bridge-endpoint normalization. |
8 | 12 |
|
9 | 13 | describe("HTTP canonical key", function () { |
| 14 | + function canonFn() { |
| 15 | + const dev = globalThis.__NS_DEV__; |
| 16 | + return dev && typeof dev.canonicalizeHttpUrlKey === "function" |
| 17 | + ? dev.canonicalizeHttpUrlKey |
| 18 | + : null; |
| 19 | + } |
10 | 20 | function canon(url) { |
11 | | - return globalThis.__nsCanonicalizeHttpUrlKey(url); |
| 21 | + return canonFn()(url); |
12 | 22 | } |
13 | 23 |
|
14 | 24 | it("is available in dev builds", function () { |
15 | | - if (typeof globalThis.__nsCanonicalizeHttpUrlKey !== "function") { |
16 | | - pending("__nsCanonicalizeHttpUrlKey not available (release build?)"); |
| 25 | + if (!canonFn()) { |
| 26 | + pending("__NS_DEV__.canonicalizeHttpUrlKey not available (release build?)"); |
17 | 27 | return; |
18 | 28 | } |
19 | | - expect(typeof globalThis.__nsCanonicalizeHttpUrlKey).toBe("function"); |
| 29 | + expect(typeof canonFn()).toBe("function"); |
20 | 30 | }); |
21 | 31 |
|
22 | 32 | it("drops the fragment and unwraps file://http wrappers", function () { |
23 | | - if (typeof globalThis.__nsCanonicalizeHttpUrlKey !== "function") { pending("release"); return; } |
| 33 | + if (!canonFn()) { pending("release"); return; } |
24 | 34 | expect(canon("http://h/ns/m/foo.js#frag")).toBe("http://h/ns/m/foo.js"); |
25 | 35 | expect(canon("file://http://h/x.js")).toBe("http://h/x.js"); |
26 | 36 | }); |
27 | 37 |
|
28 | | - it("normalizes versioned bridge endpoints but not deeper paths", function () { |
29 | | - if (typeof globalThis.__nsCanonicalizeHttpUrlKey !== "function") { pending("release"); return; } |
30 | | - expect(canon("http://h/ns/rt/42")).toBe("http://h/ns/rt"); |
31 | | - expect(canon("http://h/ns/core/13")).toBe("http://h/ns/core"); |
| 38 | + it("does NOT collapse versioned endpoint paths or path tags", function () { |
| 39 | + if (!canonFn()) { pending("release"); return; } |
| 40 | + // Path is identity — no /ns/rt/<v> → /ns/rt collapse, no |
| 41 | + // __ns_hmr__/__ns_boot__ tag folding. |
| 42 | + expect(canon("http://h/ns/rt/42")).toBe("http://h/ns/rt/42"); |
32 | 43 | expect(canon("http://h/ns/rt/42/x.js")).toBe("http://h/ns/rt/42/x.js"); |
33 | | - }); |
34 | | - |
35 | | - it("does NOT collapse __ns_hmr__/__ns_boot__ prefixes (Android loader key)", function () { |
36 | | - if (typeof globalThis.__nsCanonicalizeHttpUrlKey !== "function") { pending("release"); return; } |
37 | 44 | expect(canon("http://h/ns/m/__ns_hmr__/v7/foo.js")) |
38 | 45 | .toBe("http://h/ns/m/__ns_hmr__/v7/foo.js"); |
39 | 46 | }); |
40 | 47 |
|
41 | | - it("strips ?import and sorts remaining query params", function () { |
42 | | - if (typeof globalThis.__nsCanonicalizeHttpUrlKey !== "function") { pending("release"); return; } |
43 | | - expect(canon("http://h/a?import=1&b=2&a=3")).toBe("http://h/a?a=3&b=2"); |
44 | | - expect(canon("http://h/a?b=2&a=1")).toBe("http://h/a?a=1&b=2"); |
| 48 | + it("strips import/t/v markers and sorts remaining params on dev endpoints", function () { |
| 49 | + if (!canonFn()) { pending("release"); return; } |
| 50 | + expect(canon("http://h/ns/m/a?import=1&b=2&a=3")).toBe("http://h/ns/m/a?a=3&b=2"); |
| 51 | + expect(canon("http://h/ns/m/a?b=2&a=1")).toBe("http://h/ns/m/a?a=1&b=2"); |
45 | 52 | expect(canon("http://h/ns/core?import=1")).toBe("http://h/ns/core"); |
| 53 | + expect(canon("http://h/ns/m/a?t=123&v=abc&x=1")).toBe("http://h/ns/m/a?x=1"); |
| 54 | + }); |
| 55 | + |
| 56 | + it("preserves the query verbatim on non-dev endpoints", function () { |
| 57 | + if (!canonFn()) { pending("release"); return; } |
| 58 | + // Public-internet module URLs: the query can be part of identity |
| 59 | + // (auth, content versioning, routing) — only the fragment is dropped. |
| 60 | + expect(canon("http://h/a?import=1&b=2&a=3")).toBe("http://h/a?import=1&b=2&a=3"); |
| 61 | + expect(canon("https://cdn.example.com/pkg.js?token=x#frag")) |
| 62 | + .toBe("https://cdn.example.com/pkg.js?token=x"); |
| 63 | + }); |
| 64 | + |
| 65 | + it("preserves the t param on @ng/component endpoints", function () { |
| 66 | + if (!canonFn()) { pending("release"); return; } |
| 67 | + // Angular HMR component-update endpoint: `t` identifies a specific |
| 68 | + // recompile and must remain a distinct registry entry. |
| 69 | + expect(canon("http://h/ns/m/app/@ng/component?c=x&t=111")) |
| 70 | + .toBe("http://h/ns/m/app/@ng/component?c=x&t=111"); |
46 | 71 | }); |
47 | 72 |
|
48 | 73 | it("leaves a non-http(s) specifier unchanged", function () { |
49 | | - if (typeof globalThis.__nsCanonicalizeHttpUrlKey !== "function") { pending("release"); return; } |
| 74 | + if (!canonFn()) { pending("release"); return; } |
50 | 75 | expect(canon("~/local/foo.js")).toBe("~/local/foo.js"); |
51 | 76 | }); |
52 | 77 | }); |
0 commit comments