Black Lives Matter. Support the Equal Justice Initiative.

Source file src/syscall/js/js_test.go

Documentation: syscall/js

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

View as plain text