Files
jscriptx/bundle_finalize_test.go
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

152 lines
5.0 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 jscriptx_test
import (
"context"
"errors"
"os"
"strings"
"testing"
"git.fsdpf.net/go/jscriptx"
)
// 产物必须干净:不带 esbuild 的 CommonJS interop helper。
// 那套 helper 每建一个 VM 都要重跑一遍,是建实例开销的大头(实测差 5 倍)。
func TestBundle_产物不含interop_helper(t *testing.T) {
for _, c := range []struct{ name, src string }{
{"default 导出", `export default class H { m() { return 1 } }`},
{"只有命名导出", `export function a() { return 1 }
export const B = 2`},
{"default + 命名", `export default class H {}
export function extra() {}`},
} {
t.Run(c.name, func(t *testing.T) {
code, err := jscriptx.Bundle("p.ts", c.src)
if err != nil {
t.Fatal(err)
}
for _, helper := range []string{"__defProp", "__toCommonJS", "__copyProps", "__export", "__esModule"} {
if strings.Contains(code, helper) {
t.Errorf("产物里出现了 interop helper %q,说明打包格式退回 IIFE/CJS 了", helper)
}
}
body := code[:strings.Index(code, "//# sourceMappingURL")]
if len(body) > 300 {
t.Errorf("代码部分 %d 字节,偏大,检查是不是又带上 helper 了:\n%s", len(body), body)
}
})
}
}
// 包装不能让行号偏移,否则报错定位不回源码。
func TestBundle_包装不影响行号(t *testing.T) {
code, err := jscriptx.Bundle("p.ts", `export default class H {
m() { return 1 }
}`)
if err != nil {
t.Fatal(err)
}
// 开头的 (() => { 必须紧贴原来的第一行,不能自己占一行
first := strings.SplitN(code, "\n", 2)[0]
if !strings.HasPrefix(first, "(() => {") {
t.Fatalf("第一行是 %q,应该以 (() => { 开头", first)
}
if strings.TrimSpace(first) == "(() => {" {
t.Error("包装单独占了一行,后面所有行号都会偏移一位")
}
// 端到端确认:脚本第 3 行抛错,报出来也得是第 3 行
e := newEngine(t)
s := mustCompile(t, e, "boom.ts", `export default class H {
ok() { return 1 }
boom() { throw new Error("炸了") }
}`)
obj, err := s.New(context.Background())
if err != nil {
t.Fatal(err)
}
defer obj.Close()
_, err = obj.Call(context.Background(), "boom")
var jsErr *jscriptx.Error
if !errors.As(err, &jsErr) || len(jsErr.Stack) == 0 {
t.Fatalf("没拿到栈信息: %v", err)
}
if jsErr.Stack[0].Line != 3 {
t.Errorf("行号 = %d, want 3——包装让行号偏移了", jsErr.Stack[0].Line)
}
}
// 改写要认得 esbuild 输出的各种 export 形态。
func TestBundle_各种导出形态的改写(t *testing.T) {
e := newEngine(t)
ctx := context.Background()
for _, c := range []struct {
name, src, fn string
want any
}{
{"default class", `export default class H { m() { return "class" } }`, "m", "class"},
{"default 匿名 class", `export default class { m() { return "匿名" } }`, "m", "匿名"},
{"default 对象", `export default { m() { return "对象" } }`, "m", "对象"},
{"default 具名函数", `export default function f() { return "具名" }`, jscriptx.DefaultFunc, "具名"},
{"default 匿名函数", `export default function () { return "匿名函数" }`, jscriptx.DefaultFunc, "匿名函数"},
{"default 箭头", `export default () => "箭头"`, jscriptx.DefaultFunc, "箭头"},
{"只有命名导出", `export function a() { return "命名a" }
export function b() { return "命名b" }`, "a", "命名a"},
{"default + 命名(用 default", `export default class H { m() { return "混合" } }
export function extra() { return "额外" }`, "m", "混合"},
{"import 后再导出", `import { helper } from "./dep"
export default class H { m() { return helper() } }`, "m", "来自依赖"},
} {
t.Run(c.name, func(t *testing.T) {
var s *jscriptx.Script
if strings.Contains(c.src, `"./dep"`) {
// 需要一个可解析的依赖目录
dir := t.TempDir()
writeFile(t, dir+"/dep.ts", `export function helper() { return "来自依赖" }`)
e2 := newEngine(t, jscriptx.WithBundleOptions(jscriptx.WithResolveDir(dir)))
s = mustCompile(t, e2, "m.ts", c.src)
} else {
s = mustCompile(t, e, "m.ts", c.src)
}
obj, err := s.New(ctx)
if err != nil {
t.Fatal(err)
}
defer obj.Close()
got, err := obj.Call(ctx, c.fn)
if err != nil {
t.Fatalf("调用失败: %v", err)
}
if got != c.want {
t.Errorf("got = %#v, want %#v", got, c.want)
}
})
}
}
// 认不出 export 语句时的兜底:当作没有导出,给出明确报错,而不是静默出错。
func TestBundle_没有导出时兜底(t *testing.T) {
e := newEngine(t)
s := mustCompile(t, e, "noexport.js", `function handle() { return 1 }`)
_, err := s.Call(context.Background(), "handle")
if !errors.Is(err, jscriptx.ErrFuncNotFound) {
t.Fatalf("want ErrFuncNotFound, got %v", err)
}
var jsErr *jscriptx.Error
errors.As(err, &jsErr)
if !strings.Contains(jsErr.Msg, "没有任何导出") {
t.Errorf("报错该说清楚是没写 export: %s", jsErr.Msg)
}
}
func writeFile(t *testing.T, path, content string) {
t.Helper()
if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
t.Fatal(err)
}
}