package esm_test import ( "context" "os" "strings" "testing" "git.fsdpf.net/go/jscriptx" "git.fsdpf.net/go/jscriptx/esm" "git.fsdpf.net/go/jscriptx/internal/testext" ) // 两个独立的 .ts 文件,各自一个 class、各自一个 VM,通过同一个 scope 共享 store。 func TestScope_两个文件共享(t *testing.T) { e := newEngine(t, "testdata/app/src") cart, err := e.Script("Shared/CartController") if err != nil { t.Fatal(err) } order, err := e.Script("Shared/OrderController") if err != nil { t.Fatal(err) } st := testext.New() ctx := jscriptx.WithScope(context.Background(), jscriptx.ScopeExtensions(st)) cartObj, err := cart.New(ctx) if err != nil { t.Fatal(err) } defer cartObj.Close() orderObj, err := order.New(ctx) if err != nil { t.Fatal(err) } defer orderObj.Close() // CartController 写 if _, err := cartObj.Call(ctx, "Add", "A-1", 2); err != nil { t.Fatal(err) } if _, err := cartObj.Call(ctx, "Add", "B-2", 3); err != nil { t.Fatal(err) } if _, err := cartObj.Call(ctx, "Add", "A-1", 1); err != nil { t.Fatal(err) } // OrderController 读得到——两个文件、两个 VM,同一份 store got, err := orderObj.Call(ctx, "Checkout") if err != nil { t.Fatalf("跨文件读取失败: %v", err) } if got != "A-1,B-2|6" { t.Errorf("Checkout = %#v, want %q", got, "A-1,B-2|6") } // Go 侧看到的是同一份 if n, _ := st.Get("total").(float64); n != 6 { t.Errorf("Go 侧 total = %v, want 6", n) } // 但各自的 this.ops 是独立的:VM 不共用 if got, _ := cartObj.Call(ctx, "Ops"); got.(int64) != 3 { t.Errorf("cart.ops = %#v, want 3", got) } if got, _ := orderObj.Call(ctx, "Ops"); got.(int64) != 1 { t.Errorf("order.ops = %#v, want 1(不该被 cart 的调用带上)", got) } } // 换一个 scope,两个文件看到的是另一份 store。 func TestScope_不同scope互不干扰(t *testing.T) { e := newEngine(t, "testdata/app/src") cart, _ := e.Script("Shared/CartController") order, _ := e.Script("Shared/OrderController") newCtx := func() context.Context { return jscriptx.WithScope(context.Background(), jscriptx.ScopeExtensions(testext.New())) } ctx1, ctx2 := newCtx(), newCtx() c1, err := cart.New(ctx1) if err != nil { t.Fatal(err) } defer c1.Close() c1.Call(ctx1, "Add", "A-1", 5) // 第二个 scope 里的 OrderController 看不到第一个 scope 写的东西 o2, err := order.New(ctx2) if err != nil { t.Fatal(err) } defer o2.Close() got, err := o2.Call(ctx2, "Checkout") if err != nil { t.Fatal(err) } if got != "|null" { t.Errorf("第二个 scope 应该是空的, got %#v", got) } } // 脚本用 import 拿扩展,而不是直接用全局变量。 func TestScope_import扩展模块(t *testing.T) { loader, err := esm.NewLoader("testdata/extapp", esm.WithExtensions(testext.New())) if err != nil { t.Fatalf("NewLoader 失败: %v", err) } e := newEngineWith(t, loader) s, err := e.Script("Mod/TypedController") if err != nil { t.Fatal(err) } st := testext.New() ctx := jscriptx.WithScope(context.Background(), jscriptx.ScopeExtensions(st)) obj, err := s.New(ctx) if err != nil { t.Fatal(err) } defer obj.Close() if _, err := obj.Call(ctx, "Put", "k", "import 写进来的"); err != nil { t.Fatalf("import 的 store 不能用: %v", err) } if st.Get("k") != "import 写进来的" { t.Errorf("Go 侧读不到: %#v", st.Get("k")) } if got, _ := obj.Call(ctx, "Take", "k"); got != "import 写进来的" { t.Errorf("Take = %#v", got) } if got, _ := obj.Call(ctx, "Describe"); got.(int64) != 1 { t.Errorf("Describe = %#v", got) } } // 没把扩展交给 loader 时,import 它会在打包阶段就失败——这比运行时才发现好。 func TestScope_import未注册的扩展(t *testing.T) { _, err := esm.NewLoader("testdata/extapp") // 没有 WithExtensions if err == nil { t.Fatal("TypedController import 了 @jscriptx/store,没注册扩展时应该打包失败") } for _, want := range []string{"@jscriptx/store", "TypedController"} { if !strings.Contains(err.Error(), want) { t.Errorf("报错里没有 %q: %v", want, err) } } t.Logf("%v", err) } // 扩展模块是虚拟的:磁盘上没有这个文件,内容由扩展自己给。 func TestScope_扩展模块是虚拟的(t *testing.T) { if _, err := os.Stat("testdata/extapp/node_modules/@jscriptx"); err == nil { t.Fatal("测试前提不成立:磁盘上真有 @jscriptx 目录,那就不算虚拟模块了") } loader, err := esm.NewLoader("testdata/extapp", esm.WithExtensions(testext.New())) if err != nil { t.Fatal(err) } src, _, err := loader.Load("Mod/TypedController") if err != nil { t.Fatal(err) } // store 的方法应该被内联进产物,而不是留下一个 import if strings.Contains(src, "@jscriptx/store") && strings.Contains(src, "import") { t.Error("产物里还留着 import,说明模块没被内联") } }