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/misc/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  func TestBool(t *testing.T) {
    60  	want := true
    61  	o := dummys.Get("someBool")
    62  	if got := o.Bool(); got != want {
    63  		t.Errorf("got %#v, want %#v", got, want)
    64  	}
    65  	dummys.Set("otherBool", want)
    66  	if got := dummys.Get("otherBool").Bool(); got != want {
    67  		t.Errorf("got %#v, want %#v", got, want)
    68  	}
    69  	if !dummys.Get("someBool").Equal(dummys.Get("someBool")) {
    70  		t.Errorf("same value not equal")
    71  	}
    72  }
    73  
    74  func TestString(t *testing.T) {
    75  	want := "abc\u1234"
    76  	o := dummys.Get("someString")
    77  	if got := o.String(); got != want {
    78  		t.Errorf("got %#v, want %#v", got, want)
    79  	}
    80  	dummys.Set("otherString", want)
    81  	if got := dummys.Get("otherString").String(); got != want {
    82  		t.Errorf("got %#v, want %#v", got, want)
    83  	}
    84  	if !dummys.Get("someString").Equal(dummys.Get("someString")) {
    85  		t.Errorf("same value not equal")
    86  	}
    87  
    88  	if got, want := js.Undefined().String(), "<undefined>"; got != want {
    89  		t.Errorf("got %#v, want %#v", got, want)
    90  	}
    91  	if got, want := js.Null().String(), "<null>"; got != want {
    92  		t.Errorf("got %#v, want %#v", got, want)
    93  	}
    94  	if got, want := js.ValueOf(true).String(), "<boolean: true>"; got != want {
    95  		t.Errorf("got %#v, want %#v", got, want)
    96  	}
    97  	if got, want := js.ValueOf(42.5).String(), "<number: 42.5>"; got != want {
    98  		t.Errorf("got %#v, want %#v", got, want)
    99  	}
   100  	if got, want := js.Global().Call("Symbol").String(), "<symbol>"; got != want {
   101  		t.Errorf("got %#v, want %#v", got, want)
   102  	}
   103  	if got, want := js.Global().String(), "<object>"; got != want {
   104  		t.Errorf("got %#v, want %#v", got, want)
   105  	}
   106  	if got, want := js.Global().Get("setTimeout").String(), "<function>"; got != want {
   107  		t.Errorf("got %#v, want %#v", got, want)
   108  	}
   109  }
   110  
   111  func TestInt(t *testing.T) {
   112  	want := 42
   113  	o := dummys.Get("someInt")
   114  	if got := o.Int(); got != want {
   115  		t.Errorf("got %#v, want %#v", got, want)
   116  	}
   117  	dummys.Set("otherInt", want)
   118  	if got := dummys.Get("otherInt").Int(); got != want {
   119  		t.Errorf("got %#v, want %#v", got, want)
   120  	}
   121  	if !dummys.Get("someInt").Equal(dummys.Get("someInt")) {
   122  		t.Errorf("same value not equal")
   123  	}
   124  	if got := dummys.Get("zero").Int(); got != 0 {
   125  		t.Errorf("got %#v, want %#v", got, 0)
   126  	}
   127  }
   128  
   129  func TestIntConversion(t *testing.T) {
   130  	testIntConversion(t, 0)
   131  	testIntConversion(t, 1)
   132  	testIntConversion(t, -1)
   133  	testIntConversion(t, 1<<20)
   134  	testIntConversion(t, -1<<20)
   135  	testIntConversion(t, 1<<40)
   136  	testIntConversion(t, -1<<40)
   137  	testIntConversion(t, 1<<60)
   138  	testIntConversion(t, -1<<60)
   139  }
   140  
   141  func testIntConversion(t *testing.T, want int) {
   142  	if got := js.ValueOf(want).Int(); got != want {
   143  		t.Errorf("got %#v, want %#v", got, want)
   144  	}
   145  }
   146  
   147  func TestFloat(t *testing.T) {
   148  	want := 42.123
   149  	o := dummys.Get("someFloat")
   150  	if got := o.Float(); got != want {
   151  		t.Errorf("got %#v, want %#v", got, want)
   152  	}
   153  	dummys.Set("otherFloat", want)
   154  	if got := dummys.Get("otherFloat").Float(); got != want {
   155  		t.Errorf("got %#v, want %#v", got, want)
   156  	}
   157  	if !dummys.Get("someFloat").Equal(dummys.Get("someFloat")) {
   158  		t.Errorf("same value not equal")
   159  	}
   160  }
   161  
   162  func TestObject(t *testing.T) {
   163  	if !dummys.Get("someArray").Equal(dummys.Get("someArray")) {
   164  		t.Errorf("same value not equal")
   165  	}
   166  
   167  	// An object and its prototype should not be equal.
   168  	proto := js.Global().Get("Object").Get("prototype")
   169  	o := js.Global().Call("eval", "new Object()")
   170  	if proto.Equal(o) {
   171  		t.Errorf("object equals to its prototype")
   172  	}
   173  }
   174  
   175  func TestFrozenObject(t *testing.T) {
   176  	o := js.Global().Call("eval", "(function () { let o = new Object(); o.field = 5; Object.freeze(o); return o; })()")
   177  	want := 5
   178  	if got := o.Get("field").Int(); want != got {
   179  		t.Errorf("got %#v, want %#v", got, want)
   180  	}
   181  }
   182  
   183  func TestEqual(t *testing.T) {
   184  	if !dummys.Get("someFloat").Equal(dummys.Get("someFloat")) {
   185  		t.Errorf("same float is not equal")
   186  	}
   187  	if !dummys.Get("emptyObj").Equal(dummys.Get("emptyObj")) {
   188  		t.Errorf("same object is not equal")
   189  	}
   190  	if dummys.Get("someFloat").Equal(dummys.Get("someInt")) {
   191  		t.Errorf("different values are not unequal")
   192  	}
   193  }
   194  
   195  func TestNaN(t *testing.T) {
   196  	if !dummys.Get("NaN").IsNaN() {
   197  		t.Errorf("JS NaN is not NaN")
   198  	}
   199  	if !js.ValueOf(math.NaN()).IsNaN() {
   200  		t.Errorf("Go NaN is not NaN")
   201  	}
   202  	if dummys.Get("NaN").Equal(dummys.Get("NaN")) {
   203  		t.Errorf("NaN is equal to NaN")
   204  	}
   205  }
   206  
   207  func TestUndefined(t *testing.T) {
   208  	if !js.Undefined().IsUndefined() {
   209  		t.Errorf("undefined is not undefined")
   210  	}
   211  	if !js.Undefined().Equal(js.Undefined()) {
   212  		t.Errorf("undefined is not equal to undefined")
   213  	}
   214  	if dummys.IsUndefined() {
   215  		t.Errorf("object is undefined")
   216  	}
   217  	if js.Undefined().IsNull() {
   218  		t.Errorf("undefined is null")
   219  	}
   220  	if dummys.Set("test", js.Undefined()); !dummys.Get("test").IsUndefined() {
   221  		t.Errorf("could not set undefined")
   222  	}
   223  }
   224  
   225  func TestNull(t *testing.T) {
   226  	if !js.Null().IsNull() {
   227  		t.Errorf("null is not null")
   228  	}
   229  	if !js.Null().Equal(js.Null()) {
   230  		t.Errorf("null is not equal to null")
   231  	}
   232  	if dummys.IsNull() {
   233  		t.Errorf("object is null")
   234  	}
   235  	if js.Null().IsUndefined() {
   236  		t.Errorf("null is undefined")
   237  	}
   238  	if dummys.Set("test", js.Null()); !dummys.Get("test").IsNull() {
   239  		t.Errorf("could not set null")
   240  	}
   241  	if dummys.Set("test", nil); !dummys.Get("test").IsNull() {
   242  		t.Errorf("could not set nil")
   243  	}
   244  }
   245  
   246  func TestLength(t *testing.T) {
   247  	if got := dummys.Get("someArray").Length(); got != 3 {
   248  		t.Errorf("got %#v, want %#v", got, 3)
   249  	}
   250  }
   251  
   252  func TestGet(t *testing.T) {
   253  	// positive cases get tested per type
   254  
   255  	expectValueError(t, func() {
   256  		dummys.Get("zero").Get("badField")
   257  	})
   258  }
   259  
   260  func TestSet(t *testing.T) {
   261  	// positive cases get tested per type
   262  
   263  	expectValueError(t, func() {
   264  		dummys.Get("zero").Set("badField", 42)
   265  	})
   266  }
   267  
   268  func TestDelete(t *testing.T) {
   269  	dummys.Set("test", 42)
   270  	dummys.Delete("test")
   271  	if dummys.Call("hasOwnProperty", "test").Bool() {
   272  		t.Errorf("property still exists")
   273  	}
   274  
   275  	expectValueError(t, func() {
   276  		dummys.Get("zero").Delete("badField")
   277  	})
   278  }
   279  
   280  func TestIndex(t *testing.T) {
   281  	if got := dummys.Get("someArray").Index(1).Int(); got != 42 {
   282  		t.Errorf("got %#v, want %#v", got, 42)
   283  	}
   284  
   285  	expectValueError(t, func() {
   286  		dummys.Get("zero").Index(1)
   287  	})
   288  }
   289  
   290  func TestSetIndex(t *testing.T) {
   291  	dummys.Get("someArray").SetIndex(2, 99)
   292  	if got := dummys.Get("someArray").Index(2).Int(); got != 99 {
   293  		t.Errorf("got %#v, want %#v", got, 99)
   294  	}
   295  
   296  	expectValueError(t, func() {
   297  		dummys.Get("zero").SetIndex(2, 99)
   298  	})
   299  }
   300  
   301  func TestCall(t *testing.T) {
   302  	var i int64 = 40
   303  	if got := dummys.Call("add", i, 2).Int(); got != 42 {
   304  		t.Errorf("got %#v, want %#v", got, 42)
   305  	}
   306  	if got := dummys.Call("add", js.Global().Call("eval", "40"), 2).Int(); got != 42 {
   307  		t.Errorf("got %#v, want %#v", got, 42)
   308  	}
   309  
   310  	expectPanic(t, func() {
   311  		dummys.Call("zero")
   312  	})
   313  	expectValueError(t, func() {
   314  		dummys.Get("zero").Call("badMethod")
   315  	})
   316  }
   317  
   318  func TestInvoke(t *testing.T) {
   319  	var i int64 = 40
   320  	if got := dummys.Get("add").Invoke(i, 2).Int(); got != 42 {
   321  		t.Errorf("got %#v, want %#v", got, 42)
   322  	}
   323  
   324  	expectValueError(t, func() {
   325  		dummys.Get("zero").Invoke()
   326  	})
   327  }
   328  
   329  func TestNew(t *testing.T) {
   330  	if got := js.Global().Get("Array").New(42).Length(); got != 42 {
   331  		t.Errorf("got %#v, want %#v", got, 42)
   332  	}
   333  
   334  	expectValueError(t, func() {
   335  		dummys.Get("zero").New()
   336  	})
   337  }
   338  
   339  func TestInstanceOf(t *testing.T) {
   340  	someArray := js.Global().Get("Array").New()
   341  	if got, want := someArray.InstanceOf(js.Global().Get("Array")), true; got != want {
   342  		t.Errorf("got %#v, want %#v", got, want)
   343  	}
   344  	if got, want := someArray.InstanceOf(js.Global().Get("Function")), false; got != want {
   345  		t.Errorf("got %#v, want %#v", got, want)
   346  	}
   347  }
   348  
   349  func TestType(t *testing.T) {
   350  	if got, want := js.Undefined().Type(), js.TypeUndefined; got != want {
   351  		t.Errorf("got %s, want %s", got, want)
   352  	}
   353  	if got, want := js.Null().Type(), js.TypeNull; got != want {
   354  		t.Errorf("got %s, want %s", got, want)
   355  	}
   356  	if got, want := js.ValueOf(true).Type(), js.TypeBoolean; got != want {
   357  		t.Errorf("got %s, want %s", got, want)
   358  	}
   359  	if got, want := js.ValueOf(0).Type(), js.TypeNumber; got != want {
   360  		t.Errorf("got %s, want %s", got, want)
   361  	}
   362  	if got, want := js.ValueOf(42).Type(), js.TypeNumber; got != want {
   363  		t.Errorf("got %s, want %s", got, want)
   364  	}
   365  	if got, want := js.ValueOf("test").Type(), js.TypeString; got != want {
   366  		t.Errorf("got %s, want %s", got, want)
   367  	}
   368  	if got, want := js.Global().Get("Symbol").Invoke("test").Type(), js.TypeSymbol; got != want {
   369  		t.Errorf("got %s, want %s", got, want)
   370  	}
   371  	if got, want := js.Global().Get("Array").New().Type(), js.TypeObject; got != want {
   372  		t.Errorf("got %s, want %s", got, want)
   373  	}
   374  	if got, want := js.Global().Get("Array").Type(), js.TypeFunction; got != want {
   375  		t.Errorf("got %s, want %s", got, want)
   376  	}
   377  }
   378  
   379  type object = map[string]any
   380  type array = []any
   381  
   382  func TestValueOf(t *testing.T) {
   383  	a := js.ValueOf(array{0, array{0, 42, 0}, 0})
   384  	if got := a.Index(1).Index(1).Int(); got != 42 {
   385  		t.Errorf("got %v, want %v", got, 42)
   386  	}
   387  
   388  	o := js.ValueOf(object{"x": object{"y": 42}})
   389  	if got := o.Get("x").Get("y").Int(); got != 42 {
   390  		t.Errorf("got %v, want %v", got, 42)
   391  	}
   392  }
   393  
   394  func TestZeroValue(t *testing.T) {
   395  	var v js.Value
   396  	if !v.IsUndefined() {
   397  		t.Error("zero js.Value is not js.Undefined()")
   398  	}
   399  }
   400  
   401  func TestFuncOf(t *testing.T) {
   402  	c := make(chan struct{})
   403  	cb := js.FuncOf(func(this js.Value, args []js.Value) any {
   404  		if got := args[0].Int(); got != 42 {
   405  			t.Errorf("got %#v, want %#v", got, 42)
   406  		}
   407  		c <- struct{}{}
   408  		return nil
   409  	})
   410  	defer cb.Release()
   411  	js.Global().Call("setTimeout", cb, 0, 42)
   412  	<-c
   413  }
   414  
   415  func TestInvokeFunction(t *testing.T) {
   416  	called := false
   417  	cb := js.FuncOf(func(this js.Value, args []js.Value) any {
   418  		cb2 := js.FuncOf(func(this js.Value, args []js.Value) any {
   419  			called = true
   420  			return 42
   421  		})
   422  		defer cb2.Release()
   423  		return cb2.Invoke()
   424  	})
   425  	defer cb.Release()
   426  	if got := cb.Invoke().Int(); got != 42 {
   427  		t.Errorf("got %#v, want %#v", got, 42)
   428  	}
   429  	if !called {
   430  		t.Error("function not called")
   431  	}
   432  }
   433  
   434  func TestInterleavedFunctions(t *testing.T) {
   435  	c1 := make(chan struct{})
   436  	c2 := make(chan struct{})
   437  
   438  	js.Global().Get("setTimeout").Invoke(js.FuncOf(func(this js.Value, args []js.Value) any {
   439  		c1 <- struct{}{}
   440  		<-c2
   441  		return nil
   442  	}), 0)
   443  
   444  	<-c1
   445  	c2 <- struct{}{}
   446  	// this goroutine is running, but the callback of setTimeout did not return yet, invoke another function now
   447  	f := js.FuncOf(func(this js.Value, args []js.Value) any {
   448  		return nil
   449  	})
   450  	f.Invoke()
   451  }
   452  
   453  func ExampleFuncOf() {
   454  	var cb js.Func
   455  	cb = js.FuncOf(func(this js.Value, args []js.Value) any {
   456  		fmt.Println("button clicked")
   457  		cb.Release() // release the function if the button will not be clicked again
   458  		return nil
   459  	})
   460  	js.Global().Get("document").Call("getElementById", "myButton").Call("addEventListener", "click", cb)
   461  }
   462  
   463  // See
   464  // - https://developer.mozilla.org/en-US/docs/Glossary/Truthy
   465  // - https://stackoverflow.com/questions/19839952/all-falsey-values-in-javascript/19839953#19839953
   466  // - http://www.ecma-international.org/ecma-262/5.1/#sec-9.2
   467  func TestTruthy(t *testing.T) {
   468  	want := true
   469  	for _, key := range []string{
   470  		"someBool", "someString", "someInt", "someFloat", "someArray", "someDate",
   471  		"stringZero", // "0" is truthy
   472  		"add",        // functions are truthy
   473  		"emptyObj", "emptyArray", "Infinity", "NegInfinity",
   474  		// All objects are truthy, even if they're Number(0) or Boolean(false).
   475  		"objNumber0", "objBooleanFalse",
   476  	} {
   477  		if got := dummys.Get(key).Truthy(); got != want {
   478  			t.Errorf("%s: got %#v, want %#v", key, got, want)
   479  		}
   480  	}
   481  
   482  	want = false
   483  	if got := dummys.Get("zero").Truthy(); got != want {
   484  		t.Errorf("got %#v, want %#v", got, want)
   485  	}
   486  	if got := dummys.Get("NaN").Truthy(); got != want {
   487  		t.Errorf("got %#v, want %#v", got, want)
   488  	}
   489  	if got := js.ValueOf("").Truthy(); got != want {
   490  		t.Errorf("got %#v, want %#v", got, want)
   491  	}
   492  	if got := js.Null().Truthy(); got != want {
   493  		t.Errorf("got %#v, want %#v", got, want)
   494  	}
   495  	if got := js.Undefined().Truthy(); got != want {
   496  		t.Errorf("got %#v, want %#v", got, want)
   497  	}
   498  }
   499  
   500  func expectValueError(t *testing.T, fn func()) {
   501  	defer func() {
   502  		err := recover()
   503  		if _, ok := err.(*js.ValueError); !ok {
   504  			t.Errorf("expected *js.ValueError, got %T", err)
   505  		}
   506  	}()
   507  	fn()
   508  }
   509  
   510  func expectPanic(t *testing.T, fn func()) {
   511  	defer func() {
   512  		err := recover()
   513  		if err == nil {
   514  			t.Errorf("expected panic")
   515  		}
   516  	}()
   517  	fn()
   518  }
   519  
   520  var copyTests = []struct {
   521  	srcLen  int
   522  	dstLen  int
   523  	copyLen int
   524  }{
   525  	{5, 3, 3},
   526  	{3, 5, 3},
   527  	{0, 0, 0},
   528  }
   529  
   530  func TestCopyBytesToGo(t *testing.T) {
   531  	for _, tt := range copyTests {
   532  		t.Run(fmt.Sprintf("%d-to-%d", tt.srcLen, tt.dstLen), func(t *testing.T) {
   533  			src := js.Global().Get("Uint8Array").New(tt.srcLen)
   534  			if tt.srcLen >= 2 {
   535  				src.SetIndex(1, 42)
   536  			}
   537  			dst := make([]byte, tt.dstLen)
   538  
   539  			if got, want := js.CopyBytesToGo(dst, src), tt.copyLen; got != want {
   540  				t.Errorf("copied %d, want %d", got, want)
   541  			}
   542  			if tt.dstLen >= 2 {
   543  				if got, want := int(dst[1]), 42; got != want {
   544  					t.Errorf("got %d, want %d", got, want)
   545  				}
   546  			}
   547  		})
   548  	}
   549  }
   550  
   551  func TestCopyBytesToJS(t *testing.T) {
   552  	for _, tt := range copyTests {
   553  		t.Run(fmt.Sprintf("%d-to-%d", tt.srcLen, tt.dstLen), func(t *testing.T) {
   554  			src := make([]byte, tt.srcLen)
   555  			if tt.srcLen >= 2 {
   556  				src[1] = 42
   557  			}
   558  			dst := js.Global().Get("Uint8Array").New(tt.dstLen)
   559  
   560  			if got, want := js.CopyBytesToJS(dst, src), tt.copyLen; got != want {
   561  				t.Errorf("copied %d, want %d", got, want)
   562  			}
   563  			if tt.dstLen >= 2 {
   564  				if got, want := dst.Index(1).Int(), 42; got != want {
   565  					t.Errorf("got %d, want %d", got, want)
   566  				}
   567  			}
   568  		})
   569  	}
   570  }
   571  
   572  func TestGarbageCollection(t *testing.T) {
   573  	before := js.JSGo.Get("_values").Length()
   574  	for i := 0; i < 1000; i++ {
   575  		_ = js.Global().Get("Object").New().Call("toString").String()
   576  		runtime.GC()
   577  	}
   578  	after := js.JSGo.Get("_values").Length()
   579  	if after-before > 500 {
   580  		t.Errorf("garbage collection ineffective")
   581  	}
   582  }
   583  
   584  // BenchmarkDOM is a simple benchmark which emulates a webapp making DOM operations.
   585  // It creates a div, and sets its id. Then searches by that id and sets some data.
   586  // Finally it removes that div.
   587  func BenchmarkDOM(b *testing.B) {
   588  	document := js.Global().Get("document")
   589  	if document.IsUndefined() {
   590  		b.Skip("Not a browser environment. Skipping.")
   591  	}
   592  	const data = "someString"
   593  	for i := 0; i < b.N; i++ {
   594  		div := document.Call("createElement", "div")
   595  		div.Call("setAttribute", "id", "myDiv")
   596  		document.Get("body").Call("appendChild", div)
   597  		myDiv := document.Call("getElementById", "myDiv")
   598  		myDiv.Set("innerHTML", data)
   599  
   600  		if got, want := myDiv.Get("innerHTML").String(), data; got != want {
   601  			b.Errorf("got %s, want %s", got, want)
   602  		}
   603  		document.Get("body").Call("removeChild", div)
   604  	}
   605  }
   606  
   607  func TestGlobal(t *testing.T) {
   608  	ident := js.FuncOf(func(this js.Value, args []js.Value) any {
   609  		return args[0]
   610  	})
   611  	defer ident.Release()
   612  
   613  	if got := ident.Invoke(js.Global()); !got.Equal(js.Global()) {
   614  		t.Errorf("got %#v, want %#v", got, js.Global())
   615  	}
   616  }
   617  

View as plain text