github.com/gopherjs/gopherjs@v1.19.0-beta1.0.20240506212314-27071a8796e4/tests/js_test.go (about)

     1  //go:build js && !wasm
     2  // +build js,!wasm
     3  
     4  package tests_test
     5  
     6  import (
     7  	"fmt"
     8  	"reflect"
     9  	"strings"
    10  	"testing"
    11  	"time"
    12  
    13  	"github.com/google/go-cmp/cmp"
    14  	"github.com/gopherjs/gopherjs/js"
    15  )
    16  
    17  var dummys = js.Global.Call("eval", `({
    18  	someBool: true,
    19  	someString: "abc\u1234",
    20  	someInt: 42,
    21  	someFloat: 42.123,
    22  	someArray: [41, 42, 43],
    23  	add: function(a, b) {
    24  		return a + b;
    25  	},
    26  	mapArray: function(array, f) {
    27  		var newArray = new Array(array.length);
    28  		for (var i = 0; i < array.length; i++) {
    29  			newArray[i] = f(array[i]);
    30  		}
    31  		return newArray;
    32  	},
    33  	toUnixTimestamp: function(d) {
    34  		return d.getTime() / 1000;
    35  	},
    36  	testField: function(o) {
    37  		return o.Field;
    38  	},
    39  	testMethod: function(o) {
    40  		return o.Method(42);
    41  	},
    42  	isEqual: function(a, b) {
    43  		return a === b;
    44  	},
    45  	call: function(f, a) {
    46  		f(a);
    47  	},
    48  	return: function(x) {
    49  		return x;
    50  	},
    51  })`)
    52  
    53  func TestBool(t *testing.T) {
    54  	e := true
    55  	o := dummys.Get("someBool")
    56  	if v := o.Bool(); v != e {
    57  		t.Errorf("expected %#v, got %#v", e, v)
    58  	}
    59  	if i := o.Interface().(bool); i != e {
    60  		t.Errorf("expected %#v, got %#v", e, i)
    61  	}
    62  	if dummys.Set("otherBool", e); dummys.Get("otherBool").Bool() != e {
    63  		t.Fail()
    64  	}
    65  }
    66  
    67  func TestStr(t *testing.T) {
    68  	e := "abc\u1234"
    69  	o := dummys.Get("someString")
    70  	if v := o.String(); v != e {
    71  		t.Errorf("expected %#v, got %#v", e, v)
    72  	}
    73  	if i := o.Interface().(string); i != e {
    74  		t.Errorf("expected %#v, got %#v", e, i)
    75  	}
    76  	if dummys.Set("otherString", e); dummys.Get("otherString").String() != e {
    77  		t.Fail()
    78  	}
    79  }
    80  
    81  func TestInt(t *testing.T) {
    82  	e := 42
    83  	o := dummys.Get("someInt")
    84  	if v := o.Int(); v != e {
    85  		t.Errorf("expected %#v, got %#v", e, v)
    86  	}
    87  	if i := int(o.Interface().(float64)); i != e {
    88  		t.Errorf("expected %#v, got %#v", e, i)
    89  	}
    90  	if dummys.Set("otherInt", e); dummys.Get("otherInt").Int() != e {
    91  		t.Fail()
    92  	}
    93  }
    94  
    95  func TestFloat(t *testing.T) {
    96  	e := 42.123
    97  	o := dummys.Get("someFloat")
    98  	if v := o.Float(); v != e {
    99  		t.Errorf("expected %#v, got %#v", e, v)
   100  	}
   101  	if i := o.Interface().(float64); i != e {
   102  		t.Errorf("expected %#v, got %#v", e, i)
   103  	}
   104  	if dummys.Set("otherFloat", e); dummys.Get("otherFloat").Float() != e {
   105  		t.Fail()
   106  	}
   107  }
   108  
   109  func TestUndefined(t *testing.T) {
   110  	if dummys == js.Undefined || dummys.Get("xyz") != js.Undefined {
   111  		t.Fail()
   112  	}
   113  }
   114  
   115  func TestNull(t *testing.T) {
   116  	var null *js.Object
   117  	dummys.Set("test", nil)
   118  	if null != nil || dummys == nil || dummys.Get("test") != nil {
   119  		t.Fail()
   120  	}
   121  }
   122  
   123  func TestLength(t *testing.T) {
   124  	if dummys.Get("someArray").Length() != 3 {
   125  		t.Fail()
   126  	}
   127  }
   128  
   129  func TestIndex(t *testing.T) {
   130  	if dummys.Get("someArray").Index(1).Int() != 42 {
   131  		t.Fail()
   132  	}
   133  }
   134  
   135  func TestSetIndex(t *testing.T) {
   136  	dummys.Get("someArray").SetIndex(2, 99)
   137  	if dummys.Get("someArray").Index(2).Int() != 99 {
   138  		t.Fail()
   139  	}
   140  }
   141  
   142  func TestCall(t *testing.T) {
   143  	var i int64 = 40
   144  	if dummys.Call("add", i, 2).Int() != 42 {
   145  		t.Fail()
   146  	}
   147  	if dummys.Call("add", js.Global.Call("eval", "40"), 2).Int() != 42 {
   148  		t.Fail()
   149  	}
   150  }
   151  
   152  func TestInvoke(t *testing.T) {
   153  	var i int64 = 40
   154  	if dummys.Get("add").Invoke(i, 2).Int() != 42 {
   155  		t.Fail()
   156  	}
   157  }
   158  
   159  func TestNew(t *testing.T) {
   160  	if js.Global.Get("Array").New(42).Length() != 42 {
   161  		t.Fail()
   162  	}
   163  }
   164  
   165  type StructWithJsField1 struct {
   166  	*js.Object
   167  	Length int                  `js:"length"`
   168  	Slice  func(int, int) []int `js:"slice"`
   169  }
   170  
   171  type StructWithJsField2 struct {
   172  	object *js.Object           // to hide members from public API
   173  	Length int                  `js:"length"`
   174  	Slice  func(int, int) []int `js:"slice"`
   175  }
   176  
   177  type Wrapper1 struct {
   178  	StructWithJsField1
   179  	WrapperLength int `js:"length"`
   180  }
   181  
   182  type Wrapper2 struct {
   183  	innerStruct   *StructWithJsField2
   184  	WrapperLength int `js:"length"`
   185  }
   186  
   187  func TestReadingJsField(t *testing.T) {
   188  	a := StructWithJsField1{Object: js.Global.Get("Array").New(42)}
   189  	b := &StructWithJsField2{object: js.Global.Get("Array").New(42)}
   190  	wa := Wrapper1{StructWithJsField1: a}
   191  	wb := Wrapper2{innerStruct: b}
   192  	if a.Length != 42 || b.Length != 42 || wa.Length != 42 || wa.WrapperLength != 42 || wb.WrapperLength != 42 {
   193  		t.Fail()
   194  	}
   195  }
   196  
   197  func TestWritingJsField(t *testing.T) {
   198  	a := StructWithJsField1{Object: js.Global.Get("Object").New()}
   199  	b := &StructWithJsField2{object: js.Global.Get("Object").New()}
   200  	a.Length = 42
   201  	b.Length = 42
   202  	if a.Get("length").Int() != 42 || b.object.Get("length").Int() != 42 {
   203  		t.Fail()
   204  	}
   205  }
   206  
   207  func TestCallingJsField(t *testing.T) {
   208  	a := &StructWithJsField1{Object: js.Global.Get("Array").New(100)}
   209  	b := &StructWithJsField2{object: js.Global.Get("Array").New(100)}
   210  	a.SetIndex(3, 123)
   211  	b.object.SetIndex(3, 123)
   212  	f := a.Slice
   213  	a2 := a.Slice(2, 44)
   214  	b2 := b.Slice(2, 44)
   215  	c2 := f(2, 44)
   216  	if len(a2) != 42 || len(b2) != 42 || len(c2) != 42 || a2[1] != 123 || b2[1] != 123 || c2[1] != 123 {
   217  		t.Fail()
   218  	}
   219  }
   220  
   221  func TestReflectionOnJsField(t *testing.T) {
   222  	a := StructWithJsField1{Object: js.Global.Get("Array").New(42)}
   223  	wa := Wrapper1{StructWithJsField1: a}
   224  	if reflect.ValueOf(a).FieldByName("Length").Int() != 42 || reflect.ValueOf(&wa).Elem().FieldByName("WrapperLength").Int() != 42 {
   225  		t.Fail()
   226  	}
   227  	reflect.ValueOf(&wa).Elem().FieldByName("WrapperLength").Set(reflect.ValueOf(10))
   228  	if a.Length != 10 {
   229  		t.Fail()
   230  	}
   231  }
   232  
   233  func TestUnboxing(t *testing.T) {
   234  	a := StructWithJsField1{Object: js.Global.Get("Object").New()}
   235  	b := &StructWithJsField2{object: js.Global.Get("Object").New()}
   236  	if !dummys.Call("isEqual", a, a.Object).Bool() || !dummys.Call("isEqual", b, b.object).Bool() {
   237  		t.Fail()
   238  	}
   239  	wa := Wrapper1{StructWithJsField1: a}
   240  	wb := Wrapper2{innerStruct: b}
   241  	if !dummys.Call("isEqual", wa, a.Object).Bool() || !dummys.Call("isEqual", wb, b.object).Bool() {
   242  		t.Fail()
   243  	}
   244  }
   245  
   246  func TestBoxing(t *testing.T) {
   247  	o := js.Global.Get("Object").New()
   248  	dummys.Call("call", func(a StructWithJsField1) {
   249  		if a.Object != o {
   250  			t.Fail()
   251  		}
   252  	}, o)
   253  	dummys.Call("call", func(a *StructWithJsField2) {
   254  		if a.object != o {
   255  			t.Fail()
   256  		}
   257  	}, o)
   258  	dummys.Call("call", func(a Wrapper1) {
   259  		if a.Object != o {
   260  			t.Fail()
   261  		}
   262  	}, o)
   263  	dummys.Call("call", func(a Wrapper2) {
   264  		if a.innerStruct.object != o {
   265  			t.Fail()
   266  		}
   267  	}, o)
   268  }
   269  
   270  func TestFunc(t *testing.T) {
   271  	a := dummys.Call("mapArray", []int{1, 2, 3}, func(e int64) int64 { return e + 40 })
   272  	b := dummys.Call("mapArray", []int{1, 2, 3}, func(e ...int64) int64 { return e[0] + 40 })
   273  	if a.Index(1).Int() != 42 || b.Index(1).Int() != 42 {
   274  		t.Fail()
   275  	}
   276  
   277  	add := dummys.Get("add").Interface().(func(...interface{}) *js.Object)
   278  	var i int64 = 40
   279  	if add(i, 2).Int() != 42 {
   280  		t.Fail()
   281  	}
   282  }
   283  
   284  func TestDate(t *testing.T) {
   285  	d := time.Date(2013, time.August, 27, 22, 25, 11, 0, time.UTC)
   286  	if dummys.Call("toUnixTimestamp", d).Int() != int(d.Unix()) {
   287  		t.Fail()
   288  	}
   289  
   290  	d2 := js.Global.Get("Date").New(d.UnixNano() / 1000000).Interface().(time.Time)
   291  	if !d2.Equal(d) {
   292  		t.Fail()
   293  	}
   294  }
   295  
   296  // https://github.com/gopherjs/gopherjs/issues/287
   297  func TestInternalizeDate(t *testing.T) {
   298  	a := time.Unix(0, (123 * time.Millisecond).Nanoseconds())
   299  	var b time.Time
   300  	js.Global.Set("internalizeDate", func(t time.Time) { b = t })
   301  	js.Global.Call("eval", "(internalizeDate(new Date(123)))")
   302  	if a != b {
   303  		t.Fail()
   304  	}
   305  }
   306  
   307  func TestInternalizeStruct(t *testing.T) {
   308  	type Person struct {
   309  		Name string
   310  		Age  int
   311  	}
   312  	var a, expected Person
   313  	expected = Person{Name: "foo", Age: 952}
   314  
   315  	js.Global.Set("return_person", func(p *Person) *Person {
   316  		if p == nil {
   317  			t.Fail()
   318  			return nil
   319  		}
   320  		a = *p
   321  		return p
   322  	})
   323  
   324  	js.Global.Call("eval", "return_person({Name: 'foo', Age: 952})")
   325  	if diff := cmp.Diff(a, expected); diff != "" {
   326  		t.Errorf("Mismatch (-want +got):\n%s", diff)
   327  	}
   328  }
   329  
   330  func TestInternalizeStructUnexportedFields(t *testing.T) {
   331  	type Person struct {
   332  		Name string
   333  		age  int
   334  	}
   335  	var a, expected Person
   336  	expected = Person{Name: "foo", age: 0}
   337  	js.Global.Set("return_person", func(p *Person) *Person {
   338  		a = *p
   339  		return p
   340  	})
   341  
   342  	js.Global.Call("eval", "return_person({Name: 'foo', age: 952})")
   343  
   344  	// Manually check unexported fields
   345  	if a.age != expected.age {
   346  		t.Errorf("Mismatch in age: got %v, want %v", a.age, expected.age)
   347  	}
   348  
   349  	// Check exported fields using cmp.Diff
   350  	if diff := cmp.Diff(a.Name, expected.Name); diff != "" {
   351  		t.Errorf("Mismatch in Name (-want +got):\n%s", diff)
   352  	}
   353  }
   354  
   355  func TestInternalizeStructNested(t *testing.T) {
   356  	type FullName struct {
   357  		FirstName string
   358  		LastName  string
   359  	}
   360  	type Person struct {
   361  		Name string
   362  		Age  int
   363  		F    FullName
   364  	}
   365  	var a, expected Person
   366  	expected = Person{Name: "foo", Age: 952, F: FullName{FirstName: "John", LastName: "Doe"}}
   367  
   368  	js.Global.Set("return_person", func(p *Person) *Person {
   369  		a = *p
   370  		return p
   371  	})
   372  
   373  	js.Global.Call("eval", "return_person({Name: 'foo', Age: 952, F: {FirstName: 'John', LastName: 'Doe'}})")
   374  	if diff := cmp.Diff(a, expected); diff != "" {
   375  		t.Errorf("Mismatch (-want +got):\n%s", diff)
   376  	}
   377  }
   378  
   379  func TestInternalizeArrayOfStructs(t *testing.T) {
   380  	type Person struct {
   381  		Name string
   382  		Age  int
   383  	}
   384  	type ArrayOfStructs struct {
   385  		People []Person
   386  	}
   387  	var a, expected ArrayOfStructs
   388  	expected = ArrayOfStructs{People: []Person{{Name: "Alice", Age: 30}, {Name: "Bob", Age: 40}}}
   389  
   390  	js.Global.Set("return_people_array", func(p ArrayOfStructs) ArrayOfStructs {
   391  		a = p
   392  		return p
   393  	})
   394  
   395  	js.Global.Call("eval", `return_people_array({People: [{Name: "Alice", Age: 30}, {Name: "Bob", Age: 40}]})`)
   396  	if diff := cmp.Diff(a, expected); diff != "" {
   397  		t.Errorf("Mismatch (-want +got):\n%s", diff)
   398  	}
   399  }
   400  
   401  func TestEquality(t *testing.T) {
   402  	if js.Global.Get("Array") != js.Global.Get("Array") || js.Global.Get("Array") == js.Global.Get("String") {
   403  		t.Fail()
   404  	}
   405  	type S struct{ *js.Object }
   406  	o1 := js.Global.Get("Object").New()
   407  	o2 := js.Global.Get("Object").New()
   408  	a := S{o1}
   409  	b := S{o1}
   410  	c := S{o2}
   411  	if a != b || a == c {
   412  		t.Fail()
   413  	}
   414  }
   415  
   416  func TestUndefinedEquality(t *testing.T) {
   417  	var ui interface{} = js.Undefined
   418  	if ui != js.Undefined {
   419  		t.Fail()
   420  	}
   421  }
   422  
   423  func TestInterfaceEquality(t *testing.T) {
   424  	o := js.Global.Get("Object").New()
   425  	var i interface{} = o
   426  	if i != o {
   427  		t.Fail()
   428  	}
   429  }
   430  
   431  func TestUndefinedInternalization(t *testing.T) {
   432  	undefinedEqualsJsUndefined := func(i interface{}) bool {
   433  		return i == js.Undefined
   434  	}
   435  	js.Global.Set("undefinedEqualsJsUndefined", undefinedEqualsJsUndefined)
   436  	if !js.Global.Call("eval", "(undefinedEqualsJsUndefined(undefined))").Bool() {
   437  		t.Fail()
   438  	}
   439  }
   440  
   441  func TestSameFuncWrapper(t *testing.T) {
   442  	a := func(_ string) {} // string argument to force wrapping
   443  	b := func(_ string) {} // string argument to force wrapping
   444  	if !dummys.Call("isEqual", a, a).Bool() || dummys.Call("isEqual", a, b).Bool() {
   445  		t.Fail()
   446  	}
   447  	if !dummys.Call("isEqual", somePackageFunction, somePackageFunction).Bool() {
   448  		t.Fail()
   449  	}
   450  	if !dummys.Call("isEqual", (*T).someMethod, (*T).someMethod).Bool() {
   451  		t.Fail()
   452  	}
   453  	t1 := &T{}
   454  	t2 := &T{}
   455  	if !dummys.Call("isEqual", t1.someMethod, t1.someMethod).Bool() || dummys.Call("isEqual", t1.someMethod, t2.someMethod).Bool() {
   456  		t.Fail()
   457  	}
   458  }
   459  
   460  func somePackageFunction(_ string) {
   461  }
   462  
   463  type T struct{}
   464  
   465  func (t *T) someMethod() {
   466  	println(42)
   467  }
   468  
   469  func TestError(t *testing.T) {
   470  	defer func() {
   471  		err := recover()
   472  		if err == nil {
   473  			t.Fail()
   474  		}
   475  		if _, ok := err.(error); !ok {
   476  			t.Fail()
   477  		}
   478  		jsErr, ok := err.(*js.Error)
   479  		if !ok || !strings.Contains(jsErr.Error(), "throwsError") {
   480  			t.Fail()
   481  		}
   482  	}()
   483  	js.Global.Get("notExisting").Call("throwsError")
   484  }
   485  
   486  type F struct {
   487  	Field int
   488  }
   489  
   490  func (f F) NonPoint() int {
   491  	return 10
   492  }
   493  
   494  func (f *F) Point() int {
   495  	return 20
   496  }
   497  
   498  func TestExternalizeField(t *testing.T) {
   499  	if dummys.Call("testField", map[string]int{"Field": 42}).Int() != 42 {
   500  		t.Fail()
   501  	}
   502  	if dummys.Call("testField", F{42}).Int() != 42 {
   503  		t.Fail()
   504  	}
   505  }
   506  
   507  func TestMakeFunc(t *testing.T) {
   508  	o := js.Global.Get("Object").New()
   509  	for i := 3; i < 5; i++ {
   510  		x := i
   511  		if i == 4 {
   512  			break
   513  		}
   514  		o.Set("f", js.MakeFunc(func(this *js.Object, arguments []*js.Object) interface{} {
   515  			if this != o {
   516  				t.Fail()
   517  			}
   518  			if len(arguments) != 2 || arguments[0].Int() != 1 || arguments[1].Int() != 2 {
   519  				t.Fail()
   520  			}
   521  			return x
   522  		}))
   523  	}
   524  	if o.Call("f", 1, 2).Int() != 3 {
   525  		t.Fail()
   526  	}
   527  }
   528  
   529  type M struct {
   530  	Struct  F
   531  	Pointer *F
   532  	Array   [1]F
   533  	Slice   []*F
   534  	Name    string
   535  	f       int
   536  }
   537  
   538  func (m *M) Method(a interface{}) map[string]string {
   539  	if a.(map[string]interface{})["x"].(float64) != 1 || m.f != 42 {
   540  		return nil
   541  	}
   542  	return map[string]string{
   543  		"y": "z",
   544  	}
   545  }
   546  
   547  func (m *M) GetF() F {
   548  	return m.Struct
   549  }
   550  
   551  func (m *M) GetFPointer() *F {
   552  	return m.Pointer
   553  }
   554  
   555  func (m *M) ParamMethod(v *M) string {
   556  	return v.Name
   557  }
   558  
   559  func (m *M) Field() string {
   560  	return "rubbish"
   561  }
   562  
   563  func (m M) NonPointField() string {
   564  	return "sensible"
   565  }
   566  
   567  func TestMakeWrapper(t *testing.T) {
   568  	m := &M{f: 42}
   569  	if !js.Global.Call("eval", `(function(m) { return m.Method({x: 1})["y"] === "z"; })`).Invoke(js.MakeWrapper(m)).Bool() {
   570  		t.Fail()
   571  	}
   572  
   573  	if js.MakeWrapper(m).Interface() != m {
   574  		t.Fail()
   575  	}
   576  }
   577  
   578  func TestMakeFullWrapperType(t *testing.T) {
   579  	m := &M{f: 42}
   580  	f := func(m *M) {
   581  		if m.f != 42 {
   582  			t.Fail()
   583  		}
   584  	}
   585  
   586  	js.Global.Call("eval", `(function(f, m) { f(m); })`).Invoke(f, js.MakeFullWrapper(m))
   587  	want := "github.com/gopherjs/gopherjs/tests_test.*M"
   588  	if got := js.MakeFullWrapper(m).Get("$type").String(); got != want {
   589  		t.Errorf("wanted type string %q; got %q", want, got)
   590  	}
   591  }
   592  
   593  func TestMakeFullWrapperGettersAndSetters(t *testing.T) {
   594  	f := &F{Field: 50}
   595  	m := &M{
   596  		Name:    "Gopher",
   597  		Struct:  F{Field: 42},
   598  		Pointer: f,
   599  		Array:   [1]F{{Field: 42}},
   600  		Slice:   []*F{f},
   601  	}
   602  
   603  	const globalVar = "TestMakeFullWrapper_w1"
   604  
   605  	eval := func(s string, v ...interface{}) *js.Object {
   606  		return js.Global.Call("eval", s).Invoke(v...)
   607  	}
   608  	call := func(s string, v ...interface{}) *js.Object {
   609  		return eval(fmt.Sprintf(`(function(g) { return g["%v"]%v; })`, globalVar, s), js.Global).Invoke(v...)
   610  	}
   611  	get := func(s string) *js.Object {
   612  		return eval(fmt.Sprintf(`(function(g) { return g["%v"]%v; })`, globalVar, s), js.Global)
   613  	}
   614  	set := func(s string, v interface{}) {
   615  		eval(fmt.Sprintf(`(function(g, v) { g["%v"]%v = v; })`, globalVar, s), js.Global, v)
   616  	}
   617  
   618  	w1 := js.MakeFullWrapper(m)
   619  	{
   620  		w2 := js.MakeFullWrapper(m)
   621  
   622  		// we expect that MakeFullWrapper produces a different value each time
   623  		if eval(`(function(o, p) { return o === p; })`, w1, w2).Bool() {
   624  			t.Fatalf("w1 equalled w2 when we didn't expect it to")
   625  		}
   626  	}
   627  
   628  	set("", w1)
   629  
   630  	{
   631  		prop := ".Name"
   632  		want := m.Name
   633  		if got := get(prop).String(); got != want {
   634  			t.Fatalf("wanted w1%v to be %v; got %v", prop, want, got)
   635  		}
   636  		newVal := "JS"
   637  		set(prop, newVal)
   638  		if got := m.Name; got != newVal {
   639  			t.Fatalf("wanted m%v to be %v; got %v", prop, newVal, got)
   640  		}
   641  	}
   642  	{
   643  		prop := ".Struct.Field"
   644  		want := m.Struct.Field
   645  		if got := get(prop).Int(); got != want {
   646  			t.Fatalf("wanted w1%v to be %v; got %v", prop, want, got)
   647  		}
   648  		newVal := 40
   649  		set(prop, newVal)
   650  		if got := m.Struct.Field; got == newVal {
   651  			t.Fatalf("wanted m%v not to be %v; but was", prop, newVal)
   652  		}
   653  	}
   654  	{
   655  		prop := ".Pointer.Field"
   656  		want := m.Pointer.Field
   657  		if got := get(prop).Int(); got != want {
   658  			t.Fatalf("wanted w1%v to be %v; got %v", prop, want, got)
   659  		}
   660  		newVal := 40
   661  		set(prop, newVal)
   662  		if got := m.Pointer.Field; got != newVal {
   663  			t.Fatalf("wanted m%v to be %v; got %v", prop, newVal, got)
   664  		}
   665  	}
   666  	{
   667  		prop := ".Array[0].Field"
   668  		want := m.Array[0].Field
   669  		if got := get(prop).Int(); got != want {
   670  			t.Fatalf("wanted w1%v to be %v; got %v", prop, want, got)
   671  		}
   672  		newVal := 40
   673  		set(prop, newVal)
   674  		if got := m.Array[0].Field; got == newVal {
   675  			t.Fatalf("wanted m%v not to be %v; but was", prop, newVal)
   676  		}
   677  	}
   678  	{
   679  		prop := ".Slice[0].Field"
   680  		want := m.Slice[0].Field
   681  		if got := get(prop).Int(); got != want {
   682  			t.Fatalf("wanted w1%v to be %v; got %v", prop, want, got)
   683  		}
   684  		newVal := 40
   685  		set(prop, newVal)
   686  		if got := m.Slice[0].Field; got != newVal {
   687  			t.Fatalf("wanted m%v to be %v; got %v", prop, newVal, got)
   688  		}
   689  	}
   690  	{
   691  		prop := ".GetF().Field"
   692  		want := m.Struct.Field
   693  		if got := get(prop).Int(); got != want {
   694  			t.Fatalf("wanted w1%v to be %v; got %v", prop, want, got)
   695  		}
   696  		newVal := 105
   697  		set(prop, newVal)
   698  		if got := m.Struct.Field; got == newVal {
   699  			t.Fatalf("wanted m%v not to be %v; but was", prop, newVal)
   700  		}
   701  	}
   702  	{
   703  		method := ".ParamMethod"
   704  		want := method
   705  		m.Name = want
   706  		if got := call(method, get("")).String(); got != want {
   707  			t.Fatalf("wanted w1%v() to be %v; got %v", method, want, got)
   708  		}
   709  	}
   710  }
   711  
   712  func TestCallWithNull(t *testing.T) {
   713  	c := make(chan int, 1)
   714  	js.Global.Set("test", func() {
   715  		c <- 42
   716  	})
   717  	js.Global.Get("test").Call("call", nil)
   718  	if <-c != 42 {
   719  		t.Fail()
   720  	}
   721  }
   722  
   723  func TestReflection(t *testing.T) {
   724  	o := js.Global.Call("eval", "({ answer: 42 })")
   725  	if reflect.ValueOf(o).Interface().(*js.Object) != o {
   726  		t.Fail()
   727  	}
   728  
   729  	type S struct {
   730  		Field *js.Object
   731  	}
   732  	s := S{o}
   733  
   734  	v := reflect.ValueOf(&s).Elem()
   735  	if v.Field(0).Interface().(*js.Object).Get("answer").Int() != 42 {
   736  		t.Fail()
   737  	}
   738  	if v.Field(0).MethodByName("Get").Call([]reflect.Value{reflect.ValueOf("answer")})[0].Interface().(*js.Object).Int() != 42 {
   739  		t.Fail()
   740  	}
   741  	v.Field(0).Set(reflect.ValueOf(js.Global.Call("eval", "({ answer: 100 })")))
   742  	if s.Field.Get("answer").Int() != 100 {
   743  		t.Fail()
   744  	}
   745  
   746  	if fmt.Sprintf("%+v", s) != "{Field:[object Object]}" {
   747  		t.Fail()
   748  	}
   749  }
   750  
   751  func TestNil(t *testing.T) {
   752  	type S struct{ X int }
   753  	var s *S
   754  	if !dummys.Call("isEqual", s, nil).Bool() {
   755  		t.Fail()
   756  	}
   757  
   758  	type T struct{ Field *S }
   759  	if dummys.Call("testField", T{}) != nil {
   760  		t.Fail()
   761  	}
   762  }
   763  
   764  func TestNewArrayBuffer(t *testing.T) {
   765  	b := []byte("abcd")
   766  	a := js.NewArrayBuffer(b[1:3])
   767  	if a.Get("byteLength").Int() != 2 {
   768  		t.Fail()
   769  	}
   770  }
   771  
   772  func TestExternalize(t *testing.T) {
   773  	fn := js.Global.Call("eval", "(function(x) { return JSON.stringify(x); })")
   774  
   775  	tests := []struct {
   776  		name  string
   777  		input interface{}
   778  		want  string
   779  	}{
   780  		{
   781  			name:  "bool",
   782  			input: true,
   783  			want:  "true",
   784  		},
   785  		{
   786  			name:  "nil map",
   787  			input: func() map[string]string { return nil }(),
   788  			want:  "null",
   789  		},
   790  		{
   791  			name:  "empty map",
   792  			input: map[string]string{},
   793  			want:  "{}",
   794  		},
   795  		{
   796  			name:  "nil slice",
   797  			input: func() []string { return nil }(),
   798  			want:  "null",
   799  		},
   800  		{
   801  			name:  "empty slice",
   802  			input: []string{},
   803  			want:  "[]",
   804  		},
   805  		{
   806  			name:  "empty struct",
   807  			input: struct{}{},
   808  			want:  "{}",
   809  		},
   810  		{
   811  			name:  "nil pointer",
   812  			input: func() *int { return nil }(),
   813  			want:  "null",
   814  		},
   815  		{
   816  			name:  "nil func",
   817  			input: func() func() { return nil }(),
   818  			want:  "null",
   819  		},
   820  	}
   821  
   822  	for _, tt := range tests {
   823  		t.Run(tt.name, func(t *testing.T) {
   824  			result := fn.Invoke(tt.input).String()
   825  			if result != tt.want {
   826  				t.Errorf("Unexpected result %q != %q", result, tt.want)
   827  			}
   828  		})
   829  	}
   830  }
   831  
   832  func TestInternalizeSlice(t *testing.T) {
   833  	tests := []struct {
   834  		name string
   835  		init []int
   836  		want string
   837  	}{
   838  		{
   839  			name: `nil slice`,
   840  			init: []int(nil),
   841  			want: `[]int(nil)`,
   842  		},
   843  		{
   844  			name: `empty slice`,
   845  			init: []int{},
   846  			want: `[]int{}`,
   847  		},
   848  		{
   849  			name: `non-empty slice`,
   850  			init: []int{42, 53, 64},
   851  			want: `[]int{42, 53, 64}`,
   852  		},
   853  	}
   854  
   855  	for _, tt := range tests {
   856  		t.Run(tt.name, func(t *testing.T) {
   857  			b := struct {
   858  				*js.Object
   859  				V []int `js:"V"` // V is externalized
   860  			}{Object: js.Global.Get("Object").New()}
   861  			b.V = tt.init
   862  
   863  			result := fmt.Sprintf(`%#v`, b.V) // internalize b.V
   864  			if result != tt.want {
   865  				t.Errorf(`Unexpected result %q != %q`, result, tt.want)
   866  			}
   867  		})
   868  	}
   869  }
   870  
   871  func TestInternalizeExternalizeNull(t *testing.T) {
   872  	type S struct {
   873  		*js.Object
   874  	}
   875  	r := js.Global.Call("eval", "(function(f) { return f(null); })").Invoke(func(s S) S {
   876  		if s.Object != nil {
   877  			t.Fail()
   878  		}
   879  		return s
   880  	})
   881  	if r != nil {
   882  		t.Fail()
   883  	}
   884  }
   885  
   886  func TestInternalizeExternalizeUndefined(t *testing.T) {
   887  	type S struct {
   888  		*js.Object
   889  	}
   890  	r := js.Global.Call("eval", "(function(f) { return f(undefined); })").Invoke(func(s S) S {
   891  		if s.Object != js.Undefined {
   892  			t.Fail()
   893  		}
   894  		return s
   895  	})
   896  	if r != js.Undefined {
   897  		t.Fail()
   898  	}
   899  }
   900  
   901  func TestDereference(t *testing.T) {
   902  	s := *dummys
   903  	p := &s
   904  	if p != dummys {
   905  		t.Fail()
   906  	}
   907  }
   908  
   909  func TestSurrogatePairs(t *testing.T) {
   910  	js.Global.Set("str", "\U0001F600")
   911  	str := js.Global.Get("str")
   912  	if str.Get("length").Int() != 2 || str.Call("charCodeAt", 0).Int() != 55357 || str.Call("charCodeAt", 1).Int() != 56832 {
   913  		t.Fail()
   914  	}
   915  	if str.String() != "\U0001F600" {
   916  		t.Fail()
   917  	}
   918  }
   919  
   920  func TestUint8Array(t *testing.T) {
   921  	uint8Array := js.Global.Get("Uint8Array")
   922  	if dummys.Call("return", []byte{}).Get("constructor") != uint8Array {
   923  		t.Errorf("Empty byte array is not externalized as a Uint8Array")
   924  	}
   925  	if dummys.Call("return", []byte{0x01}).Get("constructor") != uint8Array {
   926  		t.Errorf("Non-empty byte array is not externalized as a Uint8Array")
   927  	}
   928  }
   929  
   930  func TestTypeSwitchJSObject(t *testing.T) {
   931  	obj := js.Global.Get("Object").New()
   932  	obj.Set("foo", "bar")
   933  
   934  	want := "bar"
   935  
   936  	if got := obj.Get("foo").String(); got != want {
   937  		t.Errorf("Direct access to *js.Object field gave %q, want %q", got, want)
   938  	}
   939  
   940  	var x interface{} = obj
   941  
   942  	switch x := x.(type) {
   943  	case *js.Object:
   944  		if got := x.Get("foo").String(); got != want {
   945  			t.Errorf("Value passed through interface and type switch gave %q, want %q", got, want)
   946  		}
   947  	}
   948  
   949  	if y, ok := x.(*js.Object); ok {
   950  		if got := y.Get("foo").String(); got != want {
   951  			t.Errorf("Value passed through interface and type assert gave %q, want %q", got, want)
   952  		}
   953  	}
   954  }
   955  
   956  func TestStructWithNonIdentifierJSTag(t *testing.T) {
   957  	type S struct {
   958  		*js.Object
   959  		Name string `js:"@&\"'<>//my name"`
   960  	}
   961  	s := S{Object: js.Global.Get("Object").New()}
   962  
   963  	// externalise a value via field
   964  	s.Name = "Paul"
   965  
   966  	// internalise via field
   967  	got := s.Name
   968  	if want := "Paul"; got != want {
   969  		t.Errorf("value via field with non-identifier js tag gave %q, want %q", got, want)
   970  	}
   971  
   972  	// verify we can do a Get with the struct tag
   973  	got = s.Get("@&\"'<>//my name").String()
   974  	if want := "Paul"; got != want {
   975  		t.Errorf("value via js.Object.Get gave %q, want %q", got, want)
   976  	}
   977  }