| OLD | NEW |
| (Empty) |
| 1 <html> | |
| 2 <import src="../resources/chai.sky" /> | |
| 3 <import src="../resources/mocha.sky" /> | |
| 4 <import src="/sky/framework/elements/sky-element/observe.sky" as="observe" /> | |
| 5 | |
| 6 <script> | |
| 7 | |
| 8 var Path = observe.Path; | |
| 9 var PathObserver = observe.PathObserver; | |
| 10 var ArrayObserver = observe.ArrayObserver; | |
| 11 var ObjectObserver = observe.ObjectObserver; | |
| 12 var CompoundObserver = observe.CompoundObserver; | |
| 13 var ObserverTransform = observe.ObserverTransform; | |
| 14 | |
| 15 var observer; | |
| 16 var callbackArgs = undefined; | |
| 17 var callbackInvoked = false; | |
| 18 | |
| 19 function then(fn) { | |
| 20 setTimeout(function() { | |
| 21 fn(); | |
| 22 }, 0); | |
| 23 | |
| 24 return { | |
| 25 then: function(next) { | |
| 26 return then(next); | |
| 27 } | |
| 28 }; | |
| 29 } | |
| 30 | |
| 31 function noop() {} | |
| 32 | |
| 33 function callback() { | |
| 34 callbackArgs = Array.prototype.slice.apply(arguments); | |
| 35 callbackInvoked = true; | |
| 36 } | |
| 37 | |
| 38 function doSetup() {} | |
| 39 function doTeardown() { | |
| 40 callbackInvoked = false; | |
| 41 callbackArgs = undefined; | |
| 42 } | |
| 43 | |
| 44 function assertNoChanges() { | |
| 45 if (observer) | |
| 46 observer.deliver(); | |
| 47 assert.isFalse(callbackInvoked); | |
| 48 assert.isUndefined(callbackArgs); | |
| 49 } | |
| 50 | |
| 51 function assertPathChanges(expectNewValue, expectOldValue, dontDeliver) { | |
| 52 if (!dontDeliver) | |
| 53 observer.deliver(); | |
| 54 | |
| 55 assert.isTrue(callbackInvoked); | |
| 56 | |
| 57 var newValue = callbackArgs[0]; | |
| 58 var oldValue = callbackArgs[1]; | |
| 59 assert.deepEqual(expectNewValue, newValue); | |
| 60 assert.deepEqual(expectOldValue, oldValue); | |
| 61 | |
| 62 if (!dontDeliver) { | |
| 63 assert.isTrue(window.dirtyCheckCycleCount === undefined || | |
| 64 window.dirtyCheckCycleCount === 1); | |
| 65 } | |
| 66 | |
| 67 callbackArgs = undefined; | |
| 68 callbackInvoked = false; | |
| 69 } | |
| 70 | |
| 71 function assertCompoundPathChanges(expectNewValues, expectOldValues, | |
| 72 expectObserved, dontDeliver) { | |
| 73 if (!dontDeliver) | |
| 74 observer.deliver(); | |
| 75 | |
| 76 assert.isTrue(callbackInvoked); | |
| 77 | |
| 78 var newValues = callbackArgs[0]; | |
| 79 var oldValues = callbackArgs[1]; | |
| 80 var observed = callbackArgs[2]; | |
| 81 assert.deepEqual(expectNewValues, newValues); | |
| 82 assert.deepEqual(expectOldValues, oldValues); | |
| 83 assert.deepEqual(expectObserved, observed); | |
| 84 | |
| 85 if (!dontDeliver) { | |
| 86 assert.isTrue(window.dirtyCheckCycleCount === undefined || | |
| 87 window.dirtyCheckCycleCount === 1); | |
| 88 } | |
| 89 | |
| 90 callbackArgs = undefined; | |
| 91 callbackInvoked = false; | |
| 92 } | |
| 93 | |
| 94 var createObject = ('__proto__' in {}) ? | |
| 95 function(obj) { return obj; } : | |
| 96 function(obj) { | |
| 97 var proto = obj.__proto__; | |
| 98 if (!proto) | |
| 99 return obj; | |
| 100 var newObject = Object.create(proto); | |
| 101 Object.getOwnPropertyNames(obj).forEach(function(name) { | |
| 102 Object.defineProperty(newObject, name, | |
| 103 Object.getOwnPropertyDescriptor(obj, name)); | |
| 104 }); | |
| 105 return newObject; | |
| 106 }; | |
| 107 | |
| 108 function assertPath(pathString, expectKeys, expectSerialized) { | |
| 109 var path = Path.get(pathString); | |
| 110 if (!expectKeys) { | |
| 111 assert.isFalse(path.valid); | |
| 112 return; | |
| 113 } | |
| 114 | |
| 115 assert.deepEqual(Array.prototype.slice.apply(path), expectKeys); | |
| 116 assert.strictEqual(path.toString(), expectSerialized); | |
| 117 } | |
| 118 | |
| 119 function assertInvalidPath(pathString) { | |
| 120 assertPath(pathString); | |
| 121 } | |
| 122 | |
| 123 describe('Path', function() { | |
| 124 it('constructor throws', function() { | |
| 125 assert.throws(function() { | |
| 126 new Path('foo') | |
| 127 }); | |
| 128 }); | |
| 129 | |
| 130 it('path validity', function() { | |
| 131 // invalid path get value is always undefined | |
| 132 var p = Path.get('a b'); | |
| 133 assert.isFalse(p.valid); | |
| 134 assert.isUndefined(p.getValueFrom({ a: { b: 2 }})); | |
| 135 | |
| 136 assertPath('', [], ''); | |
| 137 assertPath(' ', [], ''); | |
| 138 assertPath(null, [], ''); | |
| 139 assertPath(undefined, [], ''); | |
| 140 assertPath('a', ['a'], 'a'); | |
| 141 assertPath('a.b', ['a', 'b'], 'a.b'); | |
| 142 assertPath('a. b', ['a', 'b'], 'a.b'); | |
| 143 assertPath('a .b', ['a', 'b'], 'a.b'); | |
| 144 assertPath('a . b', ['a', 'b'], 'a.b'); | |
| 145 assertPath(' a . b ', ['a', 'b'], 'a.b'); | |
| 146 assertPath('a[0]', ['a', '0'], 'a[0]'); | |
| 147 assertPath('a [0]', ['a', '0'], 'a[0]'); | |
| 148 assertPath('a[0][1]', ['a', '0', '1'], 'a[0][1]'); | |
| 149 assertPath('a [ 0 ] [ 1 ] ', ['a', '0', '1'], 'a[0][1]'); | |
| 150 assertPath('[1234567890] ', ['1234567890'], '[1234567890]'); | |
| 151 assertPath(' [1234567890] ', ['1234567890'], '[1234567890]'); | |
| 152 assertPath('opt0', ['opt0'], 'opt0'); | |
| 153 assertPath('$foo.$bar._baz', ['$foo', '$bar', '_baz'], '$foo.$bar._baz'); | |
| 154 assertPath('foo["baz"]', ['foo', 'baz'], 'foo.baz'); | |
| 155 assertPath('foo["b\\"az"]', ['foo', 'b"az'], 'foo["b\\"az"]'); | |
| 156 assertPath("foo['b\\'az']", ['foo', "b'az"], 'foo["b\'az"]'); | |
| 157 assertPath(['a', 'b'], ['a', 'b'], 'a.b'); | |
| 158 assertPath([''], [''], '[""]'); | |
| 159 | |
| 160 function Foo(val) { this.val = val; } | |
| 161 Foo.prototype.toString = function() { return 'Foo' + this.val; }; | |
| 162 assertPath([new Foo('a'), new Foo('b')], ['Fooa', 'Foob'], 'Fooa.Foob'); | |
| 163 | |
| 164 assertInvalidPath('.'); | |
| 165 assertInvalidPath(' . '); | |
| 166 assertInvalidPath('..'); | |
| 167 assertInvalidPath('a[4'); | |
| 168 assertInvalidPath('a.b.'); | |
| 169 assertInvalidPath('a,b'); | |
| 170 assertInvalidPath('a["foo]'); | |
| 171 assertInvalidPath('[0x04]'); | |
| 172 assertInvalidPath('[0foo]'); | |
| 173 assertInvalidPath('[foo-bar]'); | |
| 174 assertInvalidPath('foo-bar'); | |
| 175 assertInvalidPath('42'); | |
| 176 assertInvalidPath('a[04]'); | |
| 177 assertInvalidPath(' a [ 04 ]'); | |
| 178 assertInvalidPath(' 42 '); | |
| 179 assertInvalidPath('foo["bar]'); | |
| 180 assertInvalidPath("foo['bar]"); | |
| 181 }); | |
| 182 | |
| 183 it('Paths are interned', function() { | |
| 184 var p = Path.get('foo.bar'); | |
| 185 var p2 = Path.get('foo.bar'); | |
| 186 assert.strictEqual(p, p2); | |
| 187 | |
| 188 var p3 = Path.get(''); | |
| 189 var p4 = Path.get(''); | |
| 190 assert.strictEqual(p3, p4); | |
| 191 }); | |
| 192 | |
| 193 it('null is empty path', function() { | |
| 194 assert.strictEqual(Path.get(''), Path.get(null)); | |
| 195 }); | |
| 196 | |
| 197 it('undefined is empty path', function() { | |
| 198 assert.strictEqual(Path.get(undefined), Path.get(null)); | |
| 199 }); | |
| 200 | |
| 201 it('Path.getValueFrom', function() { | |
| 202 var obj = { | |
| 203 a: { | |
| 204 b: { | |
| 205 c: 1 | |
| 206 } | |
| 207 } | |
| 208 }; | |
| 209 | |
| 210 var p1 = Path.get('a'); | |
| 211 var p2 = Path.get('a.b'); | |
| 212 var p3 = Path.get('a.b.c'); | |
| 213 | |
| 214 assert.strictEqual(obj.a, p1.getValueFrom(obj)); | |
| 215 assert.strictEqual(obj.a.b, p2.getValueFrom(obj)); | |
| 216 assert.strictEqual(1, p3.getValueFrom(obj)); | |
| 217 | |
| 218 obj.a.b.c = 2; | |
| 219 assert.strictEqual(2, p3.getValueFrom(obj)); | |
| 220 | |
| 221 obj.a.b = { | |
| 222 c: 3 | |
| 223 }; | |
| 224 assert.strictEqual(3, p3.getValueFrom(obj)); | |
| 225 | |
| 226 obj.a = { | |
| 227 b: 4 | |
| 228 }; | |
| 229 assert.strictEqual(undefined, p3.getValueFrom(obj)); | |
| 230 assert.strictEqual(4, p2.getValueFrom(obj)); | |
| 231 }); | |
| 232 | |
| 233 it('Path.setValueFrom', function() { | |
| 234 var obj = {}; | |
| 235 var p2 = Path.get('bar'); | |
| 236 | |
| 237 Path.get('foo').setValueFrom(obj, 3); | |
| 238 assert.equal(3, obj.foo); | |
| 239 | |
| 240 var bar = { baz: 3 }; | |
| 241 | |
| 242 Path.get('bar').setValueFrom(obj, bar); | |
| 243 assert.equal(bar, obj.bar); | |
| 244 | |
| 245 var p = Path.get('bar.baz.bat'); | |
| 246 p.setValueFrom(obj, 'not here'); | |
| 247 assert.equal(undefined, p.getValueFrom(obj)); | |
| 248 }); | |
| 249 | |
| 250 it('Degenerate Values', function() { | |
| 251 var emptyPath = Path.get(); | |
| 252 var foo = {}; | |
| 253 | |
| 254 assert.equal(null, emptyPath.getValueFrom(null)); | |
| 255 assert.equal(foo, emptyPath.getValueFrom(foo)); | |
| 256 assert.equal(3, emptyPath.getValueFrom(3)); | |
| 257 assert.equal(undefined, Path.get('a').getValueFrom(undefined)); | |
| 258 }); | |
| 259 }); | |
| 260 | |
| 261 describe('Basic Tests', function() { | |
| 262 | |
| 263 it('Exception Doesnt Stop Notification', function() { | |
| 264 var model = [1]; | |
| 265 var count = 0; | |
| 266 | |
| 267 var observer2 = new PathObserver(model, '[0]'); | |
| 268 observer2.open(function() { | |
| 269 count++; | |
| 270 throw 'ouch'; | |
| 271 }); | |
| 272 | |
| 273 var observer3 = new ArrayObserver(model); | |
| 274 observer3.open(function() { | |
| 275 count++; | |
| 276 throw 'ouch'; | |
| 277 }); | |
| 278 | |
| 279 model[0] = 2; | |
| 280 model[1] = 2; | |
| 281 | |
| 282 observer2.deliver(); | |
| 283 observer3.deliver(); | |
| 284 | |
| 285 assert.equal(2, count); | |
| 286 | |
| 287 observer2.close(); | |
| 288 observer3.close(); | |
| 289 }); | |
| 290 | |
| 291 it('Can only open once', function() { | |
| 292 observer = new PathObserver({ id: 1 }, 'id'); | |
| 293 observer.open(callback); | |
| 294 assert.throws(function() { | |
| 295 observer.open(callback); | |
| 296 }); | |
| 297 observer.close(); | |
| 298 | |
| 299 observer = new CompoundObserver(); | |
| 300 observer.open(callback); | |
| 301 assert.throws(function() { | |
| 302 observer.open(callback); | |
| 303 }); | |
| 304 observer.close(); | |
| 305 | |
| 306 observer = new ArrayObserver([], 'id'); | |
| 307 observer.open(callback); | |
| 308 assert.throws(function() { | |
| 309 observer.open(callback); | |
| 310 }); | |
| 311 observer.close(); | |
| 312 | |
| 313 }); | |
| 314 | |
| 315 }); | |
| 316 | |
| 317 describe('ObserverTransform', function() { | |
| 318 | |
| 319 it('Close Invokes Close', function() { | |
| 320 var count = 0; | |
| 321 var observer = { | |
| 322 open: function() {}, | |
| 323 close: function() { count++; } | |
| 324 }; | |
| 325 | |
| 326 var observer = new ObserverTransform(observer); | |
| 327 observer.open(); | |
| 328 observer.close(); | |
| 329 assert.strictEqual(1, count); | |
| 330 }); | |
| 331 | |
| 332 it('valueFn/setValueFn', function() { | |
| 333 var obj = { foo: 1 }; | |
| 334 | |
| 335 function valueFn(value) { return value * 2; } | |
| 336 | |
| 337 observer = new ObserverTransform(new PathObserver(obj, 'foo'), | |
| 338 valueFn); | |
| 339 observer.open(callback); | |
| 340 | |
| 341 obj.foo = 2; | |
| 342 | |
| 343 assert.strictEqual(4, observer.discardChanges()); | |
| 344 assertNoChanges(); | |
| 345 | |
| 346 observer.setValue(2); | |
| 347 assert.strictEqual(obj.foo, 2); | |
| 348 | |
| 349 obj.foo = 10; | |
| 350 assertPathChanges(20, 4); | |
| 351 | |
| 352 observer.close(); | |
| 353 }); | |
| 354 | |
| 355 it('valueFn - object literal', function() { | |
| 356 var model = {}; | |
| 357 | |
| 358 function valueFn(value) { | |
| 359 return [ value ]; | |
| 360 } | |
| 361 | |
| 362 observer = new ObserverTransform(new PathObserver(model, 'foo'), valueFn); | |
| 363 observer.open(callback); | |
| 364 | |
| 365 model.foo = 1; | |
| 366 assertPathChanges([1], [undefined]); | |
| 367 | |
| 368 model.foo = 3; | |
| 369 assertPathChanges([3], [1]); | |
| 370 | |
| 371 observer.close(); | |
| 372 }); | |
| 373 | |
| 374 it('CompoundObserver - valueFn reduction', function() { | |
| 375 var model = { a: 1, b: 2, c: 3 }; | |
| 376 | |
| 377 function valueFn(values) { | |
| 378 return values.reduce(function(last, cur) { | |
| 379 return typeof cur === 'number' ? last + cur : undefined; | |
| 380 }, 0); | |
| 381 } | |
| 382 | |
| 383 var compound = new CompoundObserver(); | |
| 384 compound.addPath(model, 'a'); | |
| 385 compound.addPath(model, 'b'); | |
| 386 compound.addPath(model, Path.get('c')); | |
| 387 | |
| 388 observer = new ObserverTransform(compound, valueFn); | |
| 389 assert.strictEqual(6, observer.open(callback)); | |
| 390 | |
| 391 model.a = -10; | |
| 392 model.b = 20; | |
| 393 model.c = 30; | |
| 394 assertPathChanges(40, 6); | |
| 395 | |
| 396 observer.close(); | |
| 397 }); | |
| 398 }) | |
| 399 | |
| 400 describe('PathObserver Tests', function() { | |
| 401 | |
| 402 beforeEach(doSetup); | |
| 403 | |
| 404 afterEach(doTeardown); | |
| 405 | |
| 406 it('Callback args', function() { | |
| 407 var obj = { | |
| 408 foo: 'bar' | |
| 409 }; | |
| 410 | |
| 411 var path = Path.get('foo'); | |
| 412 var observer = new PathObserver(obj, path); | |
| 413 | |
| 414 var args; | |
| 415 observer.open(function() { | |
| 416 args = Array.prototype.slice.apply(arguments); | |
| 417 }); | |
| 418 | |
| 419 obj.foo = 'baz'; | |
| 420 observer.deliver(); | |
| 421 assert.strictEqual(args.length, 3); | |
| 422 assert.strictEqual(args[0], 'baz'); | |
| 423 assert.strictEqual(args[1], 'bar'); | |
| 424 assert.strictEqual(args[2], observer); | |
| 425 assert.strictEqual(args[2].path, path); | |
| 426 observer.close(); | |
| 427 }); | |
| 428 | |
| 429 it('PathObserver.path', function() { | |
| 430 var obj = { | |
| 431 foo: 'bar' | |
| 432 }; | |
| 433 | |
| 434 var path = Path.get('foo'); | |
| 435 var observer = new PathObserver(obj, 'foo'); | |
| 436 assert.strictEqual(observer.path, Path.get('foo')); | |
| 437 }); | |
| 438 | |
| 439 | |
| 440 it('invalid', function() { | |
| 441 var observer = new PathObserver({ a: { b: 1 }}Â , 'a b'); | |
| 442 observer.open(callback); | |
| 443 assert.strictEqual(undefined, observer.value); | |
| 444 observer.deliver(); | |
| 445 assert.isFalse(callbackInvoked); | |
| 446 }); | |
| 447 | |
| 448 it('Optional target for callback', function() { | |
| 449 var target = { | |
| 450 changed: function(value, oldValue) { | |
| 451 this.called = true; | |
| 452 } | |
| 453 }; | |
| 454 var obj = { foo: 1 }; | |
| 455 var observer = new PathObserver(obj, 'foo'); | |
| 456 observer.open(target.changed, target); | |
| 457 obj.foo = 2; | |
| 458 observer.deliver(); | |
| 459 assert.isTrue(target.called); | |
| 460 | |
| 461 observer.close(); | |
| 462 }); | |
| 463 | |
| 464 it('Delivery Until No Changes', function() { | |
| 465 var obj = { foo: { bar: 5 }}; | |
| 466 var callbackCount = 0; | |
| 467 var observer = new PathObserver(obj, 'foo . bar'); | |
| 468 observer.open(function() { | |
| 469 callbackCount++; | |
| 470 if (!obj.foo.bar) | |
| 471 return; | |
| 472 | |
| 473 obj.foo.bar--; | |
| 474 }); | |
| 475 | |
| 476 obj.foo.bar--; | |
| 477 observer.deliver(); | |
| 478 | |
| 479 assert.equal(5, callbackCount); | |
| 480 | |
| 481 observer.close(); | |
| 482 }); | |
| 483 | |
| 484 it('Path disconnect', function() { | |
| 485 var arr = {}; | |
| 486 | |
| 487 arr.foo = 'bar'; | |
| 488 observer = new PathObserver(arr, 'foo'); | |
| 489 observer.open(callback); | |
| 490 arr.foo = 'baz'; | |
| 491 | |
| 492 assertPathChanges('baz', 'bar'); | |
| 493 arr.foo = 'bar'; | |
| 494 | |
| 495 observer.close(); | |
| 496 | |
| 497 arr.foo = 'boo'; | |
| 498 assertNoChanges(); | |
| 499 }); | |
| 500 | |
| 501 it('Path discardChanges', function() { | |
| 502 var arr = {}; | |
| 503 | |
| 504 arr.foo = 'bar'; | |
| 505 observer = new PathObserver(arr, 'foo'); | |
| 506 observer.open(callback); | |
| 507 arr.foo = 'baz'; | |
| 508 | |
| 509 assertPathChanges('baz', 'bar'); | |
| 510 | |
| 511 arr.foo = 'bat'; | |
| 512 observer.discardChanges(); | |
| 513 assertNoChanges(); | |
| 514 | |
| 515 arr.foo = 'bag'; | |
| 516 assertPathChanges('bag', 'bat'); | |
| 517 observer.close(); | |
| 518 }); | |
| 519 | |
| 520 it('Path setValue', function() { | |
| 521 var obj = {}; | |
| 522 | |
| 523 obj.foo = 'bar'; | |
| 524 observer = new PathObserver(obj, 'foo'); | |
| 525 observer.open(callback); | |
| 526 obj.foo = 'baz'; | |
| 527 | |
| 528 observer.setValue('bat'); | |
| 529 assert.strictEqual(obj.foo, 'bat'); | |
| 530 assertPathChanges('bat', 'bar'); | |
| 531 | |
| 532 observer.setValue('bot'); | |
| 533 observer.discardChanges(); | |
| 534 assertNoChanges(); | |
| 535 | |
| 536 observer.close(); | |
| 537 }); | |
| 538 | |
| 539 it('Degenerate Values', function() { | |
| 540 var emptyPath = Path.get(); | |
| 541 observer = new PathObserver(null, ''); | |
| 542 observer.open(callback); | |
| 543 assert.equal(null, observer.value); | |
| 544 observer.close(); | |
| 545 | |
| 546 var foo = {}; | |
| 547 observer = new PathObserver(foo, ''); | |
| 548 assert.equal(foo, observer.open(callback)); | |
| 549 observer.close(); | |
| 550 | |
| 551 observer = new PathObserver(3, ''); | |
| 552 assert.equal(3, observer.open(callback)); | |
| 553 observer.close(); | |
| 554 | |
| 555 observer = new PathObserver(undefined, 'a'); | |
| 556 assert.equal(undefined, observer.open(callback)); | |
| 557 observer.close(); | |
| 558 | |
| 559 var bar = { id: 23 }; | |
| 560 observer = new PathObserver(undefined, 'a/3!'); | |
| 561 assert.equal(undefined, observer.open(callback)); | |
| 562 observer.close(); | |
| 563 }); | |
| 564 | |
| 565 it('Path NaN', function() { | |
| 566 var foo = { val: 1 }; | |
| 567 observer = new PathObserver(foo, 'val'); | |
| 568 observer.open(callback); | |
| 569 foo.val = 0/0; | |
| 570 | |
| 571 // Can't use assertSummary because deepEqual() will fail with NaN | |
| 572 observer.deliver(); | |
| 573 assert.isTrue(callbackInvoked); | |
| 574 assert.isTrue(isNaN(callbackArgs[0])); | |
| 575 assert.strictEqual(1, callbackArgs[1]); | |
| 576 observer.close(); | |
| 577 }); | |
| 578 | |
| 579 it('Path Set Value Back To Same', function() { | |
| 580 var obj = {}; | |
| 581 var path = Path.get('foo'); | |
| 582 | |
| 583 path.setValueFrom(obj, 3); | |
| 584 assert.equal(3, obj.foo); | |
| 585 | |
| 586 observer = new PathObserver(obj, 'foo'); | |
| 587 assert.equal(3, observer.open(callback)); | |
| 588 | |
| 589 path.setValueFrom(obj, 2); | |
| 590 assert.equal(2, observer.discardChanges()); | |
| 591 | |
| 592 path.setValueFrom(obj, 3); | |
| 593 assert.equal(3, observer.discardChanges()); | |
| 594 | |
| 595 assertNoChanges(); | |
| 596 | |
| 597 observer.close(); | |
| 598 }); | |
| 599 | |
| 600 it('Path Triple Equals', function() { | |
| 601 var model = { }; | |
| 602 | |
| 603 observer = new PathObserver(model, 'foo'); | |
| 604 observer.open(callback); | |
| 605 | |
| 606 model.foo = null; | |
| 607 assertPathChanges(null, undefined); | |
| 608 | |
| 609 model.foo = undefined; | |
| 610 assertPathChanges(undefined, null); | |
| 611 | |
| 612 observer.close(); | |
| 613 }); | |
| 614 | |
| 615 it('Path Simple', function() { | |
| 616 var model = { }; | |
| 617 | |
| 618 observer = new PathObserver(model, 'foo'); | |
| 619 observer.open(callback); | |
| 620 | |
| 621 model.foo = 1; | |
| 622 assertPathChanges(1, undefined); | |
| 623 | |
| 624 model.foo = 2; | |
| 625 assertPathChanges(2, 1); | |
| 626 | |
| 627 delete model.foo; | |
| 628 assertPathChanges(undefined, 2); | |
| 629 | |
| 630 observer.close(); | |
| 631 }); | |
| 632 | |
| 633 it('Path Simple - path object', function() { | |
| 634 var model = { }; | |
| 635 | |
| 636 var path = Path.get('foo'); | |
| 637 observer = new PathObserver(model, path); | |
| 638 observer.open(callback); | |
| 639 | |
| 640 model.foo = 1; | |
| 641 assertPathChanges(1, undefined); | |
| 642 | |
| 643 model.foo = 2; | |
| 644 assertPathChanges(2, 1); | |
| 645 | |
| 646 delete model.foo; | |
| 647 assertPathChanges(undefined, 2); | |
| 648 | |
| 649 observer.close(); | |
| 650 }); | |
| 651 | |
| 652 it('Path - root is initially null', function(done) { | |
| 653 var model = { }; | |
| 654 | |
| 655 var path = Path.get('foo'); | |
| 656 observer = new PathObserver(model, 'foo.bar'); | |
| 657 observer.open(callback); | |
| 658 | |
| 659 model.foo = { }; | |
| 660 then(function() { | |
| 661 model.foo.bar = 1; | |
| 662 | |
| 663 }).then(function() { | |
| 664 assertPathChanges(1, undefined, true); | |
| 665 | |
| 666 observer.close(); | |
| 667 done(); | |
| 668 }); | |
| 669 }); | |
| 670 | |
| 671 it('Path With Indices', function() { | |
| 672 var model = []; | |
| 673 | |
| 674 observer = new PathObserver(model, '[0]'); | |
| 675 observer.open(callback); | |
| 676 | |
| 677 model.push(1); | |
| 678 assertPathChanges(1, undefined); | |
| 679 | |
| 680 observer.close(); | |
| 681 }); | |
| 682 | |
| 683 it('Path Observation', function() { | |
| 684 var model = { | |
| 685 a: { | |
| 686 b: { | |
| 687 c: 'hello, world' | |
| 688 } | |
| 689 } | |
| 690 }; | |
| 691 | |
| 692 observer = new PathObserver(model, 'a.b.c'); | |
| 693 observer.open(callback); | |
| 694 | |
| 695 model.a.b.c = 'hello, mom'; | |
| 696 assertPathChanges('hello, mom', 'hello, world'); | |
| 697 | |
| 698 model.a.b = { | |
| 699 c: 'hello, dad' | |
| 700 }; | |
| 701 assertPathChanges('hello, dad', 'hello, mom'); | |
| 702 | |
| 703 model.a = { | |
| 704 b: { | |
| 705 c: 'hello, you' | |
| 706 } | |
| 707 }; | |
| 708 assertPathChanges('hello, you', 'hello, dad'); | |
| 709 | |
| 710 model.a.b = 1; | |
| 711 assertPathChanges(undefined, 'hello, you'); | |
| 712 | |
| 713 // Stop observing | |
| 714 observer.close(); | |
| 715 | |
| 716 model.a.b = {c: 'hello, back again -- but not observing'}; | |
| 717 assertNoChanges(); | |
| 718 | |
| 719 // Resume observing | |
| 720 observer = new PathObserver(model, 'a.b.c'); | |
| 721 observer.open(callback); | |
| 722 | |
| 723 model.a.b.c = 'hello. Back for reals'; | |
| 724 assertPathChanges('hello. Back for reals', | |
| 725 'hello, back again -- but not observing'); | |
| 726 | |
| 727 observer.close(); | |
| 728 }); | |
| 729 | |
| 730 it('Path Set To Same As Prototype', function() { | |
| 731 var model = createObject({ | |
| 732 __proto__: { | |
| 733 id: 1 | |
| 734 } | |
| 735 }); | |
| 736 | |
| 737 observer = new PathObserver(model, 'id'); | |
| 738 observer.open(callback); | |
| 739 model.id = 1; | |
| 740 | |
| 741 assertNoChanges(); | |
| 742 observer.close(); | |
| 743 }); | |
| 744 | |
| 745 it('Path Set Read Only', function() { | |
| 746 var model = {}; | |
| 747 Object.defineProperty(model, 'x', { | |
| 748 configurable: true, | |
| 749 writable: false, | |
| 750 value: 1 | |
| 751 }); | |
| 752 observer = new PathObserver(model, 'x'); | |
| 753 observer.open(callback); | |
| 754 | |
| 755 assert.throws(function() { | |
| 756 model.x = 2; | |
| 757 }); | |
| 758 | |
| 759 assertNoChanges(); | |
| 760 observer.close(); | |
| 761 }); | |
| 762 | |
| 763 it('Path Set Shadows', function() { | |
| 764 var model = createObject({ | |
| 765 __proto__: { | |
| 766 x: 1 | |
| 767 } | |
| 768 }); | |
| 769 | |
| 770 observer = new PathObserver(model, 'x'); | |
| 771 observer.open(callback); | |
| 772 model.x = 2; | |
| 773 assertPathChanges(2, 1); | |
| 774 observer.close(); | |
| 775 }); | |
| 776 | |
| 777 it('Delete With Same Value On Prototype', function() { | |
| 778 var model = createObject({ | |
| 779 __proto__: { | |
| 780 x: 1, | |
| 781 }, | |
| 782 x: 1 | |
| 783 }); | |
| 784 | |
| 785 observer = new PathObserver(model, 'x'); | |
| 786 observer.open(callback); | |
| 787 delete model.x; | |
| 788 assertNoChanges(); | |
| 789 observer.close(); | |
| 790 }); | |
| 791 | |
| 792 it('Delete With Different Value On Prototype', function() { | |
| 793 var model = createObject({ | |
| 794 __proto__: { | |
| 795 x: 1, | |
| 796 }, | |
| 797 x: 2 | |
| 798 }); | |
| 799 | |
| 800 observer = new PathObserver(model, 'x'); | |
| 801 observer.open(callback); | |
| 802 delete model.x; | |
| 803 assertPathChanges(1, 2); | |
| 804 observer.close(); | |
| 805 }); | |
| 806 | |
| 807 it('Value Change On Prototype', function() { | |
| 808 var proto = { | |
| 809 x: 1 | |
| 810 } | |
| 811 var model = createObject({ | |
| 812 __proto__: proto | |
| 813 }); | |
| 814 | |
| 815 observer = new PathObserver(model, 'x'); | |
| 816 observer.open(callback); | |
| 817 model.x = 2; | |
| 818 assertPathChanges(2, 1); | |
| 819 | |
| 820 delete model.x; | |
| 821 assertPathChanges(1, 2); | |
| 822 | |
| 823 proto.x = 3; | |
| 824 assertPathChanges(3, 1); | |
| 825 observer.close(); | |
| 826 }); | |
| 827 | |
| 828 // FIXME: Need test of observing change on proto. | |
| 829 | |
| 830 it('Delete Of Non Configurable', function() { | |
| 831 var model = {}; | |
| 832 Object.defineProperty(model, 'x', { | |
| 833 configurable: false, | |
| 834 value: 1 | |
| 835 }); | |
| 836 | |
| 837 observer = new PathObserver(model, 'x'); | |
| 838 observer.open(callback); | |
| 839 | |
| 840 assert.throws(function() { | |
| 841 delete model.x; | |
| 842 }); | |
| 843 | |
| 844 assertNoChanges(); | |
| 845 observer.close(); | |
| 846 }); | |
| 847 | |
| 848 it('Notify', function() { | |
| 849 if (typeof Object.getNotifier !== 'function') | |
| 850 return; | |
| 851 | |
| 852 var model = { | |
| 853 a: {} | |
| 854 } | |
| 855 | |
| 856 var _b = 2; | |
| 857 | |
| 858 Object.defineProperty(model.a, 'b', { | |
| 859 get: function() { return _b; }, | |
| 860 set: function(b) { | |
| 861 Object.getNotifier(this).notify({ | |
| 862 type: 'update', | |
| 863 name: 'b', | |
| 864 oldValue: _b | |
| 865 }); | |
| 866 | |
| 867 _b = b; | |
| 868 } | |
| 869 }); | |
| 870 | |
| 871 observer = new PathObserver(model, 'a.b'); | |
| 872 observer.open(callback); | |
| 873 _b = 3; | |
| 874 assertPathChanges(3, 2); | |
| 875 | |
| 876 model.a.b = 4; // will be observed. | |
| 877 assertPathChanges(4, 3); | |
| 878 | |
| 879 observer.close(); | |
| 880 }); | |
| 881 | |
| 882 it('issue-161', function(done) { | |
| 883 var model = { model: 'model' }; | |
| 884 var ob1 = new PathObserver(model, 'obj.bar'); | |
| 885 var called = false | |
| 886 ob1.open(function() { | |
| 887 called = true; | |
| 888 }); | |
| 889 | |
| 890 var obj2 = new PathObserver(model, 'obj'); | |
| 891 obj2.open(function() { | |
| 892 model.obj.bar = true; | |
| 893 }); | |
| 894 | |
| 895 model.obj = { 'obj': 'obj' }; | |
| 896 model.obj.foo = true; | |
| 897 | |
| 898 then(function() { | |
| 899 assert.strictEqual(called, true); | |
| 900 done(); | |
| 901 }); | |
| 902 }); | |
| 903 | |
| 904 it('object cycle', function(done) { | |
| 905 var model = { a: {}, c: 1 }; | |
| 906 model.a.b = model; | |
| 907 | |
| 908 var called = 0; | |
| 909 new PathObserver(model, 'a.b.c').open(function() { | |
| 910 called++; | |
| 911 }); | |
| 912 | |
| 913 // This change should be detected, even though it's a change to the root | |
| 914 // object and isn't a change to `a`. | |
| 915 model.c = 42; | |
| 916 | |
| 917 then(function() { | |
| 918 assert.equal(called, 1); | |
| 919 done(); | |
| 920 }); | |
| 921 }); | |
| 922 | |
| 923 }); | |
| 924 | |
| 925 | |
| 926 describe('CompoundObserver Tests', function() { | |
| 927 | |
| 928 beforeEach(doSetup); | |
| 929 | |
| 930 afterEach(doTeardown); | |
| 931 | |
| 932 it('Simple', function() { | |
| 933 var model = { a: 1, b: 2, c: 3 }; | |
| 934 | |
| 935 observer = new CompoundObserver(); | |
| 936 observer.addPath(model, 'a'); | |
| 937 observer.addPath(model, 'b'); | |
| 938 observer.addPath(model, Path.get('c')); | |
| 939 observer.open(callback); | |
| 940 assertNoChanges(); | |
| 941 | |
| 942 var observerCallbackArg = [model, Path.get('a'), | |
| 943 model, Path.get('b'), | |
| 944 model, Path.get('c')]; | |
| 945 model.a = -10; | |
| 946 model.b = 20; | |
| 947 model.c = 30; | |
| 948 assertCompoundPathChanges([-10, 20, 30], [1, 2, 3], | |
| 949 observerCallbackArg); | |
| 950 | |
| 951 model.a = 'a'; | |
| 952 model.c = 'c'; | |
| 953 assertCompoundPathChanges(['a', 20, 'c'], [-10,, 30], | |
| 954 observerCallbackArg); | |
| 955 | |
| 956 model.a = 2; | |
| 957 model.b = 3; | |
| 958 model.c = 4; | |
| 959 | |
| 960 assertCompoundPathChanges([2, 3, 4], ['a', 20, 'c'], | |
| 961 observerCallbackArg); | |
| 962 | |
| 963 model.a = 'z'; | |
| 964 model.b = 'y'; | |
| 965 model.c = 'x'; | |
| 966 assert.deepEqual(['z', 'y', 'x'], observer.discardChanges()); | |
| 967 assertNoChanges(); | |
| 968 | |
| 969 assert.strictEqual('z', model.a); | |
| 970 assert.strictEqual('y', model.b); | |
| 971 assert.strictEqual('x', model.c); | |
| 972 assertNoChanges(); | |
| 973 | |
| 974 observer.close(); | |
| 975 }); | |
| 976 | |
| 977 it('reportChangesOnOpen', function() { | |
| 978 var model = { a: 1, b: 2, c: 3 }; | |
| 979 | |
| 980 observer = new CompoundObserver(true); | |
| 981 observer.addPath(model, 'a'); | |
| 982 observer.addPath(model, 'b'); | |
| 983 observer.addPath(model, Path.get('c')); | |
| 984 | |
| 985 model.a = -10; | |
| 986 model.b = 20; | |
| 987 observer.open(callback); | |
| 988 var observerCallbackArg = [model, Path.get('a'), | |
| 989 model, Path.get('b'), | |
| 990 model, Path.get('c')]; | |
| 991 assertCompoundPathChanges([-10, 20, 3], [1, 2, ], | |
| 992 observerCallbackArg, true); | |
| 993 observer.close(); | |
| 994 }); | |
| 995 | |
| 996 it('Degenerate Values', function() { | |
| 997 var model = {}; | |
| 998 observer = new CompoundObserver(); | |
| 999 observer.addPath({}, '.'); // invalid path | |
| 1000 observer.addPath('obj-value', ''); // empty path | |
| 1001 observer.addPath({}, 'foo'); // unreachable | |
| 1002 observer.addPath(3, 'bar'); // non-object with non-empty path | |
| 1003 var values = observer.open(callback); | |
| 1004 assert.strictEqual(4, values.length); | |
| 1005 assert.strictEqual(undefined, values[0]); | |
| 1006 assert.strictEqual('obj-value', values[1]); | |
| 1007 assert.strictEqual(undefined, values[2]); | |
| 1008 assert.strictEqual(undefined, values[3]); | |
| 1009 observer.close(); | |
| 1010 }); | |
| 1011 | |
| 1012 it('valueFn - return object literal', function() { | |
| 1013 var model = { a: 1}; | |
| 1014 | |
| 1015 function valueFn(values) { | |
| 1016 return {}; | |
| 1017 } | |
| 1018 | |
| 1019 observer = new CompoundObserver(valueFn); | |
| 1020 | |
| 1021 observer.addPath(model, 'a'); | |
| 1022 observer.open(callback); | |
| 1023 model.a = 2; | |
| 1024 | |
| 1025 observer.deliver(); | |
| 1026 assert.isTrue(window.dirtyCheckCycleCount === undefined || | |
| 1027 window.dirtyCheckCycleCount === 1); | |
| 1028 observer.close(); | |
| 1029 }); | |
| 1030 | |
| 1031 it('reset', function() { | |
| 1032 var model = { a: 1, b: 2, c: 3 }; | |
| 1033 var callCount = 0; | |
| 1034 function callback() { | |
| 1035 callCount++; | |
| 1036 } | |
| 1037 | |
| 1038 observer = new CompoundObserver(); | |
| 1039 | |
| 1040 observer.addPath(model, 'a'); | |
| 1041 observer.addPath(model, 'b'); | |
| 1042 assert.deepEqual([1, 2], observer.open(callback)); | |
| 1043 | |
| 1044 model.a = 2; | |
| 1045 observer.deliver(); | |
| 1046 assert.strictEqual(1, callCount); | |
| 1047 | |
| 1048 model.b = 3; | |
| 1049 observer.deliver(); | |
| 1050 assert.strictEqual(2, callCount); | |
| 1051 | |
| 1052 model.c = 4; | |
| 1053 observer.deliver(); | |
| 1054 assert.strictEqual(2, callCount); | |
| 1055 | |
| 1056 observer.startReset(); | |
| 1057 observer.addPath(model, 'b'); | |
| 1058 observer.addPath(model, 'c'); | |
| 1059 assert.deepEqual([3, 4], observer.finishReset()) | |
| 1060 | |
| 1061 model.a = 3; | |
| 1062 observer.deliver(); | |
| 1063 assert.strictEqual(2, callCount); | |
| 1064 | |
| 1065 model.b = 4; | |
| 1066 observer.deliver(); | |
| 1067 assert.strictEqual(3, callCount); | |
| 1068 | |
| 1069 model.c = 5; | |
| 1070 observer.deliver(); | |
| 1071 assert.strictEqual(4, callCount); | |
| 1072 | |
| 1073 observer.close(); | |
| 1074 }); | |
| 1075 | |
| 1076 it('Heterogeneous', function() { | |
| 1077 var model = { a: 1, b: 2 }; | |
| 1078 var otherModel = { c: 3 }; | |
| 1079 | |
| 1080 function valueFn(value) { return value * 2; } | |
| 1081 function setValueFn(value) { return value / 2; } | |
| 1082 | |
| 1083 var compound = new CompoundObserver; | |
| 1084 compound.addPath(model, 'a'); | |
| 1085 compound.addObserver(new ObserverTransform(new PathObserver(model, 'b'), | |
| 1086 valueFn, setValueFn)); | |
| 1087 compound.addObserver(new PathObserver(otherModel, 'c')); | |
| 1088 | |
| 1089 function combine(values) { | |
| 1090 return values[0] + values[1] + values[2]; | |
| 1091 }; | |
| 1092 observer = new ObserverTransform(compound, combine); | |
| 1093 assert.strictEqual(8, observer.open(callback)); | |
| 1094 | |
| 1095 model.a = 2; | |
| 1096 model.b = 4; | |
| 1097 assertPathChanges(13, 8); | |
| 1098 | |
| 1099 model.b = 10; | |
| 1100 otherModel.c = 5; | |
| 1101 assertPathChanges(27, 13); | |
| 1102 | |
| 1103 model.a = 20; | |
| 1104 model.b = 1; | |
| 1105 otherModel.c = 5; | |
| 1106 assertNoChanges(); | |
| 1107 | |
| 1108 observer.close(); | |
| 1109 }) | |
| 1110 }); | |
| 1111 | |
| 1112 describe('ArrayObserver Tests', function() { | |
| 1113 | |
| 1114 beforeEach(doSetup); | |
| 1115 | |
| 1116 afterEach(doTeardown); | |
| 1117 | |
| 1118 function ensureNonSparse(arr) { | |
| 1119 for (var i = 0; i < arr.length; i++) { | |
| 1120 if (i in arr) | |
| 1121 continue; | |
| 1122 arr[i] = undefined; | |
| 1123 } | |
| 1124 } | |
| 1125 | |
| 1126 function assertArrayChanges(expectSplices) { | |
| 1127 observer.deliver(); | |
| 1128 var splices = callbackArgs[0]; | |
| 1129 | |
| 1130 assert.isTrue(callbackInvoked); | |
| 1131 | |
| 1132 splices.forEach(function(splice) { | |
| 1133 ensureNonSparse(splice.removed); | |
| 1134 }); | |
| 1135 | |
| 1136 expectSplices.forEach(function(splice) { | |
| 1137 ensureNonSparse(splice.removed); | |
| 1138 }); | |
| 1139 | |
| 1140 assert.deepEqual(expectSplices, splices); | |
| 1141 callbackArgs = undefined; | |
| 1142 callbackInvoked = false; | |
| 1143 } | |
| 1144 | |
| 1145 function applySplicesAndAssertDeepEqual(orig, copy) { | |
| 1146 observer.deliver(); | |
| 1147 if (callbackInvoked) { | |
| 1148 var splices = callbackArgs[0]; | |
| 1149 ArrayObserver.applySplices(copy, orig, splices); | |
| 1150 } | |
| 1151 | |
| 1152 ensureNonSparse(orig); | |
| 1153 ensureNonSparse(copy); | |
| 1154 assert.deepEqual(orig, copy); | |
| 1155 callbackArgs = undefined; | |
| 1156 callbackInvoked = false; | |
| 1157 } | |
| 1158 | |
| 1159 function assertEditDistance(orig, expectDistance) { | |
| 1160 observer.deliver(); | |
| 1161 var splices = callbackArgs[0]; | |
| 1162 var actualDistance = 0; | |
| 1163 | |
| 1164 if (callbackInvoked) { | |
| 1165 splices.forEach(function(splice) { | |
| 1166 actualDistance += splice.addedCount + splice.removed.length; | |
| 1167 }); | |
| 1168 } | |
| 1169 | |
| 1170 assert.deepEqual(expectDistance, actualDistance); | |
| 1171 callbackArgs = undefined; | |
| 1172 callbackInvoked = false; | |
| 1173 } | |
| 1174 | |
| 1175 function arrayMutationTest(arr, operations) { | |
| 1176 var copy = arr.slice(); | |
| 1177 observer = new ArrayObserver(arr); | |
| 1178 observer.open(callback); | |
| 1179 operations.forEach(function(op) { | |
| 1180 switch(op.name) { | |
| 1181 case 'delete': | |
| 1182 delete arr[op.index]; | |
| 1183 break; | |
| 1184 | |
| 1185 case 'update': | |
| 1186 arr[op.index] = op.value; | |
| 1187 break; | |
| 1188 | |
| 1189 default: | |
| 1190 arr[op.name].apply(arr, op.args); | |
| 1191 break; | |
| 1192 } | |
| 1193 }); | |
| 1194 | |
| 1195 applySplicesAndAssertDeepEqual(arr, copy); | |
| 1196 observer.close(); | |
| 1197 } | |
| 1198 | |
| 1199 it('Optional target for callback', function() { | |
| 1200 var target = { | |
| 1201 changed: function(splices) { | |
| 1202 this.called = true; | |
| 1203 } | |
| 1204 }; | |
| 1205 var obj = []; | |
| 1206 var observer = new ArrayObserver(obj); | |
| 1207 observer.open(target.changed, target); | |
| 1208 obj.length = 1; | |
| 1209 observer.deliver(); | |
| 1210 assert.isTrue(target.called); | |
| 1211 observer.close(); | |
| 1212 }); | |
| 1213 | |
| 1214 it('Delivery Until No Changes', function() { | |
| 1215 var arr = [0, 1, 2, 3, 4]; | |
| 1216 var callbackCount = 0; | |
| 1217 var observer = new ArrayObserver(arr); | |
| 1218 observer.open(function() { | |
| 1219 callbackCount++; | |
| 1220 arr.shift(); | |
| 1221 }); | |
| 1222 | |
| 1223 arr.shift(); | |
| 1224 observer.deliver(); | |
| 1225 | |
| 1226 assert.equal(5, callbackCount); | |
| 1227 | |
| 1228 observer.close(); | |
| 1229 }); | |
| 1230 | |
| 1231 it('Array disconnect', function() { | |
| 1232 var arr = [ 0 ]; | |
| 1233 | |
| 1234 observer = new ArrayObserver(arr); | |
| 1235 observer.open(callback); | |
| 1236 | |
| 1237 arr[0] = 1; | |
| 1238 | |
| 1239 assertArrayChanges([{ | |
| 1240 index: 0, | |
| 1241 removed: [0], | |
| 1242 addedCount: 1 | |
| 1243 }]); | |
| 1244 | |
| 1245 observer.close(); | |
| 1246 arr[1] = 2; | |
| 1247 assertNoChanges(); | |
| 1248 }); | |
| 1249 | |
| 1250 it('Array discardChanges', function() { | |
| 1251 var arr = []; | |
| 1252 | |
| 1253 arr.push(1); | |
| 1254 observer = new ArrayObserver(arr); | |
| 1255 observer.open(callback); | |
| 1256 arr.push(2); | |
| 1257 | |
| 1258 assertArrayChanges([{ | |
| 1259 index: 1, | |
| 1260 removed: [], | |
| 1261 addedCount: 1 | |
| 1262 }]); | |
| 1263 | |
| 1264 arr.push(3); | |
| 1265 observer.discardChanges(); | |
| 1266 assertNoChanges(); | |
| 1267 | |
| 1268 arr.pop(); | |
| 1269 assertArrayChanges([{ | |
| 1270 index: 2, | |
| 1271 removed: [3], | |
| 1272 addedCount: 0 | |
| 1273 }]); | |
| 1274 observer.close(); | |
| 1275 }); | |
| 1276 | |
| 1277 it('Array', function() { | |
| 1278 var model = [0, 1]; | |
| 1279 | |
| 1280 observer = new ArrayObserver(model); | |
| 1281 observer.open(callback); | |
| 1282 | |
| 1283 model[0] = 2; | |
| 1284 | |
| 1285 assertArrayChanges([{ | |
| 1286 index: 0, | |
| 1287 removed: [0], | |
| 1288 addedCount: 1 | |
| 1289 }]); | |
| 1290 | |
| 1291 model[1] = 3; | |
| 1292 assertArrayChanges([{ | |
| 1293 index: 1, | |
| 1294 removed: [1], | |
| 1295 addedCount: 1 | |
| 1296 }]); | |
| 1297 | |
| 1298 observer.close(); | |
| 1299 }); | |
| 1300 | |
| 1301 it('Array observe non-array throws', function() { | |
| 1302 assert.throws(function () { | |
| 1303 observer = new ArrayObserver({}); | |
| 1304 }); | |
| 1305 }); | |
| 1306 | |
| 1307 it('Array Set Same', function() { | |
| 1308 var model = [1]; | |
| 1309 | |
| 1310 observer = new ArrayObserver(model); | |
| 1311 observer.open(callback); | |
| 1312 | |
| 1313 model[0] = 1; | |
| 1314 observer.deliver(); | |
| 1315 assert.isFalse(callbackInvoked); | |
| 1316 observer.close(); | |
| 1317 }); | |
| 1318 | |
| 1319 it('Array Splice', function() { | |
| 1320 var model = [0, 1] | |
| 1321 | |
| 1322 observer = new ArrayObserver(model); | |
| 1323 observer.open(callback); | |
| 1324 | |
| 1325 model.splice(1, 1, 2, 3); // [0, 2, 3] | |
| 1326 assertArrayChanges([{ | |
| 1327 index: 1, | |
| 1328 removed: [1], | |
| 1329 addedCount: 2 | |
| 1330 }]); | |
| 1331 | |
| 1332 model.splice(0, 1); // [2, 3] | |
| 1333 assertArrayChanges([{ | |
| 1334 index: 0, | |
| 1335 removed: [0], | |
| 1336 addedCount: 0 | |
| 1337 }]); | |
| 1338 | |
| 1339 model.splice(); | |
| 1340 assertNoChanges(); | |
| 1341 | |
| 1342 model.splice(0, 0); | |
| 1343 assertNoChanges(); | |
| 1344 | |
| 1345 model.splice(0, -1); | |
| 1346 assertNoChanges(); | |
| 1347 | |
| 1348 model.splice(-1, 0, 1.5); // [2, 1.5, 3] | |
| 1349 assertArrayChanges([{ | |
| 1350 index: 1, | |
| 1351 removed: [], | |
| 1352 addedCount: 1 | |
| 1353 }]); | |
| 1354 | |
| 1355 model.splice(3, 0, 0); // [2, 1.5, 3, 0] | |
| 1356 assertArrayChanges([{ | |
| 1357 index: 3, | |
| 1358 removed: [], | |
| 1359 addedCount: 1 | |
| 1360 }]); | |
| 1361 | |
| 1362 model.splice(0); // [] | |
| 1363 assertArrayChanges([{ | |
| 1364 index: 0, | |
| 1365 removed: [2, 1.5, 3, 0], | |
| 1366 addedCount: 0 | |
| 1367 }]); | |
| 1368 | |
| 1369 observer.close(); | |
| 1370 }); | |
| 1371 | |
| 1372 it('Array Splice Truncate And Expand With Length', function() { | |
| 1373 var model = ['a', 'b', 'c', 'd', 'e']; | |
| 1374 | |
| 1375 observer = new ArrayObserver(model); | |
| 1376 observer.open(callback); | |
| 1377 | |
| 1378 model.length = 2; | |
| 1379 | |
| 1380 assertArrayChanges([{ | |
| 1381 index: 2, | |
| 1382 removed: ['c', 'd', 'e'], | |
| 1383 addedCount: 0 | |
| 1384 }]); | |
| 1385 | |
| 1386 model.length = 5; | |
| 1387 | |
| 1388 assertArrayChanges([{ | |
| 1389 index: 2, | |
| 1390 removed: [], | |
| 1391 addedCount: 3 | |
| 1392 }]); | |
| 1393 | |
| 1394 observer.close(); | |
| 1395 }); | |
| 1396 | |
| 1397 it('Array Splice Delete Too Many', function() { | |
| 1398 var model = ['a', 'b', 'c']; | |
| 1399 | |
| 1400 observer = new ArrayObserver(model); | |
| 1401 observer.open(callback); | |
| 1402 | |
| 1403 model.splice(2, 3); // ['a', 'b'] | |
| 1404 assertArrayChanges([{ | |
| 1405 index: 2, | |
| 1406 removed: ['c'], | |
| 1407 addedCount: 0 | |
| 1408 }]); | |
| 1409 | |
| 1410 observer.close(); | |
| 1411 }); | |
| 1412 | |
| 1413 it('Array Length', function() { | |
| 1414 var model = [0, 1]; | |
| 1415 | |
| 1416 observer = new ArrayObserver(model); | |
| 1417 observer.open(callback); | |
| 1418 | |
| 1419 model.length = 5; // [0, 1, , , ,]; | |
| 1420 assertArrayChanges([{ | |
| 1421 index: 2, | |
| 1422 removed: [], | |
| 1423 addedCount: 3 | |
| 1424 }]); | |
| 1425 | |
| 1426 model.length = 1; | |
| 1427 assertArrayChanges([{ | |
| 1428 index: 1, | |
| 1429 removed: [1, , , ,], | |
| 1430 addedCount: 0 | |
| 1431 }]); | |
| 1432 | |
| 1433 model.length = 1; | |
| 1434 assertNoChanges(); | |
| 1435 | |
| 1436 observer.close(); | |
| 1437 }); | |
| 1438 | |
| 1439 it('Array Push', function() { | |
| 1440 var model = [0, 1]; | |
| 1441 | |
| 1442 observer = new ArrayObserver(model); | |
| 1443 observer.open(callback); | |
| 1444 | |
| 1445 model.push(2, 3); // [0, 1, 2, 3] | |
| 1446 assertArrayChanges([{ | |
| 1447 index: 2, | |
| 1448 removed: [], | |
| 1449 addedCount: 2 | |
| 1450 }]); | |
| 1451 | |
| 1452 model.push(); | |
| 1453 assertNoChanges(); | |
| 1454 | |
| 1455 observer.close(); | |
| 1456 }); | |
| 1457 | |
| 1458 it('Array Pop', function() { | |
| 1459 var model = [0, 1]; | |
| 1460 | |
| 1461 observer = new ArrayObserver(model); | |
| 1462 observer.open(callback); | |
| 1463 | |
| 1464 model.pop(); // [0] | |
| 1465 assertArrayChanges([{ | |
| 1466 index: 1, | |
| 1467 removed: [1], | |
| 1468 addedCount: 0 | |
| 1469 }]); | |
| 1470 | |
| 1471 model.pop(); // [] | |
| 1472 assertArrayChanges([{ | |
| 1473 index: 0, | |
| 1474 removed: [0], | |
| 1475 addedCount: 0 | |
| 1476 }]); | |
| 1477 | |
| 1478 model.pop(); | |
| 1479 assertNoChanges(); | |
| 1480 | |
| 1481 observer.close(); | |
| 1482 }); | |
| 1483 | |
| 1484 it('Array Shift', function() { | |
| 1485 var model = [0, 1]; | |
| 1486 | |
| 1487 observer = new ArrayObserver(model); | |
| 1488 observer.open(callback); | |
| 1489 | |
| 1490 model.shift(); // [1] | |
| 1491 assertArrayChanges([{ | |
| 1492 index: 0, | |
| 1493 removed: [0], | |
| 1494 addedCount: 0 | |
| 1495 }]); | |
| 1496 | |
| 1497 model.shift(); // [] | |
| 1498 assertArrayChanges([{ | |
| 1499 index: 0, | |
| 1500 removed: [1], | |
| 1501 addedCount: 0 | |
| 1502 }]); | |
| 1503 | |
| 1504 model.shift(); | |
| 1505 assertNoChanges(); | |
| 1506 | |
| 1507 observer.close(); | |
| 1508 }); | |
| 1509 | |
| 1510 it('Array Unshift', function() { | |
| 1511 var model = [0, 1]; | |
| 1512 | |
| 1513 observer = new ArrayObserver(model); | |
| 1514 observer.open(callback); | |
| 1515 | |
| 1516 model.unshift(-1); // [-1, 0, 1] | |
| 1517 assertArrayChanges([{ | |
| 1518 index: 0, | |
| 1519 removed: [], | |
| 1520 addedCount: 1 | |
| 1521 }]); | |
| 1522 | |
| 1523 model.unshift(-3, -2); // [] | |
| 1524 assertArrayChanges([{ | |
| 1525 index: 0, | |
| 1526 removed: [], | |
| 1527 addedCount: 2 | |
| 1528 }]); | |
| 1529 | |
| 1530 model.unshift(); | |
| 1531 assertNoChanges(); | |
| 1532 | |
| 1533 observer.close(); | |
| 1534 }); | |
| 1535 | |
| 1536 it('Array Tracker Contained', function() { | |
| 1537 arrayMutationTest( | |
| 1538 ['a', 'b'], | |
| 1539 [ | |
| 1540 { name: 'splice', args: [1, 1] }, | |
| 1541 { name: 'unshift', args: ['c', 'd', 'e'] }, | |
| 1542 { name: 'splice', args: [1, 2, 'f'] } | |
| 1543 ] | |
| 1544 ); | |
| 1545 }); | |
| 1546 | |
| 1547 it('Array Tracker Delete Empty', function() { | |
| 1548 arrayMutationTest( | |
| 1549 [], | |
| 1550 [ | |
| 1551 { name: 'delete', index: 0 }, | |
| 1552 { name: 'splice', args: [0, 0, 'a', 'b', 'c'] } | |
| 1553 ] | |
| 1554 ); | |
| 1555 }); | |
| 1556 | |
| 1557 it('Array Tracker Right Non Overlap', function() { | |
| 1558 arrayMutationTest( | |
| 1559 ['a', 'b', 'c', 'd'], | |
| 1560 [ | |
| 1561 { name: 'splice', args: [0, 1, 'e'] }, | |
| 1562 { name: 'splice', args: [2, 1, 'f', 'g'] } | |
| 1563 ] | |
| 1564 ); | |
| 1565 }); | |
| 1566 | |
| 1567 it('Array Tracker Left Non Overlap', function() { | |
| 1568 arrayMutationTest( | |
| 1569 ['a', 'b', 'c', 'd'], | |
| 1570 [ | |
| 1571 { name: 'splice', args: [3, 1, 'f', 'g'] }, | |
| 1572 { name: 'splice', args: [0, 1, 'e'] } | |
| 1573 ] | |
| 1574 ); | |
| 1575 }); | |
| 1576 | |
| 1577 it('Array Tracker Right Adjacent', function() { | |
| 1578 arrayMutationTest( | |
| 1579 ['a', 'b', 'c', 'd'], | |
| 1580 [ | |
| 1581 { name: 'splice', args: [1, 1, 'e'] }, | |
| 1582 { name: 'splice', args: [2, 1, 'f', 'g'] } | |
| 1583 ] | |
| 1584 ); | |
| 1585 }); | |
| 1586 | |
| 1587 it('Array Tracker Left Adjacent', function() { | |
| 1588 arrayMutationTest( | |
| 1589 ['a', 'b', 'c', 'd'], | |
| 1590 [ | |
| 1591 { name: 'splice', args: [2, 2, 'e'] }, | |
| 1592 { name: 'splice', args: [1, 1, 'f', 'g'] } | |
| 1593 ] | |
| 1594 ); | |
| 1595 }); | |
| 1596 | |
| 1597 it('Array Tracker Right Overlap', function() { | |
| 1598 arrayMutationTest( | |
| 1599 ['a', 'b', 'c', 'd'], | |
| 1600 [ | |
| 1601 { name: 'splice', args: [1, 1, 'e'] }, | |
| 1602 { name: 'splice', args: [1, 1, 'f', 'g'] } | |
| 1603 ] | |
| 1604 ); | |
| 1605 }); | |
| 1606 | |
| 1607 it('Array Tracker Left Overlap', function() { | |
| 1608 arrayMutationTest( | |
| 1609 ['a', 'b', 'c', 'd'], | |
| 1610 [ | |
| 1611 // a b [e f g] d | |
| 1612 { name: 'splice', args: [2, 1, 'e', 'f', 'g'] }, | |
| 1613 // a [h i j] f g d | |
| 1614 { name: 'splice', args: [1, 2, 'h', 'i', 'j'] } | |
| 1615 ] | |
| 1616 ); | |
| 1617 }); | |
| 1618 | |
| 1619 it('Array Tracker Prefix And Suffix One In', function() { | |
| 1620 arrayMutationTest( | |
| 1621 ['a', 'b', 'c', 'd'], | |
| 1622 [ | |
| 1623 { name: 'unshift', args: ['z'] }, | |
| 1624 { name: 'push', arg: ['z'] } | |
| 1625 ] | |
| 1626 ); | |
| 1627 }); | |
| 1628 | |
| 1629 it('Array Tracker Shift One', function() { | |
| 1630 arrayMutationTest( | |
| 1631 [16, 15, 15], | |
| 1632 [ | |
| 1633 { name: 'shift', args: ['z'] } | |
| 1634 ] | |
| 1635 ); | |
| 1636 }); | |
| 1637 | |
| 1638 it('Array Tracker Update Delete', function() { | |
| 1639 arrayMutationTest( | |
| 1640 ['a', 'b', 'c', 'd'], | |
| 1641 [ | |
| 1642 { name: 'splice', args: [2, 1, 'e', 'f', 'g'] }, | |
| 1643 { name: 'update', index: 0, value: 'h' }, | |
| 1644 { name: 'delete', index: 1 } | |
| 1645 ] | |
| 1646 ); | |
| 1647 }); | |
| 1648 | |
| 1649 it('Array Tracker Update After Delete', function() { | |
| 1650 arrayMutationTest( | |
| 1651 ['a', 'b', undefined, 'd'], | |
| 1652 [ | |
| 1653 { name: 'update', index: 2, value: 'e' } | |
| 1654 ] | |
| 1655 ); | |
| 1656 }); | |
| 1657 | |
| 1658 it('Array Tracker Delete Mid Array', function() { | |
| 1659 arrayMutationTest( | |
| 1660 ['a', 'b', 'c', 'd'], | |
| 1661 [ | |
| 1662 { name: 'delete', index: 2 } | |
| 1663 ] | |
| 1664 ); | |
| 1665 }); | |
| 1666 | |
| 1667 it('Array Random Case 1', function() { | |
| 1668 var model = ['a','b']; | |
| 1669 var copy = model.slice(); | |
| 1670 | |
| 1671 observer = new ArrayObserver(model); | |
| 1672 observer.open(callback); | |
| 1673 | |
| 1674 model.splice(0, 1, 'c', 'd', 'e'); | |
| 1675 model.splice(4,0,'f'); | |
| 1676 model.splice(3,2); | |
| 1677 | |
| 1678 applySplicesAndAssertDeepEqual(model, copy); | |
| 1679 }); | |
| 1680 | |
| 1681 it('Array Random Case 2', function() { | |
| 1682 var model = [3,4]; | |
| 1683 var copy = model.slice(); | |
| 1684 | |
| 1685 observer = new ArrayObserver(model); | |
| 1686 observer.open(callback); | |
| 1687 | |
| 1688 model.splice(2,0,8); | |
| 1689 model.splice(0,1,0,5); | |
| 1690 model.splice(2,2); | |
| 1691 | |
| 1692 applySplicesAndAssertDeepEqual(model, copy); | |
| 1693 }); | |
| 1694 | |
| 1695 it('Array Random Case 3', function() { | |
| 1696 var model = [1,3,6]; | |
| 1697 var copy = model.slice(); | |
| 1698 | |
| 1699 observer = new ArrayObserver(model); | |
| 1700 observer.open(callback); | |
| 1701 | |
| 1702 model.splice(1,1); | |
| 1703 model.splice(0,2,1,7); | |
| 1704 model.splice(1,0,3,7); | |
| 1705 | |
| 1706 applySplicesAndAssertDeepEqual(model, copy); | |
| 1707 }); | |
| 1708 | |
| 1709 it('Array Tracker No Proxies Edits', function() { | |
| 1710 var model = []; | |
| 1711 observer = new ArrayObserver(model); | |
| 1712 observer.open(callback); | |
| 1713 model.length = 0; | |
| 1714 model.push(1, 2, 3); | |
| 1715 assertEditDistance(model, 3); | |
| 1716 observer.close(); | |
| 1717 | |
| 1718 model = ['x', 'x', 'x', 'x', '1', '2', '3']; | |
| 1719 observer = new ArrayObserver(model); | |
| 1720 observer.open(callback); | |
| 1721 model.length = 0; | |
| 1722 model.push('1', '2', '3', 'y', 'y', 'y', 'y'); | |
| 1723 assertEditDistance(model, 8); | |
| 1724 observer.close(); | |
| 1725 | |
| 1726 model = ['1', '2', '3', '4', '5']; | |
| 1727 observer = new ArrayObserver(model); | |
| 1728 observer.open(callback); | |
| 1729 model.length = 0; | |
| 1730 model.push('a', '2', 'y', 'y', '4', '5', 'z', 'z'); | |
| 1731 assertEditDistance(model, 7); | |
| 1732 observer.close(); | |
| 1733 }); | |
| 1734 }); | |
| 1735 </script> | |
| OLD | NEW |