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
385
386
387 func TestValueErrorMethod(t *testing.T) {
388 num := dummys.Get("someInt")
389 obj := dummys
390 tests := []struct {
391 method string
392 fn func()
393 }{
394 {"Value.Get", func() { num.Get("x") }},
395 {"Value.Set", func() { num.Set("x", 1) }},
396 {"Value.Delete", func() { num.Delete("x") }},
397 {"Value.Index", func() { num.Index(0) }},
398 {"Value.SetIndex", func() { num.SetIndex(0, 1) }},
399 {"Value.Length", func() { num.Length() }},
400 {"Value.Call", func() { num.Call("x") }},
401 {"Value.Invoke", func() { num.Invoke() }},
402 {"Value.New", func() { num.New() }},
403 {"Value.Float", func() { obj.Float() }},
404 {"Value.Int", func() { obj.Int() }},
405 {"Value.Bool", func() { obj.Bool() }},
406 }
407 for _, tt := range tests {
408 t.Run(tt.method, func(t *testing.T) {
409 defer func() {
410 r := recover()
411 ve, ok := r.(*js.ValueError)
412 if !ok {
413 t.Fatalf("expected *js.ValueError, got %T (%v)", r, r)
414 }
415 if got, want := ve.Method, tt.method; got != want {
416 t.Fatalf("ValueError.Method = %q, want %q", got, want)
417 }
418 }()
419 tt.fn()
420 })
421 }
422 }
423
424 func TestInstanceOf(t *testing.T) {
425 someArray := js.Global().Get("Array").New()
426 if got, want := someArray.InstanceOf(js.Global().Get("Array")), true; got != want {
427 t.Errorf("got %#v, want %#v", got, want)
428 }
429 if got, want := someArray.InstanceOf(js.Global().Get("Function")), false; got != want {
430 t.Errorf("got %#v, want %#v", got, want)
431 }
432 }
433
434 func TestType(t *testing.T) {
435 if got, want := js.Undefined().Type(), js.TypeUndefined; got != want {
436 t.Errorf("got %s, want %s", got, want)
437 }
438 if got, want := js.Null().Type(), js.TypeNull; got != want {
439 t.Errorf("got %s, want %s", got, want)
440 }
441 if got, want := js.ValueOf(true).Type(), js.TypeBoolean; got != want {
442 t.Errorf("got %s, want %s", got, want)
443 }
444 if got, want := js.ValueOf(0).Type(), js.TypeNumber; got != want {
445 t.Errorf("got %s, want %s", got, want)
446 }
447 if got, want := js.ValueOf(42).Type(), js.TypeNumber; got != want {
448 t.Errorf("got %s, want %s", got, want)
449 }
450 if got, want := js.ValueOf("test").Type(), js.TypeString; got != want {
451 t.Errorf("got %s, want %s", got, want)
452 }
453 if got, want := js.Global().Get("Symbol").Invoke("test").Type(), js.TypeSymbol; got != want {
454 t.Errorf("got %s, want %s", got, want)
455 }
456 if got, want := js.Global().Get("Array").New().Type(), js.TypeObject; got != want {
457 t.Errorf("got %s, want %s", got, want)
458 }
459 if got, want := js.Global().Get("Array").Type(), js.TypeFunction; got != want {
460 t.Errorf("got %s, want %s", got, want)
461 }
462 }
463
464 type object = map[string]any
465 type array = []any
466
467 func TestValueOf(t *testing.T) {
468 a := js.ValueOf(array{0, array{0, 42, 0}, 0})
469 if got := a.Index(1).Index(1).Int(); got != 42 {
470 t.Errorf("got %v, want %v", got, 42)
471 }
472
473 o := js.ValueOf(object{"x": object{"y": 42}})
474 if got := o.Get("x").Get("y").Int(); got != 42 {
475 t.Errorf("got %v, want %v", got, 42)
476 }
477 }
478
479 func TestZeroValue(t *testing.T) {
480 var v js.Value
481 if !v.IsUndefined() {
482 t.Error("zero js.Value is not js.Undefined()")
483 }
484 }
485
486 func TestFuncOf(t *testing.T) {
487 c := make(chan struct{})
488 cb := js.FuncOf(func(this js.Value, args []js.Value) any {
489 if got := args[0].Int(); got != 42 {
490 t.Errorf("got %#v, want %#v", got, 42)
491 }
492 c <- struct{}{}
493 return nil
494 })
495 defer cb.Release()
496 js.Global().Call("setTimeout", cb, 0, 42)
497 <-c
498 }
499
500 func TestInvokeFunction(t *testing.T) {
501 called := false
502 cb := js.FuncOf(func(this js.Value, args []js.Value) any {
503 cb2 := js.FuncOf(func(this js.Value, args []js.Value) any {
504 called = true
505 return 42
506 })
507 defer cb2.Release()
508 return cb2.Invoke()
509 })
510 defer cb.Release()
511 if got := cb.Invoke().Int(); got != 42 {
512 t.Errorf("got %#v, want %#v", got, 42)
513 }
514 if !called {
515 t.Error("function not called")
516 }
517 }
518
519 func TestInterleavedFunctions(t *testing.T) {
520 c1 := make(chan struct{})
521 c2 := make(chan struct{})
522
523 js.Global().Get("setTimeout").Invoke(js.FuncOf(func(this js.Value, args []js.Value) any {
524 c1 <- struct{}{}
525 <-c2
526 return nil
527 }), 0)
528
529 <-c1
530 c2 <- struct{}{}
531
532 f := js.FuncOf(func(this js.Value, args []js.Value) any {
533 return nil
534 })
535 f.Invoke()
536 }
537
538 func ExampleFuncOf() {
539 var cb js.Func
540 cb = js.FuncOf(func(this js.Value, args []js.Value) any {
541 fmt.Println("button clicked")
542 cb.Release()
543 return nil
544 })
545 js.Global().Get("document").Call("getElementById", "myButton").Call("addEventListener", "click", cb)
546 }
547
548
549
550
551
552 func TestTruthy(t *testing.T) {
553 want := true
554 for _, key := range []string{
555 "someBool", "someString", "someInt", "someFloat", "someArray", "someDate",
556 "stringZero",
557 "add",
558 "emptyObj", "emptyArray", "Infinity", "NegInfinity",
559
560 "objNumber0", "objBooleanFalse",
561 } {
562 if got := dummys.Get(key).Truthy(); got != want {
563 t.Errorf("%s: got %#v, want %#v", key, got, want)
564 }
565 }
566
567 want = false
568 if got := dummys.Get("zero").Truthy(); got != want {
569 t.Errorf("got %#v, want %#v", got, want)
570 }
571 if got := dummys.Get("NaN").Truthy(); got != want {
572 t.Errorf("got %#v, want %#v", got, want)
573 }
574 if got := js.ValueOf("").Truthy(); got != want {
575 t.Errorf("got %#v, want %#v", got, want)
576 }
577 if got := js.Null().Truthy(); got != want {
578 t.Errorf("got %#v, want %#v", got, want)
579 }
580 if got := js.Undefined().Truthy(); got != want {
581 t.Errorf("got %#v, want %#v", got, want)
582 }
583 }
584
585 func expectValueError(t *testing.T, fn func()) {
586 defer func() {
587 err := recover()
588 if _, ok := err.(*js.ValueError); !ok {
589 t.Errorf("expected *js.ValueError, got %T", err)
590 }
591 }()
592 fn()
593 }
594
595 func expectPanic(t *testing.T, fn func()) {
596 defer func() {
597 err := recover()
598 if err == nil {
599 t.Errorf("expected panic")
600 }
601 }()
602 fn()
603 }
604
605 var copyTests = []struct {
606 srcLen int
607 dstLen int
608 copyLen int
609 }{
610 {5, 3, 3},
611 {3, 5, 3},
612 {0, 0, 0},
613 }
614
615 func TestCopyBytesToGo(t *testing.T) {
616 for _, tt := range copyTests {
617 t.Run(fmt.Sprintf("%d-to-%d", tt.srcLen, tt.dstLen), func(t *testing.T) {
618 src := js.Global().Get("Uint8Array").New(tt.srcLen)
619 if tt.srcLen >= 2 {
620 src.SetIndex(1, 42)
621 }
622 dst := make([]byte, tt.dstLen)
623
624 if got, want := js.CopyBytesToGo(dst, src), tt.copyLen; got != want {
625 t.Errorf("copied %d, want %d", got, want)
626 }
627 if tt.dstLen >= 2 {
628 if got, want := int(dst[1]), 42; got != want {
629 t.Errorf("got %d, want %d", got, want)
630 }
631 }
632 })
633 }
634 }
635
636 func TestCopyBytesToJS(t *testing.T) {
637 for _, tt := range copyTests {
638 t.Run(fmt.Sprintf("%d-to-%d", tt.srcLen, tt.dstLen), func(t *testing.T) {
639 src := make([]byte, tt.srcLen)
640 if tt.srcLen >= 2 {
641 src[1] = 42
642 }
643 dst := js.Global().Get("Uint8Array").New(tt.dstLen)
644
645 if got, want := js.CopyBytesToJS(dst, src), tt.copyLen; got != want {
646 t.Errorf("copied %d, want %d", got, want)
647 }
648 if tt.dstLen >= 2 {
649 if got, want := dst.Index(1).Int(), 42; got != want {
650 t.Errorf("got %d, want %d", got, want)
651 }
652 }
653 })
654 }
655 }
656
657 func TestGarbageCollection(t *testing.T) {
658 before := js.JSGo.Get("_values").Length()
659 for i := 0; i < 1000; i++ {
660 _ = js.Global().Get("Object").New().Call("toString").String()
661 runtime.GC()
662 }
663 after := js.JSGo.Get("_values").Length()
664 if after-before > 500 {
665 t.Errorf("garbage collection ineffective")
666 }
667 }
668
669
670
671
672
673
674 var allocTests = []struct {
675 argLen int
676 expected int
677 }{
678
679
680 {0, 1},
681 {2, 1},
682 {15, 1},
683 {16, 1},
684
685
686
687
688 {17, 3},
689 {32, 3},
690 {42, 3},
691 }
692
693
694 func TestCallAllocations(t *testing.T) {
695 for _, test := range allocTests {
696 args := make([]any, test.argLen)
697
698 tmpArray := js.Global().Get("Array").New(0)
699 numAllocs := testing.AllocsPerRun(100, func() {
700 tmpArray.Call("concat", args...)
701 })
702
703 if numAllocs != float64(test.expected) {
704 t.Errorf("got numAllocs %#v, want %#v", numAllocs, test.expected)
705 }
706 }
707 }
708
709
710 func TestInvokeAllocations(t *testing.T) {
711 for _, test := range allocTests {
712 args := make([]any, test.argLen)
713
714 tmpArray := js.Global().Get("Array").New(0)
715 concatFunc := tmpArray.Get("concat").Call("bind", tmpArray)
716 numAllocs := testing.AllocsPerRun(100, func() {
717 concatFunc.Invoke(args...)
718 })
719
720 if numAllocs != float64(test.expected) {
721 t.Errorf("got numAllocs %#v, want %#v", numAllocs, test.expected)
722 }
723 }
724 }
725
726
727 func TestNewAllocations(t *testing.T) {
728 arrayConstructor := js.Global().Get("Array")
729
730 for _, test := range allocTests {
731 args := make([]any, test.argLen)
732
733 numAllocs := testing.AllocsPerRun(100, func() {
734 arrayConstructor.New(args...)
735 })
736
737 if numAllocs != float64(test.expected) {
738 t.Errorf("got numAllocs %#v, want %#v", numAllocs, test.expected)
739 }
740 }
741 }
742
743
744
745
746 func BenchmarkDOM(b *testing.B) {
747 document := js.Global().Get("document")
748 if document.IsUndefined() {
749 b.Skip("Not a browser environment. Skipping.")
750 }
751 const data = "someString"
752 for i := 0; i < b.N; i++ {
753 div := document.Call("createElement", "div")
754 div.Call("setAttribute", "id", "myDiv")
755 document.Get("body").Call("appendChild", div)
756 myDiv := document.Call("getElementById", "myDiv")
757 myDiv.Set("innerHTML", data)
758
759 if got, want := myDiv.Get("innerHTML").String(), data; got != want {
760 b.Errorf("got %s, want %s", got, want)
761 }
762 document.Get("body").Call("removeChild", div)
763 }
764 }
765
766 func TestGlobal(t *testing.T) {
767 ident := js.FuncOf(func(this js.Value, args []js.Value) any {
768 return args[0]
769 })
770 defer ident.Release()
771
772 if got := ident.Invoke(js.Global()); !got.Equal(js.Global()) {
773 t.Errorf("got %#v, want %#v", got, js.Global())
774 }
775 }
776
View as plain text