Files
jscriptx/script_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

218 lines
5.9 KiB
Go

package jscriptx_test
import (
"context"
"errors"
"strings"
"testing"
"git.fsdpf.net/go/jscriptx"
)
func TestCall_基本调用(t *testing.T) {
e := newEngine(t)
s := mustCompile(t, e, "calc", `
export function add(a, b) { return a + b }
export function greet(name) { return "你好, " + name }
export function nothing() {}
`)
got, err := s.Call(context.Background(), "add", 2, 3)
if err != nil {
t.Fatalf("调用失败: %v", err)
}
if n, ok := got.(int64); !ok || n != 5 {
t.Errorf("add(2,3) = %#v, want int64(5)", got)
}
got, err = s.Call(context.Background(), "greet", "张三")
if err != nil {
t.Fatalf("调用失败: %v", err)
}
if got != "你好, 张三" {
t.Errorf("greet = %#v", got)
}
got, err = s.Call(context.Background(), "nothing")
if err != nil {
t.Fatalf("调用失败: %v", err)
}
if got != nil {
t.Errorf("无返回值的函数应该拿到 nil,实际 %#v", got)
}
}
func TestCall_脚本自身求值出的函数(t *testing.T) {
e := newEngine(t)
s := mustCompile(t, e, "default_fn", `
export default function handler(x) { return x * 2 }
`)
got, err := s.Call(context.Background(), jscriptx.DefaultFunc, 21)
if err != nil {
t.Fatalf("调用失败: %v", err)
}
if n, _ := got.(int64); n != 42 {
t.Errorf("got = %#v, want 42", got)
}
}
func TestCallInto_按类型转换(t *testing.T) {
e := newEngine(t)
s := mustCompile(t, e, "typed", `
export function count() { return 7 }
export function profile() { return {Name: "张三", Age: 30} }
`)
var n int
if err := s.CallInto(context.Background(), "count", &n); err != nil {
t.Fatalf("调用失败: %v", err)
}
if n != 7 {
t.Errorf("n = %d, want 7", n)
}
// goja 默认按 Go 字段名映射(不看 json tag),脚本里就得写 Name/Age
var p struct {
Name string
Age int
}
if err := s.CallInto(context.Background(), "profile", &p); err != nil {
t.Fatalf("调用失败: %v", err)
}
if p.Name != "张三" || p.Age != 30 {
t.Errorf("p = %+v", p)
}
}
func TestCallInto_拒绝非指针目标(t *testing.T) {
e := newEngine(t)
s := mustCompile(t, e, "x", `export function f() { return 1 }`)
var n int
err := s.CallInto(context.Background(), "f", n)
if err == nil {
t.Fatal("传值不传指针应该报错")
}
var jsErr *jscriptx.Error
if !errors.As(err, &jsErr) || jsErr.Kind != jscriptx.KindType {
t.Errorf("错误分类不对: %v", err)
}
}
// 脚本函数是绑在 VM 上的,VM 一还回池子就失效了,不能让它跨出边界。
func TestValueEscape_拒绝把脚本函数带出来(t *testing.T) {
e := newEngine(t)
s := mustCompile(t, e, "escape", `export function make() { return function(x) { return x } }`)
if _, err := s.Call(context.Background(), "make"); !errors.Is(err, jscriptx.ErrValueEscape) {
t.Errorf("Call 返回函数应该被拒绝,实际: %v", err)
}
var fn func(int) int
err := s.CallInto(context.Background(), "make", &fn)
if !errors.Is(err, jscriptx.ErrValueEscape) {
t.Errorf("CallInto 导出成 Go 函数应该被拒绝,实际: %v", err)
}
}
func TestCall_函数不存在(t *testing.T) {
e := newEngine(t)
s := mustCompile(t, e, "mod.ts", `
export const x = 1
export function other() { return "有这个" }
`)
_, err := s.Call(context.Background(), "nope")
if !errors.Is(err, jscriptx.ErrFuncNotFound) {
t.Fatalf("want ErrFuncNotFound, got %v", err)
}
if s.Has("nope") {
t.Error("Has 不该认出不存在的名字")
}
if !s.Has("other") {
t.Error("Has 认不出导出的函数")
}
// 导出的名字存在但不是函数,同样算找不到
if s.Has("x") {
t.Error("非函数的导出不该被当成函数")
}
}
func TestCompile_语法错误(t *testing.T) {
e := newEngine(t)
_, err := e.Compile("bad", `export function f( { syntax error`)
if err == nil {
t.Fatal("语法错误应该在编译期报出来")
}
var jsErr *jscriptx.Error
if !errors.As(err, &jsErr) || jsErr.Kind != jscriptx.KindCompile {
t.Errorf("错误分类不对: %v", err)
}
if jsErr.Script != "bad" {
t.Errorf("错误里没带上脚本名: %+v", jsErr)
}
}
func TestError_带定位信息(t *testing.T) {
e := newEngine(t)
s := mustCompile(t, e, "boom.js", `
export function explode(arg) {
throw new Error("业务校验失败: " + arg)
}
`)
_, err := s.Call(context.Background(), "explode", "订单号-123")
if err == nil {
t.Fatal("应该报错")
}
var jsErr *jscriptx.Error
if !errors.As(err, &jsErr) {
t.Fatalf("错误类型不对: %T", err)
}
if jsErr.Kind != jscriptx.KindRuntime {
t.Errorf("Kind = %q", jsErr.Kind)
}
if jsErr.Script != "boom.js" || jsErr.Func != "explode" {
t.Errorf("脚本/函数名不对: %+v", jsErr)
}
if !strings.Contains(jsErr.Msg, "业务校验失败") {
t.Errorf("没带上脚本抛出的信息: %q", jsErr.Msg)
}
if len(jsErr.Stack) == 0 || jsErr.Stack[0].Line != 3 {
t.Errorf("栈信息定位不到出错行: %+v", jsErr.Stack)
}
if len(jsErr.Args) != 1 || !strings.Contains(jsErr.Args[0], "订单号-123") {
t.Errorf("没记录调用参数: %v", jsErr.Args)
}
t.Logf("错误文本: %v", err)
}
// Go 函数返回的 error 透到脚本里没被 catch 时,调用方的 errors.Is 还应该匹配得上。
func TestError_透传Go侧哨兵错误(t *testing.T) {
sentinel := errors.New("库存不足")
e := newEngine(t, jscriptx.WithGlobal("checkStock", func() error { return sentinel }))
s := mustCompile(t, e, "stock", `export function run() { checkStock(); return "ok" }`)
_, err := s.Call(context.Background(), "run")
if !errors.Is(err, sentinel) {
t.Fatalf("原始 Go error 丢了: %v", err)
}
}
func TestClose_关闭后拒绝调用(t *testing.T) {
e := newEngine(t)
s := mustCompile(t, e, "x", `export function f() { return 1 }`)
if _, err := s.Call(context.Background(), "f"); err != nil {
t.Fatal(err)
}
s.Close()
if _, err := s.Call(context.Background(), "f"); !errors.Is(err, jscriptx.ErrClosed) {
t.Errorf("want ErrClosed, got %v", err)
}
s.Close() // 重复关闭不应 panic
}