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