1import { describe, expect, it } from "vite-plus/test";
2
3import {
4 compareSemverVersions,
5 normalizeSemverVersion,
6 parseSemver,
7 satisfiesSemverRange,
8} from "./semver.ts";
9
10describe("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});