js_test.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604
  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. //go:build js && wasm
  5. // To run these tests:
  6. //
  7. // - Install Node
  8. // - Add /path/to/go/misc/wasm to your $PATH (so that "go test" can find
  9. // "go_js_wasm_exec").
  10. // - GOOS=js GOARCH=wasm go test
  11. //
  12. // See -exec in "go help test", and "go help run" for details.
  13. package js_test
  14. import (
  15. "fmt"
  16. "math"
  17. "runtime"
  18. "syscall/js"
  19. "testing"
  20. )
  21. var dummys = js.Global().Call("eval", `({
  22. someBool: true,
  23. someString: "abc\u1234",
  24. someInt: 42,
  25. someFloat: 42.123,
  26. someArray: [41, 42, 43],
  27. someDate: new Date(),
  28. add: function(a, b) {
  29. return a + b;
  30. },
  31. zero: 0,
  32. stringZero: "0",
  33. NaN: NaN,
  34. emptyObj: {},
  35. emptyArray: [],
  36. Infinity: Infinity,
  37. NegInfinity: -Infinity,
  38. objNumber0: new Number(0),
  39. objBooleanFalse: new Boolean(false),
  40. })`)
  41. func TestBool(t *testing.T) {
  42. want := true
  43. o := dummys.Get("someBool")
  44. if got := o.Bool(); got != want {
  45. t.Errorf("got %#v, want %#v", got, want)
  46. }
  47. dummys.Set("otherBool", want)
  48. if got := dummys.Get("otherBool").Bool(); got != want {
  49. t.Errorf("got %#v, want %#v", got, want)
  50. }
  51. if !dummys.Get("someBool").Equal(dummys.Get("someBool")) {
  52. t.Errorf("same value not equal")
  53. }
  54. }
  55. func TestString(t *testing.T) {
  56. want := "abc\u1234"
  57. o := dummys.Get("someString")
  58. if got := o.String(); got != want {
  59. t.Errorf("got %#v, want %#v", got, want)
  60. }
  61. dummys.Set("otherString", want)
  62. if got := dummys.Get("otherString").String(); got != want {
  63. t.Errorf("got %#v, want %#v", got, want)
  64. }
  65. if !dummys.Get("someString").Equal(dummys.Get("someString")) {
  66. t.Errorf("same value not equal")
  67. }
  68. if got, want := js.Undefined().String(), "<undefined>"; got != want {
  69. t.Errorf("got %#v, want %#v", got, want)
  70. }
  71. if got, want := js.Null().String(), "<null>"; got != want {
  72. t.Errorf("got %#v, want %#v", got, want)
  73. }
  74. if got, want := js.ValueOf(true).String(), "<boolean: true>"; got != want {
  75. t.Errorf("got %#v, want %#v", got, want)
  76. }
  77. if got, want := js.ValueOf(42.5).String(), "<number: 42.5>"; got != want {
  78. t.Errorf("got %#v, want %#v", got, want)
  79. }
  80. if got, want := js.Global().Call("Symbol").String(), "<symbol>"; got != want {
  81. t.Errorf("got %#v, want %#v", got, want)
  82. }
  83. if got, want := js.Global().String(), "<object>"; got != want {
  84. t.Errorf("got %#v, want %#v", got, want)
  85. }
  86. if got, want := js.Global().Get("setTimeout").String(), "<function>"; got != want {
  87. t.Errorf("got %#v, want %#v", got, want)
  88. }
  89. }
  90. func TestInt(t *testing.T) {
  91. want := 42
  92. o := dummys.Get("someInt")
  93. if got := o.Int(); got != want {
  94. t.Errorf("got %#v, want %#v", got, want)
  95. }
  96. dummys.Set("otherInt", want)
  97. if got := dummys.Get("otherInt").Int(); got != want {
  98. t.Errorf("got %#v, want %#v", got, want)
  99. }
  100. if !dummys.Get("someInt").Equal(dummys.Get("someInt")) {
  101. t.Errorf("same value not equal")
  102. }
  103. if got := dummys.Get("zero").Int(); got != 0 {
  104. t.Errorf("got %#v, want %#v", got, 0)
  105. }
  106. }
  107. func TestIntConversion(t *testing.T) {
  108. testIntConversion(t, 0)
  109. testIntConversion(t, 1)
  110. testIntConversion(t, -1)
  111. testIntConversion(t, 1<<20)
  112. testIntConversion(t, -1<<20)
  113. testIntConversion(t, 1<<40)
  114. testIntConversion(t, -1<<40)
  115. testIntConversion(t, 1<<60)
  116. testIntConversion(t, -1<<60)
  117. }
  118. func testIntConversion(t *testing.T, want int) {
  119. if got := js.ValueOf(want).Int(); got != want {
  120. t.Errorf("got %#v, want %#v", got, want)
  121. }
  122. }
  123. func TestFloat(t *testing.T) {
  124. want := 42.123
  125. o := dummys.Get("someFloat")
  126. if got := o.Float(); got != want {
  127. t.Errorf("got %#v, want %#v", got, want)
  128. }
  129. dummys.Set("otherFloat", want)
  130. if got := dummys.Get("otherFloat").Float(); got != want {
  131. t.Errorf("got %#v, want %#v", got, want)
  132. }
  133. if !dummys.Get("someFloat").Equal(dummys.Get("someFloat")) {
  134. t.Errorf("same value not equal")
  135. }
  136. }
  137. func TestObject(t *testing.T) {
  138. if !dummys.Get("someArray").Equal(dummys.Get("someArray")) {
  139. t.Errorf("same value not equal")
  140. }
  141. // An object and its prototype should not be equal.
  142. proto := js.Global().Get("Object").Get("prototype")
  143. o := js.Global().Call("eval", "new Object()")
  144. if proto.Equal(o) {
  145. t.Errorf("object equals to its prototype")
  146. }
  147. }
  148. func TestFrozenObject(t *testing.T) {
  149. o := js.Global().Call("eval", "(function () { let o = new Object(); o.field = 5; Object.freeze(o); return o; })()")
  150. want := 5
  151. if got := o.Get("field").Int(); want != got {
  152. t.Errorf("got %#v, want %#v", got, want)
  153. }
  154. }
  155. func TestEqual(t *testing.T) {
  156. if !dummys.Get("someFloat").Equal(dummys.Get("someFloat")) {
  157. t.Errorf("same float is not equal")
  158. }
  159. if !dummys.Get("emptyObj").Equal(dummys.Get("emptyObj")) {
  160. t.Errorf("same object is not equal")
  161. }
  162. if dummys.Get("someFloat").Equal(dummys.Get("someInt")) {
  163. t.Errorf("different values are not unequal")
  164. }
  165. }
  166. func TestNaN(t *testing.T) {
  167. if !dummys.Get("NaN").IsNaN() {
  168. t.Errorf("JS NaN is not NaN")
  169. }
  170. if !js.ValueOf(math.NaN()).IsNaN() {
  171. t.Errorf("Go NaN is not NaN")
  172. }
  173. if dummys.Get("NaN").Equal(dummys.Get("NaN")) {
  174. t.Errorf("NaN is equal to NaN")
  175. }
  176. }
  177. func TestUndefined(t *testing.T) {
  178. if !js.Undefined().IsUndefined() {
  179. t.Errorf("undefined is not undefined")
  180. }
  181. if !js.Undefined().Equal(js.Undefined()) {
  182. t.Errorf("undefined is not equal to undefined")
  183. }
  184. if dummys.IsUndefined() {
  185. t.Errorf("object is undefined")
  186. }
  187. if js.Undefined().IsNull() {
  188. t.Errorf("undefined is null")
  189. }
  190. if dummys.Set("test", js.Undefined()); !dummys.Get("test").IsUndefined() {
  191. t.Errorf("could not set undefined")
  192. }
  193. }
  194. func TestNull(t *testing.T) {
  195. if !js.Null().IsNull() {
  196. t.Errorf("null is not null")
  197. }
  198. if !js.Null().Equal(js.Null()) {
  199. t.Errorf("null is not equal to null")
  200. }
  201. if dummys.IsNull() {
  202. t.Errorf("object is null")
  203. }
  204. if js.Null().IsUndefined() {
  205. t.Errorf("null is undefined")
  206. }
  207. if dummys.Set("test", js.Null()); !dummys.Get("test").IsNull() {
  208. t.Errorf("could not set null")
  209. }
  210. if dummys.Set("test", nil); !dummys.Get("test").IsNull() {
  211. t.Errorf("could not set nil")
  212. }
  213. }
  214. func TestLength(t *testing.T) {
  215. if got := dummys.Get("someArray").Length(); got != 3 {
  216. t.Errorf("got %#v, want %#v", got, 3)
  217. }
  218. }
  219. func TestGet(t *testing.T) {
  220. // positive cases get tested per type
  221. expectValueError(t, func() {
  222. dummys.Get("zero").Get("badField")
  223. })
  224. }
  225. func TestSet(t *testing.T) {
  226. // positive cases get tested per type
  227. expectValueError(t, func() {
  228. dummys.Get("zero").Set("badField", 42)
  229. })
  230. }
  231. func TestDelete(t *testing.T) {
  232. dummys.Set("test", 42)
  233. dummys.Delete("test")
  234. if dummys.Call("hasOwnProperty", "test").Bool() {
  235. t.Errorf("property still exists")
  236. }
  237. expectValueError(t, func() {
  238. dummys.Get("zero").Delete("badField")
  239. })
  240. }
  241. func TestIndex(t *testing.T) {
  242. if got := dummys.Get("someArray").Index(1).Int(); got != 42 {
  243. t.Errorf("got %#v, want %#v", got, 42)
  244. }
  245. expectValueError(t, func() {
  246. dummys.Get("zero").Index(1)
  247. })
  248. }
  249. func TestSetIndex(t *testing.T) {
  250. dummys.Get("someArray").SetIndex(2, 99)
  251. if got := dummys.Get("someArray").Index(2).Int(); got != 99 {
  252. t.Errorf("got %#v, want %#v", got, 99)
  253. }
  254. expectValueError(t, func() {
  255. dummys.Get("zero").SetIndex(2, 99)
  256. })
  257. }
  258. func TestCall(t *testing.T) {
  259. var i int64 = 40
  260. if got := dummys.Call("add", i, 2).Int(); got != 42 {
  261. t.Errorf("got %#v, want %#v", got, 42)
  262. }
  263. if got := dummys.Call("add", js.Global().Call("eval", "40"), 2).Int(); got != 42 {
  264. t.Errorf("got %#v, want %#v", got, 42)
  265. }
  266. expectPanic(t, func() {
  267. dummys.Call("zero")
  268. })
  269. expectValueError(t, func() {
  270. dummys.Get("zero").Call("badMethod")
  271. })
  272. }
  273. func TestInvoke(t *testing.T) {
  274. var i int64 = 40
  275. if got := dummys.Get("add").Invoke(i, 2).Int(); got != 42 {
  276. t.Errorf("got %#v, want %#v", got, 42)
  277. }
  278. expectValueError(t, func() {
  279. dummys.Get("zero").Invoke()
  280. })
  281. }
  282. func TestNew(t *testing.T) {
  283. if got := js.Global().Get("Array").New(42).Length(); got != 42 {
  284. t.Errorf("got %#v, want %#v", got, 42)
  285. }
  286. expectValueError(t, func() {
  287. dummys.Get("zero").New()
  288. })
  289. }
  290. func TestInstanceOf(t *testing.T) {
  291. someArray := js.Global().Get("Array").New()
  292. if got, want := someArray.InstanceOf(js.Global().Get("Array")), true; got != want {
  293. t.Errorf("got %#v, want %#v", got, want)
  294. }
  295. if got, want := someArray.InstanceOf(js.Global().Get("Function")), false; got != want {
  296. t.Errorf("got %#v, want %#v", got, want)
  297. }
  298. }
  299. func TestType(t *testing.T) {
  300. if got, want := js.Undefined().Type(), js.TypeUndefined; got != want {
  301. t.Errorf("got %s, want %s", got, want)
  302. }
  303. if got, want := js.Null().Type(), js.TypeNull; got != want {
  304. t.Errorf("got %s, want %s", got, want)
  305. }
  306. if got, want := js.ValueOf(true).Type(), js.TypeBoolean; got != want {
  307. t.Errorf("got %s, want %s", got, want)
  308. }
  309. if got, want := js.ValueOf(0).Type(), js.TypeNumber; got != want {
  310. t.Errorf("got %s, want %s", got, want)
  311. }
  312. if got, want := js.ValueOf(42).Type(), js.TypeNumber; got != want {
  313. t.Errorf("got %s, want %s", got, want)
  314. }
  315. if got, want := js.ValueOf("test").Type(), js.TypeString; got != want {
  316. t.Errorf("got %s, want %s", got, want)
  317. }
  318. if got, want := js.Global().Get("Symbol").Invoke("test").Type(), js.TypeSymbol; got != want {
  319. t.Errorf("got %s, want %s", got, want)
  320. }
  321. if got, want := js.Global().Get("Array").New().Type(), js.TypeObject; got != want {
  322. t.Errorf("got %s, want %s", got, want)
  323. }
  324. if got, want := js.Global().Get("Array").Type(), js.TypeFunction; got != want {
  325. t.Errorf("got %s, want %s", got, want)
  326. }
  327. }
  328. type object = map[string]any
  329. type array = []any
  330. func TestValueOf(t *testing.T) {
  331. a := js.ValueOf(array{0, array{0, 42, 0}, 0})
  332. if got := a.Index(1).Index(1).Int(); got != 42 {
  333. t.Errorf("got %v, want %v", got, 42)
  334. }
  335. o := js.ValueOf(object{"x": object{"y": 42}})
  336. if got := o.Get("x").Get("y").Int(); got != 42 {
  337. t.Errorf("got %v, want %v", got, 42)
  338. }
  339. }
  340. func TestZeroValue(t *testing.T) {
  341. var v js.Value
  342. if !v.IsUndefined() {
  343. t.Error("zero js.Value is not js.Undefined()")
  344. }
  345. }
  346. func TestFuncOf(t *testing.T) {
  347. c := make(chan struct{})
  348. cb := js.FuncOf(func(this js.Value, args []js.Value) any {
  349. if got := args[0].Int(); got != 42 {
  350. t.Errorf("got %#v, want %#v", got, 42)
  351. }
  352. c <- struct{}{}
  353. return nil
  354. })
  355. defer cb.Release()
  356. js.Global().Call("setTimeout", cb, 0, 42)
  357. <-c
  358. }
  359. func TestInvokeFunction(t *testing.T) {
  360. called := false
  361. cb := js.FuncOf(func(this js.Value, args []js.Value) any {
  362. cb2 := js.FuncOf(func(this js.Value, args []js.Value) any {
  363. called = true
  364. return 42
  365. })
  366. defer cb2.Release()
  367. return cb2.Invoke()
  368. })
  369. defer cb.Release()
  370. if got := cb.Invoke().Int(); got != 42 {
  371. t.Errorf("got %#v, want %#v", got, 42)
  372. }
  373. if !called {
  374. t.Error("function not called")
  375. }
  376. }
  377. func TestInterleavedFunctions(t *testing.T) {
  378. c1 := make(chan struct{})
  379. c2 := make(chan struct{})
  380. js.Global().Get("setTimeout").Invoke(js.FuncOf(func(this js.Value, args []js.Value) any {
  381. c1 <- struct{}{}
  382. <-c2
  383. return nil
  384. }), 0)
  385. <-c1
  386. c2 <- struct{}{}
  387. // this goroutine is running, but the callback of setTimeout did not return yet, invoke another function now
  388. f := js.FuncOf(func(this js.Value, args []js.Value) any {
  389. return nil
  390. })
  391. f.Invoke()
  392. }
  393. func ExampleFuncOf() {
  394. var cb js.Func
  395. cb = js.FuncOf(func(this js.Value, args []js.Value) any {
  396. fmt.Println("button clicked")
  397. cb.Release() // release the function if the button will not be clicked again
  398. return nil
  399. })
  400. js.Global().Get("document").Call("getElementById", "myButton").Call("addEventListener", "click", cb)
  401. }
  402. // See
  403. // - https://developer.mozilla.org/en-US/docs/Glossary/Truthy
  404. // - https://stackoverflow.com/questions/19839952/all-falsey-values-in-javascript/19839953#19839953
  405. // - http://www.ecma-international.org/ecma-262/5.1/#sec-9.2
  406. func TestTruthy(t *testing.T) {
  407. want := true
  408. for _, key := range []string{
  409. "someBool", "someString", "someInt", "someFloat", "someArray", "someDate",
  410. "stringZero", // "0" is truthy
  411. "add", // functions are truthy
  412. "emptyObj", "emptyArray", "Infinity", "NegInfinity",
  413. // All objects are truthy, even if they're Number(0) or Boolean(false).
  414. "objNumber0", "objBooleanFalse",
  415. } {
  416. if got := dummys.Get(key).Truthy(); got != want {
  417. t.Errorf("%s: got %#v, want %#v", key, got, want)
  418. }
  419. }
  420. want = false
  421. if got := dummys.Get("zero").Truthy(); got != want {
  422. t.Errorf("got %#v, want %#v", got, want)
  423. }
  424. if got := dummys.Get("NaN").Truthy(); got != want {
  425. t.Errorf("got %#v, want %#v", got, want)
  426. }
  427. if got := js.ValueOf("").Truthy(); got != want {
  428. t.Errorf("got %#v, want %#v", got, want)
  429. }
  430. if got := js.Null().Truthy(); got != want {
  431. t.Errorf("got %#v, want %#v", got, want)
  432. }
  433. if got := js.Undefined().Truthy(); got != want {
  434. t.Errorf("got %#v, want %#v", got, want)
  435. }
  436. }
  437. func expectValueError(t *testing.T, fn func()) {
  438. defer func() {
  439. err := recover()
  440. if _, ok := err.(*js.ValueError); !ok {
  441. t.Errorf("expected *js.ValueError, got %T", err)
  442. }
  443. }()
  444. fn()
  445. }
  446. func expectPanic(t *testing.T, fn func()) {
  447. defer func() {
  448. err := recover()
  449. if err == nil {
  450. t.Errorf("expected panic")
  451. }
  452. }()
  453. fn()
  454. }
  455. var copyTests = []struct {
  456. srcLen int
  457. dstLen int
  458. copyLen int
  459. }{
  460. {5, 3, 3},
  461. {3, 5, 3},
  462. {0, 0, 0},
  463. }
  464. func TestCopyBytesToGo(t *testing.T) {
  465. for _, tt := range copyTests {
  466. t.Run(fmt.Sprintf("%d-to-%d", tt.srcLen, tt.dstLen), func(t *testing.T) {
  467. src := js.Global().Get("Uint8Array").New(tt.srcLen)
  468. if tt.srcLen >= 2 {
  469. src.SetIndex(1, 42)
  470. }
  471. dst := make([]byte, tt.dstLen)
  472. if got, want := js.CopyBytesToGo(dst, src), tt.copyLen; got != want {
  473. t.Errorf("copied %d, want %d", got, want)
  474. }
  475. if tt.dstLen >= 2 {
  476. if got, want := int(dst[1]), 42; got != want {
  477. t.Errorf("got %d, want %d", got, want)
  478. }
  479. }
  480. })
  481. }
  482. }
  483. func TestCopyBytesToJS(t *testing.T) {
  484. for _, tt := range copyTests {
  485. t.Run(fmt.Sprintf("%d-to-%d", tt.srcLen, tt.dstLen), func(t *testing.T) {
  486. src := make([]byte, tt.srcLen)
  487. if tt.srcLen >= 2 {
  488. src[1] = 42
  489. }
  490. dst := js.Global().Get("Uint8Array").New(tt.dstLen)
  491. if got, want := js.CopyBytesToJS(dst, src), tt.copyLen; got != want {
  492. t.Errorf("copied %d, want %d", got, want)
  493. }
  494. if tt.dstLen >= 2 {
  495. if got, want := dst.Index(1).Int(), 42; got != want {
  496. t.Errorf("got %d, want %d", got, want)
  497. }
  498. }
  499. })
  500. }
  501. }
  502. func TestGarbageCollection(t *testing.T) {
  503. before := js.JSGo.Get("_values").Length()
  504. for i := 0; i < 1000; i++ {
  505. _ = js.Global().Get("Object").New().Call("toString").String()
  506. runtime.GC()
  507. }
  508. after := js.JSGo.Get("_values").Length()
  509. if after-before > 500 {
  510. t.Errorf("garbage collection ineffective")
  511. }
  512. }
  513. // BenchmarkDOM is a simple benchmark which emulates a webapp making DOM operations.
  514. // It creates a div, and sets its id. Then searches by that id and sets some data.
  515. // Finally it removes that div.
  516. func BenchmarkDOM(b *testing.B) {
  517. document := js.Global().Get("document")
  518. if document.IsUndefined() {
  519. b.Skip("Not a browser environment. Skipping.")
  520. }
  521. const data = "someString"
  522. for i := 0; i < b.N; i++ {
  523. div := document.Call("createElement", "div")
  524. div.Call("setAttribute", "id", "myDiv")
  525. document.Get("body").Call("appendChild", div)
  526. myDiv := document.Call("getElementById", "myDiv")
  527. myDiv.Set("innerHTML", data)
  528. if got, want := myDiv.Get("innerHTML").String(), data; got != want {
  529. b.Errorf("got %s, want %s", got, want)
  530. }
  531. document.Get("body").Call("removeChild", div)
  532. }
  533. }
  534. func TestGlobal(t *testing.T) {
  535. ident := js.FuncOf(func(this js.Value, args []js.Value) any {
  536. return args[0]
  537. })
  538. defer ident.Release()
  539. if got := ident.Invoke(js.Global()); !got.Equal(js.Global()) {
  540. t.Errorf("got %#v, want %#v", got, js.Global())
  541. }
  542. }