有些包把东西放在子路径下(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 包名自带一个斜杠所以前两段才是包名。
167 lines
4.7 KiB
Go
167 lines
4.7 KiB
Go
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)
|
||
}
|
||
}
|
||
}
|