Files
jscriptx/errors_hints_test.go
T
what 7c9a70712e test: 测试文件跟源文件同名,删掉复制粘贴出来的重复
改完之后「某个行为在哪测」就是查同名文件:

  lazyglobal_test.go      → engine_globals_test.go
  static_test.go          → script_static_test.go
  scope_pool_test.go      → script_vm_test.go     它测的是 VM 池,不是作用域
  esmwrap_test.go         → bundle_finalize_test.go
  missing_global_test.go  → errors_hints_test.go
  entry_test.go           → bundle_entry_test.go  并收下 bundle_test.go 里
                                                  那三个 TestEntry_*

删掉 5 组复制粘贴。其中 TestNew_同实例调用串行 和 TestInstance_调用串行 **函数体
完全一致**,只差 mustCompile 的文件名参数 counter.js / counter.ts。Instance 的
测试全部归 instance_test.go 并统一前缀成 TestInstance_,scope_test.go 只留作用域。

vmstate_test.go + govalue_test.go → goja_facts_test.go。这两个测的都是 **goja
本身**,不是这个库:Program 能不能给多个 Runtime 共用、顶层变量跟不跟 Runtime 走、
Go 的切片进到脚本里是不是真数组。它们是「为什么这个库要这么设计」的实验记录,
goja 行为变了会先红。原来那个名字看不出这层定位。

helper 集中到 helper_test.go:newEngine / mustCompile 被十几个文件依赖,原来住在
script_test.go 里,那个名字暗示「测 Script」,找不到。

caller_example_test.go 并进 caller_test.go——同一个类型没必要两个文件。

overlay_test.go 和 script_vm_test.go 保持内部测试,各写了一句为什么:前者要读
Engine.loader 和 isPrepared,后者要直接断言 vmHandle.scoped。我试过把 overlay
挪成外部测试,挪不动。
2026-09-10 15:39:25 +08:00

118 lines
3.1 KiB
Go

package jscriptx_test
import (
"context"
"strings"
"testing"
"git.fsdpf.net/go/jscriptx"
)
// 脚本碰到 goja 没有的全局时,光一句 ReferenceError 会把人带偏——
// 外部作者看到 "setTimeout is not defined" 会以为是打包漏了依赖,跑去装 polyfill。
// 这组用例守着那句补充说明。
func TestMissingGlobal_报错带上人话(t *testing.T) {
cases := []struct {
代码 string
要点 []string
}{
{`setTimeout(() => {}, 10)`, []string{"没有事件循环", "Job / Cron"}},
{`clearInterval(1)`, []string{"定时器用不了"}},
{`structuredClone({})`, []string{"JSON.parse", "深拷贝"}},
{`const 名 = "lodash"; require(名)`, []string{"ES 模块", "import"}}, // 动态名字,esbuild 打包期解析不了,才会漏到运行期
{`process.env.X`, []string{"不跑在 Node"}},
{`document.body`, []string{"没有 DOM"}},
{`fetch("http://x")`, []string{"同步执行", "Go 侧"}},
}
e, err := jscriptx.New(jscriptx.WithLogger(nil))
if err != nil {
t.Fatal(err)
}
defer e.Close()
for _, c := range cases {
s, err := e.Compile("m.ts", "export default class C { Run() { "+c.代码+" } }")
if err != nil {
t.Fatalf("%s 编译失败: %v", c.代码, err)
}
ctx := context.Background()
obj, err := s.New(ctx)
if err != nil {
t.Fatalf("%s 实例化失败: %v", c.代码, err)
}
_, err = obj.Call(ctx, "Run")
obj.Close()
if err == nil {
t.Errorf("%s 该报错", c.代码)
continue
}
for _, want := range c.要点 {
if !strings.Contains(err.Error(), want) {
t.Errorf("%s 的错误里该提到 %q\n实际: %v", c.代码, want, err)
}
}
}
}
// 只认 goja 原样的那句,别把脚本自己抛的同名字符串也当成缺全局。
func TestMissingGlobal_不误伤(t *testing.T) {
e, err := jscriptx.New(jscriptx.WithLogger(nil))
if err != nil {
t.Fatal(err)
}
defer e.Close()
s, err := e.Compile("m.ts", `
export default class C {
Run() { throw new Error("ReferenceError: setTimeout is not defined") }
}`)
if err != nil {
t.Fatal(err)
}
ctx := context.Background()
obj, _ := s.New(ctx)
defer obj.Close()
_, err = obj.Call(ctx, "Run")
if err == nil {
t.Fatal("该报错")
}
// 脚本自己抛的,goja 会加上 "Error: " 前缀,前缀对不上就不该补提示
if strings.Contains(err.Error(), "没有事件循环") {
t.Errorf("不该给脚本自己抛的异常补提示: %v", err)
}
}
// 定义成"一调就报错"的桩会破坏特性探测,所以这些全局必须保持 undefined。
func TestMissingGlobal_特性探测仍然可用(t *testing.T) {
e, err := jscriptx.New(jscriptx.WithLogger(nil))
if err != nil {
t.Fatal(err)
}
defer e.Close()
s, err := e.Compile("m.ts", `
export default class C {
Run() {
// 库里常见的降级写法
return typeof setTimeout === "undefined" ? "走降级分支" : "走定时器分支"
}
}`)
if err != nil {
t.Fatal(err)
}
ctx := context.Background()
obj, _ := s.New(ctx)
defer obj.Close()
got, err := obj.Call(ctx, "Run")
if err != nil {
t.Fatal(err)
}
if got != "走降级分支" {
t.Errorf("定时器该是 undefined,好让库能降级,拿到 %#v", got)
}
}