| 1 | import { describe, expect, it } from "vite-plus/test"; |
| 2 | |
| 3 | import { |
| 4 | compareSemverVersions, |
| 5 | normalizeSemverVersion, |
| 6 | parseSemver, |
| 7 | satisfiesSemverRange, |
| 8 | } from "./semver.ts"; |
| 9 | |
| 10 | describe("semver helpers", () => { |
| 11 | it("matches supported range groups", () => { |
| 12 | const range = "^22.16 || ^23.11 || >=24.10"; |
| 13 | |
| 14 | expect(satisfiesSemverRange("22.16.0", range)).toBe(true); |
| 15 | expect(satisfiesSemverRange("23.11.1", range)).toBe(true); |
| 16 | expect(satisfiesSemverRange("24.10.0", range)).toBe(true); |
| 17 | expect(satisfiesSemverRange("22.15.9", range)).toBe(false); |
| 18 | expect(satisfiesSemverRange("23.10.9", range)).toBe(false); |
| 19 | expect(satisfiesSemverRange("24.9.9", range)).toBe(false); |
| 20 | }); |
| 21 | |
| 22 | it("normalizes versions with a missing patch segment", () => { |
| 23 | expect(normalizeSemverVersion("2.1")).toBe("2.1.0"); |
| 24 | }); |
| 25 | |
| 26 | it("normalizes and parses shorthand major-only versions", () => { |
| 27 | expect(normalizeSemverVersion("20")).toBe("20.0.0"); |
| 28 | expect(normalizeSemverVersion("v18")).toBe("v18.0.0"); |
| 29 | expect(normalizeSemverVersion("20-rc.1")).toBe("20.0.0-rc.1"); |
| 30 | expect(parseSemver("20")).toEqual({ major: 20, minor: 0, patch: 0, prerelease: [] }); |
| 31 | }); |
| 32 | |
| 33 | it("compares shorthand versions numerically instead of lexically", () => { |
| 34 | // Regression: "20" vs "9" previously fell back to string comparison, which |
| 35 | // ordered "20" before "9" ("2" < "9"). |
| 36 | expect(compareSemverVersions("20", "9")).toBeGreaterThan(0); |
| 37 | expect(compareSemverVersions("18", "18.0.0")).toBe(0); |
| 38 | }); |
| 39 | |
| 40 | it("still rejects non-numeric shorthand and keeps empty input empty", () => { |
| 41 | expect(parseSemver("abc")).toBeNull(); |
| 42 | expect(normalizeSemverVersion("")).toBe(""); |
| 43 | }); |
| 44 | |
| 45 | it("compares prerelease versions before stable versions", () => { |
| 46 | expect(compareSemverVersions("2.1.111-beta.1", "2.1.111")).toBeLessThan(0); |
| 47 | }); |
| 48 | |
| 49 | it("falls back to lexical comparison for malformed numeric segments", () => { |
| 50 | expect(compareSemverVersions("1.2.3abc", "1.2.10")).toBeGreaterThan(0); |
| 51 | }); |
| 52 | |
| 53 | it("supports comparison comparators", () => { |
| 54 | expect(satisfiesSemverRange("24.9.0", ">=24.0 <24.10")).toBe(true); |
| 55 | expect(satisfiesSemverRange("24.10.0", ">=24.0 <24.10")).toBe(false); |
| 56 | }); |
| 57 | |
| 58 | it("honors caret range upper bounds for zero-major versions", () => { |
| 59 | expect(satisfiesSemverRange("0.2.3", "^0.2.3")).toBe(true); |
| 60 | expect(satisfiesSemverRange("0.2.9", "^0.2.3")).toBe(true); |
| 61 | expect(satisfiesSemverRange("0.3.0", "^0.2.3")).toBe(false); |
| 62 | expect(satisfiesSemverRange("0.5.0", "^0.2.3")).toBe(false); |
| 63 | expect(satisfiesSemverRange("0.0.3", "^0.0.3")).toBe(true); |
| 64 | expect(satisfiesSemverRange("0.0.4", "^0.0.3")).toBe(false); |
| 65 | }); |
| 66 | |
| 67 | it("rejects invalid versions and unsupported range syntax", () => { |
| 68 | expect(satisfiesSemverRange("not-a-version", ">=24.0")).toBe(false); |
| 69 | expect(satisfiesSemverRange("24.10.0", "~24.10")).toBe(false); |
| 70 | }); |
| 71 | |
| 72 | it("keeps the range checker stringifiable and executable as plain JavaScript", () => { |
| 73 | const source = satisfiesSemverRange.toString(); |
| 74 | const recreated = Function(`return (${source});`)() as typeof satisfiesSemverRange; |
| 75 | |
| 76 | expect(source).toContain("function satisfiesSemverRange"); |
| 77 | expect(source).not.toContain(": string"); |
| 78 | expect(source).not.toContain(": boolean"); |
| 79 | expect(recreated("24.10.0", ">=24.10")).toBe(true); |
| 80 | expect(recreated("24.9.9", ">=24.10")).toBe(false); |
| 81 | }); |
| 82 | }); |