test_observablearray_proxyhandler.html (30415B)
1 <!-- Any copyright is dedicated to the Public Domain. 2 - http://creativecommons.org/publicdomain/zero/1.0/ --> 3 <!DOCTYPE HTML> 4 <html> 5 <head> 6 <title>Test Observable Array Type</title> 7 <script src="/tests/SimpleTest/SimpleTest.js"></script> 8 <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> 9 </head> 10 <body> 11 <script> 12 /* global TestInterfaceObservableArray */ 13 14 add_task(async function init() { 15 await SpecialPowers.pushPrefEnv({set: [["dom.expose_test_interfaces", true]]}); 16 }); 17 18 add_task(function testObservableArrayExoticObjects_defineProperty() { 19 let setCallbackCount = 0; 20 let deleteCallbackCount = 0; 21 let setCallbackTests = null; 22 let deleteCallbackTests = null; 23 24 let m = new TestInterfaceObservableArray({ 25 setBooleanCallback(value, index) { 26 setCallbackCount++; 27 if (typeof setCallbackTests === 'function') { 28 setCallbackTests(value, index); 29 } 30 }, 31 deleteBooleanCallback(value, index) { 32 deleteCallbackCount++; 33 if (typeof deleteCallbackTests === 'function') { 34 deleteCallbackTests(value, index); 35 } 36 }, 37 }); 38 m.observableArrayBoolean = [true, true, true]; 39 40 let b = m.observableArrayBoolean; 41 ok(Array.isArray(b), "observable array should be an array type"); 42 is(b.length, 3, "length of observable array should be 0"); 43 44 // Test length 45 [ 46 // [descriptor, shouldThrow, expectedResult] 47 // Invalid descriptor 48 [{configurable: true, value: 0}, false, false], 49 [{enumerable: true, value: 0}, false, false], 50 [{writable: false, value: 0}, false, false], 51 [{get: ()=>{}}, false, false], 52 [{set: ()=>{}}, false, false], 53 [{get: ()=>{}, set: ()=>{}}, false, false], 54 [{get: ()=>{}, value: 0}, true], 55 // Invalid length value 56 [{value: 1.9}, true], 57 [{value: "invalid"}, true], 58 [{value: {}}, true], 59 // length value should not greater than current length 60 [{value: b.length + 1}, false, false], 61 // descriptor without value 62 [{configurable: false, enumerable: false, writable: true}, false, true], 63 // Success 64 [{value: b.length}, false, true], 65 [{value: b.length - 1}, false, true], 66 [{value: 0}, false, true], 67 ].forEach(function([descriptor, shouldThrow, expectedResult]) { 68 // Initialize 69 let oldLen = b.length; 70 let oldValues = b.slice(); 71 let deleteCallbackIndex = oldLen - 1; 72 let success = expectedResult && "value" in descriptor; 73 setCallbackCount = 0; 74 deleteCallbackCount = 0; 75 setCallbackTests = null; 76 deleteCallbackTests = function(_value, _index) { 77 is(_value, oldValues[deleteCallbackIndex], "deleteCallbackTests: test value argument"); 78 is(_index, deleteCallbackIndex, "deleteCallbackTests: test index argument"); 79 deleteCallbackIndex--; 80 }; 81 82 // Test 83 info(`defining "length" property with ${JSON.stringify(descriptor)}`); 84 try { 85 is(Reflect.defineProperty(b, "length", descriptor), expectedResult, 86 `Reflect.defineProperty should return ${expectedResult}`); 87 ok(!shouldThrow, "Reflect.defineProperty should not throw"); 88 } catch(e) { 89 ok(shouldThrow, `Reflect.defineProperty throws ${e}`); 90 } 91 is(setCallbackCount, 0, "setCallback count"); 92 is(deleteCallbackCount, success ? oldLen - descriptor.value : 0, "deleteCallback count"); 93 isDeeply(b, success ? oldValues.slice(0, descriptor.value) : oldValues, "property values"); 94 is(b.length, success ? descriptor.value : oldLen, "length of observable array"); 95 }); 96 97 // Test indexed value 98 [ 99 // [index, descriptor, shouldThrow, expectedResult] 100 // Invalid descriptor 101 [0, {configurable: false, value: true}, false, false], 102 [0, {enumerable: false, value: true}, false, false], 103 [0, {writable: false, value: true}, false, false], 104 [0, {get: ()=>{}}, false, false], 105 [0, {set: ()=>{}}, false, false], 106 [0, {get: ()=>{}, set: ()=>{}}, false, false], 107 [0, {get: ()=>{}, value: true}, true], 108 // Index could not greater than last index + 1. 109 [b.length + 1, {configurable: true, enumerable: true, value: true}, false, false], 110 // descriptor without value 111 [b.length, {configurable: true, enumerable: true}, false, true], 112 // Success 113 [b.length, {configurable: true, enumerable: true, value: true}, false, true], 114 [b.length + 1, {configurable: true, enumerable: true, value: true}, false, true], 115 ].forEach(function([index, descriptor, shouldThrow, expectedResult]) { 116 // Initialize 117 let oldLen = b.length; 118 let oldValue = b[index]; 119 let success = expectedResult && "value" in descriptor; 120 setCallbackCount = 0; 121 deleteCallbackCount = 0; 122 setCallbackTests = function(_value, _index) { 123 is(_value, descriptor.value, "setCallbackTests: test value argument"); 124 is(_index, index, "setCallbackTests: test index argument"); 125 }; 126 deleteCallbackTests = function(_value, _index) { 127 is(_value, oldValue, "deleteCallbackTests: test value argument"); 128 is(_index, index, "deleteCallbackTests: test index argument"); 129 }; 130 131 // Test 132 info(`defining ${index} property with ${JSON.stringify(descriptor)}`); 133 try { 134 is(Reflect.defineProperty(b, index, descriptor), expectedResult, 135 `Reflect.defineProperty should return ${expectedResult}`); 136 ok(!shouldThrow, "Reflect.defineProperty should not throw"); 137 } catch(e) { 138 ok(shouldThrow, `Reflect.defineProperty throws ${e}`); 139 } 140 is(setCallbackCount, success ? 1 : 0, "setCallback count"); 141 is(deleteCallbackCount, (oldLen > index) ? 1 : 0, "deleteCallback count"); 142 is(b[index], success ? descriptor.value : oldValue, "property value"); 143 is(b.length, success ? Math.max(index + 1, oldLen) : oldLen, "length of observable array"); 144 }); 145 146 // Test other property 147 [ 148 // [property, descriptor, shouldThrow, expectedResult] 149 ["prop1", {configurable: false, value: "value1"}, false, true], 150 ["prop1", {configurable: true, value: "value2"}, false, false], 151 ["prop2", {enumerable: false, value: 5}, false, true], 152 ["prop3", {enumerable: false, value: []}, false, true], 153 ["prop4", {enumerable: false, value: {}}, false, true], 154 ["prop5", {get: ()=>{}, value: true}, true, false], 155 ["prop6", {get: ()=>{}, set: ()=>{}}, false, true], 156 ].forEach(function([property, descriptor, shouldThrow, expectedResult]) { 157 // Initialize 158 let oldValue = b[property]; 159 let oldLen = b.length; 160 setCallbackCount = 0; 161 deleteCallbackCount = 0; 162 setCallbackTests = null; 163 deleteCallbackTests = null; 164 165 // Test 166 info(`defining ${property} property with ${JSON.stringify(descriptor)}`); 167 try { 168 is(Reflect.defineProperty(b, property, descriptor), expectedResult, 169 `Reflect.defineProperty should return ${expectedResult}`); 170 ok(!shouldThrow, "Reflect.defineProperty should not throw"); 171 } catch(e) { 172 ok(shouldThrow, `Reflect.defineProperty throws ${e}`); 173 } 174 is(setCallbackCount, 0, "setCallback count"); 175 is(deleteCallbackCount, 0, "deleteCallback count"); 176 is(b[property], expectedResult ? descriptor.value : oldValue, "property value"); 177 is(b.length, oldLen, "length of observable array"); 178 }); 179 }); 180 181 add_task(function testObservableArrayExoticObjects_defineProperty_callback_throw() { 182 let setCallbackCount = 0; 183 let deleteCallbackCount = 0; 184 185 const minLen = 3; 186 let m = new TestInterfaceObservableArray({ 187 setBooleanCallback(value) { 188 setCallbackCount++; 189 if (value) { 190 throw new Error("setBooleanCallback"); 191 } 192 }, 193 deleteBooleanCallback(value, index) { 194 deleteCallbackCount++; 195 if (index < minLen) { 196 throw new Error("deleteBooleanCallback"); 197 } 198 }, 199 }); 200 m.observableArrayBoolean = [false, false, false, false, false]; 201 202 let b = m.observableArrayBoolean; 203 ok(Array.isArray(b), "observable array should be an array type"); 204 is(b.length, 5, "length of observable array should be 3"); 205 206 // Test length 207 [ 208 // [length, shouldThrow] 209 [b.length, false], 210 [b.length - 1, false], 211 [0, true], 212 ].forEach(function([length, shouldThrow]) { 213 // Initialize 214 let oldValues = b.slice(); 215 let oldLen = b.length; 216 let descriptor = {value: length}; 217 setCallbackCount = 0; 218 deleteCallbackCount = 0; 219 220 // Test 221 info(`defining "length" property with ${JSON.stringify(descriptor)}`); 222 try { 223 ok(Reflect.defineProperty(b, "length", descriptor), 224 "Reflect.defineProperty should return true"); 225 ok(!shouldThrow, "Reflect.defineProperty should not throw"); 226 } catch(e) { 227 ok(shouldThrow, `Reflect.defineProperty throws ${e}`); 228 } 229 is(setCallbackCount, 0, "setCallback count"); 230 is(deleteCallbackCount, oldLen - (shouldThrow ? minLen - 1 : length), "deleteCallback count"); 231 isDeeply(b, oldValues.slice(0, shouldThrow ? minLen : length), "property values"); 232 is(b.length, shouldThrow ? minLen : length, "length of observable array"); 233 }); 234 235 // Test indexed value 236 [ 237 // [index, value, shouldThrow] 238 [b.length, true, true], 239 [b.length, false, false], 240 [b.length + 1, false, false], 241 [b.length + 1, true, true], 242 [0, true, true], 243 [0, false, true], 244 ].forEach(function([index, value, shouldThrow]) { 245 // Initialize 246 let oldValue = b[index]; 247 let oldLen = b.length; 248 let descriptor = {configurable: true, enumerable: true, value}; 249 setCallbackCount = 0; 250 deleteCallbackCount = 0; 251 252 // Test 253 info(`defining ${index} property with ${JSON.stringify(descriptor)}`); 254 try { 255 ok(Reflect.defineProperty(b, index, descriptor), "Reflect.defineProperty should return true"); 256 ok(!shouldThrow, "Reflect.defineProperty should not throw"); 257 } catch(e) { 258 ok(shouldThrow, `Reflect.defineProperty throws ${e}`); 259 } 260 is(setCallbackCount, (index < minLen) ? 0 : 1, "setCallback count"); 261 is(deleteCallbackCount, (oldLen > index) ? 1 : 0, "deleteCallback count"); 262 is(b[index], shouldThrow ? oldValue : value, "property value"); 263 is(b.length, shouldThrow ? oldLen : Math.max(oldLen, index + 1), "length of observable array"); 264 }); 265 266 // Test other property 267 [ 268 // [property, descriptor, expectedResult] 269 ["prop1", {configurable: false, value: "value1"}, true], 270 ["prop1", {configurable: true, value: "value2"}, false], 271 ["prop2", {enumerable: false, value: 5}, true], 272 ["prop3", {enumerable: false, value: []}, true], 273 ["prop4", {enumerable: false, value: {}}, true], 274 ].forEach(function([property, descriptor, expectedResult]) { 275 // Initialize 276 let oldValue = b[property]; 277 let oldLen = b.length; 278 setCallbackCount = 0; 279 deleteCallbackCount = 0; 280 281 // Test 282 info(`defining ${property} property with ${JSON.stringify(descriptor)}`); 283 try { 284 is(Reflect.defineProperty(b, property, descriptor), expectedResult, 285 `Reflect.defineProperty should return ${expectedResult}`); 286 ok(true, "Reflect.defineProperty should not throw"); 287 } catch(e) { 288 ok(false, `Reflect.defineProperty throws ${e}`); 289 } 290 is(setCallbackCount, 0, "setCallback count"); 291 is(deleteCallbackCount, 0, "deleteCallback count"); 292 is(b[property], expectedResult ? descriptor.value : oldValue, "property value"); 293 is(b.length, oldLen, "length of observable array"); 294 }); 295 }); 296 297 add_task(function testObservableArrayExoticObjects_deleteProperty() { 298 let setCallbackCount = 0; 299 let deleteCallbackCount = 0; 300 let deleteCallbackTests = null; 301 302 let m = new TestInterfaceObservableArray({ 303 setBooleanCallback() { 304 setCallbackCount++; 305 }, 306 deleteBooleanCallback(value, index) { 307 deleteCallbackCount++; 308 if (typeof deleteCallbackTests === 'function') { 309 deleteCallbackTests(value, index); 310 } 311 }, 312 }); 313 m.observableArrayBoolean = [true, true]; 314 315 let b = m.observableArrayBoolean; 316 ok(Array.isArray(b), "observable array should be an array type"); 317 is(b.length, 2, "length of observable array should be 2"); 318 319 // Test length 320 setCallbackCount = 0; 321 deleteCallbackCount = 0; 322 info("deleting length property"); 323 ok(!Reflect.deleteProperty(b, "length"), "test result of deleting length property"); 324 is(setCallbackCount, 0, "setCallback should not be called"); 325 is(deleteCallbackCount, 0, "deleteCallback should not be called"); 326 is(b.length, 2, "length should still be 2"); 327 328 // Test indexed value 329 [ 330 // [index, expectedResult] 331 [2, false], 332 [0, false], 333 [1, true], 334 ].forEach(function([index, expectedResult]) { 335 // Initialize 336 let oldLen = b.length; 337 let oldValue = b[index]; 338 setCallbackCount = 0; 339 deleteCallbackCount = 0; 340 deleteCallbackTests = function(_value, _index) { 341 is(_value, oldValue, "deleteCallbackTests: test value argument"); 342 is(_index, index, "deleteCallbackTests: test index argument"); 343 }; 344 345 // Test 346 info(`deleting ${index} property`); 347 is(Reflect.deleteProperty(b, index), expectedResult, 348 `Reflect.deleteProperty should return ${expectedResult}`); 349 is(setCallbackCount, 0, "setCallback count"); 350 is(deleteCallbackCount, expectedResult ? 1 : 0, "deleteCallback count"); 351 is(b[index], expectedResult ? undefined : oldValue, "property value"); 352 is(b.length, expectedResult ? oldLen - 1 : oldLen, 353 "length of observable array"); 354 }); 355 356 // Test other property 357 [ 358 // [property, value] 359 ["prop1", "value1"], 360 ["prop2", 5], 361 ["prop3", []], 362 ["prop4", {}], 363 ].forEach(function([property, value]) { 364 // Initialize 365 b[property] = value; 366 let oldLen = b.length; 367 setCallbackCount = 0; 368 deleteCallbackCount = 0; 369 deleteCallbackTests = null; 370 371 // Test 372 info(`deleting ${property} property`); 373 is(b[property], value, `property value should be ${value} before deleting`); 374 ok(Reflect.deleteProperty(b, property), "Reflect.deleteProperty should return true"); 375 is(setCallbackCount, 0, "setCallback count"); 376 is(deleteCallbackCount, 0, "deleteCallback count"); 377 is(b[property], undefined, "property value should be undefined after deleting"); 378 is(b.length, oldLen, "length of observable array"); 379 }); 380 }); 381 382 add_task(function testObservableArrayExoticObjects_deleteProperty_callback_throw() { 383 let setCallbackCount = 0; 384 let deleteCallbackCount = 0; 385 386 let m = new TestInterfaceObservableArray({ 387 setBooleanCallback() { 388 setCallbackCount++; 389 }, 390 deleteBooleanCallback(value) { 391 deleteCallbackCount++; 392 if (value) { 393 throw new Error("deleteBooleanCallback"); 394 } 395 }, 396 }); 397 m.observableArrayBoolean = [true, false]; 398 399 let b = m.observableArrayBoolean; 400 ok(Array.isArray(b), "observable array should be an array type"); 401 is(b.length, 2, "length of observable array should be 2"); 402 403 // Test indexed value 404 let index = b.length; 405 while (index--) { 406 // Initialize 407 let oldValue = b[index]; 408 let oldLen = b.length; 409 setCallbackCount = 0; 410 deleteCallbackCount = 0; 411 412 // Test 413 info(`deleting index ${index}`); 414 try { 415 ok(Reflect.deleteProperty(b, index), "Reflect.deleteProperty should return true"); 416 ok(!oldValue, "Reflect.deleteProperty should not throw"); 417 } catch(e) { 418 ok(oldValue, `Reflect.deleteProperty throws ${e}`); 419 } 420 is(setCallbackCount, 0, "setCallback count"); 421 is(deleteCallbackCount, 1, "deleteCallback count"); 422 is(b[index], oldValue ? oldValue : undefined, "property value"); 423 is(b.length, oldValue ? oldLen : oldLen - 1, "length of observable array"); 424 } 425 426 // Test other property 427 [ 428 // [property, value] 429 ["prop1", "value1"], 430 ["prop2", 5], 431 ["prop3", []], 432 ["prop4", {}], 433 ["prop5", false], 434 ].forEach(function([property, value]) { 435 // Initialize 436 b[property] = value; 437 let oldLen = b.length; 438 setCallbackCount = 0; 439 deleteCallbackCount = 0; 440 441 // Test 442 info(`deleting ${property} property`); 443 is(b[property], value, `property value should be ${JSON.stringify(value)} before deleting`); 444 try { 445 ok(Reflect.deleteProperty(b, property), `Reflect.deleteProperty should return true`); 446 ok(true, "Reflect.deleteProperty should not throw"); 447 } catch(e) { 448 ok(false, `Reflect.deleteProperty throws ${e}`); 449 } 450 is(setCallbackCount, 0, "setCallback count"); 451 is(deleteCallbackCount, 0, "deleteCallback count"); 452 is(b[property], undefined, `property value should be undefined after deleting`); 453 is(b.length, oldLen, "length of observable array"); 454 }); 455 }); 456 457 add_task(function testObservableArrayExoticObjects_get() { 458 let m = new TestInterfaceObservableArray(); 459 m.observableArrayBoolean = [true, false]; 460 461 let b = m.observableArrayBoolean; 462 ok(Array.isArray(b), "observable array should be an array type"); 463 is(b.length, 2, "length of observable array should be 2"); 464 465 // Test length 466 is(Reflect.get(b, "length"), 2, "test result of getting length property"); 467 468 // Test indexed value 469 is(Reflect.get(b, 0), true, "test result of getting index 0"); 470 is(Reflect.get(b, 1), false, "test result of getting index 1"); 471 is(Reflect.get(b, 2), undefined, "test result of getting index 2"); 472 473 // Test other property 474 [ 475 // [property, value] 476 ["prop1", "value1"], 477 ["prop2", 5], 478 ["prop3", []], 479 ["prop4", {}], 480 ].forEach(function([property, value]) { 481 is(Reflect.get(b, property), undefined, `test ${property} property before setting property value`); 482 b[property] = value; 483 is(Reflect.get(b, property), value, `test ${property} property after setting property value`); 484 }); 485 }); 486 487 add_task(function testObservableArrayExoticObjects_getOwnPropertyDescriptor() { 488 function TestDescriptor(object, property, exist, configurable, enumerable, 489 writable, value) { 490 let descriptor = Reflect.getOwnPropertyDescriptor(object, property); 491 if (!exist) { 492 is(descriptor, undefined, `descriptor of ${property} property should be undefined`); 493 return; 494 } 495 496 is(descriptor.configurable, configurable, `test descriptor of ${property} property (configurable)`); 497 is(descriptor.enumerable, enumerable, `test descriptor of ${property} property (enumerable)`); 498 is(descriptor.writable, writable, `test descriptor of ${property} property (writable)`); 499 is(descriptor.value, value, `test descriptor of ${property} property (value)`); 500 } 501 502 let m = new TestInterfaceObservableArray(); 503 m.observableArrayBoolean = [true, false]; 504 505 let b = m.observableArrayBoolean; 506 ok(Array.isArray(b), "observable array should be an array type"); 507 is(b.length, 2, "length of observable array should be 2"); 508 509 // Test length 510 TestDescriptor(b, "length", true, false /* configurable */, 511 false /* enumerable */, true /* writable */ , 2 /* value */); 512 513 // Test indexed value 514 TestDescriptor(b, 0, true, true /* configurable */, true /* enumerable */, 515 true /* writable */ , true /* value */); 516 TestDescriptor(b, 1, true, true /* configurable */, true /* enumerable */, 517 true /* writable */ , false /* value */); 518 TestDescriptor(b, 2, false); 519 520 // Test other property 521 [ 522 // [property, value, configurable, enumerable, writable] 523 ["prop1", "value1", true, true, true], 524 ["prop2", 5, true, true, false], 525 ["prop3", [], true, false, false], 526 ["prop4", {}, false, false, false], 527 ].forEach(function([property, value, configurable, enumerable, writable]) { 528 Object.defineProperty(b, property, { 529 value, 530 configurable, 531 enumerable, 532 writable, 533 }); 534 TestDescriptor(b, property, true, configurable, enumerable, writable , value); 535 }); 536 }); 537 538 add_task(function testObservableArrayExoticObjects_has() { 539 let m = new TestInterfaceObservableArray(); 540 m.observableArrayBoolean = [true, false]; 541 542 let b = m.observableArrayBoolean; 543 ok(Array.isArray(b), "observable array should be an array type"); 544 is(b.length, 2, "length of observable array should be 2"); 545 546 // Test length 547 ok(Reflect.has(b, "length"), `test length property`); 548 549 // Test indexed value 550 ok(Reflect.has(b, 0), `test 0 property`); 551 ok(Reflect.has(b, 1), `test 1 property`); 552 ok(!Reflect.has(b, 2), `test 2 property`); 553 554 // Test other property 555 [ 556 // [property, value] 557 ["prop1", "value1"], 558 ["prop2", 5], 559 ["prop3", []], 560 ["prop4", {}], 561 ].forEach(function([property, value]) { 562 ok(!Reflect.has(b, property), `test ${property} property before setting property value`); 563 b[property] = value; 564 ok(Reflect.has(b, property), `test ${property} property after setting property value`); 565 }); 566 }); 567 568 add_task(function testObservableArrayExoticObjects_ownKeys() { 569 let m = new TestInterfaceObservableArray(); 570 m.observableArrayBoolean = [true, false]; 571 572 let b = m.observableArrayBoolean; 573 ok(Array.isArray(b), "observable array should be an array type"); 574 is(b.length, 2, "length of observable array should be 2"); 575 576 // Add other properties 577 b.prop1 = "value1"; 578 b.prop2 = 5; 579 b.prop3 = []; 580 b.prop4 = {}; 581 582 let keys = Reflect.ownKeys(b); 583 SimpleTest.isDeeply(keys, ["0", "1", "length", "prop1", "prop2", "prop3", "prop4"], `test property keys`); 584 }); 585 586 add_task(function testObservableArrayExoticObjects_preventExtensions() { 587 let m = new TestInterfaceObservableArray(); 588 let b = m.observableArrayBoolean; 589 ok(Array.isArray(b), "observable array should be an array type"); 590 is(b.length, 0, "length of observable array should be 0"); 591 592 // Test preventExtensions 593 ok(Reflect.isExtensible(b), "test isExtensible before preventExtensions"); 594 ok(!Reflect.preventExtensions(b), "test preventExtensions"); 595 ok(Reflect.isExtensible(b), "test isExtensible after preventExtensions"); 596 }); 597 598 add_task(function testObservableArrayExoticObjects_set() { 599 let setCallbackCount = 0; 600 let deleteCallbackCount = 0; 601 let setCallbackTests = null; 602 let deleteCallbackTests = null; 603 604 let m = new TestInterfaceObservableArray({ 605 setBooleanCallback(value, index) { 606 setCallbackCount++; 607 if (typeof setCallbackTests === 'function') { 608 setCallbackTests(value, index); 609 } 610 }, 611 deleteBooleanCallback(value, index) { 612 deleteCallbackCount++; 613 if (typeof deleteCallbackTests === 'function') { 614 deleteCallbackTests(value, index); 615 } 616 }, 617 }); 618 m.observableArrayBoolean = [true, true, true]; 619 620 let b = m.observableArrayBoolean; 621 ok(Array.isArray(b), "observable array should be an array type"); 622 is(b.length, 3, "length of observable array should be 3"); 623 624 // Test length 625 [ 626 // [length, shouldThrow, expectedResult] 627 // Invalid length value 628 [1.9, true], 629 ['invalid', true], 630 [{}, true], 631 // length value should not greater than current length 632 [b.length + 1, false, false], 633 // Success 634 [b.length, false, true], 635 [b.length - 1, false, true], 636 [0, false, true], 637 ].forEach(function([length, shouldThrow, expectedResult]) { 638 // Initialize 639 let oldLen = b.length; 640 let oldValues = b.slice(); 641 setCallbackCount = 0; 642 deleteCallbackCount = 0; 643 setCallbackTests = null; 644 let deleteCallbackIndex = oldLen - 1; 645 deleteCallbackTests = function(_value, _index) { 646 is(_value, oldValues[deleteCallbackIndex], "deleteCallbackTests: test value argument"); 647 is(_index, deleteCallbackIndex, "deleteCallbackTests: test index argument"); 648 deleteCallbackIndex--; 649 }; 650 651 // Test 652 info(`setting "length" property value to ${length}`); 653 try { 654 is(Reflect.set(b, "length", length), expectedResult, `Reflect.set should return ${expectedResult}`); 655 ok(!shouldThrow, "Reflect.set should not throw"); 656 } catch(e) { 657 ok(shouldThrow, `Reflect.set throws ${e}`); 658 } 659 is(setCallbackCount, 0, "setCallback count"); 660 is(deleteCallbackCount, expectedResult ? oldLen - length : 0, "deleteCallback count"); 661 isDeeply(b, expectedResult ? oldValues.slice(0, length) : oldValues, "property values"); 662 is(b.length, expectedResult ? length : oldLen, "length of observable array"); 663 }); 664 665 // Test indexed value 666 [ 667 // [index, value, shouldThrow, expectedResult] 668 // Index could not greater than last index. 669 [b.length + 1, true, false, false], 670 // Success 671 [b.length, true, false, true], 672 [b.length + 1, true, false, true], 673 ].forEach(function([index, value, shouldThrow, expectedResult]) { 674 // Initialize 675 let oldLen = b.length; 676 let oldValue = b[index]; 677 setCallbackCount = 0; 678 deleteCallbackCount = 0; 679 setCallbackTests = function(_value, _index) { 680 is(_value, value, "setCallbackTests: test value argument"); 681 is(_index, index, "setCallbackTests: test index argument"); 682 }; 683 deleteCallbackTests = function(_value, _index) { 684 is(_value, oldValue, "deleteCallbackTests: test value argument"); 685 is(_index, index, "deleteCallbackTests: test index argument"); 686 }; 687 688 // Test 689 info(`setting ${index} property to ${value}`); 690 try { 691 is(Reflect.set(b, index, value), expectedResult, `Reflect.set should return ${expectedResult}`); 692 ok(!shouldThrow, "Reflect.set should not throw"); 693 } catch(e) { 694 ok(shouldThrow, `Reflect.set throws ${e}`); 695 } 696 is(setCallbackCount, expectedResult ? 1 : 0, "setCallback count"); 697 is(deleteCallbackCount, (oldLen > index) ? 1 : 0, "deleteCallback count"); 698 is(b[index], expectedResult ? value : oldValue, "property value"); 699 is(b.length, expectedResult ? Math.max(index + 1, oldLen) : oldLen, "length of observable array"); 700 }); 701 702 // Test other property 703 [ 704 // [property, value] 705 ["prop1", "value1"], 706 ["prop1", "value2"], 707 ["prop2", 5], 708 ["prop3", []], 709 ["prop4", {}], 710 ].forEach(function([property, value]) { 711 // Initialize 712 let oldLen = b.length; 713 setCallbackCount = 0; 714 deleteCallbackCount = 0; 715 setCallbackTests = null; 716 deleteCallbackTests = null; 717 718 // Test 719 info(`setting ${property} property to ${value}`); 720 ok(Reflect.set(b, property, value), "Reflect.defineProperty should return true"); 721 is(setCallbackCount, 0, "setCallback count"); 722 is(deleteCallbackCount, 0, "deleteCallback count"); 723 is(b[property], value, "property value"); 724 is(b.length, oldLen, "length of observable array"); 725 }); 726 }); 727 728 add_task(function testObservableArrayExoticObjects_set_callback_throw() { 729 let setCallbackCount = 0; 730 let deleteCallbackCount = 0; 731 732 const minLen = 3; 733 let m = new TestInterfaceObservableArray({ 734 setBooleanCallback(value) { 735 setCallbackCount++; 736 if (value) { 737 throw new Error("setBooleanCallback"); 738 } 739 }, 740 deleteBooleanCallback(value, index) { 741 deleteCallbackCount++; 742 if (index < minLen) { 743 throw new Error("deleteBooleanCallback"); 744 } 745 }, 746 }); 747 m.observableArrayBoolean = [false, false, false, false, false]; 748 749 let b = m.observableArrayBoolean; 750 ok(Array.isArray(b), "observable array should be an array type"); 751 is(b.length, 5, "length of observable array should be 3"); 752 753 // Test length 754 [ 755 // [value, shouldThrow] 756 [b.length, false], 757 [b.length - 1, false], 758 [0, true], 759 ].forEach(function([length, shouldThrow]) { 760 // Initialize 761 let oldValues = b.slice(); 762 let oldLen = b.length; 763 setCallbackCount = 0; 764 deleteCallbackCount = 0; 765 766 // Test 767 info(`setting "length" property to ${length}`); 768 try { 769 ok(Reflect.set(b, "length", length), "Reflect.set should return true"); 770 ok(!shouldThrow, `Reflect.set should not throw`); 771 } catch(e) { 772 ok(shouldThrow, `Reflect.set throws ${e}`); 773 } 774 is(setCallbackCount, 0, "setCallback should not be called"); 775 is(deleteCallbackCount, oldLen - (shouldThrow ? minLen - 1 : length), "deleteCallback count"); 776 isDeeply(b, oldValues.slice(0, shouldThrow ? minLen : length), "property values"); 777 is(b.length, shouldThrow ? minLen : length, "length of observable array"); 778 }); 779 780 // Test indexed value 781 [ 782 // [index, value, shouldThrow] 783 [b.length, true, true], 784 [b.length, false, false], 785 [b.length + 1, false, false], 786 [b.length + 1, true, true], 787 [0, false, true], 788 [0, true, true], 789 ].forEach(function([index, value, shouldThrow]) { 790 // Initialize 791 let oldValue = b[index]; 792 let oldLen = b.length; 793 setCallbackCount = 0; 794 deleteCallbackCount = 0; 795 796 // Test 797 info(`setting ${index} property to ${value}`); 798 try { 799 ok(Reflect.set(b, index, value), "Reflect.set should return true"); 800 ok(!shouldThrow, `Reflect.set should not throw`); 801 } catch(e) { 802 ok(shouldThrow, `Reflect.set throws ${e}`); 803 } 804 is(setCallbackCount, (index < minLen) ? 0 : 1, "setCallback count"); 805 is(deleteCallbackCount, (oldLen > index) ? 1 : 0, "deleteCallback count"); 806 is(b[index], shouldThrow ? oldValue : value, "property value"); 807 is(b.length, shouldThrow ? oldLen : Math.max(oldLen, index + 1), "length of observable array"); 808 }); 809 810 // Test other property 811 [ 812 ["prop1", "value1"], 813 ["prop1", "value2"], 814 ["prop2", 5], 815 ["prop3", []], 816 ["prop4", {}], 817 ].forEach(function([property, value]) { 818 // Initialize 819 let oldLen = b.length; 820 setCallbackCount = 0; 821 deleteCallbackCount = 0; 822 823 // Test 824 info(`setting ${property} property to ${JSON.stringify(value)}`); 825 try { 826 ok(Reflect.set(b, property, value), "Reflect.set should return true"); 827 ok(true, `Reflect.set should not throw`); 828 } catch(e) { 829 ok(false, `Reflect.set throws ${e}`); 830 } 831 is(setCallbackCount, 0, "setCallback should not be called"); 832 is(deleteCallbackCount, 0, "deleteCallback should be called"); 833 is(b[property], value, "property value"); 834 is(b.length, oldLen, "length of observable array"); 835 }); 836 }); 837 838 add_task(function testObservableArrayExoticObjects_invalidtype() { 839 let m = new TestInterfaceObservableArray(); 840 let i = m.observableArrayInterface; 841 ok(Array.isArray(i), "Observable array should be an array type"); 842 is(i.length, 0, "length should be 0"); 843 844 [true, "invalid"].forEach(function(value) { 845 SimpleTest.doesThrow(() => { 846 let descriptor = {configurable: true, enumerable: true, writable: true, value}; 847 Reflect.defineProperty(i, i.length, descriptor); 848 }, `defining ${i.length} property with ${JSON.stringify(value)} should throw`); 849 850 SimpleTest.doesThrow(() => { 851 Reflect.set(i, i.length, value); 852 }, `setting ${i.length} property to ${JSON.stringify(value)} should throw`); 853 }); 854 855 is(i.length, 0, "length should still be 0"); 856 }); 857 </script> 858 </body> 859 </html>