Source file
src/syscall/js/js_test.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
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
60
61
62 func testCallExport(a int32, b int64) int64
63
64
65 func testExport(a int32, b int64) int64 {
66 testExportCalled = true
67
68 growStack(1000)
69
70 ch := make(chan int64)
71 go func() {
72 ch <- int64(a)
73 ch <- b
74 }()
75 return <-ch + <-ch
76 }
77
78
79 func testExport0() {
80 runtime.GC()
81 }
82
83 var testExportCalled bool
84
85 func growStack(n int64) {
86 if n > 0 {
87 growStack(n - 1)
88 }
89 }
90
91 func TestWasmExport(t *testing.T) {
92 testExportCalled = false
93 a := int32(123)
94 b := int64(456)
95 want := int64(a) + b
96 if got := testCallExport(a, b); got != want {
97 t.Errorf("got %v, want %v", got, want)
98 }
99 if !testExportCalled {
100 t.Error("testExport not called")
101 }
102 }
103
104 func TestBool(t *testing.T) {
105 want := true
106 o := dummys.Get("someBool")
107 if got := o.Bool(); got != want {
108 t.Errorf("got %#v, want %#v", got, want)
109 }
110 dummys.Set("otherBool", want)
111 if got := dummys.Get("otherBool").Bool(); got != want {
112 t.Errorf("got %#v, want %#v", got, want)
113 }
114 if !dummys.Get("someBool").Equal(dummys.Get("someBool")) {
115 t.Errorf("same value not equal")
116 }
117 }
118
119 func TestString(t *testing.T) {
120 want := "abc\u1234"
121 o := dummys.Get("someString")
122 if got := o.String(); got != want {
123 t.Errorf("got %#v, want %#v", got, want)
124 }
125 dummys.Set("otherString", want)
126 if got := dummys.Get("otherString").String(); got != want {
127 t.Errorf("got %#v, want %#v", got, want)
128 }
129 if !dummys.Get("someString").Equal(dummys.Get("someString")) {
130 t.Errorf("same value not equal")
131 }
132
133 if got, want := js.Undefined().String(), "<undefined>"; got != want {
134 t.Errorf("got %#v, want %#v", got, want)
135 }
136 if got, want := js.Null().String(), "<null>"; got != want {
137 t.Errorf("got %#v, want %#v", got, want)
138 }
139 if got, want := js.ValueOf(true).String(), "<boolean: true>"; got != want {
140 t.Errorf("got %#v, want %#v", got, want)
141 }
142 if got, want := js.ValueOf(42.5).String(), "<number: 42.5>"; got != want {
143 t.Errorf("got %#v, want %#v", got, want)
144 }
145 if got, want := js.Global().Call("Symbol").String(), "<symbol>"; got != want {
146 t.Errorf("got %#v, want %#v", got, want)
147 }
148 if got, want := js.Global().String(), "<object>"; got != want {
149 t.Errorf("got %#v, want %#v", got, want)
150 }
151 if got, want := js.Global().Get("setTimeout").String(), "<function>"; got != want {
152 t.Errorf("got %#v, want %#v", got, want)
153 }
154 }
155
156 func TestInt(t *testing.T) {
157 want := 42
158 o := dummys.Get("someInt")
159 if got := o.Int(); got != want {
160 t.Errorf("got %#v, want %#v", got, want)
161 }
162 dummys.Set("otherInt", want)
163 if got := dummys.Get("otherInt").Int(); got != want {
164 t.Errorf("got %#v, want %#v", got, want)
165 }
166 if !dummys.Get("someInt").Equal(dummys.Get("someInt")) {
167 t.Errorf("same value not equal")
168 }
169 if got := dummys.Get("zero").Int(); got != 0 {
170 t.Errorf("got %#v, want %#v", got, 0)
171 }
172 }
173
174 func TestIntConversion(t *testing.T) {
175 testIntConversion(t, 0)
176 testIntConversion(t, 1)
177 testIntConversion(t, -1)
178 testIntConversion(t, 1<<20)
179 testIntConversion(t, -1<<20)
180 testIntConversion(t, 1<<40)
181 testIntConversion(t, -1<<40)
182 testIntConversion(t, 1<<60)
183 testIntConversion(t, -1<<60)
184 }
185
186 func testIntConversion(t *testing.T, want int) {
187 if got := js.ValueOf(want).Int(); got != want {
188 t.Errorf("got %#v, want %#v", got, want)
189 }
190 }
191
192 func TestFloat(t *testing.T) {
193 want := 42.123
194 o := dummys.Get("someFloat")
195 if got := o.Float(); got != want {
196 t.Errorf("got %#v, want %#v", got, want)
197 }
198 dummys.Set("otherFloat", want)
199 if got := dummys.Get("otherFloat").Float(); got != want {
200 t.Errorf("got %#v, want %#v", got, want)
201 }
202 if !dummys.Get("someFloat").Equal(dummys.Get("someFloat")) {
203 t.Errorf("same value not equal")
204 }
205 }
206
207 func TestObject(t *testing.T) {
208 if !dummys.Get("someArray").Equal(dummys.Get("someArray")) {
209 t.Errorf("same value not equal")
210 }
211
212
213 proto := js.Global().Get("Object").Get("prototype")
214 o := js.Global().Call("eval", "new Object()")
215 if proto.Equal(o) {
216 t.Errorf("object equals to its prototype")
217 }
218 }
219
220 func TestFrozenObject(t *testing.T) {
221 o := js.Global().Call("eval", "(function () { let o = new Object(); o.field = 5; Object.freeze(o); return o; })()")
222 want := 5
223 if got := o.Get("field").Int(); want != got {
224 t.Errorf("got %#v, want %#v", got, want)
225 }
226 }
227
228 func TestEqual(t *testing.T) {
229 if !dummys.Get("someFloat").Equal(dummys.Get("someFloat")) {
230 t.Errorf("same float is not equal")
231 }
232 if !dummys.Get("emptyObj").Equal(dummys.Get("emptyObj")) {
233 t.Errorf("same object is not equal")
234 }
235 if dummys.Get("someFloat").Equal(dummys.Get("someInt")) {
236 t.Errorf("different values are not unequal")
237 }
238 }
239
240 func TestNaN(t *testing.T) {
241 if !dummys.Get("NaN").IsNaN() {
242 t.Errorf("JS NaN is not NaN")
243 }
244 if !js.ValueOf(math.NaN()).IsNaN() {
245 t.Errorf("Go NaN is not NaN")
246 }
247 if dummys.Get("NaN").Equal(dummys.Get("NaN")) {
248 t.Errorf("NaN is equal to NaN")
249 }
250 }
251
252 func TestUndefined(t *testing.T) {
253 if !js.Undefined().IsUndefined() {
254 t.Errorf("undefined is not undefined")
255 }
256 if !js.Undefined().Equal(js.Undefined()) {
257 t.Errorf("undefined is not equal to undefined")
258 }
259 if dummys.IsUndefined() {
260 t.Errorf("object is undefined")
261 }
262 if js.Undefined().IsNull() {
263 t.Errorf("undefined is null")
264 }
265 if dummys.Set("test", js.Undefined()); !dummys.Get("test").IsUndefined() {
266 t.Errorf("could not set undefined")
267 }
268 }
269
270 func TestNull(t *testing.T) {
271 if !js.Null().IsNull() {
272 t.Errorf("null is not null")
273 }
274 if !js.Null().Equal(js.Null()) {
275 t.Errorf("null is not equal to null")
276 }
277 if dummys.IsNull() {
278 t.Errorf("object is null")
279 }
280 if js.Null().IsUndefined() {
281 t.Errorf("null is undefined")
282 }
283 if dummys.Set("test", js.Null()); !dummys.Get("test").IsNull() {
284 t.Errorf("could not set null")
285 }
286 if dummys.Set("test", nil); !dummys.Get("test").IsNull() {
287 t.Errorf("could not set nil")
288 }
289 }
290
291 func TestLength(t *testing.T) {
292 if got := dummys.Get("someArray").Length(); got != 3 {
293 t.Errorf("got %#v, want %#v", got, 3)
294 }
295 }
296
297 func TestGet(t *testing.T) {
298
299
300 expectValueError(t, func() {
301 dummys.Get("zero").Get("badField")
302 })
303 }
304
305 func TestSet(t *testing.T) {
306
307
308 expectValueError(t, func() {
309 dummys.Get("zero").Set("badField", 42)
310 })
311 }
312
313 func TestDelete(t *testing.T) {
314 dummys.Set("test", 42)
315 dummys.Delete("test")
316 if dummys.Call("hasOwnProperty", "test").Bool() {
317 t.Errorf("property still exists")
318 }
319
320 expectValueError(t, func() {
321 dummys.Get("zero").Delete("badField")
322 })
323 }
324
325 func TestIndex(t *testing.T) {
326 if got := dummys.Get("someArray").Index(1).Int(); got != 42 {
327 t.Errorf("got %#v, want %#v", got, 42)
328 }
329
330 expectValueError(t, func() {
331 dummys.Get("zero").Index(1)
332 })
333 }
334
335 func TestSetIndex(t *testing.T) {
336 dummys.Get("someArray").SetIndex(2, 99)
337 if got := dummys.Get("someArray").Index(2).Int(); got != 99 {
338 t.Errorf("got %#v, want %#v", got, 99)
339 }
340
341 expectValueError(t, func() {
342 dummys.Get("zero").SetIndex(2, 99)
343 })
344 }
345
346 func TestCall(t *testing.T) {
347 var i int64 = 40
348 if got := dummys.Call("add", i, 2).Int(); got != 42 {
349 t.Errorf("got %#v, want %#v", got, 42)
350 }
351 if got := dummys.Call("add", js.Global().Call("eval", "40"), 2).Int(); got != 42 {
352 t.Errorf("got %#v, want %#v", got, 42)
353 }
354
355 expectPanic(t, func() {
356 dummys.Call("zero")
357 })
358 expectValueError(t, func() {
359 dummys.Get("zero").Call("badMethod")
360 })
361 }
362
363 func TestInvoke(t *testing.T) {
364 var i int64 = 40
365 if got := dummys.Get("add").Invoke(i, 2).Int(); got != 42 {
366 t.Errorf("got %#v, want %#v", got, 42)
367 }
368
369 expectValueError(t, func() {
370 dummys.Get("zero").Invoke()
371 })
372 }
373
374 func TestNew(t *testing.T) {
375 if got := js.Global().Get("Array").New(42).Length(); got != 42 {
376 t.Errorf("got %#v, want %#v", got, 42)
377 }
378
379 expectValueError(t, func() {
380 dummys.Get("zero").New()
381 })
382 }
383
384 func TestInstanceOf(t *testing.T) {
385 someArray := js.Global().Get("Array").New()
386 if got, want := someArray.InstanceOf(js.Global().Get("Array")), true; got != want {
387 t.Errorf("got %#v, want %#v", got, want)
388 }
389 if got, want := someArray.InstanceOf(js.Global().Get("Function")), false; got != want {
390 t.Errorf("got %#v, want %#v", got, want)
391 }
392 }
393
394 func TestType(t *testing.T) {
395 if got, want := js.Undefined().Type(), js.TypeUndefined; got != want {
396 t.Errorf("got %s, want %s", got, want)
397 }
398 if got, want := js.Null().Type(), js.TypeNull; got != want {
399 t.Errorf("got %s, want %s", got, want)
400 }
401 if got, want := js.ValueOf(true).Type(), js.TypeBoolean; got != want {
402 t.Errorf("got %s, want %s", got, want)
403 }
404 if got, want := js.ValueOf(0).Type(), js.TypeNumber; got != want {
405 t.Errorf("got %s, want %s", got, want)
406 }
407 if got, want := js.ValueOf(42).Type(), js.TypeNumber; got != want {
408 t.Errorf("got %s, want %s", got, want)
409 }
410 if got, want := js.ValueOf("test").Type(), js.TypeString; got != want {
411 t.Errorf("got %s, want %s", got, want)
412 }
413 if got, want := js.Global().Get("Symbol").Invoke("test").Type(), js.TypeSymbol; got != want {
414 t.Errorf("got %s, want %s", got, want)
415 }
416 if got, want := js.Global().Get("Array").New().Type(), js.TypeObject; got != want {
417 t.Errorf("got %s, want %s", got, want)
418 }
419 if got, want := js.Global().Get("Array").Type(), js.TypeFunction; got != want {
420 t.Errorf("got %s, want %s", got, want)
421 }
422 }
423
424 type object = map[string]any
425 type array = []any
426
427 func TestValueOf(t *testing.T) {
428 a := js.ValueOf(array{0, array{0, 42, 0}, 0})
429 if got := a.Index(1).Index(1).Int(); got != 42 {
430 t.Errorf("got %v, want %v", got, 42)
431 }
432
433 o := js.ValueOf(object{"x": object{"y": 42}})
434 if got := o.Get("x").Get("y").Int(); got != 42 {
435 t.Errorf("got %v, want %v", got, 42)
436 }
437 }
438
439 func TestZeroValue(t *testing.T) {
440 var v js.Value
441 if !v.IsUndefined() {
442 t.Error("zero js.Value is not js.Undefined()")
443 }
444 }
445
446 func TestFuncOf(t *testing.T) {
447 c := make(chan struct{})
448 cb := js.FuncOf(func(this js.Value, args []js.Value) any {
449 if got := args[0].Int(); got != 42 {
450 t.Errorf("got %#v, want %#v", got, 42)
451 }
452 c <- struct{}{}
453 return nil
454 })
455 defer cb.Release()
456 js.Global().Call("setTimeout", cb, 0, 42)
457 <-c
458 }
459
460 func TestInvokeFunction(t *testing.T) {
461 called := false
462 cb := js.FuncOf(func(this js.Value, args []js.Value) any {
463 cb2 := js.FuncOf(func(this js.Value, args []js.Value) any {
464 called = true
465 return 42
466 })
467 defer cb2.Release()
468 return cb2.Invoke()
469 })
470 defer cb.Release()
471 if got := cb.Invoke().Int(); got != 42 {
472 t.Errorf("got %#v, want %#v", got, 42)
473 }
474 if !called {
475 t.Error("function not called")
476 }
477 }
478
479 func TestInterleavedFunctions(t *testing.T) {
480 c1 := make(chan struct{})
481 c2 := make(chan struct{})
482
483 js.Global().Get("setTimeout").Invoke(js.FuncOf(func(this js.Value, args []js.Value) any {
484 c1 <- struct{}{}
485 <-c2
486 return nil
487 }), 0)
488
489 <-c1
490 c2 <- struct{}{}
491
492 f := js.FuncOf(func(this js.Value, args []js.Value) any {
493 return nil
494 })
495 f.Invoke()
496 }
497
498 func ExampleFuncOf() {
499 var cb js.Func
500 cb = js.FuncOf(func(this js.Value, args []js.Value) any {
501 fmt.Println("button clicked")
502 cb.Release()
503 return nil
504 })
505 js.Global().Get("document").Call("getElementById", "myButton").Call("addEventListener", "click", cb)
506 }
507
508
509
510
511
512 func TestTruthy(t *testing.T) {
513 want := true
514 for _, key := range []string{
515 "someBool", "someString", "someInt", "someFloat", "someArray", "someDate",
516 "stringZero",
517 "add",
518 "emptyObj", "emptyArray", "Infinity", "NegInfinity",
519
520 "objNumber0", "objBooleanFalse",
521 } {
522 if got := dummys.Get(key).Truthy(); got != want {
523 t.Errorf("%s: got %#v, want %#v", key, got, want)
524 }
525 }
526
527 want = false
528 if got := dummys.Get("zero").Truthy(); got != want {
529 t.Errorf("got %#v, want %#v", got, want)
530 }
531 if got := dummys.Get("NaN").Truthy(); got != want {
532 t.Errorf("got %#v, want %#v", got, want)
533 }
534 if got := js.ValueOf("").Truthy(); got != want {
535 t.Errorf("got %#v, want %#v", got, want)
536 }
537 if got := js.Null().Truthy(); got != want {
538 t.Errorf("got %#v, want %#v", got, want)
539 }
540 if got := js.Undefined().Truthy(); got != want {
541 t.Errorf("got %#v, want %#v", got, want)
542 }
543 }
544
545 func expectValueError(t *testing.T, fn func()) {
546 defer func() {
547 err := recover()
548 if _, ok := err.(*js.ValueError); !ok {
549 t.Errorf("expected *js.ValueError, got %T", err)
550 }
551 }()
552 fn()
553 }
554
555 func expectPanic(t *testing.T, fn func()) {
556 defer func() {
557 err := recover()
558 if err == nil {
559 t.Errorf("expected panic")
560 }
561 }()
562 fn()
563 }
564
565 var copyTests = []struct {
566 srcLen int
567 dstLen int
568 copyLen int
569 }{
570 {5, 3, 3},
571 {3, 5, 3},
572 {0, 0, 0},
573 }
574
575 func TestCopyBytesToGo(t *testing.T) {
576 for _, tt := range copyTests {
577 t.Run(fmt.Sprintf("%d-to-%d", tt.srcLen, tt.dstLen), func(t *testing.T) {
578 src := js.Global().Get("Uint8Array").New(tt.srcLen)
579 if tt.srcLen >= 2 {
580 src.SetIndex(1, 42)
581 }
582 dst := make([]byte, tt.dstLen)
583
584 if got, want := js.CopyBytesToGo(dst, src), tt.copyLen; got != want {
585 t.Errorf("copied %d, want %d", got, want)
586 }
587 if tt.dstLen >= 2 {
588 if got, want := int(dst[1]), 42; got != want {
589 t.Errorf("got %d, want %d", got, want)
590 }
591 }
592 })
593 }
594 }
595
596 func TestCopyBytesToJS(t *testing.T) {
597 for _, tt := range copyTests {
598 t.Run(fmt.Sprintf("%d-to-%d", tt.srcLen, tt.dstLen), func(t *testing.T) {
599 src := make([]byte, tt.srcLen)
600 if tt.srcLen >= 2 {
601 src[1] = 42
602 }
603 dst := js.Global().Get("Uint8Array").New(tt.dstLen)
604
605 if got, want := js.CopyBytesToJS(dst, src), tt.copyLen; got != want {
606 t.Errorf("copied %d, want %d", got, want)
607 }
608 if tt.dstLen >= 2 {
609 if got, want := dst.Index(1).Int(), 42; got != want {
610 t.Errorf("got %d, want %d", got, want)
611 }
612 }
613 })
614 }
615 }
616
617 func TestGarbageCollection(t *testing.T) {
618 before := js.JSGo.Get("_values").Length()
619 for i := 0; i < 1000; i++ {
620 _ = js.Global().Get("Object").New().Call("toString").String()
621 runtime.GC()
622 }
623 after := js.JSGo.Get("_values").Length()
624 if after-before > 500 {
625 t.Errorf("garbage collection ineffective")
626 }
627 }
628
629
630
631
632
633
634 var allocTests = []struct {
635 argLen int
636 expected int
637 }{
638
639
640 {0, 1},
641 {2, 1},
642 {15, 1},
643 {16, 1},
644
645
646
647
648 {17, 3},
649 {32, 3},
650 {42, 3},
651 }
652
653
654 func TestCallAllocations(t *testing.T) {
655 for _, test := range allocTests {
656 args := make([]any, test.argLen)
657
658 tmpArray := js.Global().Get("Array").New(0)
659 numAllocs := testing.AllocsPerRun(100, func() {
660 tmpArray.Call("concat", args...)
661 })
662
663 if numAllocs != float64(test.expected) {
664 t.Errorf("got numAllocs %#v, want %#v", numAllocs, test.expected)
665 }
666 }
667 }
668
669
670 func TestInvokeAllocations(t *testing.T) {
671 for _, test := range allocTests {
672 args := make([]any, test.argLen)
673
674 tmpArray := js.Global().Get("Array").New(0)
675 concatFunc := tmpArray.Get("concat").Call("bind", tmpArray)
676 numAllocs := testing.AllocsPerRun(100, func() {
677 concatFunc.Invoke(args...)
678 })
679
680 if numAllocs != float64(test.expected) {
681 t.Errorf("got numAllocs %#v, want %#v", numAllocs, test.expected)
682 }
683 }
684 }
685
686
687 func TestNewAllocations(t *testing.T) {
688 arrayConstructor := js.Global().Get("Array")
689
690 for _, test := range allocTests {
691 args := make([]any, test.argLen)
692
693 numAllocs := testing.AllocsPerRun(100, func() {
694 arrayConstructor.New(args...)
695 })
696
697 if numAllocs != float64(test.expected) {
698 t.Errorf("got numAllocs %#v, want %#v", numAllocs, test.expected)
699 }
700 }
701 }
702
703
704
705
706 func BenchmarkDOM(b *testing.B) {
707 document := js.Global().Get("document")
708 if document.IsUndefined() {
709 b.Skip("Not a browser environment. Skipping.")
710 }
711 const data = "someString"
712 for i := 0; i < b.N; i++ {
713 div := document.Call("createElement", "div")
714 div.Call("setAttribute", "id", "myDiv")
715 document.Get("body").Call("appendChild", div)
716 myDiv := document.Call("getElementById", "myDiv")
717 myDiv.Set("innerHTML", data)
718
719 if got, want := myDiv.Get("innerHTML").String(), data; got != want {
720 b.Errorf("got %s, want %s", got, want)
721 }
722 document.Get("body").Call("removeChild", div)
723 }
724 }
725
726 func TestGlobal(t *testing.T) {
727 ident := js.FuncOf(func(this js.Value, args []js.Value) any {
728 return args[0]
729 })
730 defer ident.Release()
731
732 if got := ident.Invoke(js.Global()); !got.Equal(js.Global()) {
733 t.Errorf("got %#v, want %#v", got, js.Global())
734 }
735 }
736
View as plain text