package goja_bindings_test import ( "errors" "fmt" "os" "seanime/internal/extension" "seanime/internal/extension_repo" "seanime/internal/util" "testing" "time" "github.com/dop251/goja" "github.com/dop251/goja/parser" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func setupTestVM(t *testing.T) *goja.Runtime { vm := goja.New() vm.SetParserOptions(parser.WithDisableSourceMaps) // Bind the shared bindings extension_repo.ShareBinds(vm, util.NewLogger()) fm := extension_repo.FieldMapper{} vm.SetFieldNameMapper(fm) return vm } func divide(a, b float64) (float64, error) { if b == 0 { return 0, errors.New("division by zero") } return a / b, nil } func TestDivideFunction(t *testing.T) { vm := goja.New() vm.Set("divide", divide) // Case 1: Successful division result, err := vm.RunString("divide(10, 3);") assert.NoError(t, err) assert.Equal(t, 3.3333333333333335, result.Export()) // Case 2: Division by zero should throw an exception _, err = vm.RunString("divide(10, 0);") assert.Error(t, err) assert.Contains(t, err.Error(), "division by zero") // Case 3: Handling error with try-catch in JS result, err = vm.RunString(` try { divide(10, 0); } catch (e) { e.toString(); } `) assert.NoError(t, err) assert.Equal(t, "GoError: division by zero", result.Export()) } func multipleReturns() (int, string, float64) { return 42, "hello", 3.14 } func TestMultipleReturns(t *testing.T) { vm := goja.New() vm.Set("multiReturn", multipleReturns) v, err := vm.RunString("multiReturn();") assert.NoError(t, err) util.Spew(v.Export()) } func TestUserConfig(t *testing.T) { vm := setupTestVM(t) defer vm.ClearInterrupt() ext := &extension.Extension{ UserConfig: &extension.UserConfig{ Fields: []extension.ConfigField{ { Name: "test", }, { Name: "test2", Default: "Default value", }, }, }, SavedUserConfig: &extension.SavedUserConfig{ Values: map[string]string{ "test": "Hello World!", }, }, } extension_repo.ShareBinds(vm, util.NewLogger()) extension_repo.BindUserConfig(vm, ext, util.NewLogger()) vm.RunString(` const result = $getUserPreference("test"); console.log(result); const result2 = $getUserPreference("test2"); console.log(result2); `) } func TestByteSliceToUint8Array(t *testing.T) { // Initialize a new Goja VM vm := goja.New() // Create a Go byte slice data := []byte("hello") // Set the byte slice in the Goja VM vm.Set("data", data) extension_repo.ShareBinds(vm, util.NewLogger()) // JavaScript code to verify the type and contents of 'data' jsCode := ` console.log(typeof data, data); const dataArrayBuffer = new ArrayBuffer(5); const uint8Array = new Uint8Array(dataArrayBuffer); uint8Array[0] = 104; uint8Array[1] = 101; uint8Array[2] = 108; uint8Array[3] = 108; uint8Array[4] = 111; console.log(typeof uint8Array, uint8Array); console.log("toString", $toString(uint8Array)); console.log("toString", uint8Array.toString()); true; // Return true if all checks pass ` // Run the JavaScript code in the Goja VM result, err := vm.RunString(jsCode) if err != nil { t.Fatalf("JavaScript error: %v", err) } // Assert that the result is true assert.Equal(t, true, result.Export()) } func TestGojaDocument(t *testing.T) { vm := setupTestVM(t) defer vm.ClearInterrupt() tests := []struct { entry string }{ {entry: "./js/test/doc-example.ts"}, {entry: "./js/test/doc-example-2.ts"}, } for _, tt := range tests { t.Run(tt.entry, func(t *testing.T) { fileB, err := os.ReadFile(tt.entry) require.NoError(t, err) now := time.Now() source, err := extension_repo.JSVMTypescriptToJS(string(fileB)) require.NoError(t, err) _, err = vm.RunString(source) require.NoError(t, err) _, err = vm.RunString(`function NewProvider() { return new Provider() }`) require.NoError(t, err) newProviderFunc, ok := goja.AssertFunction(vm.Get("NewProvider")) require.True(t, ok) classObjVal, err := newProviderFunc(goja.Undefined()) require.NoError(t, err) classObj := classObjVal.ToObject(vm) testFunc, ok := goja.AssertFunction(classObj.Get("test")) require.True(t, ok) ret, err := testFunc(classObj) require.NoError(t, err) promise := ret.Export().(*goja.Promise) for promise.State() == goja.PromiseStatePending { time.Sleep(10 * time.Millisecond) } if promise.State() == goja.PromiseStateFulfilled { t.Logf("Fulfilled: %v", promise.Result()) } else { t.Fatalf("Rejected: %v", promise.Result()) } fmt.Println(time.Since(now).Seconds()) }) } } func TestOptionalParams(t *testing.T) { vm := setupTestVM(t) defer vm.ClearInterrupt() type Options struct { Add int `json:"add"` } vm.Set("test", func(a int, opts Options) int { fmt.Println("opts", opts) return a + opts.Add }) vm.RunString(` const result = test(1); console.log(result); const result2 = test(1, { add: 10 }); console.log(result2); `) }