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
挪成外部测试,挪不动。
This commit is contained in:
@@ -0,0 +1,269 @@
|
||||
package jscriptx_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.fsdpf.net/go/jscriptx"
|
||||
)
|
||||
|
||||
// 所有脚本都要经 esbuild 打包,入口只有一条路:模块导出。
|
||||
// 这里把各种导出形态过一遍。
|
||||
func TestEntry_各种导出形态(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
src string
|
||||
fn string
|
||||
args []any
|
||||
want any
|
||||
newObj bool // 需要先 New 出实例(导出的是 class)
|
||||
}{
|
||||
{
|
||||
name: "export default class",
|
||||
src: `export default class DeviceHandler {
|
||||
constructor(id) { this.id = id }
|
||||
info() { return "class:" + this.id }
|
||||
}`,
|
||||
fn: "info", newObj: true, args: []any{"d1"}, want: "class:d1",
|
||||
},
|
||||
{
|
||||
name: "export default 匿名 class",
|
||||
src: `export default class {
|
||||
constructor(id) { this.id = id }
|
||||
info() { return "匿名:" + this.id }
|
||||
}`,
|
||||
fn: "info", newObj: true, args: []any{"d2"}, want: "匿名:d2",
|
||||
},
|
||||
{
|
||||
name: "export default 实例",
|
||||
src: `class H { info() { return "实例" } }
|
||||
export default new H()`,
|
||||
fn: "info", want: "实例",
|
||||
},
|
||||
{
|
||||
name: "export default 对象字面量",
|
||||
src: `export default {
|
||||
info: function () { return "对象" }
|
||||
}`,
|
||||
fn: "info", want: "对象",
|
||||
},
|
||||
{
|
||||
name: "export default 具名函数",
|
||||
src: `export default function handler(x) { return x * 2 }`,
|
||||
fn: jscriptx.DefaultFunc, args: []any{21}, want: int64(42),
|
||||
},
|
||||
{
|
||||
name: "export default 箭头函数",
|
||||
src: `export default (x) => x + 1`,
|
||||
fn: jscriptx.DefaultFunc, args: []any{41}, want: int64(42),
|
||||
},
|
||||
{
|
||||
name: "只有命名导出",
|
||||
src: `export function Options() { return "opts" }
|
||||
export function Version() { return "v1" }`,
|
||||
fn: "Options", want: "opts",
|
||||
},
|
||||
{
|
||||
name: "继承来的方法也找得到",
|
||||
src: `class Base { base() { return "base" } }
|
||||
export default class Derived extends Base {
|
||||
own() { return "own" }
|
||||
}`,
|
||||
fn: "base", newObj: true, want: "base",
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
e := newEngine(t)
|
||||
s := mustCompile(t, e, "mod.ts", c.src)
|
||||
ctx := context.Background()
|
||||
|
||||
var got any
|
||||
var err error
|
||||
if c.newObj {
|
||||
obj, e2 := s.New(ctx, c.args...)
|
||||
if e2 != nil {
|
||||
t.Fatalf("New 失败: %v", e2)
|
||||
}
|
||||
defer obj.Close()
|
||||
got, err = obj.Call(ctx, c.fn)
|
||||
} else {
|
||||
got, err = s.Call(ctx, c.fn, c.args...)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("调用失败: %v", err)
|
||||
}
|
||||
if got != c.want {
|
||||
t.Errorf("got = %#v, want %#v", got, c.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TypeScript 直接能写。
|
||||
func TestEntry_TypeScript(t *testing.T) {
|
||||
e := newEngine(t)
|
||||
s := mustCompile(t, e, "ts.ts", `
|
||||
interface Cfg { name: string }
|
||||
|
||||
export default class C {
|
||||
private count: number = 0
|
||||
constructor(private cfg: Cfg) {}
|
||||
Bump(): number { return ++this.count }
|
||||
Name(): string { return this.cfg.name }
|
||||
}
|
||||
`)
|
||||
ctx := context.Background()
|
||||
obj, err := s.New(ctx, map[string]any{"name": "配置A"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer obj.Close()
|
||||
|
||||
if got, _ := obj.Call(ctx, "Name"); got != "配置A" {
|
||||
t.Errorf("Name = %#v", got)
|
||||
}
|
||||
if got, _ := obj.Call(ctx, "Bump"); got.(int64) != 1 {
|
||||
t.Errorf("Bump = %#v", got)
|
||||
}
|
||||
}
|
||||
|
||||
// 不写 export 的脚本会被打包器摇空,报错要说清楚这一点。
|
||||
func TestEntry_没有导出时报错有指引(t *testing.T) {
|
||||
e := newEngine(t)
|
||||
s := mustCompile(t, e, "noexport.js", `function handle(x) { return x * 2 }`)
|
||||
|
||||
_, err := s.Call(context.Background(), "handle", 1)
|
||||
if !errors.Is(err, jscriptx.ErrFuncNotFound) {
|
||||
t.Fatalf("want ErrFuncNotFound, got %v", err)
|
||||
}
|
||||
var jsErr *jscriptx.Error
|
||||
errors.As(err, &jsErr)
|
||||
for _, want := range []string{"没有任何导出", "export"} {
|
||||
if !strings.Contains(jsErr.Msg, want) {
|
||||
t.Errorf("报错里没有 %q: %s", want, jsErr.Msg)
|
||||
}
|
||||
}
|
||||
t.Logf("%s", jsErr.Msg)
|
||||
}
|
||||
|
||||
// constructor 抛异常要报清楚是构造阶段失败。
|
||||
func TestEntry_构造失败(t *testing.T) {
|
||||
e := newEngine(t)
|
||||
s := mustCompile(t, e, "bad.ts", `
|
||||
export default class H {
|
||||
constructor(cfg) {
|
||||
if (!cfg) { throw new Error("缺少配置") }
|
||||
this.cfg = cfg
|
||||
}
|
||||
run() { return this.cfg }
|
||||
}
|
||||
`)
|
||||
|
||||
_, err := s.New(context.Background()) // 故意不传构造参数
|
||||
if err == nil {
|
||||
t.Fatal("构造失败应该报错")
|
||||
}
|
||||
var jsErr *jscriptx.Error
|
||||
if !errors.As(err, &jsErr) {
|
||||
t.Fatalf("错误类型不对: %T", err)
|
||||
}
|
||||
if jsErr.Func != "constructor" {
|
||||
t.Errorf("应该指明是 constructor 出错: %+v", jsErr)
|
||||
}
|
||||
if !strings.Contains(jsErr.Msg, "缺少配置") {
|
||||
t.Errorf("没带上脚本的错误信息: %q", jsErr.Msg)
|
||||
}
|
||||
}
|
||||
|
||||
// 池化调用下,实例状态 = VM 状态,换了 VM 就归零,只能当缓存。
|
||||
func TestEntry_池化下实例状态会丢(t *testing.T) {
|
||||
e := newEngine(t, jscriptx.WithTimeout(80*time.Millisecond))
|
||||
s := mustCompile(t, e, "counter.ts", `
|
||||
export default class Counter {
|
||||
constructor() { this.count = 0 }
|
||||
bump() { return ++this.count }
|
||||
spin() { while (true) {} }
|
||||
}
|
||||
`)
|
||||
ctx := context.Background()
|
||||
|
||||
if got, _ := s.Call(ctx, "bump"); got.(int64) != 1 {
|
||||
t.Fatalf("got %#v", got)
|
||||
}
|
||||
if got, _ := s.Call(ctx, "bump"); got.(int64) != 2 {
|
||||
t.Fatalf("同一个 VM 内应该累加, got %#v", got)
|
||||
}
|
||||
|
||||
if _, err := s.Call(ctx, "spin"); !errors.Is(err, jscriptx.ErrTimeout) {
|
||||
t.Fatalf("want ErrTimeout, got %v", err)
|
||||
}
|
||||
|
||||
got, err := s.Call(ctx, "bump")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got.(int64) != 1 {
|
||||
t.Errorf("池化模式换了 VM,实例状态应该归零,got %#v", got)
|
||||
}
|
||||
}
|
||||
|
||||
// 直接 Compile 的源码不能有 import,报错要指出走 esm 子包。
|
||||
func TestEntry_Compile不支持import(t *testing.T) {
|
||||
e := newEngine(t)
|
||||
_, err := e.Compile("imp.ts", `import x from "./other"
|
||||
export default class H { m() { return x } }`)
|
||||
if err == nil {
|
||||
t.Fatal("应该报错")
|
||||
}
|
||||
for _, want := range []string{"Could not resolve", "jscriptx/esm"} {
|
||||
if !strings.Contains(err.Error(), want) {
|
||||
t.Errorf("报错里没有 %q: %v", want, err)
|
||||
}
|
||||
}
|
||||
t.Logf("%v", err)
|
||||
}
|
||||
|
||||
// 配了 WithResolveDir 之后,直接 Compile 的源码也能 import。
|
||||
func TestEntry_ResolveDir(t *testing.T) {
|
||||
e := newEngine(t, jscriptx.WithBundleOptions(
|
||||
jscriptx.WithResolveDir("esm/testdata/app/src/Resource"),
|
||||
))
|
||||
s, err := e.Compile("inline.ts", `import ResExecuter from "./ResExecuter"
|
||||
export default class H {
|
||||
m() { return new ResExecuter().Store("cfg") }
|
||||
}`)
|
||||
if err != nil {
|
||||
t.Fatalf("配了 resolveDir 应该能 import: %v", err)
|
||||
}
|
||||
ctx := context.Background()
|
||||
obj, err := s.New(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer obj.Close()
|
||||
if got, _ := obj.Call(ctx, "m"); got != "stored:cfg" {
|
||||
t.Errorf("got = %#v", got)
|
||||
}
|
||||
}
|
||||
|
||||
// 语法错误在 Compile 阶段就报,带文件位置。
|
||||
func TestEntry_语法错误(t *testing.T) {
|
||||
e := newEngine(t)
|
||||
_, err := e.Compile("bad.ts", `export default class { 语法错误 (((`)
|
||||
if err == nil {
|
||||
t.Fatal("应该报语法错误")
|
||||
}
|
||||
var jsErr *jscriptx.Error
|
||||
if !errors.As(err, &jsErr) || jsErr.Kind != jscriptx.KindCompile {
|
||||
t.Fatalf("错误分类不对: %v", err)
|
||||
}
|
||||
if !strings.Contains(err.Error(), "bad.ts") {
|
||||
t.Errorf("报错没带文件名: %v", err)
|
||||
}
|
||||
t.Logf("%v", err)
|
||||
}
|
||||
Reference in New Issue
Block a user