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 }