Files
jscriptx/esm/npm/semver_test.go
what ad722b12f9 fix(esm): Vendor 支持子路径,不然 es-toolkit/compat 这种装不了
有些包把东西放在子路径下(es-toolkit 的 toString 只在 compat 里,主入口没有),
而摊平之后原包的 exports 映射就没了,import "es-toolkit/compat" 解析不到。

现在直接写子路径就行:

    esm.Install(ctx, "es-toolkit/compat", "app/node_modules")

拉的是根包,摊平的是子路径,落到 node_modules/es-toolkit/compat/。根包和子路径
可以共存——子路径目录嵌在根包目录里,而最小 package.json 不写 exports,
所以解析器认得出来。

npm.SplitPath 负责拆名字,scoped 包名自带一个斜杠所以前两段才是包名。
2026-09-07 10:58:34 +08:00

167 lines
4.7 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)
}
}
}