]> Cypherpunks.ru repositories - gostls13.git/blob - src/syscall/js/js_test.go
all: implement wasmimport directive
[gostls13.git] / src / syscall / js / js_test.go
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 //go: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 //go:wasmimport _gotest add
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         // An object and its prototype should not be equal.
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         // positive cases get tested per type
254
255         expectValueError(t, func() {
256                 dummys.Get("zero").Get("badField")
257         })
258 }
259
260 func TestSet(t *testing.T) {
261         // positive cases get tested per type
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         // this goroutine is running, but the callback of setTimeout did not return yet, invoke another function now
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() // release the function if the button will not be clicked again
458                 return nil
459         })
460         js.Global().Get("document").Call("getElementById", "myButton").Call("addEventListener", "click", cb)
461 }
462
463 // See
464 // - https://developer.mozilla.org/en-US/docs/Glossary/Truthy
465 // - https://stackoverflow.com/questions/19839952/all-falsey-values-in-javascript/19839953#19839953
466 // - http://www.ecma-international.org/ecma-262/5.1/#sec-9.2
467 func TestTruthy(t *testing.T) {
468         want := true
469         for _, key := range []string{
470                 "someBool", "someString", "someInt", "someFloat", "someArray", "someDate",
471                 "stringZero", // "0" is truthy
472                 "add",        // functions are truthy
473                 "emptyObj", "emptyArray", "Infinity", "NegInfinity",
474                 // All objects are truthy, even if they're Number(0) or Boolean(false).
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 // BenchmarkDOM is a simple benchmark which emulates a webapp making DOM operations.
585 // It creates a div, and sets its id. Then searches by that id and sets some data.
586 // Finally it removes that div.
587 func BenchmarkDOM(b *testing.B) {
588         document := js.Global().Get("document")
589         if document.IsUndefined() {
590                 b.Skip("Not a browser environment. Skipping.")
591         }
592         const data = "someString"
593         for i := 0; i < b.N; i++ {
594                 div := document.Call("createElement", "div")
595                 div.Call("setAttribute", "id", "myDiv")
596                 document.Get("body").Call("appendChild", div)
597                 myDiv := document.Call("getElementById", "myDiv")
598                 myDiv.Set("innerHTML", data)
599
600                 if got, want := myDiv.Get("innerHTML").String(), data; got != want {
601                         b.Errorf("got %s, want %s", got, want)
602                 }
603                 document.Get("body").Call("removeChild", div)
604         }
605 }
606
607 func TestGlobal(t *testing.T) {
608         ident := js.FuncOf(func(this js.Value, args []js.Value) any {
609                 return args[0]
610         })
611         defer ident.Release()
612
613         if got := ident.Invoke(js.Global()); !got.Equal(js.Global()) {
614                 t.Errorf("got %#v, want %#v", got, js.Global())
615         }
616 }