github.com/guyezi/gofrontend@v0.0.0-20200228202240-7a62a49e62c0/libgo/go/syscall/js/js_test.go (about) 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 }