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  func TestInstanceOf(t *testing.T) {
   385  	someArray := js.Global().Get("Array").New()
   386  	if got, want := someArray.InstanceOf(js.Global().Get("Array")), true; got != want {
   387  		t.Errorf("got %#v, want %#v", got, want)
   388  	}
   389  	if got, want := someArray.InstanceOf(js.Global().Get("Function")), false; got != want {
   390  		t.Errorf("got %#v, want %#v", got, want)
   391  	}
   392  }
   393  
   394  func TestType(t *testing.T) {
   395  	if got, want := js.Undefined().Type(), js.TypeUndefined; got != want {
   396  		t.Errorf("got %s, want %s", got, want)
   397  	}
   398  	if got, want := js.Null().Type(), js.TypeNull; got != want {
   399  		t.Errorf("got %s, want %s", got, want)
   400  	}
   401  	if got, want := js.ValueOf(true).Type(), js.TypeBoolean; got != want {
   402  		t.Errorf("got %s, want %s", got, want)
   403  	}
   404  	if got, want := js.ValueOf(0).Type(), js.TypeNumber; got != want {
   405  		t.Errorf("got %s, want %s", got, want)
   406  	}
   407  	if got, want := js.ValueOf(42).Type(), js.TypeNumber; got != want {
   408  		t.Errorf("got %s, want %s", got, want)
   409  	}
   410  	if got, want := js.ValueOf("test").Type(), js.TypeString; got != want {
   411  		t.Errorf("got %s, want %s", got, want)
   412  	}
   413  	if got, want := js.Global().Get("Symbol").Invoke("test").Type(), js.TypeSymbol; got != want {
   414  		t.Errorf("got %s, want %s", got, want)
   415  	}
   416  	if got, want := js.Global().Get("Array").New().Type(), js.TypeObject; got != want {
   417  		t.Errorf("got %s, want %s", got, want)
   418  	}
   419  	if got, want := js.Global().Get("Array").Type(), js.TypeFunction; got != want {
   420  		t.Errorf("got %s, want %s", got, want)
   421  	}
   422  }
   423  
   424  type object = map[string]any
   425  type array = []any
   426  
   427  func TestValueOf(t *testing.T) {
   428  	a := js.ValueOf(array{0, array{0, 42, 0}, 0})
   429  	if got := a.Index(1).Index(1).Int(); got != 42 {
   430  		t.Errorf("got %v, want %v", got, 42)
   431  	}
   432  
   433  	o := js.ValueOf(object{"x": object{"y": 42}})
   434  	if got := o.Get("x").Get("y").Int(); got != 42 {
   435  		t.Errorf("got %v, want %v", got, 42)
   436  	}
   437  }
   438  
   439  func TestZeroValue(t *testing.T) {
   440  	var v js.Value
   441  	if !v.IsUndefined() {
   442  		t.Error("zero js.Value is not js.Undefined()")
   443  	}
   444  }
   445  
   446  func TestFuncOf(t *testing.T) {
   447  	c := make(chan struct{})
   448  	cb := js.FuncOf(func(this js.Value, args []js.Value) any {
   449  		if got := args[0].Int(); got != 42 {
   450  			t.Errorf("got %#v, want %#v", got, 42)
   451  		}
   452  		c <- struct{}{}
   453  		return nil
   454  	})
   455  	defer cb.Release()
   456  	js.Global().Call("setTimeout", cb, 0, 42)
   457  	<-c
   458  }
   459  
   460  func TestInvokeFunction(t *testing.T) {
   461  	called := false
   462  	cb := js.FuncOf(func(this js.Value, args []js.Value) any {
   463  		cb2 := js.FuncOf(func(this js.Value, args []js.Value) any {
   464  			called = true
   465  			return 42
   466  		})
   467  		defer cb2.Release()
   468  		return cb2.Invoke()
   469  	})
   470  	defer cb.Release()
   471  	if got := cb.Invoke().Int(); got != 42 {
   472  		t.Errorf("got %#v, want %#v", got, 42)
   473  	}
   474  	if !called {
   475  		t.Error("function not called")
   476  	}
   477  }
   478  
   479  func TestInterleavedFunctions(t *testing.T) {
   480  	c1 := make(chan struct{})
   481  	c2 := make(chan struct{})
   482  
   483  	js.Global().Get("setTimeout").Invoke(js.FuncOf(func(this js.Value, args []js.Value) any {
   484  		c1 <- struct{}{}
   485  		<-c2
   486  		return nil
   487  	}), 0)
   488  
   489  	<-c1
   490  	c2 <- struct{}{}
   491  	// this goroutine is running, but the callback of setTimeout did not return yet, invoke another function now
   492  	f := js.FuncOf(func(this js.Value, args []js.Value) any {
   493  		return nil
   494  	})
   495  	f.Invoke()
   496  }
   497  
   498  func ExampleFuncOf() {
   499  	var cb js.Func
   500  	cb = js.FuncOf(func(this js.Value, args []js.Value) any {
   501  		fmt.Println("button clicked")
   502  		cb.Release() // release the function if the button will not be clicked again
   503  		return nil
   504  	})
   505  	js.Global().Get("document").Call("getElementById", "myButton").Call("addEventListener", "click", cb)
   506  }
   507  
   508  // See
   509  // - https://developer.mozilla.org/en-US/docs/Glossary/Truthy
   510  // - https://stackoverflow.com/questions/19839952/all-falsey-values-in-javascript/19839953#19839953
   511  // - http://www.ecma-international.org/ecma-262/5.1/#sec-9.2
   512  func TestTruthy(t *testing.T) {
   513  	want := true
   514  	for _, key := range []string{
   515  		"someBool", "someString", "someInt", "someFloat", "someArray", "someDate",
   516  		"stringZero", // "0" is truthy
   517  		"add",        // functions are truthy
   518  		"emptyObj", "emptyArray", "Infinity", "NegInfinity",
   519  		// All objects are truthy, even if they're Number(0) or Boolean(false).
   520  		"objNumber0", "objBooleanFalse",
   521  	} {
   522  		if got := dummys.Get(key).Truthy(); got != want {
   523  			t.Errorf("%s: got %#v, want %#v", key, got, want)
   524  		}
   525  	}
   526  
   527  	want = false
   528  	if got := dummys.Get("zero").Truthy(); got != want {
   529  		t.Errorf("got %#v, want %#v", got, want)
   530  	}
   531  	if got := dummys.Get("NaN").Truthy(); got != want {
   532  		t.Errorf("got %#v, want %#v", got, want)
   533  	}
   534  	if got := js.ValueOf("").Truthy(); got != want {
   535  		t.Errorf("got %#v, want %#v", got, want)
   536  	}
   537  	if got := js.Null().Truthy(); got != want {
   538  		t.Errorf("got %#v, want %#v", got, want)
   539  	}
   540  	if got := js.Undefined().Truthy(); got != want {
   541  		t.Errorf("got %#v, want %#v", got, want)
   542  	}
   543  }
   544  
   545  func expectValueError(t *testing.T, fn func()) {
   546  	defer func() {
   547  		err := recover()
   548  		if _, ok := err.(*js.ValueError); !ok {
   549  			t.Errorf("expected *js.ValueError, got %T", err)
   550  		}
   551  	}()
   552  	fn()
   553  }
   554  
   555  func expectPanic(t *testing.T, fn func()) {
   556  	defer func() {
   557  		err := recover()
   558  		if err == nil {
   559  			t.Errorf("expected panic")
   560  		}
   561  	}()
   562  	fn()
   563  }
   564  
   565  var copyTests = []struct {
   566  	srcLen  int
   567  	dstLen  int
   568  	copyLen int
   569  }{
   570  	{5, 3, 3},
   571  	{3, 5, 3},
   572  	{0, 0, 0},
   573  }
   574  
   575  func TestCopyBytesToGo(t *testing.T) {
   576  	for _, tt := range copyTests {
   577  		t.Run(fmt.Sprintf("%d-to-%d", tt.srcLen, tt.dstLen), func(t *testing.T) {
   578  			src := js.Global().Get("Uint8Array").New(tt.srcLen)
   579  			if tt.srcLen >= 2 {
   580  				src.SetIndex(1, 42)
   581  			}
   582  			dst := make([]byte, tt.dstLen)
   583  
   584  			if got, want := js.CopyBytesToGo(dst, src), tt.copyLen; got != want {
   585  				t.Errorf("copied %d, want %d", got, want)
   586  			}
   587  			if tt.dstLen >= 2 {
   588  				if got, want := int(dst[1]), 42; got != want {
   589  					t.Errorf("got %d, want %d", got, want)
   590  				}
   591  			}
   592  		})
   593  	}
   594  }
   595  
   596  func TestCopyBytesToJS(t *testing.T) {
   597  	for _, tt := range copyTests {
   598  		t.Run(fmt.Sprintf("%d-to-%d", tt.srcLen, tt.dstLen), func(t *testing.T) {
   599  			src := make([]byte, tt.srcLen)
   600  			if tt.srcLen >= 2 {
   601  				src[1] = 42
   602  			}
   603  			dst := js.Global().Get("Uint8Array").New(tt.dstLen)
   604  
   605  			if got, want := js.CopyBytesToJS(dst, src), tt.copyLen; got != want {
   606  				t.Errorf("copied %d, want %d", got, want)
   607  			}
   608  			if tt.dstLen >= 2 {
   609  				if got, want := dst.Index(1).Int(), 42; got != want {
   610  					t.Errorf("got %d, want %d", got, want)
   611  				}
   612  			}
   613  		})
   614  	}
   615  }
   616  
   617  func TestGarbageCollection(t *testing.T) {
   618  	before := js.JSGo.Get("_values").Length()
   619  	for i := 0; i < 1000; i++ {
   620  		_ = js.Global().Get("Object").New().Call("toString").String()
   621  		runtime.GC()
   622  	}
   623  	after := js.JSGo.Get("_values").Length()
   624  	if after-before > 500 {
   625  		t.Errorf("garbage collection ineffective")
   626  	}
   627  }
   628  
   629  // This table is used for allocation tests. We expect a specific allocation
   630  // behavior to be seen, depending on the number of arguments applied to various
   631  // JavaScript functions.
   632  // Note: All JavaScript functions return a JavaScript array, which will cause
   633  // one allocation to be created to track the Value.gcPtr for the Value finalizer.
   634  var allocTests = []struct {
   635  	argLen   int // The number of arguments to use for the syscall
   636  	expected int // The expected number of allocations
   637  }{
   638  	// For less than or equal to 16 arguments, we expect 1 allocation:
   639  	// - makeValue new(ref)
   640  	{0, 1},
   641  	{2, 1},
   642  	{15, 1},
   643  	{16, 1},
   644  	// For greater than 16 arguments, we expect 3 allocation:
   645  	// - makeValue: new(ref)
   646  	// - makeArgSlices: argVals = make([]Value, size)
   647  	// - makeArgSlices: argRefs = make([]ref, size)
   648  	{17, 3},
   649  	{32, 3},
   650  	{42, 3},
   651  }
   652  
   653  // TestCallAllocations ensures the correct allocation profile for Value.Call
   654  func TestCallAllocations(t *testing.T) {
   655  	for _, test := range allocTests {
   656  		args := make([]any, test.argLen)
   657  
   658  		tmpArray := js.Global().Get("Array").New(0)
   659  		numAllocs := testing.AllocsPerRun(100, func() {
   660  			tmpArray.Call("concat", args...)
   661  		})
   662  
   663  		if numAllocs != float64(test.expected) {
   664  			t.Errorf("got numAllocs %#v, want %#v", numAllocs, test.expected)
   665  		}
   666  	}
   667  }
   668  
   669  // TestInvokeAllocations ensures the correct allocation profile for Value.Invoke
   670  func TestInvokeAllocations(t *testing.T) {
   671  	for _, test := range allocTests {
   672  		args := make([]any, test.argLen)
   673  
   674  		tmpArray := js.Global().Get("Array").New(0)
   675  		concatFunc := tmpArray.Get("concat").Call("bind", tmpArray)
   676  		numAllocs := testing.AllocsPerRun(100, func() {
   677  			concatFunc.Invoke(args...)
   678  		})
   679  
   680  		if numAllocs != float64(test.expected) {
   681  			t.Errorf("got numAllocs %#v, want %#v", numAllocs, test.expected)
   682  		}
   683  	}
   684  }
   685  
   686  // TestNewAllocations ensures the correct allocation profile for Value.New
   687  func TestNewAllocations(t *testing.T) {
   688  	arrayConstructor := js.Global().Get("Array")
   689  
   690  	for _, test := range allocTests {
   691  		args := make([]any, test.argLen)
   692  
   693  		numAllocs := testing.AllocsPerRun(100, func() {
   694  			arrayConstructor.New(args...)
   695  		})
   696  
   697  		if numAllocs != float64(test.expected) {
   698  			t.Errorf("got numAllocs %#v, want %#v", numAllocs, test.expected)
   699  		}
   700  	}
   701  }
   702  
   703  // BenchmarkDOM is a simple benchmark which emulates a webapp making DOM operations.
   704  // It creates a div, and sets its id. Then searches by that id and sets some data.
   705  // Finally it removes that div.
   706  func BenchmarkDOM(b *testing.B) {
   707  	document := js.Global().Get("document")
   708  	if document.IsUndefined() {
   709  		b.Skip("Not a browser environment. Skipping.")
   710  	}
   711  	const data = "someString"
   712  	for i := 0; i < b.N; i++ {
   713  		div := document.Call("createElement", "div")
   714  		div.Call("setAttribute", "id", "myDiv")
   715  		document.Get("body").Call("appendChild", div)
   716  		myDiv := document.Call("getElementById", "myDiv")
   717  		myDiv.Set("innerHTML", data)
   718  
   719  		if got, want := myDiv.Get("innerHTML").String(), data; got != want {
   720  			b.Errorf("got %s, want %s", got, want)
   721  		}
   722  		document.Get("body").Call("removeChild", div)
   723  	}
   724  }
   725  
   726  func TestGlobal(t *testing.T) {
   727  	ident := js.FuncOf(func(this js.Value, args []js.Value) any {
   728  		return args[0]
   729  	})
   730  	defer ident.Release()
   731  
   732  	if got := ident.Invoke(js.Global()); !got.Equal(js.Global()) {
   733  		t.Errorf("got %#v, want %#v", got, js.Global())
   734  	}
   735  }
   736  

View as plain text