github.com/nuvolaris/goja@v0.0.0-20230825100449-967811910c6d/object_test.go (about) 1 package goja 2 3 import ( 4 "fmt" 5 "reflect" 6 "strings" 7 "testing" 8 ) 9 10 func TestDefineProperty(t *testing.T) { 11 r := New() 12 o := r.NewObject() 13 14 err := o.DefineDataProperty("data", r.ToValue(42), FLAG_TRUE, FLAG_TRUE, FLAG_TRUE) 15 if err != nil { 16 t.Fatal(err) 17 } 18 19 err = o.DefineAccessorProperty("accessor_ro", r.ToValue(func() int { 20 return 1 21 }), nil, FLAG_TRUE, FLAG_TRUE) 22 if err != nil { 23 t.Fatal(err) 24 } 25 26 err = o.DefineAccessorProperty("accessor_rw", 27 r.ToValue(func(call FunctionCall) Value { 28 return o.Get("__hidden") 29 }), 30 r.ToValue(func(call FunctionCall) (ret Value) { 31 o.Set("__hidden", call.Argument(0)) 32 return 33 }), 34 FLAG_TRUE, FLAG_TRUE) 35 36 if err != nil { 37 t.Fatal(err) 38 } 39 40 if v := o.Get("accessor_ro"); v.ToInteger() != 1 { 41 t.Fatalf("Unexpected accessor value: %v", v) 42 } 43 44 err = o.Set("accessor_ro", r.ToValue(2)) 45 if err == nil { 46 t.Fatal("Expected an error") 47 } 48 if ex, ok := err.(*Exception); ok { 49 if msg := ex.Error(); msg != "TypeError: Cannot assign to read only property 'accessor_ro'" { 50 t.Fatalf("Unexpected error: '%s'", msg) 51 } 52 } else { 53 t.Fatalf("Unexected error type: %T", err) 54 } 55 56 err = o.Set("accessor_rw", 42) 57 if err != nil { 58 t.Fatal(err) 59 } 60 61 if v := o.Get("accessor_rw"); v.ToInteger() != 42 { 62 t.Fatalf("Unexpected value: %v", v) 63 } 64 } 65 66 func TestPropertyOrder(t *testing.T) { 67 const SCRIPT = ` 68 var o = {}; 69 var sym1 = Symbol(1); 70 var sym2 = Symbol(2); 71 o[sym2] = 1; 72 o[4294967294] = 1; 73 o[2] = 1; 74 o[1] = 1; 75 o[0] = 1; 76 o["02"] = 1; 77 o[4294967295] = 1; 78 o["01"] = 1; 79 o["00"] = 1; 80 o[sym1] = 1; 81 var expected = ["0", "1", "2", "4294967294", "02", "4294967295", "01", "00", sym2, sym1]; 82 var actual = Reflect.ownKeys(o); 83 if (actual.length !== expected.length) { 84 throw new Error("Unexpected length: "+actual.length); 85 } 86 for (var i = 0; i < actual.length; i++) { 87 if (actual[i] !== expected[i]) { 88 throw new Error("Unexpected list: " + actual); 89 } 90 } 91 ` 92 93 testScript(SCRIPT, _undefined, t) 94 } 95 96 func TestDefinePropertiesSymbol(t *testing.T) { 97 const SCRIPT = ` 98 var desc = {}; 99 desc[Symbol.toStringTag] = {value: "Test"}; 100 var o = {}; 101 Object.defineProperties(o, desc); 102 o[Symbol.toStringTag] === "Test"; 103 ` 104 105 testScript(SCRIPT, valueTrue, t) 106 } 107 108 func TestObjectShorthandProperties(t *testing.T) { 109 const SCRIPT = ` 110 var b = 1; 111 var a = {b, get() {return "c"}}; 112 113 assert.sameValue(a.b, b, "#1"); 114 assert.sameValue(a.get(), "c", "#2"); 115 116 var obj = { 117 w\u0069th() { return 42; } 118 }; 119 120 assert.sameValue(obj['with'](), 42, 'property exists'); 121 ` 122 testScriptWithTestLib(SCRIPT, _undefined, t) 123 } 124 125 func TestObjectAssign(t *testing.T) { 126 const SCRIPT = ` 127 assert.sameValue(Object.assign({ b: 1 }, { get a() { 128 Object.defineProperty(this, "b", { 129 value: 3, 130 enumerable: false 131 }); 132 }, b: 2 }).b, 1, "#1"); 133 134 assert.sameValue(Object.assign({ b: 1 }, { get a() { 135 delete this.b; 136 }, b: 2 }).b, 1, "#2"); 137 ` 138 testScriptWithTestLib(SCRIPT, _undefined, t) 139 } 140 141 func TestExportCircular(t *testing.T) { 142 vm := New() 143 o := vm.NewObject() 144 o.Set("o", o) 145 v := o.Export() 146 if m, ok := v.(map[string]interface{}); ok { 147 if reflect.ValueOf(m["o"]).Pointer() != reflect.ValueOf(v).Pointer() { 148 t.Fatal("Unexpected value") 149 } 150 } else { 151 t.Fatal("Unexpected type") 152 } 153 154 res, err := vm.RunString(`var a = []; a[0] = a;`) 155 if err != nil { 156 t.Fatal(err) 157 } 158 v = res.Export() 159 if a, ok := v.([]interface{}); ok { 160 if reflect.ValueOf(a[0]).Pointer() != reflect.ValueOf(v).Pointer() { 161 t.Fatal("Unexpected value") 162 } 163 } else { 164 t.Fatal("Unexpected type") 165 } 166 } 167 168 type test_s struct { 169 S *test_s1 170 } 171 type test_s1 struct { 172 S *test_s 173 } 174 175 func TestExportToCircular(t *testing.T) { 176 vm := New() 177 o := vm.NewObject() 178 o.Set("o", o) 179 var m map[string]interface{} 180 err := vm.ExportTo(o, &m) 181 if err != nil { 182 t.Fatal(err) 183 } 184 185 type K string 186 type T map[K]T 187 var m1 T 188 err = vm.ExportTo(o, &m1) 189 if err != nil { 190 t.Fatal(err) 191 } 192 193 type A []A 194 var a A 195 res, err := vm.RunString("var a = []; a[0] = a;") 196 if err != nil { 197 t.Fatal(err) 198 } 199 err = vm.ExportTo(res, &a) 200 if err != nil { 201 t.Fatal(err) 202 } 203 if &a[0] != &a[0][0] { 204 t.Fatal("values do not match") 205 } 206 207 o = vm.NewObject() 208 o.Set("S", o) 209 var s test_s 210 err = vm.ExportTo(o, &s) 211 if err != nil { 212 t.Fatal(err) 213 } 214 if s.S.S != &s { 215 t.Fatalf("values do not match: %v, %v", s.S.S, &s) 216 } 217 218 type test_s2 struct { 219 S interface{} 220 S1 *test_s2 221 } 222 223 var s2 test_s2 224 o.Set("S1", o) 225 226 err = vm.ExportTo(o, &s2) 227 if err != nil { 228 t.Fatal(err) 229 } 230 231 if m, ok := s2.S.(map[string]interface{}); ok { 232 if reflect.ValueOf(m["S"]).Pointer() != reflect.ValueOf(m).Pointer() { 233 t.Fatal("Unexpected m.S") 234 } 235 } else { 236 t.Fatalf("Unexpected s2.S type: %T", s2.S) 237 } 238 if s2.S1 != &s2 { 239 t.Fatal("Unexpected s2.S1") 240 } 241 242 o1 := vm.NewObject() 243 o1.Set("S", o) 244 o1.Set("S1", o) 245 err = vm.ExportTo(o1, &s2) 246 if err != nil { 247 t.Fatal(err) 248 } 249 if s2.S1.S1 != s2.S1 { 250 t.Fatal("Unexpected s2.S1.S1") 251 } 252 } 253 254 func TestExportWrappedMap(t *testing.T) { 255 vm := New() 256 m := map[string]interface{}{ 257 "test": "failed", 258 } 259 exported := vm.ToValue(m).Export() 260 if exportedMap, ok := exported.(map[string]interface{}); ok { 261 exportedMap["test"] = "passed" 262 if v := m["test"]; v != "passed" { 263 t.Fatalf("Unexpected m[\"test\"]: %v", v) 264 } 265 } else { 266 t.Fatalf("Unexpected export type: %T", exported) 267 } 268 } 269 270 func TestExportToWrappedMap(t *testing.T) { 271 vm := New() 272 m := map[string]interface{}{ 273 "test": "failed", 274 } 275 var exported map[string]interface{} 276 err := vm.ExportTo(vm.ToValue(m), &exported) 277 if err != nil { 278 t.Fatal(err) 279 } 280 exported["test"] = "passed" 281 if v := m["test"]; v != "passed" { 282 t.Fatalf("Unexpected m[\"test\"]: %v", v) 283 } 284 } 285 286 func TestExportToWrappedMapCustom(t *testing.T) { 287 type CustomMap map[string]bool 288 vm := New() 289 m := CustomMap{} 290 var exported CustomMap 291 err := vm.ExportTo(vm.ToValue(m), &exported) 292 if err != nil { 293 t.Fatal(err) 294 } 295 exported["test"] = true 296 if v := m["test"]; v != true { 297 t.Fatalf("Unexpected m[\"test\"]: %v", v) 298 } 299 } 300 301 func TestExportToSliceNonIterable(t *testing.T) { 302 vm := New() 303 o := vm.NewObject() 304 var a []interface{} 305 err := vm.ExportTo(o, &a) 306 if err == nil { 307 t.Fatal("Expected an error") 308 } 309 if len(a) != 0 { 310 t.Fatalf("a: %v", a) 311 } 312 if msg := err.Error(); msg != "cannot convert [object Object] to []interface {}: not an array or iterable" { 313 t.Fatalf("Unexpected error: %v", err) 314 } 315 } 316 317 func ExampleRuntime_ExportTo_iterableToSlice() { 318 vm := New() 319 v, err := vm.RunString(` 320 function reverseIterator() { 321 const arr = this; 322 let idx = arr.length; 323 return { 324 next: () => idx > 0 ? {value: arr[--idx]} : {done: true} 325 } 326 } 327 const arr = [1,2,3]; 328 arr[Symbol.iterator] = reverseIterator; 329 arr; 330 `) 331 if err != nil { 332 panic(err) 333 } 334 335 var arr []int 336 err = vm.ExportTo(v, &arr) 337 if err != nil { 338 panic(err) 339 } 340 341 fmt.Println(arr) 342 // Output: [3 2 1] 343 } 344 345 func TestRuntime_ExportTo_proxiedIterableToSlice(t *testing.T) { 346 vm := New() 347 v, err := vm.RunString(` 348 function reverseIterator() { 349 const arr = this; 350 let idx = arr.length; 351 return { 352 next: () => idx > 0 ? {value: arr[--idx]} : {done: true} 353 } 354 } 355 const arr = [1,2,3]; 356 arr[Symbol.iterator] = reverseIterator; 357 new Proxy(arr, {}); 358 `) 359 if err != nil { 360 t.Fatal(err) 361 } 362 363 var arr []int 364 err = vm.ExportTo(v, &arr) 365 if err != nil { 366 t.Fatal(err) 367 } 368 if out := fmt.Sprint(arr); out != "[3 2 1]" { 369 t.Fatal(out) 370 } 371 } 372 373 func ExampleRuntime_ExportTo_arrayLikeToSlice() { 374 vm := New() 375 v, err := vm.RunString(` 376 ({ 377 length: 3, 378 0: 1, 379 1: 2, 380 2: 3 381 }); 382 `) 383 if err != nil { 384 panic(err) 385 } 386 387 var arr []int 388 err = vm.ExportTo(v, &arr) 389 if err != nil { 390 panic(err) 391 } 392 393 fmt.Println(arr) 394 // Output: [1 2 3] 395 } 396 397 func TestExportArrayToArrayMismatchedLengths(t *testing.T) { 398 vm := New() 399 a := vm.NewArray(1, 2) 400 var a1 [3]int 401 err := vm.ExportTo(a, &a1) 402 if err == nil { 403 t.Fatal("expected error") 404 } 405 if msg := err.Error(); !strings.Contains(msg, "lengths mismatch") { 406 t.Fatalf("unexpected error: %v", err) 407 } 408 } 409 410 func TestExportIterableToArrayMismatchedLengths(t *testing.T) { 411 vm := New() 412 a, err := vm.RunString(` 413 new Map([[1, true], [2, true]]); 414 `) 415 if err != nil { 416 t.Fatal(err) 417 } 418 419 var a1 [3]interface{} 420 err = vm.ExportTo(a, &a1) 421 if err == nil { 422 t.Fatal("expected error") 423 } 424 if msg := err.Error(); !strings.Contains(msg, "lengths mismatch") { 425 t.Fatalf("unexpected error: %v", err) 426 } 427 } 428 429 func TestExportArrayLikeToArrayMismatchedLengths(t *testing.T) { 430 vm := New() 431 a, err := vm.RunString(` 432 ({ 433 length: 2, 434 0: true, 435 1: true 436 }); 437 `) 438 if err != nil { 439 t.Fatal(err) 440 } 441 442 var a1 [3]interface{} 443 err = vm.ExportTo(a, &a1) 444 if err == nil { 445 t.Fatal("expected error") 446 } 447 if msg := err.Error(); !strings.Contains(msg, "lengths mismatch") { 448 t.Fatalf("unexpected error: %v", err) 449 } 450 } 451 452 func TestSetForeignReturnValue(t *testing.T) { 453 const SCRIPT = ` 454 var array = [1, 2, 3]; 455 var arrayTarget = new Proxy(array, {}); 456 457 Object.preventExtensions(array); 458 459 !Reflect.set(arrayTarget, "foo", 2); 460 ` 461 462 testScript(SCRIPT, valueTrue, t) 463 } 464 465 func TestDefinePropertiesUndefinedVal(t *testing.T) { 466 const SCRIPT = ` 467 var target = {}; 468 var sym = Symbol(); 469 target[sym] = 1; 470 target.foo = 2; 471 target[0] = 3; 472 473 var getOwnKeys = []; 474 var proxy = new Proxy(target, { 475 getOwnPropertyDescriptor: function(_target, key) { 476 getOwnKeys.push(key); 477 }, 478 }); 479 480 Object.defineProperties({}, proxy); 481 true; 482 ` 483 484 testScript(SCRIPT, valueTrue, t) 485 } 486 487 func ExampleObject_Delete() { 488 vm := New() 489 obj := vm.NewObject() 490 _ = obj.Set("test", true) 491 before := obj.Get("test") 492 _ = obj.Delete("test") 493 after := obj.Get("test") 494 fmt.Printf("before: %v, after: %v", before, after) 495 // Output: before: true, after: <nil> 496 } 497 498 func TestObjectEquality(t *testing.T) { 499 type CustomInt int 500 type S struct { 501 F CustomInt 502 } 503 vm := New() 504 vm.Set("s", S{}) 505 // indexOf() and includes() use different equality checks (StrictEquals and SameValueZero respectively), 506 // but for objects they must behave the same. De-referencing s.F creates a new wrapper each time. 507 vm.testScriptWithTestLib(` 508 assert.sameValue([s.F].indexOf(s.F), 0, "indexOf"); 509 assert([s.F].includes(s.F)); 510 `, _undefined, t) 511 } 512 513 func BenchmarkPut(b *testing.B) { 514 v := &Object{} 515 516 o := &baseObject{ 517 val: v, 518 extensible: true, 519 } 520 v.self = o 521 522 o.init() 523 524 var key Value = asciiString("test") 525 var val Value = valueInt(123) 526 527 for i := 0; i < b.N; i++ { 528 v.setOwn(key, val, false) 529 } 530 } 531 532 func BenchmarkPutStr(b *testing.B) { 533 v := &Object{} 534 535 o := &baseObject{ 536 val: v, 537 extensible: true, 538 } 539 540 o.init() 541 542 v.self = o 543 544 var val Value = valueInt(123) 545 546 for i := 0; i < b.N; i++ { 547 o.setOwnStr("test", val, false) 548 } 549 } 550 551 func BenchmarkGet(b *testing.B) { 552 v := &Object{} 553 554 o := &baseObject{ 555 val: v, 556 extensible: true, 557 } 558 559 o.init() 560 561 v.self = o 562 var n Value = asciiString("test") 563 564 for i := 0; i < b.N; i++ { 565 v.get(n, nil) 566 } 567 568 } 569 570 func BenchmarkGetStr(b *testing.B) { 571 v := &Object{} 572 573 o := &baseObject{ 574 val: v, 575 extensible: true, 576 } 577 v.self = o 578 579 o.init() 580 581 for i := 0; i < b.N; i++ { 582 o.getStr("test", nil) 583 } 584 } 585 586 func __toString(v Value) string { 587 switch v := v.(type) { 588 case asciiString: 589 return string(v) 590 default: 591 return "" 592 } 593 } 594 595 func BenchmarkToString1(b *testing.B) { 596 v := asciiString("test") 597 598 for i := 0; i < b.N; i++ { 599 v.toString() 600 } 601 } 602 603 func BenchmarkToString2(b *testing.B) { 604 v := asciiString("test") 605 606 for i := 0; i < b.N; i++ { 607 __toString(v) 608 } 609 } 610 611 func BenchmarkConv(b *testing.B) { 612 count := int64(0) 613 for i := 0; i < b.N; i++ { 614 count += valueInt(123).ToInteger() 615 } 616 if count == 0 { 617 b.Fatal("zero") 618 } 619 } 620 621 func BenchmarkToUTF8String(b *testing.B) { 622 var s String = asciiString("test") 623 for i := 0; i < b.N; i++ { 624 _ = s.String() 625 } 626 } 627 628 func BenchmarkAdd(b *testing.B) { 629 var x, y Value 630 x = valueInt(2) 631 y = valueInt(2) 632 633 for i := 0; i < b.N; i++ { 634 if xi, ok := x.(valueInt); ok { 635 if yi, ok := y.(valueInt); ok { 636 x = xi + yi 637 } 638 } 639 } 640 } 641 642 func BenchmarkAddString(b *testing.B) { 643 var x, y Value 644 645 tst := asciiString("22") 646 x = asciiString("2") 647 y = asciiString("2") 648 649 for i := 0; i < b.N; i++ { 650 var z Value 651 if xi, ok := x.(String); ok { 652 if yi, ok := y.(String); ok { 653 z = xi.Concat(yi) 654 } 655 } 656 if !z.StrictEquals(tst) { 657 b.Fatalf("Unexpected result %v", x) 658 } 659 } 660 }