package npm import "testing" func TestRange_挑版本(t *testing.T) { // 一个包发布过的所有版本,挑的时候要从里面选 versions := []string{ "0.0.1", "0.0.2", "0.1.0", "0.1.9", "0.2.0", "1.0.0", "1.2.3", "1.2.9", "1.9.0", "2.0.0", "2.1.0", "3.0.0-beta.1", // 预发布,任何情况下都不该被选中 } for _, c := range []struct { rng string want string why string }{ {"^1.2.3", "1.9.0", "^ 锁主版本,取该主版本下最高的"}, {"^1", "1.9.0", "只写主版本"}, {"^2", "2.1.0", ""}, {"^0.1.0", "0.1.9", "主版本是 0 时,^ 锁到次版本"}, {"^0.0.1", "0.0.1", "主次都是 0 时,^ 锁死修订号"}, {"~1.2.3", "1.2.9", "~ 锁到次版本"}, {"~1.2", "1.2.9", ""}, {"~1", "1.9.0", "只写主版本时 ~ 等于锁主版本"}, {"1.2.3", "1.2.3", "精确"}, {"=1.2.3", "1.2.3", ""}, {"1.x", "1.9.0", "x 等价于锁住写出来的那几段"}, {"0.x", "0.2.0", "主版本 0 时也是锁主版本,不能套 ^ 的特殊规则"}, {"1.2.x", "1.2.9", ""}, {">=2.0.0", "2.1.0", "只有下界"}, {"*", "2.1.0", "任意版本取最高的——但不能是预发布"}, {"", "2.1.0", "空范围同上"}, } { r, err := parseRange(c.rng) if err != nil { t.Errorf("parseRange(%q) 报错: %v", c.rng, err) continue } got, err := pick(versions, r) if err != nil { t.Errorf("%q 挑不出版本: %v(%s)", c.rng, err, c.why) continue } if got != c.want { t.Errorf("%q → %s,该是 %s(%s)", c.rng, got, c.want, c.why) } } } // 预发布版本一律不选,跟 npm 的默认行为一致。 func TestRange_不选预发布(t *testing.T) { r, _ := parseRange("^3") if got, err := pick([]string{"3.0.0-beta.1", "3.0.0-rc.2"}, r); err == nil { t.Errorf("只有预发布版本时该挑不出来,却挑了 %s", got) } // 但正式版在的话要选得到 r2, _ := parseRange("^3") if got, _ := pick([]string{"3.0.0-beta.1", "3.0.0"}, r2); got != "3.0.0" { t.Errorf("拿到 %s,该是 3.0.0", got) } } // 支持不了的写法要**明确报错**,不能猜一个版本——猜错了装出来能跑但行为不对。 func TestRange_不支持的写法要报错(t *testing.T) { for _, s := range []string{ "^1 || ^2", // 或 ">=1.0 <2.0", // 复合 "<2.0.0", // 只有上界 "npm:other@^1", "github:user/repo", "file:../local", } { if _, err := parseRange(s); err == nil { t.Errorf("%q 该报错,却解析成功了", s) } } } func TestVersion_解析(t *testing.T) { for _, c := range []struct { in string maj, min, pat int pre string }{ {"1.2.3", 1, 2, 3, ""}, {"0.0.1", 0, 0, 1, ""}, {"10.20.30", 10, 20, 30, ""}, {"1.2.3-beta.1", 1, 2, 3, "beta.1"}, {"1.2.3+build.5", 1, 2, 3, ""}, // 构建元数据不参与比较,丢掉 } { v, err := parseVersion(c.in) if err != nil { t.Errorf("parseVersion(%q): %v", c.in, err) continue } if v.major != c.maj || v.minor != c.min || v.patch != c.pat || v.pre != c.pre { t.Errorf("parseVersion(%q) = %d.%d.%d-%q", c.in, v.major, v.minor, v.patch, v.pre) } } for _, bad := range []string{"1.2", "1", "a.b.c", "", "1.2.x"} { if _, err := parseVersion(bad); err == nil { t.Errorf("parseVersion(%q) 该报错", bad) } } } func TestSplitSpec(t *testing.T) { for _, c := range []struct{ spec, name, rng string }{ {"qs", "qs", ""}, {"qs@6.16.0", "qs", "6.16.0"}, {"qs@^6", "qs", "^6"}, {"@scope/pkg", "@scope/pkg", ""}, {"@scope/pkg@~1.2", "@scope/pkg", "~1.2"}, } { name, rng := SplitSpec(c.spec) if name != c.name || rng != c.rng { t.Errorf("SplitSpec(%q) = %q, %q,该是 %q, %q", c.spec, name, rng, c.name, c.rng) } } } func TestSatisfies(t *testing.T) { for _, c := range []struct { ver, rng string want bool }{ {"6.16.0", "^6", true}, {"5.9.0", "^6", false}, {"6.16.0", "", true}, // 空范围=任意 {"6.16.0", "*", true}, {"1.2.9", "~1.2", true}, {"1.3.0", "~1.2", false}, {"1.2.3", "1.2.3", true}, } { got, err := Satisfies(c.ver, c.rng) if err != nil { t.Errorf("Satisfies(%q, %q): %v", c.ver, c.rng, err) continue } if got != c.want { t.Errorf("Satisfies(%q, %q) = %v", c.ver, c.rng, got) } } if _, err := Satisfies("1.0.0", "^1 || ^2"); err == nil { t.Error("支持不了的范围该报错") } } func TestSplitPath(t *testing.T) { for _, c := range []struct{ in, pkg, sub string }{ {"es-toolkit", "es-toolkit", ""}, {"es-toolkit/compat", "es-toolkit", "compat"}, {"lodash/isString", "lodash", "isString"}, {"@scope/pkg", "@scope/pkg", ""}, {"@scope/pkg/sub", "@scope/pkg", "sub"}, {"@scope/pkg/a/b", "@scope/pkg", "a/b"}, } { pkg, sub := SplitPath(c.in) if pkg != c.pkg || sub != c.sub { t.Errorf("SplitPath(%q) = %q, %q,该是 %q, %q", c.in, pkg, sub, c.pkg, c.sub) } } }