Source file src/syscall/js/js_test.go

     1  // Copyright 2018 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  //go:build js && wasm
     6  
     7  // To run these tests:
     8  //
     9  // - Install Node
    10  // - Add /path/to/go/lib/wasm to your $PATH (so that "go test" can find
    11  //   "go_js_wasm_exec").
    12  // - GOOS=js GOARCH=wasm go test
    13  //
    14  // See -exec in "go help test", and "go help run" for details.
    15  
    16  package js_test
    17  
    18  import (
    19  	"fmt"
    20  	"math"
    21  	"runtime"
    22  	"syscall/js"
    23  	"testing"
    24  )
    25  
    26  var dummys = js.Global().Call("eval", `({
    27  	someBool: true,
    28  	someString: "abc\u1234",
    29  	someInt: 42,
    30  	someFloat: 42.123,
    31  	someArray: [41, 42, 43],
    32  	someDate: new Date(),
    33  	add: function(a, b) {
    34  		return a + b;
    35  	},
    36  	zero: 0,
    37  	stringZero: "0",
    38  	NaN: NaN,
    39  	emptyObj: {},
    40  	emptyArray: [],
    41  	Infinity: Infinity,
    42  	NegInfinity: -Infinity,
    43  	objNumber0: new Number(0),
    44  	objBooleanFalse: new Boolean(false),
    45  })`)
    46  
    47  //go:wasmimport _gotest add
    48  func testAdd(uint32, uint32) uint32
    49  
    50  func TestWasmImport(t *testing.T) {
    51  	a := uint32(3)
    52  	b := uint32(5)
    53  	want := a + b
    54  	if got := testAdd(a, b); got != want {
    55  		t.Errorf("got %v, want %v", got, want)
    56  	}
    57  }
    58  
    59  // testCallExport is imported from host (wasm_exec.js), which calls testExport.
    60  //
    61  //go:wasmimport _gotest callExport
    62  func testCallExport(a int32, b int64) int64
    63  
    64  //go:wasmexport testExport
    65  func testExport(a int32, b int64) int64 {
    66  	testExportCalled = true
    67  	// test stack growth
    68  	growStack(1000)
    69  	// force a goroutine switch
    70  	ch := make(chan int64)
    71  	go func() {
    72  		ch <- int64(a)
    73  		ch <- b
    74  	}()
    75  	return <-ch + <-ch
    76  }
    77  
    78  //go:wasmexport testExport0
    79  func testExport0() { // no arg or result (see issue 69584)
    80  	runtime.GC()
    81  }
    82  
    83  var testExportCalled bool
    84  
    85  func growStack(n int64) {
    86  	if n > 0 {
    87  		growStack(n - 1)
    88  	}
    89  }
    90  
    91  func TestWasmExport(t *testing.T) {
    92  	testExportCalled = false
    93  	a := int32(123)
    94  	b := int64(456)
    95  	want := int64(a) + b
    96  	if got := testCallExport(a, b); got != want {
    97  		t.Errorf("got %v, want %v", got, want)
    98  	}
    99  	if !testExportCalled {
   100  		t.Error("testExport not called")
   101  	}
   102  }
   103  
   104  func TestBool(t *testing.T) {
   105  	want := true
   106  	o := dummys.Get("someBool")
   107  	if got := o.Bool(); got != want {
   108  		t.Errorf("got %#v, want %#v", got, want)
   109  	}
   110  	dummys.Set("otherBool", want)
   111  	if got := dummys.Get("otherBool").Bool(); got != want {
   112  		t.Errorf("got %#v, want %#v", got, want)
   113  	}
   114  	if !dummys.Get("someBool").Equal(dummys.Get("someBool")) {
   115  		t.Errorf("same value not equal")
   116  	}
   117  }
   118  
   119  func TestString(t *testing.T) {
   120  	want := "abc\u1234"
   121  	o := dummys.Get("someString")
   122  	if got := o.String(); got != want {
   123  		t.Errorf("got %#v, want %#v", got, want)
   124  	}
   125  	dummys.Set("otherString", want)
   126  	if got := dummys.Get("otherString").String(); got != want {
   127  		t.Errorf("got %#v, want %#v", got, want)
   128  	}
   129  	if !dummys.Get("someString").Equal(dummys.Get("someString")) {
   130  		t.Errorf("same value not equal")
   131  	}
   132  
   133  	if got, want := js.Undefined().String(), "<undefined>"; got != want {
   134  		t.Errorf("got %#v, want %#v", got, want)
   135  	}
   136  	if got, want := js.Null().String(), "<null>"; got != want {
   137  		t.Errorf("got %#v, want %#v", got, want)
   138  	}
   139  	if got, want := js.ValueOf(true).String(), "<boolean: true>"; got != want {
   140  		t.Errorf("got %#v, want %#v", got, want)
   141  	}
   142  	if got, want := js.ValueOf(42.5).String(), "<number: 42.5>"; got != want {
   143  		t.Errorf("got %#v, want %#v", got, want)
   144  	}
   145  	if got, want := js.Global().Call("Symbol").String(), "<symbol>"; got != want {
   146  		t.Errorf("got %#v, want %#v", got, want)
   147  	}
   148  	if got, want := js.Global().String(), "<object>"; got != want {
   149  		t.Errorf("got %#v, want %#v", got, want)
   150  	}
   151  	if got, want := js.Global().Get("setTimeout").String(), "<function>"; got != want {
   152  		t.Errorf("got %#v, want %#v", got, want)
   153  	}
   154  }
   155  
   156  func TestInt(t *testing.T) {
   157  	want := 42
   158  	o := dummys.Get("someInt")
   159  	if got := o.Int(); got != want {
   160  		t.Errorf("got %#v, want %#v", got, want)
   161  	}
   162  	dummys.Set("otherInt", want)
   163  	if got := dummys.Get("otherInt").Int(); got != want {
   164  		t.Errorf("got %#v, want %#v", got, want)
   165  	}
   166  	if !dummys.Get("someInt").Equal(dummys.Get("someInt")) {
   167  		t.Errorf("same value not equal")
   168  	}
   169  	if got := dummys.Get("zero").Int(); got != 0 {
   170  		t.Errorf("got %#v, want %#v", got, 0)
   171  	}
   172  }
   173  
   174  func TestIntConversion(t *testing.T) {
   175  	testIntConversion(t, 0)
   176  	testIntConversion(t, 1)
   177  	testIntConversion(t, -1)
   178  	testIntConversion(t, 1<<20)
   179  	testIntConversion(t, -1<<20)
   180  	testIntConversion(t, 1<<40)
   181  	testIntConversion(t, -1<<40)
   182  	testIntConversion(t, 1<<60)
   183  	testIntConversion(t, -1<<60)
   184  }
   185  
   186  func testIntConversion(t *testing.T, want int) {
   187  	if got := js.ValueOf(want).Int(); got != want {
   188  		t.Errorf("got %#v, want %#v", got, want)
   189  	}
   190  }
   191  
   192  func TestFloat(t *testing.T) {
   193  	want := 42.123
   194  	o := dummys.Get("someFloat")
   195  	if got := o.Float(); got != want {
   196  		t.Errorf("got %#v, want %#v", got, want)
   197  	}
   198  	dummys.Set("otherFloat", want)
   199  	if got := dummys.Get("otherFloat").Float(); got != want {
   200  		t.Errorf("got %#v, want %#v", got, want)
   201  	}
   202  	if !dummys.Get("someFloat").Equal(dummys.Get("someFloat")) {
   203  		t.Errorf("same value not equal")
   204  	}
   205  }
   206  
   207  func TestObject(t *testing.T) {
   208  	if !dummys.Get("someArray").Equal(dummys.Get("someArray")) {
   209  		t.Errorf("same value not equal")
   210  	}
   211  
   212  	// An object and its prototype should not be equal.
   213  	proto := js.Global().Get("Object").Get("prototype")
   214  	o := js.Global().Call("eval", "new Object()")
   215  	if proto.Equal(o) {
   216  		t.Errorf("object equals to its prototype")
   217  	}
   218  }
   219  
   220  func TestFrozenObject(t *testing.T) {
   221  	o := js.Global().Call("eval", "(function () { let o = new Object(); o.field = 5; Object.freeze(o); return o; })()")
   222  	want := 5
   223  	if got := o.Get("field").Int(); want != got {
   224  		t.Errorf("got %#v, want %#v", got, want)
   225  	}
   226  }
   227  
   228  func TestEqual(t *testing.T) {
   229  	if !dummys.Get("someFloat").Equal(dummys.Get("someFloat")) {
   230  		t.Errorf("same float is not equal")
   231  	}
   232  	if !dummys.Get("emptyObj").Equal(dummys.Get("emptyObj")) {
   233  		t.Errorf("same object is not equal")
   234  	}
   235  	if dummys.Get("someFloat").Equal(dummys.Get("someInt")) {
   236  		t.Errorf("different values are not unequal")
   237  	}
   238  }
   239  
   240  func TestNaN(t *testing.T) {
   241  	if !dummys.Get("NaN").IsNaN() {
   242  		t.Errorf("JS NaN is not NaN")
   243  	}
   244  	if !js.ValueOf(math.NaN()).IsNaN() {
   245  		t.Errorf("Go NaN is not NaN")
   246  	}
   247  	if dummys.Get("NaN").Equal(dummys.Get("NaN")) {
   248  		t.Errorf("NaN is equal to NaN")
   249  	}
   250  }
   251  
   252  func TestUndefined(t *testing.T) {
   253  	if !js.Undefined().IsUndefined() {
   254  		t.Errorf("undefined is not undefined")
   255  	}
   256  	if !js.Undefined().Equal(js.Undefined()) {
   257  		t.Errorf("undefined is not equal to undefined")
   258  	}
   259  	if dummys.IsUndefined() {
   260  		t.Errorf("object is undefined")
   261  	}
   262  	if js.Undefined().IsNull() {
   263  		t.Errorf("undefined is null")
   264  	}
   265  	if dummys.Set("test", js.Undefined()); !dummys.Get("test").IsUndefined() {
   266  		t.Errorf("could not set undefined")
   267  	}
   268  }
   269  
   270  func TestNull(t *testing.T) {
   271  	if !js.Null().IsNull() {
   272  		t.Errorf("null is not null")
   273  	}
   274  	if !js.Null().Equal(js.Null()) {
   275  		t.Errorf("null is not equal to null")
   276  	}
   277  	if dummys.IsNull() {
   278  		t.Errorf("object is null")
   279  	}
   280  	if js.Null().IsUndefined() {
   281  		t.Errorf("null is undefined")
   282  	}
   283  	if dummys.Set("test", js.Null()); !dummys.Get("test").IsNull() {
   284  		t.Errorf("could not set null")
   285  	}
   286  	if dummys.Set("test", nil); !dummys.Get("test").IsNull() {
   287  		t.Errorf("could not set nil")
   288  	}
   289  }
   290  
   291  func TestLength(t *testing.T) {
   292  	if got := dummys.Get("someArray").Length(); got != 3 {
   293  		t.Errorf("got %#v, want %#v", got, 3)
   294  	}
   295  }
   296  
   297  func TestGet(t *testing.T) {
   298  	// positive cases get tested per type
   299  
   300  	expectValueError(t, func() {
   301  		dummys.Get("zero").Get("badField")
   302  	})
   303  }
   304  
   305  func TestSet(t *testing.T) {
   306  	// positive cases get tested per type
   307  
   308  	expectValueError(t, func() {
   309  		dummys.Get("zero").Set("badField", 42)
   310  	})
   311  }
   312  
   313  func TestDelete(t *testing.T) {
   314  	dummys.Set("test", 42)
   315  	dummys.Delete("test")
   316  	if dummys.Call("hasOwnProperty", "test").Bool() {
   317  		t.Errorf("property still exists")
   318  	}
   319  
   320  	expectValueError(t, func() {
   321  		dummys.Get("zero").Delete("badField")
   322  	})
   323  }
   324  
   325  func TestIndex(t *testing.T) {
   326  	if got := dummys.Get("someArray").Index(1).Int(); got != 42 {
   327  		t.Errorf("got %#v, want %#v", got, 42)
   328  	}
   329  
   330  	expectValueError(t, func() {
   331  		dummys.Get("zero").Index(1)
   332  	})
   333  }
   334  
   335  func TestSetIndex(t *testing.T) {
   336  	dummys.Get("someArray").SetIndex(2, 99)
   337  	if got := dummys.Get("someArray").Index(2).Int(); got != 99 {
   338  		t.Errorf("got %#v, want %#v", got, 99)
   339  	}
   340  
   341  	expectValueError(t, func() {
   342  		dummys.Get("zero").SetIndex(2, 99)
   343  	})
   344  }
   345  
   346  func TestCall(t *testing.T) {
   347  	var i int64 = 40
   348  	if got := dummys.Call("add", i, 2).Int(); got != 42 {
   349  		t.Errorf("got %#v, want %#v", got, 42)
   350  	}
   351  	if got := dummys.Call("add", js.Global().Call("eval", "40"), 2).Int(); got != 42 {
   352  		t.Errorf("got %#v, want %#v", got, 42)
   353  	}
   354  
   355  	expectPanic(t, func() {
   356  		dummys.Call("zero")
   357  	})
   358  	expectValueError(t, func() {
   359  		dummys.Get("zero").Call("badMethod")
   360  	})
   361  }
   362  
   363  func TestInvoke(t *testing.T) {
   364  	var i int64 = 40
   365  	if got := dummys.Get("add").Invoke(i, 2).Int(); got != 42 {
   366  		t.Errorf("got %#v, want %#v", got, 42)
   367  	}
   368  
   369  	expectValueError(t, func() {
   370  		dummys.Get("zero").Invoke()
   371  	})
   372  }
   373  
   374  func TestNew(t *testing.T) {
   375  	if got := js.Global().Get("Array").New(42).Length(); got != 42 {
   376  		t.Errorf("got %#v, want %#v", got, 42)
   377  	}
   378  
   379  	expectValueError(t, func() {
   380  		dummys.Get("zero").New()
   381  	})
   382  }
   383  
   384  // TestValueErrorMethod ensures that when a Value method panics with a
   385  // *js.ValueError, the ValueError reports the name of the method that was
   386  // actually called, and not the name of some other method.
   387  func TestValueErrorMethod(t *testing.T) {
   388  	num := dummys.Get("someInt") // a JavaScript number
   389  	obj := dummys                // a JavaScript object
   390  	tests := []struct {
   391  		method string
   392  		fn     func()
   393  	}{
   394  		{"Value.Get", func() { num.Get("x") }},
   395  		{"Value.Set", func() { num.Set("x", 1) }},
   396  		{"Value.Delete", func() { num.Delete("x") }},
   397  		{"Value.Index", func() { num.Index(0) }},
   398  		{"Value.SetIndex", func() { num.SetIndex(0, 1) }},
   399  		{"Value.Length", func() { num.Length() }},
   400  		{"Value.Call", func() { num.Call("x") }},
   401  		{"Value.Invoke", func() { num.Invoke() }},
   402  		{"Value.New", func() { num.New() }},
   403  		{"Value.Float", func() { obj.Float() }},
   404  		{"Value.Int", func() { obj.Int() }},
   405  		{"Value.Bool", func() { obj.Bool() }},
   406  	}
   407  	for _, tt := range tests {
   408  		t.Run(tt.method, func(t *testing.T) {
   409  			defer func() {
   410  				r := recover()
   411  				ve, ok := r.(*js.ValueError)
   412  				if !ok {
   413  					t.Fatalf("expected *js.ValueError, got %T (%v)", r, r)
   414  				}
   415  				if got, want := ve.Method, tt.method; got != want {
   416  					t.Fatalf("ValueError.Method = %q, want %q", got, want)
   417  				}
   418  			}()
   419  			tt.fn()
   420  		})
   421  	}
   422  }
   423  
   424  func TestInstanceOf(t *testing.T) {
   425  	someArray := js.Global().Get("Array").New()
   426  	if got, want := someArray.InstanceOf(js.Global().Get("Array")), true; got != want {
   427  		t.Errorf("got %#v, want %#v", got, want)
   428  	}
   429  	if got, want := someArray.InstanceOf(js.Global().Get("Function")), false; got != want {
   430  		t.Errorf("got %#v, want %#v", got, want)
   431  	}
   432  }
   433  
   434  func TestType(t *testing.T) {
   435  	if got, want := js.Undefined().Type(), js.TypeUndefined; got != want {
   436  		t.Errorf("got %s, want %s", got, want)
   437  	}
   438  	if got, want := js.Null().Type(), js.TypeNull; got != want {
   439  		t.Errorf("got %s, want %s", got, want)
   440  	}
   441  	if got, want := js.ValueOf(true).Type(), js.TypeBoolean; got != want {
   442  		t.Errorf("got %s, want %s", got, want)
   443  	}
   444  	if got, want := js.ValueOf(0).Type(), js.TypeNumber; got != want {
   445  		t.Errorf("got %s, want %s", got, want)
   446  	}
   447  	if got, want := js.ValueOf(42).Type(), js.TypeNumber; got != want {
   448  		t.Errorf("got %s, want %s", got, want)
   449  	}
   450  	if got, want := js.ValueOf("test").Type(), js.TypeString; got != want {
   451  		t.Errorf("got %s, want %s", got, want)
   452  	}
   453  	if got, want := js.Global().Get("Symbol").Invoke("test").Type(), js.TypeSymbol; got != want {
   454  		t.Errorf("got %s, want %s", got, want)
   455  	}
   456  	if got, want := js.Global().Get("Array").New().Type(), js.TypeObject; got != want {
   457  		t.Errorf("got %s, want %s", got, want)
   458  	}
   459  	if got, want := js.Global().Get("Array").Type(), js.TypeFunction; got != want {
   460  		t.Errorf("got %s, want %s", got, want)
   461  	}
   462  }
   463  
   464  type object = map[string]any
   465  type array = []any
   466  
   467  func TestValueOf(t *testing.T) {
   468  	a := js.ValueOf(array{0, array{0, 42, 0}, 0})
   469  	if got := a.Index(1).Index(1).Int(); got != 42 {
   470  		t.Errorf("got %v, want %v", got, 42)
   471  	}
   472  
   473  	o := js.ValueOf(object{"x": object{"y": 42}})
   474  	if got := o.Get("x").Get("y").Int(); got != 42 {
   475  		t.Errorf("got %v, want %v", got, 42)
   476  	}
   477  }
   478  
   479  func TestZeroValue(t *testing.T) {
   480  	var v js.Value
   481  	if !v.IsUndefined() {
   482  		t.Error("zero js.Value is not js.Undefined()")
   483  	}
   484  }
   485  
   486  func TestFuncOf(t *testing.T) {
   487  	c := make(chan struct{})
   488  	cb := js.FuncOf(func(this js.Value, args []js.Value) any {
   489  		if got := args[0].Int(); got != 42 {
   490  			t.Errorf("got %#v, want %#v", got, 42)
   491  		}
   492  		c <- struct{}{}
   493  		return nil
   494  	})
   495  	defer cb.Release()
   496  	js.Global().Call("setTimeout", cb, 0, 42)
   497  	<-c
   498  }
   499  
   500  func TestInvokeFunction(t *testing.T) {
   501  	called := false
   502  	cb := js.FuncOf(func(this js.Value, args []js.Value) any {
   503  		cb2 := js.FuncOf(func(this js.Value, args []js.Value) any {
   504  			called = true
   505  			return 42
   506  		})
   507  		defer cb2.Release()
   508  		return cb2.Invoke()
   509  	})
   510  	defer cb.Release()
   511  	if got := cb.Invoke().Int(); got != 42 {
   512  		t.Errorf("got %#v, want %#v", got, 42)
   513  	}
   514  	if !called {
   515  		t.Error("function not called")
   516  	}
   517  }
   518  
   519  func TestInterleavedFunctions(t *testing.T) {
   520  	c1 := make(chan struct{})
   521  	c2 := make(chan struct{})
   522  
   523  	js.Global().Get("setTimeout").Invoke(js.FuncOf(func(this js.Value, args []js.Value) any {
   524  		c1 <- struct{}{}
   525  		<-c2
   526  		return nil
   527  	}), 0)
   528  
   529  	<-c1
   530  	c2 <- struct{}{}
   531  	// this goroutine is running, but the callback of setTimeout did not return yet, invoke another function now
   532  	f := js.FuncOf(func(this js.Value, args []js.Value) any {
   533  		return nil
   534  	})
   535  	f.Invoke()
   536  }
   537  
   538  func ExampleFuncOf() {
   539  	var cb js.Func
   540  	cb = js.FuncOf(func(this js.Value, args []js.Value) any {
   541  		fmt.Println("button clicked")
   542  		cb.Release() // release the function if the button will not be clicked again
   543  		return nil
   544  	})
   545  	js.Global().Get("document").Call("getElementById", "myButton").Call("addEventListener", "click", cb)
   546  }
   547  
   548  // See
   549  // - https://developer.mozilla.org/en-US/docs/Glossary/Truthy
   550  // - https://stackoverflow.com/questions/19839952/all-falsey-values-in-javascript/19839953#19839953
   551  // - http://www.ecma-international.org/ecma-262/5.1/#sec-9.2
   552  func TestTruthy(t *testing.T) {
   553  	want := true
   554  	for _, key := range []string{
   555  		"someBool", "someString", "someInt", "someFloat", "someArray", "someDate",
   556  		"stringZero", // "0" is truthy
   557  		"add",        // functions are truthy
   558  		"emptyObj", "emptyArray", "Infinity", "NegInfinity",
   559  		// All objects are truthy, even if they're Number(0) or Boolean(false).
   560  		"objNumber0", "objBooleanFalse",
   561  	} {
   562  		if got := dummys.Get(key).Truthy(); got != want {
   563  			t.Errorf("%s: got %#v, want %#v", key, got, want)
   564  		}
   565  	}
   566  
   567  	want = false
   568  	if got := dummys.Get("zero").Truthy(); got != want {
   569  		t.Errorf("got %#v, want %#v", got, want)
   570  	}
   571  	if got := dummys.Get("NaN").Truthy(); got != want {
   572  		t.Errorf("got %#v, want %#v", got, want)
   573  	}
   574  	if got := js.ValueOf("").Truthy(); got != want {
   575  		t.Errorf("got %#v, want %#v", got, want)
   576  	}
   577  	if got := js.Null().Truthy(); got != want {
   578  		t.Errorf("got %#v, want %#v", got, want)
   579  	}
   580  	if got := js.Undefined().Truthy(); got != want {
   581  		t.Errorf("got %#v, want %#v", got, want)
   582  	}
   583  }
   584  
   585  func expectValueError(t *testing.T, fn func()) {
   586  	defer func() {
   587  		err := recover()
   588  		if _, ok := err.(*js.ValueError); !ok {
   589  			t.Errorf("expected *js.ValueError, got %T", err)
   590  		}
   591  	}()
   592  	fn()
   593  }
   594  
   595  func expectPanic(t *testing.T, fn func()) {
   596  	defer func() {
   597  		err := recover()
   598  		if err == nil {
   599  			t.Errorf("expected panic")
   600  		}
   601  	}()
   602  	fn()
   603  }
   604  
   605  var copyTests = []struct {
   606  	srcLen  int
   607  	dstLen  int
   608  	copyLen int
   609  }{
   610  	{5, 3, 3},
   611  	{3, 5, 3},
   612  	{0, 0, 0},
   613  }
   614  
   615  func TestCopyBytesToGo(t *testing.T) {
   616  	for _, tt := range copyTests {
   617  		t.Run(fmt.Sprintf("%d-to-%d", tt.srcLen, tt.dstLen), func(t *testing.T) {
   618  			src := js.Global().Get("Uint8Array").New(tt.srcLen)
   619  			if tt.srcLen >= 2 {
   620  				src.SetIndex(1, 42)
   621  			}
   622  			dst := make([]byte, tt.dstLen)
   623  
   624  			if got, want := js.CopyBytesToGo(dst, src), tt.copyLen; got != want {
   625  				t.Errorf("copied %d, want %d", got, want)
   626  			}
   627  			if tt.dstLen >= 2 {
   628  				if got, want := int(dst[1]), 42; got != want {
   629  					t.Errorf("got %d, want %d", got, want)
   630  				}
   631  			}
   632  		})
   633  	}
   634  }
   635  
   636  func TestCopyBytesToJS(t *testing.T) {
   637  	for _, tt := range copyTests {
   638  		t.Run(fmt.Sprintf("%d-to-%d", tt.srcLen, tt.dstLen), func(t *testing.T) {
   639  			src := make([]byte, tt.srcLen)
   640  			if tt.srcLen >= 2 {
   641  				src[1] = 42
   642  			}
   643  			dst := js.Global().Get("Uint8Array").New(tt.dstLen)
   644  
   645  			if got, want := js.CopyBytesToJS(dst, src), tt.copyLen; got != want {
   646  				t.Errorf("copied %d, want %d", got, want)
   647  			}
   648  			if tt.dstLen >= 2 {
   649  				if got, want := dst.Index(1).Int(), 42; got != want {
   650  					t.Errorf("got %d, want %d", got, want)
   651  				}
   652  			}
   653  		})
   654  	}
   655  }
   656  
   657  func TestGarbageCollection(t *testing.T) {
   658  	before := js.JSGo.Get("_values").Length()
   659  	for i := 0; i < 1000; i++ {
   660  		_ = js.Global().Get("Object").New().Call("toString").String()
   661  		runtime.GC()
   662  	}
   663  	after := js.JSGo.Get("_values").Length()
   664  	if after-before > 500 {
   665  		t.Errorf("garbage collection ineffective")
   666  	}
   667  }
   668  
   669  // This table is used for allocation tests. We expect a specific allocation
   670  // behavior to be seen, depending on the number of arguments applied to various
   671  // JavaScript functions.
   672  // Note: All JavaScript functions return a JavaScript array, which will cause
   673  // one allocation to be created to track the Value.gcPtr for the Value finalizer.
   674  var allocTests = []struct {
   675  	argLen   int // The number of arguments to use for the syscall
   676  	expected int // The expected number of allocations
   677  }{
   678  	// For less than or equal to 16 arguments, we expect 1 allocation:
   679  	// - makeValue new(ref)
   680  	{0, 1},
   681  	{2, 1},
   682  	{15, 1},
   683  	{16, 1},
   684  	// For greater than 16 arguments, we expect 3 allocation:
   685  	// - makeValue: new(ref)
   686  	// - makeArgSlices: argVals = make([]Value, size)
   687  	// - makeArgSlices: argRefs = make([]ref, size)
   688  	{17, 3},
   689  	{32, 3},
   690  	{42, 3},
   691  }
   692  
   693  // TestCallAllocations ensures the correct allocation profile for Value.Call
   694  func TestCallAllocations(t *testing.T) {
   695  	for _, test := range allocTests {
   696  		args := make([]any, test.argLen)
   697  
   698  		tmpArray := js.Global().Get("Array").New(0)
   699  		numAllocs := testing.AllocsPerRun(100, func() {
   700  			tmpArray.Call("concat", args...)
   701  		})
   702  
   703  		if numAllocs != float64(test.expected) {
   704  			t.Errorf("got numAllocs %#v, want %#v", numAllocs, test.expected)
   705  		}
   706  	}
   707  }
   708  
   709  // TestInvokeAllocations ensures the correct allocation profile for Value.Invoke
   710  func TestInvokeAllocations(t *testing.T) {
   711  	for _, test := range allocTests {
   712  		args := make([]any, test.argLen)
   713  
   714  		tmpArray := js.Global().Get("Array").New(0)
   715  		concatFunc := tmpArray.Get("concat").Call("bind", tmpArray)
   716  		numAllocs := testing.AllocsPerRun(100, func() {
   717  			concatFunc.Invoke(args...)
   718  		})
   719  
   720  		if numAllocs != float64(test.expected) {
   721  			t.Errorf("got numAllocs %#v, want %#v", numAllocs, test.expected)
   722  		}
   723  	}
   724  }
   725  
   726  // TestNewAllocations ensures the correct allocation profile for Value.New
   727  func TestNewAllocations(t *testing.T) {
   728  	arrayConstructor := js.Global().Get("Array")
   729  
   730  	for _, test := range allocTests {
   731  		args := make([]any, test.argLen)
   732  
   733  		numAllocs := testing.AllocsPerRun(100, func() {
   734  			arrayConstructor.New(args...)
   735  		})
   736  
   737  		if numAllocs != float64(test.expected) {
   738  			t.Errorf("got numAllocs %#v, want %#v", numAllocs, test.expected)
   739  		}
   740  	}
   741  }
   742  
   743  // BenchmarkDOM is a simple benchmark which emulates a webapp making DOM operations.
   744  // It creates a div, and sets its id. Then searches by that id and sets some data.
   745  // Finally it removes that div.
   746  func BenchmarkDOM(b *testing.B) {
   747  	document := js.Global().Get("document")
   748  	if document.IsUndefined() {
   749  		b.Skip("Not a browser environment. Skipping.")
   750  	}
   751  	const data = "someString"
   752  	for i := 0; i < b.N; i++ {
   753  		div := document.Call("createElement", "div")
   754  		div.Call("setAttribute", "id", "myDiv")
   755  		document.Get("body").Call("appendChild", div)
   756  		myDiv := document.Call("getElementById", "myDiv")
   757  		myDiv.Set("innerHTML", data)
   758  
   759  		if got, want := myDiv.Get("innerHTML").String(), data; got != want {
   760  			b.Errorf("got %s, want %s", got, want)
   761  		}
   762  		document.Get("body").Call("removeChild", div)
   763  	}
   764  }
   765  
   766  func TestGlobal(t *testing.T) {
   767  	ident := js.FuncOf(func(this js.Value, args []js.Value) any {
   768  		return args[0]
   769  	})
   770  	defer ident.Release()
   771  
   772  	if got := ident.Invoke(js.Global()); !got.Equal(js.Global()) {
   773  		t.Errorf("got %#v, want %#v", got, js.Global())
   774  	}
   775  }
   776  

View as plain text