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) } }