test_observablearray.html (17847B)
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 testObservableArray_length() { 19 let setCallbackCount = 0; 20 let deleteCallbackCount = 0; 21 let deleteCallbackTests = null; 22 23 let m = new TestInterfaceObservableArray({ 24 setBooleanCallback() { 25 setCallbackCount++; 26 }, 27 deleteBooleanCallback(value, index) { 28 deleteCallbackCount++; 29 if (typeof deleteCallbackTests === 'function') { 30 deleteCallbackTests(value, index); 31 } 32 }, 33 }); 34 m.observableArrayBoolean = [true, true, true, true, true]; 35 36 let b = m.observableArrayBoolean; 37 ok(Array.isArray(b), "observable array should be an array type"); 38 is(b.length, 5, "length of observable array should be 5"); 39 40 [ 41 // [length, shouldThrow, expectedResult] 42 ["invalid", true, false], 43 [b.length + 1, false, false], 44 [b.length, false, true], 45 [b.length - 1, false, true], 46 [0, false, true], 47 ].forEach(function([length, shouldThrow, expectedResult]) { 48 // Initialize 49 let oldValues = b.slice(); 50 let oldLen = b.length; 51 let shouldSuccess = !shouldThrow && expectedResult; 52 setCallbackCount = 0; 53 deleteCallbackCount = 0; 54 deleteCallbackTests = null; 55 if (shouldSuccess) { 56 let deleteCallbackIndex = b.length - 1; 57 deleteCallbackTests = function(_value, _index) { 58 info(`delete callback for ${_index}`); 59 is(_value, oldValues[deleteCallbackIndex], "deleteCallbackTests: test value argument"); 60 is(_index, deleteCallbackIndex, "deleteCallbackTests: test index argument"); 61 deleteCallbackIndex--; 62 }; 63 } 64 65 // Test 66 info(`setting length to ${length}`); 67 try { 68 b.length = length; 69 ok(!shouldThrow, `setting length should throw`); 70 } catch(e) { 71 ok(shouldThrow, `setting length throws ${e}`); 72 } 73 is(setCallbackCount, 0, "setCallback should not be called"); 74 is(deleteCallbackCount, shouldSuccess ? (oldLen - length) : 0, "deleteCallback count"); 75 isDeeply(b, shouldSuccess ? oldValues.slice(0, length) : oldValues, "property values"); 76 is(b.length, shouldSuccess ? length : oldLen, `length of observable array`); 77 }); 78 }); 79 80 add_task(function testObservableArray_length_callback_throw() { 81 let setCallbackCount = 0; 82 let deleteCallbackCount = 0; 83 84 let m = new TestInterfaceObservableArray({ 85 setBooleanCallback() { 86 setCallbackCount++; 87 }, 88 deleteBooleanCallback(value) { 89 deleteCallbackCount++; 90 if (value) { 91 throw new Error("deleteBooleanCallback"); 92 } 93 }, 94 }); 95 m.observableArrayBoolean = [true, true, false, false, false]; 96 97 let b = m.observableArrayBoolean; 98 ok(Array.isArray(b), "observable array should be an array type"); 99 is(b.length, 5, "length of observable array should be 5"); 100 101 // Initialize 102 setCallbackCount = 0; 103 deleteCallbackCount = 0; 104 105 // Test 106 info(`setting length to 0`); 107 try { 108 b.length = 0; 109 ok(false, `setting length should throw`); 110 } catch(e) { 111 ok(true, `setting length throws ${e}`); 112 } 113 is(setCallbackCount, 0, "setCallback should not be called"); 114 is(deleteCallbackCount, 4, "deleteCallback should be called"); 115 isDeeply(b, [true, true], "property values"); 116 is(b.length, 2, `length of observable array`); 117 }); 118 119 add_task(function testObservableArray_setter() { 120 let setCallbackCount = 0; 121 let deleteCallbackCount = 0; 122 let setCallbackTests = null; 123 let deleteCallbackTests = null; 124 125 let m = new TestInterfaceObservableArray({ 126 setBooleanCallback(value, index) { 127 setCallbackCount++; 128 if (typeof setCallbackTests === 'function') { 129 setCallbackTests(value, index); 130 } 131 }, 132 deleteBooleanCallback(value, index) { 133 deleteCallbackCount++; 134 if (typeof deleteCallbackTests === 'function') { 135 deleteCallbackTests(value, index); 136 } 137 }, 138 }); 139 140 let b = m.observableArrayBoolean; 141 ok(Array.isArray(b), "observable array should be an array type"); 142 is(b.length, 0, "length of observable array should be 0"); 143 144 [ 145 // [values, shouldThrow] 146 ["invalid", true], 147 [[1,[],{},"invalid"], false], 148 [[0,NaN,null,undefined,""], false], 149 [[true,true], false], 150 [[false,false,false], false], 151 ].forEach(function([values, shouldThrow]) { 152 // Initialize 153 let oldValues = b.slice(); 154 let oldLen = b.length; 155 setCallbackCount = 0; 156 deleteCallbackCount = 0; 157 setCallbackTests = null; 158 deleteCallbackTests = null; 159 if (!shouldThrow) { 160 let setCallbackIndex = 0; 161 setCallbackTests = function(_value, _index) { 162 info(`set callback for ${_index}`); 163 is(_value, !!values[setCallbackIndex], "setCallbackTests: test value argument"); 164 is(_index, setCallbackIndex, "setCallbackTests: test index argument"); 165 setCallbackIndex++; 166 }; 167 168 let deleteCallbackIndex = b.length - 1; 169 deleteCallbackTests = function(_value, _index) { 170 info(`delete callback for ${_index}`); 171 is(_value, oldValues[deleteCallbackIndex], "deleteCallbackTests: test value argument"); 172 is(_index, deleteCallbackIndex, "deleteCallbackTests: test index argument"); 173 deleteCallbackIndex--; 174 }; 175 } 176 177 // Test 178 info(`setting value to ${JSON.stringify(values)}`); 179 try { 180 m.observableArrayBoolean = values; 181 ok(!shouldThrow, `setting value should not throw`); 182 } catch(e) { 183 ok(shouldThrow, `setting value throws ${e}`); 184 } 185 is(setCallbackCount, shouldThrow ? 0 : values.length, "setCallback count"); 186 is(deleteCallbackCount, oldLen, "deleteCallback should be called"); 187 isDeeply(b, shouldThrow ? [] : values.map(v => !!v), "property values"); 188 is(b.length, shouldThrow ? 0 : values.length, `length of observable array`); 189 }); 190 }); 191 192 add_task(function testObservableArray_setter_invalid_item() { 193 let setCallbackCount = 0; 194 let deleteCallbackCount = 0; 195 let setCallbackTests = null; 196 let deleteCallbackTests = null; 197 198 let m = new TestInterfaceObservableArray({ 199 setInterfaceCallback(value, index) { 200 setCallbackCount++; 201 if (typeof setCallbackTests === 'function') { 202 setCallbackTests(value, index); 203 } 204 }, 205 deleteInterfaceCallback(value, index) { 206 deleteCallbackCount++; 207 if (typeof deleteCallbackTests === 'function') { 208 deleteCallbackTests(value, index); 209 } 210 }, 211 }); 212 213 let b = m.observableArrayInterface; 214 ok(Array.isArray(b), "observable array should be an array type"); 215 is(b.length, 0, "length of observable array should be 0"); 216 217 [ 218 // [values, shouldThrow] 219 [[m,m,m,m], false], 220 [["invalid"], true], 221 [[m,m], false], 222 [[m,"invalid"], true], 223 [[m,m,m], false], 224 ].forEach(function([values, shouldThrow]) { 225 // Initialize 226 let oldValues = b.slice(); 227 let oldLen = b.length; 228 let setCallbackIndex = 0; 229 setCallbackTests = function(_value, _index) { 230 info(`set callback for ${_index}`); 231 is(_value, values[setCallbackIndex], "setCallbackTests: test value argument"); 232 is(_index, setCallbackIndex, "setCallbackTests: test index argument"); 233 setCallbackIndex++; 234 }; 235 let deleteCallbackIndex = b.length - 1; 236 deleteCallbackTests = function(_value, _index) { 237 info(`delete callback for ${_index}`); 238 is(_value, oldValues[deleteCallbackIndex], "deleteCallbackTests: test value argument"); 239 is(_index, deleteCallbackIndex, "deleteCallbackTests: test index argument"); 240 deleteCallbackIndex--; 241 }; 242 setCallbackCount = 0; 243 deleteCallbackCount = 0; 244 245 // Test 246 info(`setting value to ${values}`); 247 try { 248 m.observableArrayInterface = values; 249 ok(!shouldThrow, `setting value should not throw`); 250 } catch(e) { 251 ok(shouldThrow, `setting value throws ${e}`); 252 } 253 is(setCallbackCount, shouldThrow ? 0 : values.length, "setCallback count"); 254 is(deleteCallbackCount, shouldThrow ? 0 : oldLen, "deleteCallback should be called"); 255 isDeeply(b, shouldThrow ? oldValues : values, "property values"); 256 is(b.length, shouldThrow ? oldLen : values.length, `length of observable array`); 257 }); 258 }); 259 260 add_task(function testObservableArray_setter_callback_throw() { 261 let setCallbackCount = 0; 262 let deleteCallbackCount = 0; 263 264 let m = new TestInterfaceObservableArray({ 265 setBooleanCallback(value, index) { 266 setCallbackCount++; 267 if (index >= 3) { 268 throw new Error("setBooleanCallback"); 269 } 270 }, 271 deleteBooleanCallback(value) { 272 deleteCallbackCount++; 273 if (value) { 274 throw new Error("deleteBooleanCallback"); 275 } 276 }, 277 }); 278 m.observableArrayBoolean = [false, false, false]; 279 280 let b = m.observableArrayBoolean; 281 ok(Array.isArray(b), "observable array should be an array type"); 282 is(b.length, 3, "length of observable array should be 3"); 283 284 [ 285 // [values, shouldThrow, expectedLength, expectedSetCbCount, expectedDeleteCbCount] 286 [[false,false], false, 2, 2, 3], 287 [[false,true,false,false], true, 3, 4, 2], 288 [[false,false,true], true, 2, 0, 2], 289 ].forEach(function([values, shouldThrow, expectedLength, expectedSetCbCount, 290 expectedDeleteCbCount]) { 291 // Initialize 292 setCallbackCount = 0; 293 deleteCallbackCount = 0; 294 295 // Test 296 info(`setting value to ${values}`); 297 try { 298 m.observableArrayBoolean = values; 299 ok(!shouldThrow, `setting value should not throw`); 300 } catch(e) { 301 ok(shouldThrow, `setting length throws ${e}`); 302 } 303 is(setCallbackCount, expectedSetCbCount, "setCallback should be called"); 304 is(deleteCallbackCount, expectedDeleteCbCount, "deleteCallback should be called"); 305 is(b.length, expectedLength, `length of observable array`); 306 }); 307 }); 308 309 add_task(function testObservableArray_indexed_setter() { 310 let setCallbackCount = 0; 311 let deleteCallbackCount = 0; 312 let setCallbackTests = null; 313 let deleteCallbackTests = null; 314 315 let m = new TestInterfaceObservableArray({ 316 setBooleanCallback(value, index) { 317 setCallbackCount++; 318 if (typeof setCallbackTests === 'function') { 319 setCallbackTests(value, index); 320 } 321 }, 322 deleteBooleanCallback(value, index) { 323 deleteCallbackCount++; 324 if (typeof deleteCallbackTests === 'function') { 325 deleteCallbackTests(value, index); 326 } 327 }, 328 }); 329 330 let b = m.observableArrayBoolean; 331 ok(Array.isArray(b), "observable array should be an array type"); 332 is(b.length, 0, "length of observable array should be 0"); 333 334 [ 335 // [index, value, expectedResult] 336 [b.length + 1, false, false], 337 [b.length, false, true], 338 [b.length + 1, false, true], 339 [b.length + 1, true, true], 340 ].forEach(function([index, value, expectedResult]) { 341 // Initialize 342 let oldValue = b[index]; 343 let oldLen = b.length; 344 setCallbackCount = 0; 345 deleteCallbackCount = 0; 346 setCallbackTests = function(_value, _index) { 347 info(`set callback for ${_index}`); 348 is(_value, value, "setCallbackTests: test value argument"); 349 is(_index, index, "setCallbackTests: test index argument"); 350 }; 351 deleteCallbackTests = function(_value, _index) { 352 info(`delete callback for ${_index}`); 353 is(_value, oldValue, "deleteCallbackTests: test value argument"); 354 is(_index, index, "deleteCallbackTests: test index argument"); 355 }; 356 357 // Test 358 info(`setting value of property ${index} to ${value}`); 359 try { 360 b[index] = value; 361 ok(true, `setting value should not throw`); 362 } catch(e) { 363 ok(false, `setting value throws ${e}`); 364 } 365 is(setCallbackCount, expectedResult ? 1 : 0, "setCallback should be called"); 366 is(deleteCallbackCount, (oldLen > index) ? 1 : 0, "deleteCallback should be called"); 367 is(b[index], expectedResult ? value : oldValue, `property value`); 368 is(b.length, expectedResult ? Math.max(oldLen, index + 1) : oldLen, `length of observable array`); 369 }); 370 }); 371 372 add_task(function testObservableArray_indexed_setter_invalid() { 373 let setCallbackCount = 0; 374 let deleteCallbackCount = 0; 375 let setCallbackTests = null; 376 let deleteCallbackTests = null; 377 378 let m = new TestInterfaceObservableArray({ 379 setInterfaceCallback(value, index) { 380 setCallbackCount++; 381 if (typeof setCallbackTests === 'function') { 382 setCallbackTests(value, index); 383 } 384 }, 385 deleteInterfaceCallback(value, index) { 386 deleteCallbackCount++; 387 if (typeof deleteCallbackTests === 'function') { 388 deleteCallbackTests(value, index); 389 } 390 }, 391 }); 392 393 let b = m.observableArrayInterface; 394 ok(Array.isArray(b), "observable array should be an array type"); 395 is(b.length, 0, "length of observable array should be 0"); 396 397 [ 398 // [index, value, shouldThrow] 399 [b.length, "invalid", true], 400 [b.length, m, false], 401 [b.length + 1, m, false], 402 [b.length + 1, "invalid", true], 403 ].forEach(function([index, value, shouldThrow]) { 404 // Initialize 405 let oldValue = b[index]; 406 let oldLen = b.length; 407 setCallbackCount = 0; 408 deleteCallbackCount = 0; 409 setCallbackTests = function(_value, _index) { 410 info(`set callback for ${_index}`); 411 is(_value, value, "setCallbackTests: test value argument"); 412 is(_index, index, "setCallbackTests: test index argument"); 413 }; 414 deleteCallbackTests = function(_value, _index) { 415 info(`delete callback for ${_index}`); 416 is(_value, oldValue, "deleteCallbackTests: test value argument"); 417 is(_index, index, "deleteCallbackTests: test index argument"); 418 }; 419 420 // Test 421 info(`setting value of property ${index} to ${value}`); 422 try { 423 b[index] = value; 424 ok(!shouldThrow, `setting value should not throw`); 425 } catch(e) { 426 ok(shouldThrow, `setting value throws ${e}`); 427 } 428 is(setCallbackCount, shouldThrow ? 0 : 1, "setCallback count"); 429 is(deleteCallbackCount, ((oldLen > index) && !shouldThrow) ? 1 : 0, "deleteCallback count"); 430 is(b[index], shouldThrow ? oldValue : value, `property value`); 431 is(b.length, shouldThrow ? oldLen : Math.max(oldLen, index + 1), `length of observable array`); 432 }); 433 }); 434 435 add_task(function testObservableArray_indexed_setter_callback_throw() { 436 let setCallbackCount = 0; 437 let deleteCallbackCount = 0; 438 439 let m = new TestInterfaceObservableArray({ 440 setBooleanCallback(value) { 441 setCallbackCount++; 442 if (value) { 443 throw new Error("setBooleanCallback"); 444 } 445 }, 446 deleteBooleanCallback(value, index) { 447 deleteCallbackCount++; 448 if (index < 2) { 449 throw new Error("deleteBooleanCallback"); 450 } 451 }, 452 }); 453 m.observableArrayBoolean = [false, false, false]; 454 455 let b = m.observableArrayBoolean; 456 ok(Array.isArray(b), "observable array should be an array type"); 457 is(b.length, 3, "length of observable array should be 3"); 458 459 [ 460 // [index, value, shouldThrow] 461 [b.length, true, true], 462 [b.length, false, false], 463 [b.length, true, true], 464 [0, false, true], 465 [0, true, true] 466 ].forEach(function([index, value, shouldThrow]) { 467 // Initialize 468 let oldValue = b[index]; 469 let oldLen = b.length; 470 setCallbackCount = 0; 471 deleteCallbackCount = 0; 472 473 // Test 474 info(`setting value of property ${index} to ${value}`); 475 try { 476 b[index] = value; 477 ok(!shouldThrow, `setting value should not throw`); 478 } catch(e) { 479 ok(shouldThrow, `setting value throws ${e}`); 480 } 481 is(setCallbackCount, (shouldThrow && index < 2) ? 0 : 1, "setCallback should be called"); 482 is(deleteCallbackCount, (oldLen > index) ? 1 : 0, "deleteCallback should be called"); 483 is(b[index], shouldThrow ? oldValue : value, "property value"); 484 is(b.length, shouldThrow ? oldLen : Math.max(oldLen, index + 1), `length of observable array`); 485 }); 486 }); 487 488 add_task(function testObservableArray_object() { 489 let setCallbackCount = 0; 490 let deleteCallbackCount = 0; 491 let callbackIndex = 0; 492 493 let values = [ 494 {property1: false, property2: "property2"}, 495 {property1: [], property2: 2}, 496 ]; 497 498 let m = new TestInterfaceObservableArray({ 499 setObjectCallback(value, index) { 500 setCallbackCount++; 501 is(index, callbackIndex++, "setCallbackTests: test index argument"); 502 isDeeply(values[index], value, "setCallbackTests: test value argument"); 503 }, 504 deleteObjectCallback() { 505 deleteCallbackCount++; 506 }, 507 }); 508 509 m.observableArrayObject = values; 510 511 let b = m.observableArrayObject; 512 ok(Array.isArray(b), "observable array should be an array type"); 513 is(b.length, 2, "length of observable array should be 2"); 514 is(setCallbackCount, values.length, "setCallback should be called"); 515 is(deleteCallbackCount, 0, "deleteCallback should not be called"); 516 517 for(let i = 0; i < values.length; i++) { 518 isDeeply(values[i], b[i], `check index ${i}`); 519 } 520 }); 521 522 add_task(function testObservableArray_xrays() { 523 let m = new TestInterfaceObservableArray({ 524 setObjectCallback() { 525 ok(false, "Shouldn't reach setObjectCallback"); 526 }, 527 deleteObjectCallback() { 528 ok(false, "Shouldn't reach deleteObjectCallback"); 529 }, 530 }); 531 532 let wrapper = SpecialPowers.wrap(m); 533 ok(SpecialPowers.Cu.isXrayWrapper(wrapper), "Should be a wrapper"); 534 let observableArray = wrapper.observableArrayBoolean; 535 ok(!!observableArray, "Should not throw"); 536 is("length" in observableArray, false, "Should be opaque"); 537 538 try { 539 wrapper.observableArrayBoolean = [true, false, false]; 540 ok(false, "Expected to throw, for now"); 541 } catch (ex) {} 542 }); 543 544 </script> 545 </body> 546 </html>