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.
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
14 // See -exec in "go help test", and "go help run" for details.
26 var dummys = js.Global().Call("eval", `({
28 someString: "abc\u1234",
31 someArray: [41, 42, 43],
42 NegInfinity: -Infinity,
43 objNumber0: new Number(0),
44 objBooleanFalse: new Boolean(false),
47 //go:wasmimport _gotest add
48 func testAdd(uint32, uint32) uint32
50 func TestWasmImport(t *testing.T) {
54 if got := testAdd(a, b); got != want {
55 t.Errorf("got %v, want %v", got, want)
59 func TestBool(t *testing.T) {
61 o := dummys.Get("someBool")
62 if got := o.Bool(); got != want {
63 t.Errorf("got %#v, want %#v", got, want)
65 dummys.Set("otherBool", want)
66 if got := dummys.Get("otherBool").Bool(); got != want {
67 t.Errorf("got %#v, want %#v", got, want)
69 if !dummys.Get("someBool").Equal(dummys.Get("someBool")) {
70 t.Errorf("same value not equal")
74 func TestString(t *testing.T) {
76 o := dummys.Get("someString")
77 if got := o.String(); got != want {
78 t.Errorf("got %#v, want %#v", got, want)
80 dummys.Set("otherString", want)
81 if got := dummys.Get("otherString").String(); got != want {
82 t.Errorf("got %#v, want %#v", got, want)
84 if !dummys.Get("someString").Equal(dummys.Get("someString")) {
85 t.Errorf("same value not equal")
88 if got, want := js.Undefined().String(), "<undefined>"; got != want {
89 t.Errorf("got %#v, want %#v", got, want)
91 if got, want := js.Null().String(), "<null>"; got != want {
92 t.Errorf("got %#v, want %#v", got, want)
94 if got, want := js.ValueOf(true).String(), "<boolean: true>"; got != want {
95 t.Errorf("got %#v, want %#v", got, want)
97 if got, want := js.ValueOf(42.5).String(), "<number: 42.5>"; got != want {
98 t.Errorf("got %#v, want %#v", got, want)
100 if got, want := js.Global().Call("Symbol").String(), "<symbol>"; got != want {
101 t.Errorf("got %#v, want %#v", got, want)
103 if got, want := js.Global().String(), "<object>"; got != want {
104 t.Errorf("got %#v, want %#v", got, want)
106 if got, want := js.Global().Get("setTimeout").String(), "<function>"; got != want {
107 t.Errorf("got %#v, want %#v", got, want)
111 func TestInt(t *testing.T) {
113 o := dummys.Get("someInt")
114 if got := o.Int(); got != want {
115 t.Errorf("got %#v, want %#v", got, want)
117 dummys.Set("otherInt", want)
118 if got := dummys.Get("otherInt").Int(); got != want {
119 t.Errorf("got %#v, want %#v", got, want)
121 if !dummys.Get("someInt").Equal(dummys.Get("someInt")) {
122 t.Errorf("same value not equal")
124 if got := dummys.Get("zero").Int(); got != 0 {
125 t.Errorf("got %#v, want %#v", got, 0)
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)
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)
147 func TestFloat(t *testing.T) {
149 o := dummys.Get("someFloat")
150 if got := o.Float(); got != want {
151 t.Errorf("got %#v, want %#v", got, want)
153 dummys.Set("otherFloat", want)
154 if got := dummys.Get("otherFloat").Float(); got != want {
155 t.Errorf("got %#v, want %#v", got, want)
157 if !dummys.Get("someFloat").Equal(dummys.Get("someFloat")) {
158 t.Errorf("same value not equal")
162 func TestObject(t *testing.T) {
163 if !dummys.Get("someArray").Equal(dummys.Get("someArray")) {
164 t.Errorf("same value not equal")
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()")
171 t.Errorf("object equals to its prototype")
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; })()")
178 if got := o.Get("field").Int(); want != got {
179 t.Errorf("got %#v, want %#v", got, want)
183 func TestEqual(t *testing.T) {
184 if !dummys.Get("someFloat").Equal(dummys.Get("someFloat")) {
185 t.Errorf("same float is not equal")
187 if !dummys.Get("emptyObj").Equal(dummys.Get("emptyObj")) {
188 t.Errorf("same object is not equal")
190 if dummys.Get("someFloat").Equal(dummys.Get("someInt")) {
191 t.Errorf("different values are not unequal")
195 func TestNaN(t *testing.T) {
196 if !dummys.Get("NaN").IsNaN() {
197 t.Errorf("JS NaN is not NaN")
199 if !js.ValueOf(math.NaN()).IsNaN() {
200 t.Errorf("Go NaN is not NaN")
202 if dummys.Get("NaN").Equal(dummys.Get("NaN")) {
203 t.Errorf("NaN is equal to NaN")
207 func TestUndefined(t *testing.T) {
208 if !js.Undefined().IsUndefined() {
209 t.Errorf("undefined is not undefined")
211 if !js.Undefined().Equal(js.Undefined()) {
212 t.Errorf("undefined is not equal to undefined")
214 if dummys.IsUndefined() {
215 t.Errorf("object is undefined")
217 if js.Undefined().IsNull() {
218 t.Errorf("undefined is null")
220 if dummys.Set("test", js.Undefined()); !dummys.Get("test").IsUndefined() {
221 t.Errorf("could not set undefined")
225 func TestNull(t *testing.T) {
226 if !js.Null().IsNull() {
227 t.Errorf("null is not null")
229 if !js.Null().Equal(js.Null()) {
230 t.Errorf("null is not equal to null")
233 t.Errorf("object is null")
235 if js.Null().IsUndefined() {
236 t.Errorf("null is undefined")
238 if dummys.Set("test", js.Null()); !dummys.Get("test").IsNull() {
239 t.Errorf("could not set null")
241 if dummys.Set("test", nil); !dummys.Get("test").IsNull() {
242 t.Errorf("could not set nil")
246 func TestLength(t *testing.T) {
247 if got := dummys.Get("someArray").Length(); got != 3 {
248 t.Errorf("got %#v, want %#v", got, 3)
252 func TestGet(t *testing.T) {
253 // positive cases get tested per type
255 expectValueError(t, func() {
256 dummys.Get("zero").Get("badField")
260 func TestSet(t *testing.T) {
261 // positive cases get tested per type
263 expectValueError(t, func() {
264 dummys.Get("zero").Set("badField", 42)
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")
275 expectValueError(t, func() {
276 dummys.Get("zero").Delete("badField")
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)
285 expectValueError(t, func() {
286 dummys.Get("zero").Index(1)
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)
296 expectValueError(t, func() {
297 dummys.Get("zero").SetIndex(2, 99)
301 func TestCall(t *testing.T) {
303 if got := dummys.Call("add", i, 2).Int(); got != 42 {
304 t.Errorf("got %#v, want %#v", got, 42)
306 if got := dummys.Call("add", js.Global().Call("eval", "40"), 2).Int(); got != 42 {
307 t.Errorf("got %#v, want %#v", got, 42)
310 expectPanic(t, func() {
313 expectValueError(t, func() {
314 dummys.Get("zero").Call("badMethod")
318 func TestInvoke(t *testing.T) {
320 if got := dummys.Get("add").Invoke(i, 2).Int(); got != 42 {
321 t.Errorf("got %#v, want %#v", got, 42)
324 expectValueError(t, func() {
325 dummys.Get("zero").Invoke()
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)
334 expectValueError(t, func() {
335 dummys.Get("zero").New()
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)
344 if got, want := someArray.InstanceOf(js.Global().Get("Function")), false; got != want {
345 t.Errorf("got %#v, want %#v", got, want)
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)
353 if got, want := js.Null().Type(), js.TypeNull; got != want {
354 t.Errorf("got %s, want %s", got, want)
356 if got, want := js.ValueOf(true).Type(), js.TypeBoolean; got != want {
357 t.Errorf("got %s, want %s", got, want)
359 if got, want := js.ValueOf(0).Type(), js.TypeNumber; got != want {
360 t.Errorf("got %s, want %s", got, want)
362 if got, want := js.ValueOf(42).Type(), js.TypeNumber; got != want {
363 t.Errorf("got %s, want %s", got, want)
365 if got, want := js.ValueOf("test").Type(), js.TypeString; got != want {
366 t.Errorf("got %s, want %s", got, want)
368 if got, want := js.Global().Get("Symbol").Invoke("test").Type(), js.TypeSymbol; got != want {
369 t.Errorf("got %s, want %s", got, want)
371 if got, want := js.Global().Get("Array").New().Type(), js.TypeObject; got != want {
372 t.Errorf("got %s, want %s", got, want)
374 if got, want := js.Global().Get("Array").Type(), js.TypeFunction; got != want {
375 t.Errorf("got %s, want %s", got, want)
379 type object = map[string]any
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)
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)
394 func TestZeroValue(t *testing.T) {
396 if !v.IsUndefined() {
397 t.Error("zero js.Value is not js.Undefined()")
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)
411 js.Global().Call("setTimeout", cb, 0, 42)
415 func TestInvokeFunction(t *testing.T) {
417 cb := js.FuncOf(func(this js.Value, args []js.Value) any {
418 cb2 := js.FuncOf(func(this js.Value, args []js.Value) any {
426 if got := cb.Invoke().Int(); got != 42 {
427 t.Errorf("got %#v, want %#v", got, 42)
430 t.Error("function not called")
434 func TestInterleavedFunctions(t *testing.T) {
435 c1 := make(chan struct{})
436 c2 := make(chan struct{})
438 js.Global().Get("setTimeout").Invoke(js.FuncOf(func(this js.Value, args []js.Value) any {
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 {
453 func ExampleFuncOf() {
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
460 js.Global().Get("document").Call("getElementById", "myButton").Call("addEventListener", "click", cb)
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) {
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",
477 if got := dummys.Get(key).Truthy(); got != want {
478 t.Errorf("%s: got %#v, want %#v", key, got, want)
483 if got := dummys.Get("zero").Truthy(); got != want {
484 t.Errorf("got %#v, want %#v", got, want)
486 if got := dummys.Get("NaN").Truthy(); got != want {
487 t.Errorf("got %#v, want %#v", got, want)
489 if got := js.ValueOf("").Truthy(); got != want {
490 t.Errorf("got %#v, want %#v", got, want)
492 if got := js.Null().Truthy(); got != want {
493 t.Errorf("got %#v, want %#v", got, want)
495 if got := js.Undefined().Truthy(); got != want {
496 t.Errorf("got %#v, want %#v", got, want)
500 func expectValueError(t *testing.T, fn func()) {
503 if _, ok := err.(*js.ValueError); !ok {
504 t.Errorf("expected *js.ValueError, got %T", err)
510 func expectPanic(t *testing.T, fn func()) {
514 t.Errorf("expected panic")
520 var copyTests = []struct {
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)
537 dst := make([]byte, tt.dstLen)
539 if got, want := js.CopyBytesToGo(dst, src), tt.copyLen; got != want {
540 t.Errorf("copied %d, want %d", got, want)
543 if got, want := int(dst[1]), 42; got != want {
544 t.Errorf("got %d, want %d", got, want)
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)
558 dst := js.Global().Get("Uint8Array").New(tt.dstLen)
560 if got, want := js.CopyBytesToJS(dst, src), tt.copyLen; got != want {
561 t.Errorf("copied %d, want %d", got, want)
564 if got, want := dst.Index(1).Int(), 42; got != want {
565 t.Errorf("got %d, want %d", got, want)
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()
578 after := js.JSGo.Get("_values").Length()
579 if after-before > 500 {
580 t.Errorf("garbage collection ineffective")
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.")
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)
600 if got, want := myDiv.Get("innerHTML").String(), data; got != want {
601 b.Errorf("got %s, want %s", got, want)
603 document.Get("body").Call("removeChild", div)
607 func TestGlobal(t *testing.T) {
608 ident := js.FuncOf(func(this js.Value, args []js.Value) any {
611 defer ident.Release()
613 if got := ident.Invoke(js.Global()); !got.Equal(js.Global()) {
614 t.Errorf("got %#v, want %#v", got, js.Global())