| 1 | import { describe, expect, it } from "vite-plus/test"; |
| 2 | |
| 3 | import { |
| 4 | decodeThirdPartyLicenseManifest, |
| 5 | filterThirdPartyLicenseEntries, |
| 6 | findThirdPartyLicenseEntry, |
| 7 | formatLicenseBundles, |
| 8 | thirdPartyLicenseEntryKey, |
| 9 | type ThirdPartyLicenseEntry, |
| 10 | } from "./thirdPartyLicenses.js"; |
| 11 | |
| 12 | const ENTRIES: ReadonlyArray<ThirdPartyLicenseEntry> = [ |
| 13 | { |
| 14 | bundles: ["web"], |
| 15 | kind: "package", |
| 16 | license: "MIT", |
| 17 | name: "react", |
| 18 | noticeText: "React license", |
| 19 | sourceUrl: "https://react.dev", |
| 20 | version: "19.2.6", |
| 21 | }, |
| 22 | { |
| 23 | bundles: ["assets", "mobile"], |
| 24 | kind: "custom", |
| 25 | license: "CC-BY-4.0", |
| 26 | name: "sample-icons", |
| 27 | noticeText: "Icon notice", |
| 28 | sourceUrl: null, |
| 29 | version: null, |
| 30 | }, |
| 31 | ]; |
| 32 | |
| 33 | describe("third-party license manifests", () => { |
| 34 | it("decodes the generated manifest shape", () => { |
| 35 | expect(decodeThirdPartyLicenseManifest({ schemaVersion: 1, entries: ENTRIES })).toEqual({ |
| 36 | schemaVersion: 1, |
| 37 | entries: ENTRIES, |
| 38 | }); |
| 39 | }); |
| 40 | |
| 41 | it("rejects unsupported manifest versions", () => { |
| 42 | expect(() => decodeThirdPartyLicenseManifest({ schemaVersion: 2, entries: [] })).toThrow( |
| 43 | "unsupported format", |
| 44 | ); |
| 45 | }); |
| 46 | |
| 47 | it("rejects unsafe source links", () => { |
| 48 | expect(() => |
| 49 | decodeThirdPartyLicenseManifest({ |
| 50 | schemaVersion: 1, |
| 51 | entries: [{ ...ENTRIES[0]!, sourceUrl: "javascript:alert(1)" }], |
| 52 | }), |
| 53 | ).toThrow("invalid shape"); |
| 54 | }); |
| 55 | |
| 56 | it("rejects duplicate navigation keys", () => { |
| 57 | expect(() => |
| 58 | decodeThirdPartyLicenseManifest({ schemaVersion: 1, entries: [ENTRIES[0]!, ENTRIES[0]!] }), |
| 59 | ).toThrow("duplicate entry"); |
| 60 | }); |
| 61 | |
| 62 | it("filters by package, license, version, and bundle", () => { |
| 63 | expect(filterThirdPartyLicenseEntries(ENTRIES, "react 19.2")).toEqual([ENTRIES[0]]); |
| 64 | expect(filterThirdPartyLicenseEntries(ENTRIES, "cc-by mobile")).toEqual([ENTRIES[1]]); |
| 65 | expect(filterThirdPartyLicenseEntries(ENTRIES, "apache")).toEqual([]); |
| 66 | }); |
| 67 | |
| 68 | it("formats platform bundle names for display", () => { |
| 69 | expect( |
| 70 | formatLicenseBundles(["android", "assets", "ios", "mobile", "plugin", "constructor"]), |
| 71 | ).toBe("Android, Assets, iOS, Mobile, plugin, constructor"); |
| 72 | }); |
| 73 | |
| 74 | it("finds an entry by its stable navigation key", () => { |
| 75 | const entry = ENTRIES[0]!; |
| 76 | expect(findThirdPartyLicenseEntry(ENTRIES, thirdPartyLicenseEntryKey(entry))).toBe(entry); |
| 77 | }); |
| 78 | |
| 79 | it("uses path-safe navigation keys for scoped packages", () => { |
| 80 | expect(thirdPartyLicenseEntryKey({ ...ENTRIES[0]!, name: "@scope/package" })).not.toContain( |
| 81 | "/", |
| 82 | ); |
| 83 | }); |
| 84 | |
| 85 | it("uses distinct navigation keys when names and versions contain delimiters", () => { |
| 86 | const first = thirdPartyLicenseEntryKey({ ...ENTRIES[0]!, name: "a:1", version: null }); |
| 87 | const second = thirdPartyLicenseEntryKey({ ...ENTRIES[0]!, name: "a", version: "1:custom" }); |
| 88 | |
| 89 | expect(first).not.toBe(second); |
| 90 | }); |
| 91 | }); |