From 9d9012846350fa23eaae42cb6d613fca69c94666 Mon Sep 17 00:00:00 2001 From: Jeff Cross Date: Tue, 26 May 2015 17:12:38 -0700 Subject: [PATCH] refactor(ChangeDetection): convert change detection tests to typescript --- modules/angular2/src/facade/lang.ts | 4 +- modules/angular2/src/test_lib/test_bed.ts | 4 +- modules/angular2/src/test_lib/test_lib.ts | 20 +- modules/angular2/src/util/decorators.ts | 4 +- ...ction_spec.js => change_detection_spec.ts} | 9 +- .../change_detection/change_detector_spec.js | 1012 ---------------- .../change_detection/change_detector_spec.ts | 1021 +++++++++++++++++ .../test/change_detection/coalesce_spec.js | 82 -- .../test/change_detection/coalesce_spec.ts | 58 + .../test/change_detection/iterable.es6 | 9 - .../test/change_detection/iterable.ts | 8 + .../parser/{lexer_spec.js => lexer_spec.ts} | 77 +- .../parser/{locals_spec.js => locals_spec.ts} | 8 +- .../parser/{parser_spec.js => parser_spec.ts} | 250 ++-- .../pipes/iterable_changes_spec.js | 299 ----- .../pipes/iterable_changes_spec.ts | 295 +++++ .../change_detection/pipes/json_pipe_spec.js | 97 -- .../change_detection/pipes/json_pipe_spec.ts | 88 ++ ...anges_spec.js => keyvalue_changes_spec.ts} | 143 ++- ...se_pipe_spec.js => lowercase_pipe_spec.ts} | 4 +- .../pipes/observable_pipe_spec.js | 115 -- .../pipes/observable_pipe_spec.ts | 125 ++ ...registry_spec.js => pipe_registry_spec.ts} | 36 +- .../pipes/promise_pipe_spec.js | 119 -- .../pipes/promise_pipe_spec.ts | 127 ++ ...se_pipe_spec.js => uppercase_pipe_spec.ts} | 4 +- .../angular2/test/change_detection/util.js | 29 - .../angular2/test/change_detection/util.ts | 28 + tools/broccoli/trees/node_tree.ts | 25 +- 29 files changed, 2009 insertions(+), 2091 deletions(-) rename modules/angular2/test/change_detection/{change_detection_spec.js => change_detection_spec.ts} (80%) delete mode 100644 modules/angular2/test/change_detection/change_detector_spec.js create mode 100644 modules/angular2/test/change_detection/change_detector_spec.ts delete mode 100644 modules/angular2/test/change_detection/coalesce_spec.js create mode 100644 modules/angular2/test/change_detection/coalesce_spec.ts delete mode 100644 modules/angular2/test/change_detection/iterable.es6 create mode 100644 modules/angular2/test/change_detection/iterable.ts rename modules/angular2/test/change_detection/parser/{lexer_spec.js => lexer_spec.ts} (77%) rename modules/angular2/test/change_detection/parser/{locals_spec.js => locals_spec.ts} (84%) rename modules/angular2/test/change_detection/parser/{parser_spec.js => parser_spec.ts} (70%) delete mode 100644 modules/angular2/test/change_detection/pipes/iterable_changes_spec.js create mode 100644 modules/angular2/test/change_detection/pipes/iterable_changes_spec.ts delete mode 100644 modules/angular2/test/change_detection/pipes/json_pipe_spec.js create mode 100644 modules/angular2/test/change_detection/pipes/json_pipe_spec.ts rename modules/angular2/test/change_detection/pipes/{keyvalue_changes_spec.js => keyvalue_changes_spec.ts} (56%) rename modules/angular2/test/change_detection/pipes/{lowercase_pipe_spec.js => lowercase_pipe_spec.ts} (91%) delete mode 100644 modules/angular2/test/change_detection/pipes/observable_pipe_spec.js create mode 100644 modules/angular2/test/change_detection/pipes/observable_pipe_spec.ts rename modules/angular2/test/change_detection/pipes/{pipe_registry_spec.js => pipe_registry_spec.ts} (52%) delete mode 100644 modules/angular2/test/change_detection/pipes/promise_pipe_spec.js create mode 100644 modules/angular2/test/change_detection/pipes/promise_pipe_spec.ts rename modules/angular2/test/change_detection/pipes/{uppercase_pipe_spec.js => uppercase_pipe_spec.ts} (91%) delete mode 100644 modules/angular2/test/change_detection/util.js create mode 100644 modules/angular2/test/change_detection/util.ts diff --git a/modules/angular2/src/facade/lang.ts b/modules/angular2/src/facade/lang.ts index 49aba2896d..4fcc90e7ae 100644 --- a/modules/angular2/src/facade/lang.ts +++ b/modules/angular2/src/facade/lang.ts @@ -2,7 +2,7 @@ var _global: BrowserNodeGlobal = (typeof window === 'undefined' ? global : export {_global as global}; export var Type = Function; -export type Type = new (...args: any[]) => any; +export type Type = new (... args: any[]) => any; export class BaseException extends Error { message; @@ -116,7 +116,7 @@ export class StringWrapper { } static replaceAllMapped(s: string, from: RegExp, cb: Function): string { - return s.replace(from, function(...matches) { + return s.replace(from, function(... matches) { // Remove offset & string from the result array matches.splice(-2, 2); // The callback receives match, p1, ..., pn diff --git a/modules/angular2/src/test_lib/test_bed.ts b/modules/angular2/src/test_lib/test_bed.ts index e2c347f658..7d766c4c7a 100644 --- a/modules/angular2/src/test_lib/test_bed.ts +++ b/modules/angular2/src/test_lib/test_bed.ts @@ -78,8 +78,8 @@ export class TestBed { * @return {Promise} */ createView(component: Type, - {context = null, html = null}: {context?: any, - html?: string} = {}): Promise { + {context = null, + html = null}: {context?: any, html?: string} = {}): Promise { if (isBlank(component) && isBlank(context)) { throw new BaseException('You must specified at least a component or a context'); } diff --git a/modules/angular2/src/test_lib/test_lib.ts b/modules/angular2/src/test_lib/test_lib.ts index ef4e8f768a..727dd3f956 100644 --- a/modules/angular2/src/test_lib/test_lib.ts +++ b/modules/angular2/src/test_lib/test_lib.ts @@ -72,25 +72,25 @@ class BeforeEachRunner { // Reset the test bindings before each test jsmBeforeEach(() => { testBindings = []; }); -function _describe(jsmFn, ...args) { +function _describe(jsmFn, ... args) { var parentRunner = runnerStack.length === 0 ? null : runnerStack[runnerStack.length - 1]; var runner = new BeforeEachRunner(parentRunner); runnerStack.push(runner); - var suite = jsmFn(...args); + var suite = jsmFn(... args); runnerStack.pop(); return suite; } -export function describe(...args) { - return _describe(jsmDescribe, ...args); +export function describe(... args) { + return _describe(jsmDescribe, ... args); } -export function ddescribe(...args) { - return _describe(jsmDDescribe, ...args); +export function ddescribe(... args) { + return _describe(jsmDDescribe, ... args); } -export function xdescribe(...args) { - return _describe(jsmXDescribe, ...args); +export function xdescribe(... args) { + return _describe(jsmXDescribe, ... args); } export function beforeEach(fn) { @@ -123,7 +123,7 @@ export function beforeEachBindings(fn) { jsmBeforeEach(() => { var bindings = fn(); if (!bindings) return; - testBindings = [...testBindings, ...bindings]; + testBindings = [... testBindings, ... bindings]; }); } @@ -142,7 +142,7 @@ function _it(jsmFn, name, fn) { return new AsyncTestCompleter(done); }); - var injector = createTestInjector([...testBindings, completerBinding]); + var injector = createTestInjector([... testBindings, completerBinding]); runner.run(injector); if (!(fn instanceof FunctionWithParamTokens)) { diff --git a/modules/angular2/src/util/decorators.ts b/modules/angular2/src/util/decorators.ts index 330fec99e9..44cbc35b6a 100644 --- a/modules/angular2/src/util/decorators.ts +++ b/modules/angular2/src/util/decorators.ts @@ -1,7 +1,7 @@ import {global} from 'angular2/src/facade/lang'; export function makeDecorator(annotationCls) { - return function(...args) { + return function(... args) { var Reflect = global.Reflect; if (!(Reflect && Reflect.getMetadata)) { throw 'reflect-metadata shim is required when using class decorators'; @@ -20,7 +20,7 @@ export function makeDecorator(annotationCls) { } export function makeParamDecorator(annotationCls): any { - return function(...args) { + return function(... args) { var Reflect = global.Reflect; if (!(Reflect && Reflect.getMetadata)) { throw 'reflect-metadata shim is required when using parameter decorators'; diff --git a/modules/angular2/test/change_detection/change_detection_spec.js b/modules/angular2/test/change_detection/change_detection_spec.ts similarity index 80% rename from modules/angular2/test/change_detection/change_detection_spec.js rename to modules/angular2/test/change_detection/change_detection_spec.ts index 45df478068..912c3b5b74 100644 --- a/modules/angular2/test/change_detection/change_detection_spec.js +++ b/modules/angular2/test/change_detection/change_detection_spec.ts @@ -1,6 +1,11 @@ import {ddescribe, describe, it, iit, xit, expect, beforeEach, afterEach} from 'angular2/test_lib'; -import {PreGeneratedChangeDetection, ChangeDetectorDefinition, ProtoChangeDetector, DynamicProtoChangeDetector} from 'angular2/change_detection'; +import { + PreGeneratedChangeDetection, + ChangeDetectorDefinition, + ProtoChangeDetector, + DynamicProtoChangeDetector +} from 'angular2/change_detection'; class DummyChangeDetector extends ProtoChangeDetector {} @@ -15,7 +20,7 @@ export function main() { }); it("should return a proto change detector when one is available", () => { - var map = {'id' : (registry) => proto}; + var map = {'id': (registry) => proto}; var cd = new PreGeneratedChangeDetection(null, map); expect(cd.createProtoChangeDetector(def)).toBe(proto) diff --git a/modules/angular2/test/change_detection/change_detector_spec.js b/modules/angular2/test/change_detection/change_detector_spec.js deleted file mode 100644 index 8978818cdd..0000000000 --- a/modules/angular2/test/change_detection/change_detector_spec.js +++ /dev/null @@ -1,1012 +0,0 @@ -import {ddescribe, describe, it, iit, xit, expect, beforeEach, afterEach, IS_DARTIUM} from 'angular2/test_lib'; - -import {isPresent, isBlank, isJsObject, BaseException, FunctionWrapper} from 'angular2/src/facade/lang'; -import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection'; - -import {ChangeDispatcher, DynamicChangeDetector, ChangeDetectionError, BindingRecord, DirectiveRecord, DirectiveIndex, - PipeRegistry, Pipe, CHECK_ALWAYS, CHECK_ONCE, CHECKED, DETACHED, ON_PUSH, DEFAULT, WrappedValue, - JitProtoChangeDetector, DynamicProtoChangeDetector, ChangeDetectorDefinition, - Lexer, Parser, Locals} from 'angular2/change_detection'; - - -export function main() { - describe("change detection", () => { - StringMapWrapper.forEach( - { "dynamic": (bindingRecords, variableBindings = null, directiveRecords = null, registry = null, strategy = null) => - new DynamicProtoChangeDetector( - registry, new ChangeDetectorDefinition( - null, - strategy, - isBlank(variableBindings) ? [] : variableBindings, - isBlank(bindingRecords) ? [] : bindingRecords, - isBlank(directiveRecords) ? [] : directiveRecords)), - - "JIT": (bindingRecords, variableBindings = null, directiveRecords = null, registry = null, strategy = null) => - new JitProtoChangeDetector( - registry, new ChangeDetectorDefinition( - null, - strategy, - isBlank(variableBindings) ? [] : variableBindings, - isBlank(bindingRecords) ? [] : bindingRecords, - isBlank(directiveRecords) ? [] : directiveRecords)) - - }, (createProtoChangeDetector, name) => { - - if (name == "JIT" && IS_DARTIUM) return; - - var parser = new Parser(new Lexer()); - - function ast(exp:string, location:string = 'location') { - return parser.parseBinding(exp, location); - } - - function dirs(directives:List) { - return new FakeDirectives(directives, []); - } - - function convertLocalsToVariableBindings(locals) { - var variableBindings = []; - var loc = locals; - while(isPresent(loc)) { - MapWrapper.forEach(loc.current, (v, k) => ListWrapper.push(variableBindings, k)); - loc = loc.parent; - } - return variableBindings; - } - - function createChangeDetector(propName:string, exp:string, context = null, locals = null, registry = null) { - var dispatcher = new TestDispatcher(); - - var variableBindings = convertLocalsToVariableBindings(locals); - - var records = [BindingRecord.createForElement(ast(exp), 0, propName)]; - var pcd = createProtoChangeDetector(records, variableBindings, [], registry); - var cd = pcd.instantiate(dispatcher); - cd.hydrate(context, locals, null); - - return {"changeDetector" : cd, "dispatcher" : dispatcher}; - } - - function executeWatch(memo:string, exp:string, context = null, locals = null) { - var res = createChangeDetector(memo, exp, context, locals); - res["changeDetector"].detectChanges(); - return res["dispatcher"].log; - } - - describe(`${name} change detection`, () => { - var dispatcher; - - beforeEach(() => { - dispatcher = new TestDispatcher(); - }); - - it('should do simple watching', () => { - var person = new Person("misko"); - - var c = createChangeDetector('name', 'name', person); - var cd = c["changeDetector"]; - var dispatcher = c["dispatcher"]; - - cd.detectChanges(); - expect(dispatcher.log).toEqual(['name=misko']); - dispatcher.clear(); - - cd.detectChanges(); - expect(dispatcher.log).toEqual([]); - dispatcher.clear(); - - person.name = "Misko"; - cd.detectChanges(); - expect(dispatcher.log).toEqual(['name=Misko']); - }); - - it('should report all changes on the first run including uninitialized values', () => { - expect(executeWatch('value', 'value', new Uninitialized())).toEqual(['value=null']); - }); - - it('should report all changes on the first run including null values', () => { - var td = new TestData(null); - expect(executeWatch('a', 'a', td)).toEqual(['a=null']); - }); - - it("should support literals", () => { - expect(executeWatch('const', '10')).toEqual(['const=10']); - expect(executeWatch('const', '"str"')).toEqual(['const=str']); - expect(executeWatch('const', '"a\n\nb"')).toEqual(['const=a\n\nb']); - }); - - it('simple chained property access', () => { - var address = new Address('Grenoble'); - var person = new Person('Victor', address); - - expect(executeWatch('address.city', 'address.city', person)) - .toEqual(['address.city=Grenoble']); - }); - - it("should support method calls", () => { - var person = new Person('Victor'); - expect(executeWatch('m', 'sayHi("Jim")', person)).toEqual(['m=Hi, Jim']); - }); - - it("should support function calls", () => { - var td = new TestData(() => (a) => a); - expect(executeWatch('value', 'a()(99)', td)).toEqual(['value=99']); - }); - - it("should support chained method calls", () => { - var person = new Person('Victor'); - var td = new TestData(person); - expect(executeWatch('m', 'a.sayHi("Jim")', td)).toEqual(['m=Hi, Jim']); - }); - - it("should support literal array", () => { - var c = createChangeDetector('array', '[1,2]'); - c["changeDetector"].detectChanges(); - expect(c["dispatcher"].loggedValues).toEqual([[1, 2]]); - - c = createChangeDetector('array', '[1,a]', new TestData(2)); - c["changeDetector"].detectChanges(); - expect(c["dispatcher"].loggedValues).toEqual([[1, 2]]); - }); - - it("should support literal maps", () => { - var c = createChangeDetector('map', '{z:1}'); - c["changeDetector"].detectChanges(); - expect(c["dispatcher"].loggedValues[0]['z']).toEqual(1); - - c = createChangeDetector('map', '{z:a}', new TestData(1)); - c["changeDetector"].detectChanges(); - expect(c["dispatcher"].loggedValues[0]['z']).toEqual(1); - }); - - it("should support binary operations", () => { - expect(executeWatch('exp', '10 + 2')).toEqual(['exp=12']); - expect(executeWatch('exp', '10 - 2')).toEqual(['exp=8']); - - expect(executeWatch('exp', '10 * 2')).toEqual(['exp=20']); - expect(executeWatch('exp', '10 / 2')).toEqual([`exp=${5.0}`]); //dart exp=5.0, js exp=5 - expect(executeWatch('exp', '11 % 2')).toEqual(['exp=1']); - - expect(executeWatch('exp', '1 == 1')).toEqual(['exp=true']); - if (IS_DARTIUM) { - expect(executeWatch('exp', '1 == "1"')).toEqual(['exp=false']); - } else { - expect(executeWatch('exp', '1 == "1"')).toEqual(['exp=true']); - } - expect(executeWatch('exp', '1 != 1')).toEqual(['exp=false']); - - expect(executeWatch('exp', '1 === 1')).toEqual(['exp=true']); - expect(executeWatch('exp', '1 !== 1')).toEqual(['exp=false']); - expect(executeWatch('exp', '1 === "1"')).toEqual(['exp=false']); - - expect(executeWatch('exp', '1 < 2')).toEqual(['exp=true']); - expect(executeWatch('exp', '2 < 1')).toEqual(['exp=false']); - - expect(executeWatch('exp', '2 > 1')).toEqual(['exp=true']); - expect(executeWatch('exp', '2 < 1')).toEqual(['exp=false']); - - expect(executeWatch('exp', '1 <= 2')).toEqual(['exp=true']); - expect(executeWatch('exp', '2 <= 2')).toEqual(['exp=true']); - expect(executeWatch('exp', '2 <= 1')).toEqual(['exp=false']); - - expect(executeWatch('exp', '2 >= 1')).toEqual(['exp=true']); - expect(executeWatch('exp', '2 >= 2')).toEqual(['exp=true']); - expect(executeWatch('exp', '1 >= 2')).toEqual(['exp=false']); - - expect(executeWatch('exp', 'true && true')).toEqual(['exp=true']); - expect(executeWatch('exp', 'true && false')).toEqual(['exp=false']); - - expect(executeWatch('exp', 'true || false')).toEqual(['exp=true']); - expect(executeWatch('exp', 'false || false')).toEqual(['exp=false']); - }); - - it("should support negate", () => { - expect(executeWatch('exp', '!true')).toEqual(['exp=false']); - expect(executeWatch('exp', '!!true')).toEqual(['exp=true']); - }); - - it("should support conditionals", () => { - expect(executeWatch('m', '1 < 2 ? 1 : 2')).toEqual(['m=1']); - expect(executeWatch('m', '1 > 2 ? 1 : 2')).toEqual(['m=2']); - }); - - describe("keyed access", () => { - it("should support accessing a list item", () => { - expect(executeWatch('array[0]', '["foo", "bar"][0]')).toEqual(['array[0]=foo']); - }); - - it("should support accessing a map item", () => { - expect(executeWatch('map[foo]', '{"foo": "bar"}["foo"]')).toEqual(['map[foo]=bar']); - }); - }); - - it("should support interpolation", () => { - var ast = parser.parseInterpolation("B{{a}}A", "location"); - var pcd = createProtoChangeDetector([BindingRecord.createForElement(ast, 0, "memo")]); - - var cd = pcd.instantiate(dispatcher); - cd.hydrate(new TestData("value"), null, null); - - cd.detectChanges(); - - expect(dispatcher.log).toEqual(["memo=BvalueA"]); - }); - - describe("change notification", () => { - describe("simple checks", () => { - it("should pass a change record to the dispatcher", () => { - var person = new Person('bob'); - var c = createChangeDetector('name', 'name', person); - var cd = c["changeDetector"]; - var dispatcher = c["dispatcher"]; - - cd.detectChanges(); - - expect(dispatcher.loggedValues).toEqual(['bob']); - }); - }); - - describe("pipes", () => { - it("should pass a change record to the dispatcher", () => { - var registry = new FakePipeRegistry('pipe', () => new CountingPipe()); - - var person = new Person('bob'); - var c = createChangeDetector('name', 'name | pipe', person, null, registry); - var cd = c["changeDetector"]; - var dispatcher = c["dispatcher"]; - - cd.detectChanges(); - - expect(dispatcher.loggedValues).toEqual(['bob state:0']); - }); - }); - - describe("updating directives", () => { - var dirRecord1 = new DirectiveRecord(new DirectiveIndex(0, 0), true, true, DEFAULT); - var dirRecord2 = new DirectiveRecord(new DirectiveIndex(0, 1), true, true, DEFAULT); - var dirRecordNoCallbacks = new DirectiveRecord(new DirectiveIndex(0, 0), false, false, DEFAULT); - - function updateA(exp:string, dirRecord) { - return BindingRecord.createForDirective(ast(exp), "a", (o,v) => o.a = v, dirRecord); - } - - function updateB(exp:string, dirRecord) { - return BindingRecord.createForDirective(ast(exp), "b", (o,v) => o.b = v, dirRecord); - } - - var directive1; - var directive2; - - beforeEach(() => { - directive1 = new TestDirective(); - directive2 = new TestDirective(); - }); - - it("should happen directly, without invoking the dispatcher", () => { - var pcd = createProtoChangeDetector([updateA("42", dirRecord1)], [], [dirRecord1]); - - var cd = pcd.instantiate(dispatcher); - - cd.hydrate(null, null, dirs([directive1])); - - cd.detectChanges(); - - expect(dispatcher.loggedValues).toEqual([]); - expect(directive1.a).toEqual(42); - }); - - describe("onChange", () => { - it("should notify the directive when a group of records changes", () => { - var pcd = createProtoChangeDetector([ - updateA("1", dirRecord1), - updateB("2", dirRecord1), - updateA("3", dirRecord2) - ], [], [dirRecord1, dirRecord2]); - - var cd = pcd.instantiate(dispatcher); - - cd.hydrate(null, null, dirs([directive1, directive2])); - - cd.detectChanges(); - - expect(directive1.changes).toEqual({'a': 1, 'b': 2}); - expect(directive2.changes).toEqual({'a': 3}); - }); - - it("should not call onChange when callOnChange is false", () => { - var pcd = createProtoChangeDetector([ - updateA("1", dirRecordNoCallbacks) - ], [], [dirRecordNoCallbacks]); - - var cd = pcd.instantiate(dispatcher); - - cd.hydrate(null, null, dirs([directive1])); - - cd.detectChanges(); - - expect(directive1.changes).toEqual(null); - }); - }); - - describe("onAllChangesDone", () => { - it("should be called after processing all the children", () => { - var pcd = createProtoChangeDetector([], [], [dirRecord1, dirRecord2]); - - var cd = pcd.instantiate(dispatcher); - cd.hydrate(null, null, dirs([directive1, directive2])); - - cd.detectChanges(); - - expect(directive1.onChangesDoneCalled).toBe(true); - expect(directive2.onChangesDoneCalled).toBe(true); - - // reset directives - directive1.onChangesDoneCalled = false; - directive2.onChangesDoneCalled = false; - - // Verify that checking should not call them. - cd.checkNoChanges(); - - expect(directive1.onChangesDoneCalled).toBe(false); - expect(directive2.onChangesDoneCalled).toBe(false); - - // re-verify that changes are still detected - cd.detectChanges(); - - expect(directive1.onChangesDoneCalled).toBe(true); - expect(directive2.onChangesDoneCalled).toBe(true); - }); - - - it("should not be called when onAllChangesDone is false", () => { - var pcd = createProtoChangeDetector([ - updateA("1", dirRecordNoCallbacks) - ], [], [dirRecordNoCallbacks]); - - var cd = pcd.instantiate(dispatcher); - - cd.hydrate(null, null, dirs([directive1])); - - cd.detectChanges(); - - expect(directive1.onChangesDoneCalled).toEqual(false); - }); - - it("should be called in reverse order so the child is always notified before the parent", () => { - var pcd = createProtoChangeDetector([], [], [dirRecord1, dirRecord2]); - var cd = pcd.instantiate(dispatcher); - - var onChangesDoneCalls = []; - var td1; - td1 = new TestDirective(() => ListWrapper.push(onChangesDoneCalls, td1)); - var td2; - td2 = new TestDirective(() => ListWrapper.push(onChangesDoneCalls, td2)); - cd.hydrate(null, null, dirs([td1, td2])); - - cd.detectChanges(); - - expect(onChangesDoneCalls).toEqual([td2, td1]); - }); - - it("should be called before processing shadow dom children", () => { - var pcd = createProtoChangeDetector([], null, [dirRecord1]); - var shadowDomChildPCD = createProtoChangeDetector([updateA("1", dirRecord1)], null, [dirRecord1]); - - var parent = pcd.instantiate(dispatcher); - - var child = shadowDomChildPCD.instantiate(dispatcher); - parent.addShadowDomChild(child); - - var directiveInShadowDom = new TestDirective(); - var parentDirective = new TestDirective(() => { - expect(directiveInShadowDom.a).toBe(null); - }); - - parent.hydrate(null, null, dirs([parentDirective])); - child.hydrate(null, null, dirs([directiveInShadowDom])); - - parent.detectChanges(); - }); - }); - }); - }); - - describe("reading directives", () => { - var index = new DirectiveIndex(0, 0); - var dirRecord = new DirectiveRecord(index, false, false, DEFAULT); - - it("should read directive properties", () => { - var directive = new TestDirective(); - directive.a = "aaa"; - - var pcd = createProtoChangeDetector([BindingRecord.createForHostProperty(index, ast("a"), "prop")], null, [dirRecord]); - var cd = pcd.instantiate(dispatcher); - cd.hydrate(null, null, dirs([directive])); - - cd.detectChanges(); - - expect(dispatcher.loggedValues).toEqual(['aaa']); - }); - }); - - describe("enforce no new changes", () => { - it("should throw when a record gets changed after it has been checked", () => { - var pcd = createProtoChangeDetector([ - BindingRecord.createForElement(ast("a"), 0, "a") - ]); - - var dispatcher = new TestDispatcher(); - var cd = pcd.instantiate(dispatcher); - cd.hydrate(new TestData('value'), null, null); - - expect(() => { - cd.checkNoChanges(); - }).toThrowError(new RegExp("Expression 'a in location' has changed after it was checked")); - }); - - it("should not break the next run", () => { - var pcd = createProtoChangeDetector([ - BindingRecord.createForElement(ast("a"), 0, "a") - ]); - - var dispatcher = new TestDispatcher(); - var cd = pcd.instantiate(dispatcher); - cd.hydrate(new TestData('value'), null, null); - - expect(() => cd.checkNoChanges()).toThrowError(new RegExp( - "Expression 'a in location' has changed after it was checked.")); - - cd.detectChanges(); - - expect(dispatcher.loggedValues).toEqual(['value']); - }); - }); - - //TODO vsavkin: implement it - describe("error handling", () => { - xit("should wrap exceptions into ChangeDetectionError", () => { - var pcd = createProtoChangeDetector(); - var cd = pcd.instantiate(new TestDispatcher(), [ - BindingRecord.createForElement(ast("invalidProp"), 0, "a") - ], null, []); - cd.hydrate(null, null); - - try { - cd.detectChanges(); - - throw new BaseException("fail"); - } catch (e) { - expect(e).toBeAnInstanceOf(ChangeDetectionError); - expect(e.location).toEqual("invalidProp in someComponent"); - } - }); - }); - - describe("Locals", () => { - it('should read a value from locals', () => { - var locals = new Locals(null, - MapWrapper.createFromPairs([["key", "value"]])); - - expect(executeWatch('key', 'key', null, locals)) - .toEqual(['key=value']); - }); - - it('should invoke a function from local', () => { - var locals = new Locals(null, - MapWrapper.createFromPairs([["key", () => "value"]])); - - expect(executeWatch('key', 'key()', null, locals)) - .toEqual(['key=value']); - }); - - it('should handle nested locals', () => { - var nested = new Locals(null, - MapWrapper.createFromPairs([["key", "value"]])); - var locals = new Locals(nested, MapWrapper.create()); - - expect(executeWatch('key', 'key', null, locals)) - .toEqual(['key=value']); - }); - - it("should fall back to a regular field read when the locals map" + - "does not have the requested field", () => { - var locals = new Locals(null, - MapWrapper.createFromPairs([["key", "value"]])); - - expect(executeWatch('name', 'name', new Person("Jim"), locals)) - .toEqual(['name=Jim']); - }); - - it('should correctly handle nested properties', () => { - var address = new Address('Grenoble'); - var person = new Person('Victor', address); - var locals = new Locals(null, - MapWrapper.createFromPairs([['city', 'MTV']])); - expect(executeWatch('address.city', 'address.city', person, locals)) - .toEqual(['address.city=Grenoble']); - expect(executeWatch('city', 'city', person, locals)) - .toEqual(['city=MTV']); - }); - - }); - - describe("handle children", () => { - var parent, child; - - beforeEach(() => { - parent = createProtoChangeDetector([]).instantiate(null); - child = createProtoChangeDetector([]).instantiate(null); - }); - - it("should add light dom children", () => { - parent.addChild(child); - - expect(parent.lightDomChildren.length).toEqual(1); - expect(parent.lightDomChildren[0]).toBe(child); - }); - - it("should add shadow dom children", () => { - parent.addShadowDomChild(child); - - expect(parent.shadowDomChildren.length).toEqual(1); - expect(parent.shadowDomChildren[0]).toBe(child); - }); - - it("should remove light dom children", () => { - parent.addChild(child); - parent.removeChild(child); - - expect(parent.lightDomChildren).toEqual([]); - }); - - it("should remove shadow dom children", () => { - parent.addShadowDomChild(child); - parent.removeShadowDomChild(child); - - expect(parent.shadowDomChildren.length).toEqual(0); - }); - }); - }); - - describe("mode", () => { - it("should set the mode to CHECK_ALWAYS when the default change detection is used", () => { - var proto = createProtoChangeDetector([], [], [], null, DEFAULT); - var cd = proto.instantiate(null); - - expect(cd.mode).toEqual(null); - - cd.hydrate(null, null, null); - - expect(cd.mode).toEqual(CHECK_ALWAYS); - }); - - it("should set the mode to CHECK_ONCE when the push change detection is used", () => { - var proto = createProtoChangeDetector([], [], [], null, ON_PUSH); - var cd = proto.instantiate(null); - cd.hydrate(null, null, null); - - expect(cd.mode).toEqual(CHECK_ONCE); - }); - - it("should not check a detached change detector", () => { - var c = createChangeDetector('name', 'a', new TestData("value")); - var cd = c["changeDetector"]; - var dispatcher = c["dispatcher"]; - - cd.mode = DETACHED; - cd.detectChanges(); - - expect(dispatcher.log).toEqual([]); - }); - - it("should not check a checked change detector", () => { - var c = createChangeDetector('name', 'a', new TestData("value")); - var cd = c["changeDetector"]; - var dispatcher = c["dispatcher"]; - - cd.mode = CHECKED; - cd.detectChanges(); - - expect(dispatcher.log).toEqual([]); - }); - - it("should change CHECK_ONCE to CHECKED", () => { - var cd = createProtoChangeDetector([]).instantiate(null); - cd.mode = CHECK_ONCE; - - cd.detectChanges(); - - expect(cd.mode).toEqual(CHECKED); - }); - - it("should not change the CHECK_ALWAYS", () => { - var cd = createProtoChangeDetector([]).instantiate(null); - cd.mode = CHECK_ALWAYS; - - cd.detectChanges(); - - expect(cd.mode).toEqual(CHECK_ALWAYS); - }); - - describe("marking ON_PUSH detectors as CHECK_ONCE after an update", () => { - var checkedDetector; - var dirRecordWithOnPush; - var updateDirWithOnPushRecord; - var directives; - - beforeEach(() => { - var proto = createProtoChangeDetector([], [], [], null, ON_PUSH); - checkedDetector = proto.instantiate(null); - checkedDetector.hydrate(null, null, null); - checkedDetector.mode = CHECKED; - - // this directive is a component with ON_PUSH change detection - dirRecordWithOnPush = new DirectiveRecord(new DirectiveIndex(0, 0), false, false, ON_PUSH); - - // a record updating a component - updateDirWithOnPushRecord = - BindingRecord.createForDirective(ast("42"), "a", (o,v) => o.a = v, dirRecordWithOnPush); - - var targetDirective = new TestData(null); - directives = new FakeDirectives([targetDirective], [checkedDetector]); - }); - - it("should set the mode to CHECK_ONCE when a binding is updated", () => { - var proto = createProtoChangeDetector([updateDirWithOnPushRecord], [], [dirRecordWithOnPush]); - - var cd = proto.instantiate(null); - cd.hydrate(null, null, directives); - - expect(checkedDetector.mode).toEqual(CHECKED); - - // evaluate the record, update the targetDirective, and mark its detector as CHECK_ONCE - cd.detectChanges(); - - expect(checkedDetector.mode).toEqual(CHECK_ONCE); - }); - }); - }); - - describe("markPathToRootAsCheckOnce", () => { - function changeDetector(mode, parent) { - var cd = createProtoChangeDetector([]).instantiate(null); - cd.mode = mode; - if (isPresent(parent)) parent.addChild(cd); - return cd; - } - - it("should mark all checked detectors as CHECK_ONCE " + - "until reaching a detached one", () => { - - var root = changeDetector(CHECK_ALWAYS, null); - var disabled = changeDetector(DETACHED, root); - var parent = changeDetector(CHECKED, disabled); - var checkAlwaysChild = changeDetector(CHECK_ALWAYS, parent); - var checkOnceChild = changeDetector(CHECK_ONCE, checkAlwaysChild); - var checkedChild = changeDetector(CHECKED, checkOnceChild); - - checkedChild.markPathToRootAsCheckOnce(); - - expect(root.mode).toEqual(CHECK_ALWAYS); - expect(disabled.mode).toEqual(DETACHED); - expect(parent.mode).toEqual(CHECK_ONCE); - expect(checkAlwaysChild.mode).toEqual(CHECK_ALWAYS); - expect(checkOnceChild.mode).toEqual(CHECK_ONCE); - expect(checkedChild.mode).toEqual(CHECK_ONCE); - }); - }); - - describe("hydration", () => { - it("should be able to rehydrate a change detector", () => { - var c = createChangeDetector("memo", "name"); - var cd = c["changeDetector"]; - - cd.hydrate("some context", null, null); - expect(cd.hydrated()).toBe(true); - - cd.dehydrate(); - expect(cd.hydrated()).toBe(false); - - cd.hydrate("other context", null, null); - expect(cd.hydrated()).toBe(true); - }); - - it("should destroy all active pipes during dehyration", () => { - var pipe = new OncePipe(); - var registry = new FakePipeRegistry('pipe', () => pipe); - var c = createChangeDetector("memo", "name | pipe", new Person('bob'), null, registry); - var cd = c["changeDetector"]; - - cd.detectChanges(); - - cd.dehydrate(); - - expect(pipe.destroyCalled).toBe(true); - }); - }); - - describe("pipes", () => { - it("should support pipes", () => { - var registry = new FakePipeRegistry('pipe', () => new CountingPipe()); - var ctx = new Person("Megatron"); - - var c = createChangeDetector("memo", "name | pipe", ctx, null, registry); - var cd = c["changeDetector"]; - var dispatcher = c["dispatcher"]; - - cd.detectChanges(); - - expect(dispatcher.log).toEqual(['memo=Megatron state:0']); - - dispatcher.clear(); - cd.detectChanges(); - - expect(dispatcher.log).toEqual(['memo=Megatron state:1']); - }); - - it("should lookup pipes in the registry when the context is not supported", () => { - var registry = new FakePipeRegistry('pipe', () => new OncePipe()); - var ctx = new Person("Megatron"); - - var c = createChangeDetector("memo", "name | pipe", ctx, null, registry); - var cd = c["changeDetector"]; - - cd.detectChanges(); - - expect(registry.numberOfLookups).toEqual(1); - - ctx.name = "Optimus Prime"; - cd.detectChanges(); - - expect(registry.numberOfLookups).toEqual(2); - }); - - it("should invoke onDestroy on a pipe before switching to another one", () => { - var pipe = new OncePipe(); - var registry = new FakePipeRegistry('pipe', () => pipe); - var ctx = new Person("Megatron"); - - var c = createChangeDetector("memo", "name | pipe", ctx, null, registry); - var cd = c["changeDetector"]; - - cd.detectChanges(); - ctx.name = "Optimus Prime"; - cd.detectChanges(); - - expect(pipe.destroyCalled).toEqual(true); - }); - - it("should inject the ChangeDetectorRef " + - "of the encompassing component into a pipe", () => { - - var registry = new FakePipeRegistry('pipe', () => new IdentityPipe()); - var c = createChangeDetector("memo", "name | pipe", new Person('bob'), null, registry); - var cd = c["changeDetector"]; - - cd.detectChanges(); - - expect(registry.cdRef).toBe(cd.ref); - }); - }); - - it("should do nothing when no change", () => { - var registry = new FakePipeRegistry('pipe', () => new IdentityPipe()) - var ctx = new Person("Megatron"); - - var c = createChangeDetector("memo", "name | pipe", ctx, null, registry); - var cd = c["changeDetector"]; - var dispatcher = c["dispatcher"]; - - cd.detectChanges(); - - expect(dispatcher.log).toEqual(['memo=Megatron']); - - dispatcher.clear(); - cd.detectChanges(); - - expect(dispatcher.log).toEqual([]); - }); - - it("should unwrap the wrapped value", () => { - var registry = new FakePipeRegistry('pipe', () => new WrappedPipe()) - var ctx = new Person("Megatron"); - - var c = createChangeDetector("memo", "name | pipe", ctx, null, registry); - var cd = c["changeDetector"]; - var dispatcher = c["dispatcher"]; - - cd.detectChanges(); - - expect(dispatcher.log).toEqual(['memo=Megatron']); - }); - }); - }); -} - -class CountingPipe extends Pipe { - state:number; - - constructor() { - super(); - this.state = 0; - } - - supports(newValue) { - return true; - } - - transform(value) { - return `${value} state:${this.state ++}`; - } -} - -class OncePipe extends Pipe { - called:boolean; - destroyCalled:boolean; - - constructor() { - super(); - this.called = false; - this.destroyCalled = false; - } - - supports(newValue) { - return !this.called; - } - - onDestroy() { - this.destroyCalled = true; - } - - transform(value) { - this.called = true; - return value; - } -} - -class IdentityPipe extends Pipe { - transform(value) { - return value; - } -} - -class WrappedPipe extends Pipe { - transform(value) { - return WrappedValue.wrap(value); - } -} - -class FakePipeRegistry extends PipeRegistry { - numberOfLookups:number; - pipeType:string; - factory:Function; - cdRef:any; - - constructor(pipeType, factory) { - super({}); - this.pipeType = pipeType; - this.factory = factory; - this.numberOfLookups = 0; - } - - get(type:string, obj, cdRef) { - if (type != this.pipeType) return null; - this.numberOfLookups ++; - this.cdRef = cdRef; - return this.factory(); - } -} - -class TestDirective { - a; - b; - changes; - onChangesDoneCalled; - onChangesDoneSpy; - - constructor(onChangesDoneSpy = null) { - this.onChangesDoneCalled = false; - this.onChangesDoneSpy = onChangesDoneSpy; - this.a = null; - this.b = null; - this.changes = null; - } - - onChange(changes) { - var r = {}; - StringMapWrapper.forEach(changes, (c, key) => r[key] = c.currentValue); - this.changes = r; - } - - onAllChangesDone() { - this.onChangesDoneCalled = true; - if(isPresent(this.onChangesDoneSpy)) { - this.onChangesDoneSpy(); - } - } -} - -class Person { - name:string; - age:number; - address:Address; - constructor(name:string, address:Address = null) { - this.name = name; - this.address = address; - } - - sayHi(m) { - return `Hi, ${m}`; - } - - toString():string { - var address = this.address == null ? '' : ' address=' + this.address.toString(); - - return 'name=' + this.name + address; - } -} - -class Address { - city:string; - constructor(city:string) { - this.city = city; - } - - toString():string { - return this.city; - } -} - -class Uninitialized { - value:any; -} - -class TestData { - a; - - constructor(a) { - this.a = a; - } -} - -class FakeDirectives { - directives:List; - detectors:List; - - constructor(directives:List, detectors:List) { - this.directives = directives; - this.detectors = detectors; - } - - getDirectiveFor(di:DirectiveIndex) { - return this.directives[di.directiveIndex]; - } - - getDetectorFor(di:DirectiveIndex) { - return this.detectors[di.directiveIndex]; - } -} - -class TestDispatcher extends ChangeDispatcher { - log:List; - loggedValues:List; - - constructor() { - super(); - this.clear(); - } - - clear() { - this.log = ListWrapper.create(); - this.loggedValues = ListWrapper.create(); - } - - notifyOnBinding(binding, value) { - ListWrapper.push(this.log, `${binding.propertyName}=${this._asString(value)}`); - ListWrapper.push(this.loggedValues, value); - } - - _asString(value) { - return (isBlank(value) ? 'null' : value.toString()); - } -} diff --git a/modules/angular2/test/change_detection/change_detector_spec.ts b/modules/angular2/test/change_detection/change_detector_spec.ts new file mode 100644 index 0000000000..99d64c8c62 --- /dev/null +++ b/modules/angular2/test/change_detection/change_detector_spec.ts @@ -0,0 +1,1021 @@ +import { + ddescribe, + describe, + it, + iit, + xit, + expect, + beforeEach, + afterEach, + IS_DARTIUM +} from 'angular2/test_lib'; + +import { + isPresent, + isBlank, + isJsObject, + BaseException, + FunctionWrapper +} from 'angular2/src/facade/lang'; +import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection'; + +import { + ChangeDispatcher, + DynamicChangeDetector, + ChangeDetectionError, + BindingRecord, + DirectiveRecord, + DirectiveIndex, + PipeRegistry, + Pipe, + CHECK_ALWAYS, + CHECK_ONCE, + CHECKED, + DETACHED, + ON_PUSH, + DEFAULT, + WrappedValue, + JitProtoChangeDetector, + DynamicProtoChangeDetector, + ChangeDetectorDefinition, + Lexer, + Parser, + Locals, + ProtoChangeDetector +} from 'angular2/change_detection'; + + +export function main() { + describe("change detection", () => { + StringMapWrapper.forEach( + { + "dynamic": (bindingRecords, variableBindings = null, directiveRecords = null, + registry = null, strategy = null) => + new DynamicProtoChangeDetector( + registry, + new ChangeDetectorDefinition( + null, strategy, isBlank(variableBindings) ? [] : variableBindings, + isBlank(bindingRecords) ? [] : bindingRecords, + isBlank(directiveRecords) ? [] : directiveRecords)), + + "JIT": + (bindingRecords, variableBindings = null, directiveRecords = null, registry = null, + strategy = null) => + new JitProtoChangeDetector( + registry, new ChangeDetectorDefinition( + null, strategy, isBlank(variableBindings) ? [] : + variableBindings, + isBlank(bindingRecords) ? [] : bindingRecords, + isBlank(directiveRecords) ? [] : directiveRecords)) + + }, + (createProtoChangeDetector, name) => { + + if (name == "JIT" && IS_DARTIUM) return; + + var parser = new Parser(new Lexer()); + + function ast(exp: string, location: string = 'location') { + return parser.parseBinding(exp, location); + } + + function dirs(directives: List) { return new FakeDirectives(directives, []); } + + function convertLocalsToVariableBindings(locals) { + var variableBindings = []; + var loc = locals; + while (isPresent(loc)) { + MapWrapper.forEach(loc.current, (v, k) => ListWrapper.push(variableBindings, k)); + loc = loc.parent; + } + return variableBindings; + } + + function createChangeDetector(propName: string, exp: string, context = null, + locals = null, registry = null) { + var dispatcher = new TestDispatcher(); + + var variableBindings = convertLocalsToVariableBindings(locals); + + var records = [BindingRecord.createForElement(ast(exp), 0, propName)]; + var pcd = createProtoChangeDetector(records, variableBindings, [], registry); + var cd = pcd.instantiate(dispatcher); + cd.hydrate(context, locals, null); + + return {"changeDetector": cd, "dispatcher": dispatcher}; + } + + function executeWatch(memo: string, exp: string, context = null, locals = null) { + var res = createChangeDetector(memo, exp, context, locals); + res["changeDetector"].detectChanges(); + return res["dispatcher"].log; + } + + describe(`${name} change detection`, () => { + var dispatcher; + + beforeEach(() => { dispatcher = new TestDispatcher(); }); + + it('should do simple watching', () => { + var person = new Person("misko"); + + var c = createChangeDetector('name', 'name', person); + var cd = c["changeDetector"]; + var dispatcher = c["dispatcher"]; + + cd.detectChanges(); + expect(dispatcher.log).toEqual(['name=misko']); + dispatcher.clear(); + + cd.detectChanges(); + expect(dispatcher.log).toEqual([]); + dispatcher.clear(); + + person.name = "Misko"; + cd.detectChanges(); + expect(dispatcher.log).toEqual(['name=Misko']); + }); + + it('should report all changes on the first run including uninitialized values', () => { + expect(executeWatch('value', 'value', new Uninitialized())).toEqual(['value=null']); + }); + + it('should report all changes on the first run including null values', () => { + var td = new TestData(null); + expect(executeWatch('a', 'a', td)).toEqual(['a=null']); + }); + + it("should support literals", () => { + expect(executeWatch('const', '10')).toEqual(['const=10']); + expect(executeWatch('const', '"str"')).toEqual(['const=str']); + expect(executeWatch('const', '"a\n\nb"')).toEqual(['const=a\n\nb']); + }); + + it('simple chained property access', () => { + var address = new Address('Grenoble'); + var person = new Person('Victor', address); + + expect(executeWatch('address.city', 'address.city', person)) + .toEqual(['address.city=Grenoble']); + }); + + it("should support method calls", () => { + var person = new Person('Victor'); + expect(executeWatch('m', 'sayHi("Jim")', person)).toEqual(['m=Hi, Jim']); + }); + + it("should support function calls", () => { + var td = new TestData(() => (a) => a); + expect(executeWatch('value', 'a()(99)', td)).toEqual(['value=99']); + }); + + it("should support chained method calls", () => { + var person = new Person('Victor'); + var td = new TestData(person); + expect(executeWatch('m', 'a.sayHi("Jim")', td)).toEqual(['m=Hi, Jim']); + }); + + it("should support literal array", () => { + var c = createChangeDetector('array', '[1,2]'); + c["changeDetector"].detectChanges(); + expect(c["dispatcher"].loggedValues).toEqual([[1, 2]]); + + c = createChangeDetector('array', '[1,a]', new TestData(2)); + c["changeDetector"].detectChanges(); + expect(c["dispatcher"].loggedValues).toEqual([[1, 2]]); + }); + + it("should support literal maps", () => { + var c = createChangeDetector('map', '{z:1}'); + c["changeDetector"].detectChanges(); + expect(c["dispatcher"].loggedValues[0]['z']).toEqual(1); + + c = createChangeDetector('map', '{z:a}', new TestData(1)); + c["changeDetector"].detectChanges(); + expect(c["dispatcher"].loggedValues[0]['z']).toEqual(1); + }); + + it("should support binary operations", () => { + expect(executeWatch('exp', '10 + 2')).toEqual(['exp=12']); + expect(executeWatch('exp', '10 - 2')).toEqual(['exp=8']); + + expect(executeWatch('exp', '10 * 2')).toEqual(['exp=20']); + expect(executeWatch('exp', '10 / 2')) + .toEqual([`exp=${5.0}`]); // dart exp=5.0, js exp=5 + expect(executeWatch('exp', '11 % 2')).toEqual(['exp=1']); + + expect(executeWatch('exp', '1 == 1')).toEqual(['exp=true']); + if (IS_DARTIUM) { + expect(executeWatch('exp', '1 == "1"')).toEqual(['exp=false']); + } else { + expect(executeWatch('exp', '1 == "1"')).toEqual(['exp=true']); + } + expect(executeWatch('exp', '1 != 1')).toEqual(['exp=false']); + + expect(executeWatch('exp', '1 === 1')).toEqual(['exp=true']); + expect(executeWatch('exp', '1 !== 1')).toEqual(['exp=false']); + expect(executeWatch('exp', '1 === "1"')).toEqual(['exp=false']); + + expect(executeWatch('exp', '1 < 2')).toEqual(['exp=true']); + expect(executeWatch('exp', '2 < 1')).toEqual(['exp=false']); + + expect(executeWatch('exp', '2 > 1')).toEqual(['exp=true']); + expect(executeWatch('exp', '2 < 1')).toEqual(['exp=false']); + + expect(executeWatch('exp', '1 <= 2')).toEqual(['exp=true']); + expect(executeWatch('exp', '2 <= 2')).toEqual(['exp=true']); + expect(executeWatch('exp', '2 <= 1')).toEqual(['exp=false']); + + expect(executeWatch('exp', '2 >= 1')).toEqual(['exp=true']); + expect(executeWatch('exp', '2 >= 2')).toEqual(['exp=true']); + expect(executeWatch('exp', '1 >= 2')).toEqual(['exp=false']); + + expect(executeWatch('exp', 'true && true')).toEqual(['exp=true']); + expect(executeWatch('exp', 'true && false')).toEqual(['exp=false']); + + expect(executeWatch('exp', 'true || false')).toEqual(['exp=true']); + expect(executeWatch('exp', 'false || false')).toEqual(['exp=false']); + }); + + it("should support negate", () => { + expect(executeWatch('exp', '!true')).toEqual(['exp=false']); + expect(executeWatch('exp', '!!true')).toEqual(['exp=true']); + }); + + it("should support conditionals", () => { + expect(executeWatch('m', '1 < 2 ? 1 : 2')).toEqual(['m=1']); + expect(executeWatch('m', '1 > 2 ? 1 : 2')).toEqual(['m=2']); + }); + + describe("keyed access", () => { + it("should support accessing a list item", () => { + expect(executeWatch('array[0]', '["foo", "bar"][0]')).toEqual(['array[0]=foo']); + }); + + it("should support accessing a map item", () => { + expect(executeWatch('map[foo]', '{"foo": "bar"}["foo"]')).toEqual(['map[foo]=bar']); + }); + }); + + it("should support interpolation", () => { + var ast = parser.parseInterpolation("B{{a}}A", "location"); + var pcd = createProtoChangeDetector([BindingRecord.createForElement(ast, 0, "memo")]); + + var cd = pcd.instantiate(dispatcher); + cd.hydrate(new TestData("value"), null, null); + + cd.detectChanges(); + + expect(dispatcher.log).toEqual(["memo=BvalueA"]); + }); + + describe("change notification", () => { + describe("simple checks", () => { + it("should pass a change record to the dispatcher", () => { + var person = new Person('bob'); + var c = createChangeDetector('name', 'name', person); + var cd = c["changeDetector"]; + var dispatcher = c["dispatcher"]; + + cd.detectChanges(); + + expect(dispatcher.loggedValues).toEqual(['bob']); + }); + }); + + describe("pipes", () => { + it("should pass a change record to the dispatcher", () => { + var registry = new FakePipeRegistry('pipe', () => new CountingPipe()); + + var person = new Person('bob'); + var c = createChangeDetector('name', 'name | pipe', person, null, registry); + var cd = c["changeDetector"]; + var dispatcher = c["dispatcher"]; + + cd.detectChanges(); + + expect(dispatcher.loggedValues).toEqual(['bob state:0']); + }); + }); + + describe("updating directives", () => { + var dirRecord1 = new DirectiveRecord(new DirectiveIndex(0, 0), true, true, DEFAULT); + var dirRecord2 = new DirectiveRecord(new DirectiveIndex(0, 1), true, true, DEFAULT); + var dirRecordNoCallbacks = + new DirectiveRecord(new DirectiveIndex(0, 0), false, false, DEFAULT); + + function updateA(exp: string, dirRecord) { + return BindingRecord.createForDirective(ast(exp), "a", (o, v) => o.a = v, + dirRecord); + } + + function updateB(exp: string, dirRecord) { + return BindingRecord.createForDirective(ast(exp), "b", (o, v) => o.b = v, + dirRecord); + } + + var directive1; + var directive2; + + beforeEach(() => { + directive1 = new TestDirective(); + directive2 = new TestDirective(); + }); + + it("should happen directly, without invoking the dispatcher", () => { + var pcd = + createProtoChangeDetector([updateA("42", dirRecord1)], [], [dirRecord1]); + + var cd = pcd.instantiate(dispatcher); + + cd.hydrate(null, null, dirs([directive1])); + + cd.detectChanges(); + + expect(dispatcher.loggedValues).toEqual([]); + expect(directive1.a).toEqual(42); + }); + + describe("onChange", () => { + it("should notify the directive when a group of records changes", () => { + var pcd = createProtoChangeDetector([ + updateA("1", dirRecord1), + updateB("2", dirRecord1), + updateA("3", dirRecord2) + ], + [], [dirRecord1, dirRecord2]); + + var cd = pcd.instantiate(dispatcher); + + cd.hydrate(null, null, dirs([directive1, directive2])); + + cd.detectChanges(); + + expect(directive1.changes).toEqual({'a': 1, 'b': 2}); + expect(directive2.changes).toEqual({'a': 3}); + }); + + it("should not call onChange when callOnChange is false", () => { + var pcd = createProtoChangeDetector([updateA("1", dirRecordNoCallbacks)], [], + [dirRecordNoCallbacks]); + + var cd = pcd.instantiate(dispatcher); + + cd.hydrate(null, null, dirs([directive1])); + + cd.detectChanges(); + + expect(directive1.changes).toEqual(null); + }); + }); + + describe("onAllChangesDone", () => { + it("should be called after processing all the children", () => { + var pcd = createProtoChangeDetector([], [], [dirRecord1, dirRecord2]); + + var cd = pcd.instantiate(dispatcher); + cd.hydrate(null, null, dirs([directive1, directive2])); + + cd.detectChanges(); + + expect(directive1.onChangesDoneCalled).toBe(true); + expect(directive2.onChangesDoneCalled).toBe(true); + + // reset directives + directive1.onChangesDoneCalled = false; + directive2.onChangesDoneCalled = false; + + // Verify that checking should not call them. + cd.checkNoChanges(); + + expect(directive1.onChangesDoneCalled).toBe(false); + expect(directive2.onChangesDoneCalled).toBe(false); + + // re-verify that changes are still detected + cd.detectChanges(); + + expect(directive1.onChangesDoneCalled).toBe(true); + expect(directive2.onChangesDoneCalled).toBe(true); + }); + + + it("should not be called when onAllChangesDone is false", () => { + var pcd = createProtoChangeDetector([updateA("1", dirRecordNoCallbacks)], [], + [dirRecordNoCallbacks]); + + var cd = pcd.instantiate(dispatcher); + + cd.hydrate(null, null, dirs([directive1])); + + cd.detectChanges(); + + expect(directive1.onChangesDoneCalled).toEqual(false); + }); + + it("should be called in reverse order so the child is always notified before the parent", + () => { + var pcd = createProtoChangeDetector([], [], [dirRecord1, dirRecord2]); + var cd = pcd.instantiate(dispatcher); + + var onChangesDoneCalls = []; + var td1; + td1 = new TestDirective(() => ListWrapper.push(onChangesDoneCalls, td1)); + var td2; + td2 = new TestDirective(() => ListWrapper.push(onChangesDoneCalls, td2)); + cd.hydrate(null, null, dirs([td1, td2])); + + cd.detectChanges(); + + expect(onChangesDoneCalls).toEqual([td2, td1]); + }); + + it("should be called before processing shadow dom children", () => { + var pcd = createProtoChangeDetector([], null, [dirRecord1]); + var shadowDomChildPCD = + createProtoChangeDetector([updateA("1", dirRecord1)], null, [dirRecord1]); + + var parent = pcd.instantiate(dispatcher); + + var child = shadowDomChildPCD.instantiate(dispatcher); + parent.addShadowDomChild(child); + + var directiveInShadowDom = new TestDirective(); + var parentDirective = + new TestDirective(() => { expect(directiveInShadowDom.a).toBe(null); }); + + parent.hydrate(null, null, dirs([parentDirective])); + child.hydrate(null, null, dirs([directiveInShadowDom])); + + parent.detectChanges(); + }); + }); + }); + }); + + describe("reading directives", () => { + var index = new DirectiveIndex(0, 0); + var dirRecord = new DirectiveRecord(index, false, false, DEFAULT); + + it("should read directive properties", () => { + var directive = new TestDirective(); + directive.a = "aaa"; + + var pcd = createProtoChangeDetector( + [BindingRecord.createForHostProperty(index, ast("a"), "prop")], null, + [dirRecord]); + var cd = pcd.instantiate(dispatcher); + cd.hydrate(null, null, dirs([directive])); + + cd.detectChanges(); + + expect(dispatcher.loggedValues).toEqual(['aaa']); + }); + }); + + describe("enforce no new changes", () => { + it("should throw when a record gets changed after it has been checked", () => { + var pcd = + createProtoChangeDetector([BindingRecord.createForElement(ast("a"), 0, "a")]); + + var dispatcher = new TestDispatcher(); + var cd = pcd.instantiate(dispatcher); + cd.hydrate(new TestData('value'), null, null); + + expect(() => { cd.checkNoChanges(); }) + .toThrowError( + new RegExp("Expression 'a in location' has changed after it was checked")); + }); + + it("should not break the next run", () => { + var pcd = + createProtoChangeDetector([BindingRecord.createForElement(ast("a"), 0, "a")]); + + var dispatcher = new TestDispatcher(); + var cd = pcd.instantiate(dispatcher); + cd.hydrate(new TestData('value'), null, null); + + expect(() => cd.checkNoChanges()) + .toThrowError( + new RegExp("Expression 'a in location' has changed after it was checked.")); + + cd.detectChanges(); + + expect(dispatcher.loggedValues).toEqual(['value']); + }); + }); + + // TODO vsavkin: implement it + describe("error handling", () => { + xit("should wrap exceptions into ChangeDetectionError", () => { + var pcd = createProtoChangeDetector(); + var cd = pcd.instantiate( + new TestDispatcher(), + [BindingRecord.createForElement(ast("invalidProp"), 0, "a")], null, []); + cd.hydrate(null, null); + + try { + cd.detectChanges(); + + throw new BaseException("fail"); + } catch (e) { + expect(e).toBeAnInstanceOf(ChangeDetectionError); + expect(e.location).toEqual("invalidProp in someComponent"); + } + }); + }); + + describe("Locals", () => { + it('should read a value from locals', () => { + var locals = new Locals(null, MapWrapper.createFromPairs([["key", "value"]])); + + expect(executeWatch('key', 'key', null, locals)).toEqual(['key=value']); + }); + + it('should invoke a function from local', () => { + var locals = new Locals(null, MapWrapper.createFromPairs([["key", () => "value"]])); + + expect(executeWatch('key', 'key()', null, locals)).toEqual(['key=value']); + }); + + it('should handle nested locals', () => { + var nested = new Locals(null, MapWrapper.createFromPairs([["key", "value"]])); + var locals = new Locals(nested, MapWrapper.create()); + + expect(executeWatch('key', 'key', null, locals)).toEqual(['key=value']); + }); + + it("should fall back to a regular field read when the locals map" + + "does not have the requested field", + () => { + var locals = new Locals(null, MapWrapper.createFromPairs([["key", "value"]])); + + expect(executeWatch('name', 'name', new Person("Jim"), locals)) + .toEqual(['name=Jim']); + }); + + it('should correctly handle nested properties', () => { + var address = new Address('Grenoble'); + var person = new Person('Victor', address); + var locals = new Locals(null, MapWrapper.createFromPairs([['city', 'MTV']])); + expect(executeWatch('address.city', 'address.city', person, locals)) + .toEqual(['address.city=Grenoble']); + expect(executeWatch('city', 'city', person, locals)).toEqual(['city=MTV']); + }); + + }); + + describe("handle children", () => { + var parent, child; + + beforeEach(() => { + parent = createProtoChangeDetector([]).instantiate(null); + child = createProtoChangeDetector([]).instantiate(null); + }); + + it("should add light dom children", () => { + parent.addChild(child); + + expect(parent.lightDomChildren.length).toEqual(1); + expect(parent.lightDomChildren[0]).toBe(child); + }); + + it("should add shadow dom children", () => { + parent.addShadowDomChild(child); + + expect(parent.shadowDomChildren.length).toEqual(1); + expect(parent.shadowDomChildren[0]).toBe(child); + }); + + it("should remove light dom children", () => { + parent.addChild(child); + parent.removeChild(child); + + expect(parent.lightDomChildren).toEqual([]); + }); + + it("should remove shadow dom children", () => { + parent.addShadowDomChild(child); + parent.removeShadowDomChild(child); + + expect(parent.shadowDomChildren.length).toEqual(0); + }); + }); + }); + + describe("mode", () => { + it("should set the mode to CHECK_ALWAYS when the default change detection is used", + () => { + var proto = createProtoChangeDetector([], [], [], null, DEFAULT); + var cd = proto.instantiate(null); + + expect(cd.mode).toEqual(null); + + cd.hydrate(null, null, null); + + expect(cd.mode).toEqual(CHECK_ALWAYS); + }); + + it("should set the mode to CHECK_ONCE when the push change detection is used", () => { + var proto = createProtoChangeDetector([], [], [], null, ON_PUSH); + var cd = proto.instantiate(null); + cd.hydrate(null, null, null); + + expect(cd.mode).toEqual(CHECK_ONCE); + }); + + it("should not check a detached change detector", () => { + var c = createChangeDetector('name', 'a', new TestData("value")); + var cd = c["changeDetector"]; + var dispatcher = c["dispatcher"]; + + cd.mode = DETACHED; + cd.detectChanges(); + + expect(dispatcher.log).toEqual([]); + }); + + it("should not check a checked change detector", () => { + var c = createChangeDetector('name', 'a', new TestData("value")); + var cd = c["changeDetector"]; + var dispatcher = c["dispatcher"]; + + cd.mode = CHECKED; + cd.detectChanges(); + + expect(dispatcher.log).toEqual([]); + }); + + it("should change CHECK_ONCE to CHECKED", () => { + var cd = createProtoChangeDetector([]).instantiate(null); + cd.mode = CHECK_ONCE; + + cd.detectChanges(); + + expect(cd.mode).toEqual(CHECKED); + }); + + it("should not change the CHECK_ALWAYS", () => { + var cd = createProtoChangeDetector([]).instantiate(null); + cd.mode = CHECK_ALWAYS; + + cd.detectChanges(); + + expect(cd.mode).toEqual(CHECK_ALWAYS); + }); + + describe("marking ON_PUSH detectors as CHECK_ONCE after an update", () => { + var checkedDetector; + var dirRecordWithOnPush; + var updateDirWithOnPushRecord; + var directives; + + beforeEach(() => { + var proto = createProtoChangeDetector([], [], [], null, ON_PUSH); + checkedDetector = proto.instantiate(null); + checkedDetector.hydrate(null, null, null); + checkedDetector.mode = CHECKED; + + // this directive is a component with ON_PUSH change detection + dirRecordWithOnPush = + new DirectiveRecord(new DirectiveIndex(0, 0), false, false, ON_PUSH); + + // a record updating a component + updateDirWithOnPushRecord = BindingRecord.createForDirective( + ast("42"), "a", (o, v) => o.a = v, dirRecordWithOnPush); + + var targetDirective = new TestData(null); + directives = new FakeDirectives([targetDirective], [checkedDetector]); + }); + + it("should set the mode to CHECK_ONCE when a binding is updated", () => { + var proto = createProtoChangeDetector([updateDirWithOnPushRecord], [], + [dirRecordWithOnPush]); + + var cd = proto.instantiate(null); + cd.hydrate(null, null, directives); + + expect(checkedDetector.mode).toEqual(CHECKED); + + // evaluate the record, update the targetDirective, and mark its detector as + // CHECK_ONCE + cd.detectChanges(); + + expect(checkedDetector.mode).toEqual(CHECK_ONCE); + }); + }); + }); + + describe("markPathToRootAsCheckOnce", () => { + function changeDetector(mode, parent) { + var cd = createProtoChangeDetector([]).instantiate(null); + cd.mode = mode; + if (isPresent(parent)) parent.addChild(cd); + return cd; + } + + it("should mark all checked detectors as CHECK_ONCE " + "until reaching a detached one", + () => { + + var root = changeDetector(CHECK_ALWAYS, null); + var disabled = changeDetector(DETACHED, root); + var parent = changeDetector(CHECKED, disabled); + var checkAlwaysChild = changeDetector(CHECK_ALWAYS, parent); + var checkOnceChild = changeDetector(CHECK_ONCE, checkAlwaysChild); + var checkedChild = changeDetector(CHECKED, checkOnceChild); + + checkedChild.markPathToRootAsCheckOnce(); + + expect(root.mode).toEqual(CHECK_ALWAYS); + expect(disabled.mode).toEqual(DETACHED); + expect(parent.mode).toEqual(CHECK_ONCE); + expect(checkAlwaysChild.mode).toEqual(CHECK_ALWAYS); + expect(checkOnceChild.mode).toEqual(CHECK_ONCE); + expect(checkedChild.mode).toEqual(CHECK_ONCE); + }); + }); + + describe("hydration", () => { + it("should be able to rehydrate a change detector", () => { + var c = createChangeDetector("memo", "name"); + var cd = c["changeDetector"]; + + cd.hydrate("some context", null, null); + expect(cd.hydrated()).toBe(true); + + cd.dehydrate(); + expect(cd.hydrated()).toBe(false); + + cd.hydrate("other context", null, null); + expect(cd.hydrated()).toBe(true); + }); + + it("should destroy all active pipes during dehyration", () => { + var pipe = new OncePipe(); + var registry = new FakePipeRegistry('pipe', () => pipe); + var c = + createChangeDetector("memo", "name | pipe", new Person('bob'), null, registry); + var cd = c["changeDetector"]; + + cd.detectChanges(); + + cd.dehydrate(); + + expect(pipe.destroyCalled).toBe(true); + }); + }); + + describe("pipes", () => { + it("should support pipes", () => { + var registry = new FakePipeRegistry('pipe', () => new CountingPipe()); + var ctx = new Person("Megatron"); + + var c = createChangeDetector("memo", "name | pipe", ctx, null, registry); + var cd = c["changeDetector"]; + var dispatcher = c["dispatcher"]; + + cd.detectChanges(); + + expect(dispatcher.log).toEqual(['memo=Megatron state:0']); + + dispatcher.clear(); + cd.detectChanges(); + + expect(dispatcher.log).toEqual(['memo=Megatron state:1']); + }); + + it("should lookup pipes in the registry when the context is not supported", () => { + var registry = new FakePipeRegistry('pipe', () => new OncePipe()); + var ctx = new Person("Megatron"); + + var c = createChangeDetector("memo", "name | pipe", ctx, null, registry); + var cd = c["changeDetector"]; + + cd.detectChanges(); + + expect(registry.numberOfLookups).toEqual(1); + + ctx.name = "Optimus Prime"; + cd.detectChanges(); + + expect(registry.numberOfLookups).toEqual(2); + }); + + it("should invoke onDestroy on a pipe before switching to another one", () => { + var pipe = new OncePipe(); + var registry = new FakePipeRegistry('pipe', () => pipe); + var ctx = new Person("Megatron"); + + var c = createChangeDetector("memo", "name | pipe", ctx, null, registry); + var cd = c["changeDetector"]; + + cd.detectChanges(); + ctx.name = "Optimus Prime"; + cd.detectChanges(); + + expect(pipe.destroyCalled).toEqual(true); + }); + + it("should inject the ChangeDetectorRef " + "of the encompassing component into a pipe", + () => { + + var registry = new FakePipeRegistry('pipe', () => new IdentityPipe()); + var c = + createChangeDetector("memo", "name | pipe", new Person('bob'), null, registry); + var cd = c["changeDetector"]; + + cd.detectChanges(); + + expect(registry.cdRef).toBe(cd.ref); + }); + }); + + it("should do nothing when no change", () => { + var registry = new FakePipeRegistry('pipe', () => new IdentityPipe()); + var ctx = new Person("Megatron"); + + var c = createChangeDetector("memo", "name | pipe", ctx, null, registry); + var cd = c["changeDetector"]; + var dispatcher = c["dispatcher"]; + + cd.detectChanges(); + + expect(dispatcher.log).toEqual(['memo=Megatron']); + + dispatcher.clear(); + cd.detectChanges(); + + expect(dispatcher.log).toEqual([]); + }); + + it("should unwrap the wrapped value", () => { + var registry = new FakePipeRegistry('pipe', () => new WrappedPipe()); + var ctx = new Person("Megatron"); + + var c = createChangeDetector("memo", "name | pipe", ctx, null, registry); + var cd = c["changeDetector"]; + var dispatcher = c["dispatcher"]; + + cd.detectChanges(); + + expect(dispatcher.log).toEqual(['memo=Megatron']); + }); + }); + }); +} + +class CountingPipe extends Pipe { + state: number; + + constructor() { + super(); + this.state = 0; + } + + supports(newValue) { return true; } + + transform(value) { return `${value} state:${this.state ++}`; } +} + +class OncePipe extends Pipe { + called: boolean; + destroyCalled: boolean; + + constructor() { + super(); + this.called = false; + this.destroyCalled = false; + } + + supports(newValue) { return !this.called; } + + onDestroy() { this.destroyCalled = true; } + + transform(value) { + this.called = true; + return value; + } +} + +class IdentityPipe extends Pipe { + transform(value) { return value; } +} + +class WrappedPipe extends Pipe { + transform(value) { return WrappedValue.wrap(value); } +} + +class FakePipeRegistry extends PipeRegistry { + numberOfLookups: number; + pipeType: string; + factory: Function; + cdRef: any; + + constructor(pipeType, factory) { + super({}); + this.pipeType = pipeType; + this.factory = factory; + this.numberOfLookups = 0; + } + + get(type: string, obj, cdRef) { + if (type != this.pipeType) return null; + this.numberOfLookups++; + this.cdRef = cdRef; + return this.factory(); + } +} + +class TestDirective { + a; + b; + changes; + onChangesDoneCalled; + onChangesDoneSpy; + + constructor(onChangesDoneSpy = null) { + this.onChangesDoneCalled = false; + this.onChangesDoneSpy = onChangesDoneSpy; + this.a = null; + this.b = null; + this.changes = null; + } + + onChange(changes) { + var r = {}; + StringMapWrapper.forEach(changes, (c, key) => r[key] = c.currentValue); + this.changes = r; + } + + onAllChangesDone() { + this.onChangesDoneCalled = true; + if (isPresent(this.onChangesDoneSpy)) { + this.onChangesDoneSpy(); + } + } +} + +class Person { + name: string; + age: number; + address: Address; + constructor(name: string, address: Address = null) { + this.name = name; + this.address = address; + } + + sayHi(m) { return `Hi, ${m}`; } + + toString(): string { + var address = this.address == null ? '' : ' address=' + this.address.toString(); + + return 'name=' + this.name + address; + } +} + +class Address { + city: string; + constructor(city: string) { this.city = city; } + + toString(): string { return this.city; } +} + +class Uninitialized { + value: any; +} + +class TestData { + a; + + constructor(a) { this.a = a; } +} + +class FakeDirectives { + constructor(public directives: List, + public detectors: List) {} + + getDirectiveFor(di: DirectiveIndex) { return this.directives[di.directiveIndex]; } + + getDetectorFor(di: DirectiveIndex) { return this.detectors[di.directiveIndex]; } +} + +class TestDispatcher extends ChangeDispatcher { + log: List; + loggedValues: List; + + constructor() { + super(); + this.clear(); + } + + clear() { + this.log = ListWrapper.create(); + this.loggedValues = ListWrapper.create(); + } + + notifyOnBinding(binding, value) { + ListWrapper.push(this.log, `${binding.propertyName}=${this._asString(value)}`); + ListWrapper.push(this.loggedValues, value); + } + + _asString(value) { return (isBlank(value) ? 'null' : value.toString()); } +} diff --git a/modules/angular2/test/change_detection/coalesce_spec.js b/modules/angular2/test/change_detection/coalesce_spec.js deleted file mode 100644 index 64f6aec55d..0000000000 --- a/modules/angular2/test/change_detection/coalesce_spec.js +++ /dev/null @@ -1,82 +0,0 @@ -import {ddescribe, describe, it, iit, xit, expect, beforeEach, afterEach} from 'angular2/test_lib'; - -import {coalesce} from 'angular2/src/change_detection/coalesce'; -import {RECORD_TYPE_SELF, ProtoRecord} from 'angular2/src/change_detection/proto_record'; - -export function main() { - function r(funcOrValue, args, contextIndex, selfIndex, lastInBinding = false) { - return new ProtoRecord(99, "name", funcOrValue, args, null, contextIndex, null, selfIndex, - null, null, lastInBinding, false); - } - - describe("change detection - coalesce", () => { - it("should work with an empty list", () => { - expect(coalesce([])).toEqual([]); - }); - - it("should remove non-terminal duplicate records" + - " and update the context indices referencing them", () => { - var rs = coalesce([ - r("user", [], 0, 1), - r("first", [], 1, 2), - r("user", [], 0, 3), - r("last", [], 3, 4) - ]); - - expect(rs).toEqual([ - r("user", [], 0, 1), - r("first", [], 1, 2), - r("last", [], 1, 3) - ]); - }); - - it("should update indices of other records", () => { - var rs = coalesce([ - r("dup", [], 0, 1), - r("dup", [], 0, 2), - r("user", [], 0, 3), - r("first", [3], 3, 4) - ]); - - expect(rs).toEqual([ - r("dup", [], 0, 1), - r("user", [], 0, 2), - r("first", [2], 2, 3) - ]); - }); - - it("should remove non-terminal duplicate records" + - " and update the args indices referencing them", () => { - var rs = coalesce([ - r("user1", [], 0, 1), - r("user2", [], 0, 2), - r("hi", [1], 0, 3), - r("hi", [1], 0, 4), - r("hi", [2], 0, 5) - ]); - - expect(rs).toEqual([ - r("user1", [], 0, 1), - r("user2", [], 0, 2), - r("hi", [1], 0, 3), - r("hi", [2], 0, 4) - ]); - }); - - it("should replace duplicate terminal records with" + - " self records", () => { - - var rs = coalesce([ - r("user", [], 0, 1, true), - r("user", [], 0, 2, true) - ]); - - expect(rs[1]).toEqual(new ProtoRecord( - RECORD_TYPE_SELF, "self", null, - [], null, 1, null, 2, - null, null, - true, false) - ); - }); - }); -} diff --git a/modules/angular2/test/change_detection/coalesce_spec.ts b/modules/angular2/test/change_detection/coalesce_spec.ts new file mode 100644 index 0000000000..620e0ad561 --- /dev/null +++ b/modules/angular2/test/change_detection/coalesce_spec.ts @@ -0,0 +1,58 @@ +import {ddescribe, describe, it, iit, xit, expect, beforeEach, afterEach} from 'angular2/test_lib'; + +import {coalesce} from 'angular2/src/change_detection/coalesce'; +import {RECORD_TYPE_SELF, ProtoRecord} from 'angular2/src/change_detection/proto_record'; + +export function main() { + function r(funcOrValue, args, contextIndex, selfIndex, lastInBinding = false) { + return new ProtoRecord(99, "name", funcOrValue, args, null, contextIndex, null, selfIndex, null, + null, lastInBinding, false); + } + + describe("change detection - coalesce", () => { + it("should work with an empty list", () => { expect(coalesce([])).toEqual([]); }); + + it("should remove non-terminal duplicate records" + + " and update the context indices referencing them", + () => { + var rs = coalesce([ + r("user", [], 0, 1), + r("first", [], 1, 2), + r("user", [], 0, 3), + r("last", [], 3, 4) + ]); + + expect(rs).toEqual([r("user", [], 0, 1), r("first", [], 1, 2), r("last", [], 1, 3)]); + }); + + it("should update indices of other records", () => { + var rs = coalesce( + [r("dup", [], 0, 1), r("dup", [], 0, 2), r("user", [], 0, 3), r("first", [3], 3, 4)]); + + expect(rs).toEqual([r("dup", [], 0, 1), r("user", [], 0, 2), r("first", [2], 2, 3)]); + }); + + it("should remove non-terminal duplicate records" + + " and update the args indices referencing them", + () => { + var rs = coalesce([ + r("user1", [], 0, 1), + r("user2", [], 0, 2), + r("hi", [1], 0, 3), + r("hi", [1], 0, 4), + r("hi", [2], 0, 5) + ]); + + expect(rs).toEqual( + [r("user1", [], 0, 1), r("user2", [], 0, 2), r("hi", [1], 0, 3), r("hi", [2], 0, 4)]); + }); + + it("should replace duplicate terminal records with" + " self records", () => { + + var rs = coalesce([r("user", [], 0, 1, true), r("user", [], 0, 2, true)]); + + expect(rs[1]).toEqual(new ProtoRecord(RECORD_TYPE_SELF, "self", null, [], null, 1, null, 2, + null, null, true, false)); + }); + }); +} diff --git a/modules/angular2/test/change_detection/iterable.es6 b/modules/angular2/test/change_detection/iterable.es6 deleted file mode 100644 index eef0367b79..0000000000 --- a/modules/angular2/test/change_detection/iterable.es6 +++ /dev/null @@ -1,9 +0,0 @@ -export class TestIterable { - constructor() { - this.list = []; - } - - [Symbol.iterator]() { - return this.list[Symbol.iterator](); - } -} diff --git a/modules/angular2/test/change_detection/iterable.ts b/modules/angular2/test/change_detection/iterable.ts new file mode 100644 index 0000000000..a322db6e66 --- /dev/null +++ b/modules/angular2/test/change_detection/iterable.ts @@ -0,0 +1,8 @@ +import {List} from 'angular2/src/facade/collection'; + +export class TestIterable { + list: List; + constructor() { this.list = []; } + + [Symbol.iterator]() { return this.list[Symbol.iterator](); } +} diff --git a/modules/angular2/test/change_detection/parser/lexer_spec.js b/modules/angular2/test/change_detection/parser/lexer_spec.ts similarity index 77% rename from modules/angular2/test/change_detection/parser/lexer_spec.js rename to modules/angular2/test/change_detection/parser/lexer_spec.ts index 4d49686762..e73662f904 100644 --- a/modules/angular2/test/change_detection/parser/lexer_spec.js +++ b/modules/angular2/test/change_detection/parser/lexer_spec.ts @@ -3,9 +3,9 @@ import {ddescribe, describe, it, expect} from 'angular2/test_lib'; import {Lexer, Token} from 'angular2/src/change_detection/parser/lexer'; import {List, ListWrapper} from "angular2/src/facade/collection"; -import {StringWrapper, int} from "angular2/src/facade/lang"; +import {StringWrapper} from "angular2/src/facade/lang"; -function lex(text:string):List { +function lex(text: string): List { return new Lexer().tokenize(text); } @@ -53,52 +53,49 @@ export function main() { describe('lexer', function() { describe('token', function() { it('should tokenize a simple identifier', function() { - var tokens:List = lex("j"); + var tokens: List = lex("j"); expect(tokens.length).toEqual(1); expectIdentifierToken(tokens[0], 0, 'j'); }); it('should tokenize a dotted identifier', function() { - var tokens:List = lex("j.k"); + var tokens: List = lex("j.k"); expect(tokens.length).toEqual(3); expectIdentifierToken(tokens[0], 0, 'j'); - expectCharacterToken (tokens[1], 1, '.'); + expectCharacterToken(tokens[1], 1, '.'); expectIdentifierToken(tokens[2], 2, 'k'); }); it('should tokenize an operator', function() { - var tokens:List = lex("j-k"); + var tokens: List = lex("j-k"); expect(tokens.length).toEqual(3); expectOperatorToken(tokens[1], 1, '-'); }); it('should tokenize an indexed operator', function() { - var tokens:List = lex("j[k]"); + var tokens: List = lex("j[k]"); expect(tokens.length).toEqual(4); expectCharacterToken(tokens[1], 1, "["); expectCharacterToken(tokens[3], 3, "]"); }); it('should tokenize numbers', function() { - var tokens:List = lex("88"); + var tokens: List = lex("88"); expect(tokens.length).toEqual(1); expectNumberToken(tokens[0], 0, 88); }); - it('should tokenize numbers within index ops', function() { - expectNumberToken(lex("a[22]")[2], 2, 22); - }); + it('should tokenize numbers within index ops', + function() { expectNumberToken(lex("a[22]")[2], 2, 22); }); - it('should tokenize simple quoted strings', function() { - expectStringToken(lex('"a"')[0], 0, "a"); - }); + it('should tokenize simple quoted strings', + function() { expectStringToken(lex('"a"')[0], 0, "a"); }); - it('should tokenize quoted strings with escaped quotes', function() { - expectStringToken(lex('"a\\""')[0], 0, 'a"'); - }); + it('should tokenize quoted strings with escaped quotes', + function() { expectStringToken(lex('"a\\""')[0], 0, 'a"'); }); it('should tokenize a string', function() { - var tokens:List = lex("j-a.bc[22]+1.3|f:'a\\\'c':\"d\\\"e\""); + var tokens: List = lex("j-a.bc[22]+1.3|f:'a\\\'c':\"d\\\"e\""); expectIdentifierToken(tokens[0], 0, 'j'); expectOperatorToken(tokens[1], 1, '-'); expectIdentifierToken(tokens[2], 2, 'a'); @@ -118,39 +115,39 @@ export function main() { }); it('should tokenize undefined', function() { - var tokens:List = lex("undefined"); + var tokens: List = lex("undefined"); expectKeywordToken(tokens[0], 0, "undefined"); expect(tokens[0].isKeywordUndefined()).toBe(true); }); it('should ignore whitespace', function() { - var tokens:List = lex("a \t \n \r b"); + var tokens: List = lex("a \t \n \r b"); expectIdentifierToken(tokens[0], 0, 'a'); expectIdentifierToken(tokens[1], 8, 'b'); }); it('should tokenize quoted string', function() { var str = "['\\'', \"\\\"\"]"; - var tokens:List = lex(str); + var tokens: List = lex(str); expectStringToken(tokens[1], 1, "'"); expectStringToken(tokens[3], 7, '"'); }); it('should tokenize escaped quoted string', function() { var str = '"\\"\\n\\f\\r\\t\\v\\u00A0"'; - var tokens:List = lex(str); + var tokens: List = lex(str); expect(tokens.length).toEqual(1); expect(tokens[0].toString()).toEqual('"\n\f\r\t\v\u00A0'); }); it('should tokenize unicode', function() { - var tokens:List = lex('"\\u00A0"'); + var tokens: List = lex('"\\u00A0"'); expect(tokens.length).toEqual(1); expect(tokens[0].toString()).toEqual('\u00a0'); }); it('should tokenize relation', function() { - var tokens:List = lex("! == != < > <= >= === !=="); + var tokens: List = lex("! == != < > <= >= === !=="); expectOperatorToken(tokens[0], 0, '!'); expectOperatorToken(tokens[1], 2, '=='); expectOperatorToken(tokens[2], 5, '!='); @@ -163,7 +160,7 @@ export function main() { }); it('should tokenize statements', function() { - var tokens:List = lex("a;b;"); + var tokens: List = lex("a;b;"); expectIdentifierToken(tokens[0], 0, 'a'); expectCharacterToken(tokens[1], 1, ';'); expectIdentifierToken(tokens[2], 2, 'b'); @@ -171,19 +168,19 @@ export function main() { }); it('should tokenize function invocation', function() { - var tokens:List = lex("a()"); + var tokens: List = lex("a()"); expectIdentifierToken(tokens[0], 0, 'a'); expectCharacterToken(tokens[1], 1, '('); expectCharacterToken(tokens[2], 2, ')'); }); it('should tokenize simple method invocations', function() { - var tokens:List = lex("a.method()"); + var tokens: List = lex("a.method()"); expectIdentifierToken(tokens[2], 2, 'method'); }); it('should tokenize method invocation', function() { - var tokens:List = lex("a.b.c (d) - e.f()"); + var tokens: List = lex("a.b.c (d) - e.f()"); expectIdentifierToken(tokens[0], 0, 'a'); expectCharacterToken(tokens[1], 1, '.'); expectIdentifierToken(tokens[2], 2, 'b'); @@ -201,7 +198,7 @@ export function main() { }); it('should tokenize number', function() { - var tokens:List = lex("0.5"); + var tokens: List = lex("0.5"); expectNumberToken(tokens[0], 0, 0.5); }); @@ -212,7 +209,7 @@ export function main() { // }); it('should tokenize number with exponent', function() { - var tokens:List = lex("0.5E-10"); + var tokens: List = lex("0.5E-10"); expect(tokens.length).toEqual(1); expectNumberToken(tokens[0], 0, 0.5E-10); tokens = lex("0.5E+10"); @@ -220,28 +217,26 @@ export function main() { }); it('should throws exception for invalid exponent', function() { - expect(function() { - lex("0.5E-"); - }).toThrowError('Lexer Error: Invalid exponent at column 4 in expression [0.5E-]'); + expect(function() { lex("0.5E-"); }) + .toThrowError('Lexer Error: Invalid exponent at column 4 in expression [0.5E-]'); - expect(function() { - lex("0.5E-A"); - }).toThrowError('Lexer Error: Invalid exponent at column 4 in expression [0.5E-A]'); + expect(function() { lex("0.5E-A"); }) + .toThrowError('Lexer Error: Invalid exponent at column 4 in expression [0.5E-A]'); }); it('should tokenize number starting with a dot', function() { - var tokens:List = lex(".5"); + var tokens: List = lex(".5"); expectNumberToken(tokens[0], 0, 0.5); }); it('should throw error on invalid unicode', function() { - expect(function() { - lex("'\\u1''bla'"); - }).toThrowError("Lexer Error: Invalid unicode escape [\\u1''b] at column 2 in expression ['\\u1''bla']"); + expect(function() { lex("'\\u1''bla'"); }) + .toThrowError( + "Lexer Error: Invalid unicode escape [\\u1''b] at column 2 in expression ['\\u1''bla']"); }); it('should tokenize hash as operator', function() { - var tokens:List = lex("#"); + var tokens: List = lex("#"); expectOperatorToken(tokens[0], 0, '#'); }); diff --git a/modules/angular2/test/change_detection/parser/locals_spec.js b/modules/angular2/test/change_detection/parser/locals_spec.ts similarity index 84% rename from modules/angular2/test/change_detection/parser/locals_spec.js rename to modules/angular2/test/change_detection/parser/locals_spec.ts index 98ed1d687d..9e4f2a6485 100644 --- a/modules/angular2/test/change_detection/parser/locals_spec.js +++ b/modules/angular2/test/change_detection/parser/locals_spec.ts @@ -8,8 +8,7 @@ export function main() { describe('Locals', () => { var locals; beforeEach(() => { - locals = new Locals(null, - MapWrapper.createFromPairs([['key', 'value'], ['nullKey', null]])); + locals = new Locals(null, MapWrapper.createFromPairs([['key', 'value'], ['nullKey', null]])); }); it('should support getting values', () => { @@ -28,9 +27,8 @@ export function main() { expect(locals.get('key')).toBe('bar'); }); - it('should not support setting keys that are not present already', () => { - expect(() => locals.set('notPresent', 'bar')).toThrowError(); - }); + it('should not support setting keys that are not present already', + () => { expect(() => locals.set('notPresent', 'bar')).toThrowError(); }); it('should clearValues', () => { locals.clearValues(); diff --git a/modules/angular2/test/change_detection/parser/parser_spec.js b/modules/angular2/test/change_detection/parser/parser_spec.ts similarity index 70% rename from modules/angular2/test/change_detection/parser/parser_spec.js rename to modules/angular2/test/change_detection/parser/parser_spec.ts index 8dc841fb93..88477f6238 100644 --- a/modules/angular2/test/change_detection/parser/parser_spec.js +++ b/modules/angular2/test/change_detection/parser/parser_spec.ts @@ -5,59 +5,42 @@ import {MapWrapper, ListWrapper} from 'angular2/src/facade/collection'; import {Parser} from 'angular2/src/change_detection/parser/parser'; import {Lexer} from 'angular2/src/change_detection/parser/lexer'; import {Locals} from 'angular2/src/change_detection/parser/locals'; -import {Pipe, LiteralPrimitive} from 'angular2/src/change_detection/parser/ast'; +import {Pipe, LiteralPrimitive, AccessMember} from 'angular2/src/change_detection/parser/ast'; class TestData { - a; - b; - fnReturnValue; - constructor(a, b, fnReturnValue) { - this.a = a; - this.b = b; - this.fnReturnValue = fnReturnValue; - } + constructor(public a?: any, public b?: any, public fnReturnValue?: any) {} - fn() { - return this.fnReturnValue; - } + fn() { return this.fnReturnValue; } - add(a, b) { - return a + b; - } + add(a, b) { return a + b; } } export function main() { - function td(a = 0, b = 0, fnReturnValue = "constant") { + function td(a: any = 0, b: any = 0, fnReturnValue: any = "constant") { return new TestData(a, b, fnReturnValue); } - function createParser() { - return new Parser(new Lexer(), reflector); - } + function createParser() { return new Parser(new Lexer(), reflector); } - function parseAction(text, location = null) { + function parseAction(text, location = null): any { return createParser().parseAction(text, location); } - function parseBinding(text, location = null) { + function parseBinding(text, location = null): any { return createParser().parseBinding(text, location); } - function parseTemplateBindings(text, location = null) { + function parseTemplateBindings(text, location = null): any { return createParser().parseTemplateBindings(text, location); } - function parseInterpolation(text, location = null) { + function parseInterpolation(text, location = null): any { return createParser().parseInterpolation(text, location); } - function addPipes(ast, pipes) { - return createParser().addPipes(ast, pipes); - } + function addPipes(ast, pipes): any { return createParser().addPipes(ast, pipes); } - function emptyLocals() { - return new Locals(null, MapWrapper.create()); - } + function emptyLocals() { return new Locals(null, MapWrapper.create()); } function expectEval(text, passedInContext = null, passedInLocals = null) { var c = isBlank(passedInContext) ? td() : passedInContext; @@ -74,7 +57,7 @@ export function main() { function evalAsts(asts, passedInContext = null) { var c = isBlank(passedInContext) ? td() : passedInContext; var res = []; - for (var i=0; i { describe("parseAction", () => { describe("basic expressions", () => { - it('should parse numerical expressions', () => { - expectEval("1").toEqual(1); - }); + it('should parse numerical expressions', () => { expectEval("1").toEqual(1); }); it('should parse strings', () => { expectEval("'1'").toEqual('1'); expectEval('"1"').toEqual('1'); }); - it('should parse null', () => { - expectEval("null").toBe(null); - }); + it('should parse null', () => { expectEval("null").toBe(null); }); it('should parse unary - expressions', () => { expectEval("-1").toEqual(-1); @@ -107,13 +86,10 @@ export function main() { expectEval("!!!true").toEqual(!!!true); }); - it('should parse multiplicative expressions', () => { - expectEval("3*4/2%5").toEqual(3 * 4 / 2 % 5); - }); + it('should parse multiplicative expressions', + () => { expectEval("3*4/2%5").toEqual(3 * 4 / 2 % 5); }); - it('should parse additive expressions', () => { - expectEval("3+6-2").toEqual(3 + 6 - 2); - }); + it('should parse additive expressions', () => { expectEval("3+6-2").toEqual(3 + 6 - 2); }); it('should parse relational expressions', () => { expectEval("2<3").toEqual(2 < 3); @@ -124,23 +100,23 @@ export function main() { it('should parse equality expressions', () => { expectEval("2==3").toEqual(2 == 3); - expectEval("2=='2'").toEqual(2 == '2'); - expectEval("2=='3'").toEqual(2 == '3'); + expectEval("2=='2'").toEqual(2 == '2'); + expectEval("2=='3'").toEqual(2 == '3'); expectEval("2!=3").toEqual(2 != 3); - expectEval("2!='3'").toEqual(2 != '3'); - expectEval("2!='2'").toEqual(2 != '2'); - expectEval("2!=!false").toEqual(2!=!false); + expectEval("2!='3'").toEqual(2 != '3'); + expectEval("2!='2'").toEqual(2 != '2'); + expectEval("2!=!false").toEqual(2 != !false); }); it('should parse strict equality expressions', () => { expectEval("2===3").toEqual(2 === 3); - expectEval("2==='3'").toEqual(2 === '3'); - expectEval("2==='2'").toEqual(2 === '2'); + expectEval("2==='3'").toEqual(2 === '3'); + expectEval("2==='2'").toEqual(2 === '2'); expectEval("2!==3").toEqual(2 !== 3); - expectEval("2!=='3'").toEqual(2 !== '3'); - expectEval("2!=='2'").toEqual(2 !== '2'); - expectEval("false===!true").toEqual(false===!true); - expectEval("false!==!!true").toEqual(false!==!!true); + expectEval("2!=='3'").toEqual(2 !== '3'); + expectEval("2!=='2'").toEqual(2 !== '2'); + expectEval("false===!true").toEqual(false === !true); + expectEval("false!==!!true").toEqual(false !== !!true); }); it('should parse logicalAND expressions', () => { @@ -153,21 +129,16 @@ export function main() { expectEval("false||false").toEqual(false || false); }); - it('should short-circuit AND operator', () => { - expectEval('false && a()', td(() => {throw "BOOM"})).toBe(false); - }); + it('should short-circuit AND operator', + () => { expectEval('false && a()', td(() => {throw "BOOM"})).toBe(false); }); - it('should short-circuit OR operator', () => { - expectEval('true || a()', td(() => {throw "BOOM"})).toBe(true); - }); + it('should short-circuit OR operator', + () => { expectEval('true || a()', td(() => {throw "BOOM"})).toBe(true); }); - it('should evaluate grouped expressions', () => { - expectEval("(1+2)*3").toEqual((1+2)*3); - }); + it('should evaluate grouped expressions', + () => { expectEval("(1+2)*3").toEqual((1 + 2) * 3); }); - it('should parse an empty string', () => { - expectEval('').toBeNull(); - }); + it('should parse an empty string', () => { expectEval('').toBeNull(); }); }); describe("literals", () => { @@ -190,8 +161,10 @@ export function main() { }); it('should only allow identifier, string, or keyword as map key', () => { - expectEvalError('{(:0}').toThrowError(new RegExp('expected identifier, keyword, or string')); - expectEvalError('{1234:0}').toThrowError(new RegExp('expected identifier, keyword, or string')); + expectEvalError('{(:0}') + .toThrowError(new RegExp('expected identifier, keyword, or string')); + expectEvalError('{1234:0}') + .toThrowError(new RegExp('expected identifier, keyword, or string')); }); }); @@ -201,9 +174,8 @@ export function main() { expectEval("a.a", td(td(999))).toEqual(999); }); - it('should throw when accessing a field on null', () => { - expectEvalError("a.a.a").toThrowError(); - }); + it('should throw when accessing a field on null', + () => { expectEvalError("a.a.a").toThrowError(); }); it('should only allow identifier or keyword as member names', () => { expectEvalError('x.(').toThrowError(new RegExp('identifier or keyword')); @@ -212,23 +184,22 @@ export function main() { }); it("should read a field from Locals", () => { - var locals = new Locals(null, - MapWrapper.createFromPairs([["key", "value"]])); + var locals = new Locals(null, MapWrapper.createFromPairs([["key", "value"]])); expectEval("key", null, locals).toEqual("value"); }); it("should handle nested Locals", () => { - var nested = new Locals(null, - MapWrapper.createFromPairs([["key", "value"]])); + var nested = new Locals(null, MapWrapper.createFromPairs([["key", "value"]])); var locals = new Locals(nested, MapWrapper.create()); expectEval("key", null, locals).toEqual("value"); }); - it("should fall back to a regular field read when Locals "+ - "does not have the requested field", () => { - var locals = new Locals(null, MapWrapper.create()); - expectEval("a", td(999), locals).toEqual(999); - }); + it("should fall back to a regular field read when Locals " + + "does not have the requested field", + () => { + var locals = new Locals(null, MapWrapper.create()); + expectEval("a", td(999), locals).toEqual(999); + }); }); describe("method calls", () => { @@ -243,37 +214,30 @@ export function main() { expectEvalError("fn(1,2,3,4,5,6,7,8,9,10,11)").toThrowError(new RegExp('more than')); }); - it('should throw when no method', () => { - expectEvalError("blah()").toThrowError(); - }); + it('should throw when no method', () => { expectEvalError("blah()").toThrowError(); }); it('should evaluate a method from Locals', () => { - var locals = new Locals( - null, - MapWrapper.createFromPairs([['fn', () => 'child']]) - ); + var locals = new Locals(null, MapWrapper.createFromPairs([['fn', () => 'child']])); expectEval("fn()", td(0, 0, 'parent'), locals).toEqual('child'); }); it('should fall back to the parent context when Locals does not ' + - 'have the requested method', () => { - var locals = new Locals(null, MapWrapper.create()); - expectEval("fn()", td(0, 0, 'parent'), locals).toEqual('parent'); - }); + 'have the requested method', + () => { + var locals = new Locals(null, MapWrapper.create()); + expectEval("fn()", td(0, 0, 'parent'), locals).toEqual('parent'); + }); }); describe("functional calls", () => { - it("should evaluate function calls", () => { - expectEval("fn()(1,2)", td(0, 0, (a, b) => a + b)).toEqual(3); - }); + it("should evaluate function calls", + () => { expectEval("fn()(1,2)", td(0, 0, (a, b) => a + b)).toEqual(3); }); - it('should throw on non-function function calls', () => { - expectEvalError("4()").toThrowError(new RegExp('4 is not a function')); - }); + it('should throw on non-function function calls', + () => { expectEvalError("4()").toThrowError(new RegExp('4 is not a function')); }); - it('should parse functions for object indices', () => { - expectEval('a[b()]()', td([()=>6], () => 0)).toEqual(6); - }); + it('should parse functions for object indices', + () => { expectEval('a[b()]()', td([() => 6], () => 0)).toEqual(6); }); }); describe("conditional", () => { @@ -283,8 +247,8 @@ export function main() { }); it('should throw on incorrect ternary operator syntax', () => { - expectEvalError("true?1"). - toThrowError(new RegExp('Parser Error: Conditional expression true\\?1 requires all 3 expressions')); + expectEvalError("true?1").toThrowError(new RegExp( + 'Parser Error: Conditional expression true\\?1 requires all 3 expressions')); }); }); @@ -315,13 +279,13 @@ export function main() { }); it("should support map updates", () => { - var context = td({"key" : 100}); + var context = td({"key": 100}); expectEval('a["key"] = 200', context).toEqual(200); expect(context.a["key"]).toEqual(200); }); it("should support array/map updates", () => { - var context = td([{"key" : 100}]); + var context = td([{"key": 100}]); expectEval('a[0]["key"] = 200', context).toEqual(200); expect(context.a[0]["key"]).toEqual(200); }); @@ -345,28 +309,29 @@ export function main() { it('should throw when reassigning a variable binding', () => { var locals = new Locals(null, MapWrapper.createFromPairs([["key", "value"]])); - expectEvalError('key = 200', null, locals).toThrowError(new RegExp("Cannot reassign a variable binding")); + expectEvalError('key = 200', null, locals) + .toThrowError(new RegExp("Cannot reassign a variable binding")); }); }); describe("general error handling", () => { it("should throw on an unexpected token", () => { - expectEvalError("[1,2] trac") - .toThrowError(new RegExp('Unexpected token \'trac\'')); + expectEvalError("[1,2] trac").toThrowError(new RegExp('Unexpected token \'trac\'')); }); it('should throw a reasonable error for unconsumed tokens', () => { - expectEvalError(")").toThrowError(new RegExp("Unexpected token \\) at column 1 in \\[\\)\\]")); + expectEvalError(")") + .toThrowError(new RegExp("Unexpected token \\) at column 1 in \\[\\)\\]")); }); it('should throw on missing expected token', () => { - expectEvalError("a(b").toThrowError(new RegExp("Missing expected \\) at the end of the expression \\[a\\(b\\]")); + expectEvalError("a(b").toThrowError( + new RegExp("Missing expected \\) at the end of the expression \\[a\\(b\\]")); }); }); - it("should error when using pipes", () => { - expectEvalError('x|blah').toThrowError(new RegExp('Cannot have a pipe')); - }); + it("should error when using pipes", + () => { expectEvalError('x|blah').toThrowError(new RegExp('Cannot have a pipe')); }); it('should pass exceptions', () => { expect(() => { @@ -381,13 +346,11 @@ export function main() { }); }); - it('should store the source in the result', () => { - expect(parseAction('someExpr').source).toBe('someExpr'); - }); + it('should store the source in the result', + () => { expect(parseAction('someExpr').source).toBe('someExpr'); }); - it('should store the passed-in location', () => { - expect(parseAction('someExpr', 'location').location).toBe('location'); - }); + it('should store the passed-in location', + () => { expect(parseAction('someExpr', 'location').location).toBe('location'); }); }); describe("parseBinding", () => { @@ -425,19 +388,19 @@ export function main() { it('should only allow identifier or keyword as formatter names', () => { expect(() => parseBinding('"Foo"|(')).toThrowError(new RegExp('identifier or keyword')); - expect(() => parseBinding('"Foo"|1234')).toThrowError(new RegExp('identifier or keyword')); - expect(() => parseBinding('"Foo"|"uppercase"')).toThrowError(new RegExp('identifier or keyword')); + expect(() => parseBinding('"Foo"|1234')) + .toThrowError(new RegExp('identifier or keyword')); + expect(() => parseBinding('"Foo"|"uppercase"')) + .toThrowError(new RegExp('identifier or keyword')); }); }); - it('should store the source in the result', () => { - expect(parseBinding('someExpr').source).toBe('someExpr'); - }); + it('should store the source in the result', + () => { expect(parseBinding('someExpr').source).toBe('someExpr'); }); - it('should store the passed-in location', () => { - expect(parseBinding('someExpr', 'location').location).toBe('location'); - }); + it('should store the passed-in location', + () => { expect(parseBinding('someExpr', 'location').location).toBe('location'); }); it('should throw on chain expressions', () => { expect(() => parseBinding("1;2")).toThrowError(new RegExp("contain chained expression")); @@ -451,7 +414,7 @@ export function main() { describe('parseTemplateBindings', () => { function keys(templateBindings) { - return ListWrapper.map(templateBindings, (binding) => binding.key ); + return ListWrapper.map(templateBindings, (binding) => binding.key); } function keyValues(templateBindings) { @@ -459,19 +422,21 @@ export function main() { if (binding.keyIsVar) { return '#' + binding.key + (isBlank(binding.name) ? '' : '=' + binding.name); } else { - return binding.key + (isBlank(binding.expression) ? '' : `=${binding.expression}`) + return binding.key + (isBlank(binding.expression) ? '' : `=${binding.expression}`) } }); } function exprSources(templateBindings) { - return ListWrapper.map(templateBindings, - (binding) => isPresent(binding.expression) ? binding.expression.source : null ); + return ListWrapper.map(templateBindings, (binding) => isPresent(binding.expression) ? + binding.expression.source : + null); } function exprAsts(templateBindings) { - return ListWrapper.map(templateBindings, - (binding) => isPresent(binding.expression) ? binding.expression : null ); + return ListWrapper.map(templateBindings, (binding) => isPresent(binding.expression) ? + binding.expression : + null); } it('should parse an empty string', () => { @@ -497,13 +462,11 @@ export function main() { bindings = parseTemplateBindings("a-b:'c'"); expect(keys(bindings)).toEqual(['a-b']); - expect( () => { - parseTemplateBindings('(:0'); - }).toThrowError(new RegExp('expected identifier, keyword, or string')); + expect(() => { parseTemplateBindings('(:0'); }) + .toThrowError(new RegExp('expected identifier, keyword, or string')); - expect( () => { - parseTemplateBindings('1234:0'); - }).toThrowError(new RegExp('expected identifier, keyword, or string')); + expect(() => { parseTemplateBindings('1234:0'); }) + .toThrowError(new RegExp('expected identifier, keyword, or string')); }); it('should detect expressions as value', () => { @@ -565,20 +528,20 @@ export function main() { expect(keyValues(bindings)).toEqual(['keyword', '#item=\$implicit', '#i=k']); bindings = parseTemplateBindings("directive: var item in expr; var a = b", 'location'); - expect(keyValues(bindings)).toEqual(['directive', '#item=\$implicit', 'directive-in=expr in location', '#a=b']); + expect(keyValues(bindings)) + .toEqual(['directive', '#item=\$implicit', 'directive-in=expr in location', '#a=b']); }); it('should parse pipes', () => { var bindings = parseTemplateBindings('key value|pipe'); - var ast = bindings[0].expression.ast + var ast = bindings[0].expression.ast; expect(ast).toBeAnInstanceOf(Pipe); }); }); describe('parseInterpolation', () => { - it('should return null if no interpolation', () => { - expect(parseInterpolation('nothing')).toBe(null); - }); + it('should return null if no interpolation', + () => { expect(parseInterpolation('nothing')).toBe(null); }); it('should parse no prefix/suffix interpolation', () => { var ast = parseInterpolation('{{a}}').ast; @@ -621,7 +584,8 @@ export function main() { describe('wrapLiteralPrimitive', () => { it('should wrap a literal primitive', () => { - expect(createParser().wrapLiteralPrimitive("foo", null).eval(null, emptyLocals())).toEqual("foo"); + expect(createParser().wrapLiteralPrimitive("foo", null).eval(null, emptyLocals())) + .toEqual("foo"); }); }); }); diff --git a/modules/angular2/test/change_detection/pipes/iterable_changes_spec.js b/modules/angular2/test/change_detection/pipes/iterable_changes_spec.js deleted file mode 100644 index 769bfc69c4..0000000000 --- a/modules/angular2/test/change_detection/pipes/iterable_changes_spec.js +++ /dev/null @@ -1,299 +0,0 @@ -import {describe, it, iit, xit, expect, beforeEach, afterEach} from 'angular2/test_lib'; -import {IterableChanges} from 'angular2/src/change_detection/pipes/iterable_changes'; - -import {NumberWrapper} from 'angular2/src/facade/lang'; -import {ListWrapper, MapWrapper} from 'angular2/src/facade/collection'; - -import {TestIterable} from '../iterable'; -import {iterableChangesAsString} from '../util'; - -// todo(vicb): UnmodifiableListView / frozen object when implemented -export function main() { - describe('collection_changes', function() { - describe('CollectionChanges', function() { - var changes; - var l; - - beforeEach(() => { - changes = new IterableChanges(); - }); - - afterEach(() => { - changes = null; - }); - - it('should support list and iterables', () => { - expect(IterableChanges.supportsObj([])).toBeTruthy(); - expect(IterableChanges.supportsObj(new TestIterable())).toBeTruthy(); - expect(IterableChanges.supportsObj(MapWrapper.create())).toBeFalsy(); - expect(IterableChanges.supportsObj(null)).toBeFalsy(); - }); - - it('should support iterables', () => { - l = new TestIterable(); - - changes.check(l); - expect(changes.toString()).toEqual(iterableChangesAsString({ - collection: [] - })); - - l.list = [1]; - changes.check(l); - expect(changes.toString()).toEqual(iterableChangesAsString({ - collection: ['1[null->0]'], - additions: ['1[null->0]'] - })); - - l.list = [2, 1]; - changes.check(l); - expect(changes.toString()).toEqual(iterableChangesAsString({ - collection: ['2[null->0]', '1[0->1]'], - previous: ['1[0->1]'], - additions: ['2[null->0]'], - moves: ['1[0->1]'] - })); - }); - - it('should detect additions', () => { - l = []; - changes.check(l); - expect(changes.toString()).toEqual(iterableChangesAsString({ - collection: [] - })); - - ListWrapper.push(l, 'a'); - changes.check(l); - expect(changes.toString()).toEqual(iterableChangesAsString({ - collection: ['a[null->0]'], - additions: ['a[null->0]'] - })); - - ListWrapper.push(l, 'b'); - changes.check(l); - expect(changes.toString()).toEqual(iterableChangesAsString({ - collection: ['a', 'b[null->1]'], - previous: ['a'], - additions: ['b[null->1]'] - })); - }); - - it('should support changing the reference', () => { - l = [0]; - changes.check(l); - - l = [1, 0]; - changes.check(l); - expect(changes.toString()).toEqual(iterableChangesAsString({ - collection: ['1[null->0]', '0[0->1]'], - previous: ['0[0->1]'], - additions: ['1[null->0]'], - moves: ['0[0->1]'] - })); - - l = [2, 1, 0]; - changes.check(l); - expect(changes.toString()).toEqual(iterableChangesAsString({ - collection: ['2[null->0]', '1[0->1]', '0[1->2]'], - previous: ['1[0->1]', '0[1->2]'], - additions: ['2[null->0]'], - moves: ['1[0->1]', '0[1->2]'] - })); - }); - - it('should handle swapping element', () => { - l = [1, 2]; - changes.check(l); - - ListWrapper.clear(l); - ListWrapper.push(l, 2); - ListWrapper.push(l, 1); - changes.check(l); - expect(changes.toString()).toEqual(iterableChangesAsString({ - collection: ['2[1->0]', '1[0->1]'], - previous: ['1[0->1]', '2[1->0]'], - moves: ['2[1->0]', '1[0->1]'] - })); - }); - - it('should handle swapping element', () => { - l = ['a', 'b', 'c']; - changes.check(l); - - ListWrapper.removeAt(l, 1); - ListWrapper.insert(l, 0, 'b'); - changes.check(l); - expect(changes.toString()).toEqual(iterableChangesAsString({ - collection: ['b[1->0]', 'a[0->1]', 'c'], - previous: ['a[0->1]', 'b[1->0]', 'c'], - moves: ['b[1->0]', 'a[0->1]'] - })); - - ListWrapper.removeAt(l, 1); - ListWrapper.push(l, 'a'); - changes.check(l); - expect(changes.toString()).toEqual(iterableChangesAsString({ - collection: ['b', 'c[2->1]', 'a[1->2]'], - previous: ['b', 'a[1->2]', 'c[2->1]'], - moves: ['c[2->1]', 'a[1->2]'] - })); - }); - - it('should detect changes in list', () => { - l = []; - changes.check(l); - - ListWrapper.push(l, 'a'); - changes.check(l); - expect(changes.toString()).toEqual(iterableChangesAsString({ - collection: ['a[null->0]'], - additions: ['a[null->0]'] - })); - - ListWrapper.push(l, 'b'); - changes.check(l); - expect(changes.toString()).toEqual(iterableChangesAsString({ - collection: ['a', 'b[null->1]'], - previous: ['a'], - additions: ['b[null->1]'] - })); - - ListWrapper.push(l, 'c'); - ListWrapper.push(l, 'd'); - changes.check(l); - expect(changes.toString()).toEqual(iterableChangesAsString({ - collection: ['a', 'b', 'c[null->2]', 'd[null->3]'], - previous: ['a', 'b'], - additions: ['c[null->2]', 'd[null->3]'] - })); - - ListWrapper.removeAt(l, 2); - changes.check(l); - expect(changes.toString()).toEqual(iterableChangesAsString({ - collection: ['a', 'b', 'd[3->2]'], - previous: ['a', 'b', 'c[2->null]', 'd[3->2]'], - moves: ['d[3->2]'], - removals: ['c[2->null]'] - })); - - ListWrapper.clear(l); - ListWrapper.push(l, 'd'); - ListWrapper.push(l, 'c'); - ListWrapper.push(l, 'b'); - ListWrapper.push(l, 'a'); - changes.check(l); - expect(changes.toString()).toEqual(iterableChangesAsString({ - collection: ['d[2->0]', 'c[null->1]', 'b[1->2]', 'a[0->3]'], - previous: ['a[0->3]', 'b[1->2]', 'd[2->0]'], - additions: ['c[null->1]'], - moves: ['d[2->0]', 'b[1->2]', 'a[0->3]'] - })); - }); - - it('should test string by value rather than by reference (Dart)', () => { - l = ['a', 'boo']; - changes.check(l); - - var b = 'b'; - var oo = 'oo'; - ListWrapper.set(l, 1, b + oo); - changes.check(l); - expect(changes.toString()).toEqual(iterableChangesAsString({ - collection: ['a', 'boo'], - previous: ['a', 'boo'] - })); - }); - - it('should ignore [NaN] != [NaN] (JS)', () => { - l = [NumberWrapper.NaN]; - changes.check(l); - changes.check(l); - expect(changes.toString()).toEqual(iterableChangesAsString({ - collection: [NumberWrapper.NaN], - previous: [NumberWrapper.NaN] - })); - }); - - it('should detect [NaN] moves', () => { - l = [NumberWrapper.NaN, NumberWrapper.NaN]; - changes.check(l); - - ListWrapper.insert(l, 0, 'foo'); - changes.check(l); - expect(changes.toString()).toEqual(iterableChangesAsString({ - collection: ['foo[null->0]', 'NaN[0->1]', 'NaN[1->2]'], - previous: ['NaN[0->1]', 'NaN[1->2]'], - additions: ['foo[null->0]'], - moves: ['NaN[0->1]', 'NaN[1->2]']} - )); - }); - - it('should remove and add same item', () => { - l = ['a', 'b', 'c']; - changes.check(l); - - ListWrapper.removeAt(l, 1); - changes.check(l); - expect(changes.toString()).toEqual(iterableChangesAsString({ - collection: ['a', 'c[2->1]'], - previous: ['a', 'b[1->null]', 'c[2->1]'], - moves: ['c[2->1]'], - removals: ['b[1->null]'] - })); - - ListWrapper.insert(l, 1, 'b'); - changes.check(l); - expect(changes.toString()).toEqual(iterableChangesAsString({ - collection: ['a', 'b[null->1]', 'c[1->2]'], - previous: ['a', 'c[1->2]'], - additions: ['b[null->1]'], - moves: ['c[1->2]'] - })); - }); - - it('should support duplicates', () => { - l = ['a', 'a', 'a', 'b', 'b']; - changes.check(l); - - ListWrapper.removeAt(l, 0); - changes.check(l); - expect(changes.toString()).toEqual(iterableChangesAsString({ - collection: ['a', 'a', 'b[3->2]', 'b[4->3]'], - previous: ['a', 'a', 'a[2->null]', 'b[3->2]', 'b[4->3]'], - moves: ['b[3->2]', 'b[4->3]'], - removals: ['a[2->null]'] - })); - }); - - it('should support insertions/moves', () => { - l = ['a', 'a', 'b', 'b']; - changes.check(l); - - ListWrapper.insert(l, 0, 'b'); - changes.check(l); - expect(changes.toString()).toEqual(iterableChangesAsString({ - collection: ['b[2->0]', 'a[0->1]', 'a[1->2]', 'b', 'b[null->4]'], - previous: ['a[0->1]', 'a[1->2]', 'b[2->0]', 'b'], - additions: ['b[null->4]'], - moves: ['b[2->0]', 'a[0->1]', 'a[1->2]'] - })); - }); - - it('should not report unnecessary moves', () => { - l = ['a', 'b', 'c']; - changes.check(l); - - ListWrapper.clear(l); - ListWrapper.push(l, 'b'); - ListWrapper.push(l, 'a'); - ListWrapper.push(l, 'c'); - changes.check(l); - expect(changes.toString()).toEqual(iterableChangesAsString({ - collection: ['b[1->0]', 'a[0->1]', 'c'], - previous: ['a[0->1]', 'b[1->0]', 'c'], - moves: ['b[1->0]', 'a[0->1]'] - })); - }); - }); - }); -} - diff --git a/modules/angular2/test/change_detection/pipes/iterable_changes_spec.ts b/modules/angular2/test/change_detection/pipes/iterable_changes_spec.ts new file mode 100644 index 0000000000..a374106ace --- /dev/null +++ b/modules/angular2/test/change_detection/pipes/iterable_changes_spec.ts @@ -0,0 +1,295 @@ +import {describe, it, iit, xit, expect, beforeEach, afterEach} from 'angular2/test_lib'; +import {IterableChanges} from 'angular2/src/change_detection/pipes/iterable_changes'; + +import {NumberWrapper} from 'angular2/src/facade/lang'; +import {ListWrapper, MapWrapper} from 'angular2/src/facade/collection'; + +import {TestIterable} from '../iterable'; +import {iterableChangesAsString} from '../util'; + +// todo(vicb): UnmodifiableListView / frozen object when implemented +export function main() { + describe('collection_changes', function() { + describe('CollectionChanges', function() { + var changes; + var l; + + beforeEach(() => { changes = new IterableChanges(); }); + + afterEach(() => { changes = null; }); + + it('should support list and iterables', () => { + expect(IterableChanges.supportsObj([])).toBeTruthy(); + expect(IterableChanges.supportsObj(new TestIterable())).toBeTruthy(); + expect(IterableChanges.supportsObj(MapWrapper.create())).toBeFalsy(); + expect(IterableChanges.supportsObj(null)).toBeFalsy(); + }); + + it('should support iterables', () => { + l = new TestIterable(); + + changes.check(l); + expect(changes.toString()).toEqual(iterableChangesAsString({collection: []})); + + l.list = [1]; + changes.check(l); + expect(changes.toString()) + .toEqual( + iterableChangesAsString({collection: ['1[null->0]'], additions: ['1[null->0]']})); + + l.list = [2, 1]; + changes.check(l); + expect(changes.toString()) + .toEqual(iterableChangesAsString({ + collection: ['2[null->0]', '1[0->1]'], + previous: ['1[0->1]'], + additions: ['2[null->0]'], + moves: ['1[0->1]'] + })); + }); + + it('should detect additions', () => { + l = []; + changes.check(l); + expect(changes.toString()).toEqual(iterableChangesAsString({collection: []})); + + ListWrapper.push(l, 'a'); + changes.check(l); + expect(changes.toString()) + .toEqual( + iterableChangesAsString({collection: ['a[null->0]'], additions: ['a[null->0]']})); + + ListWrapper.push(l, 'b'); + changes.check(l); + expect(changes.toString()) + .toEqual(iterableChangesAsString( + {collection: ['a', 'b[null->1]'], previous: ['a'], additions: ['b[null->1]']})); + }); + + it('should support changing the reference', () => { + l = [0]; + changes.check(l); + + l = [1, 0]; + changes.check(l); + expect(changes.toString()) + .toEqual(iterableChangesAsString({ + collection: ['1[null->0]', '0[0->1]'], + previous: ['0[0->1]'], + additions: ['1[null->0]'], + moves: ['0[0->1]'] + })); + + l = [2, 1, 0]; + changes.check(l); + expect(changes.toString()) + .toEqual(iterableChangesAsString({ + collection: ['2[null->0]', '1[0->1]', '0[1->2]'], + previous: ['1[0->1]', '0[1->2]'], + additions: ['2[null->0]'], + moves: ['1[0->1]', '0[1->2]'] + })); + }); + + it('should handle swapping element', () => { + l = [1, 2]; + changes.check(l); + + ListWrapper.clear(l); + ListWrapper.push(l, 2); + ListWrapper.push(l, 1); + changes.check(l); + expect(changes.toString()) + .toEqual(iterableChangesAsString({ + collection: ['2[1->0]', '1[0->1]'], + previous: ['1[0->1]', '2[1->0]'], + moves: ['2[1->0]', '1[0->1]'] + })); + }); + + it('should handle swapping element', () => { + l = ['a', 'b', 'c']; + changes.check(l); + + ListWrapper.removeAt(l, 1); + ListWrapper.insert(l, 0, 'b'); + changes.check(l); + expect(changes.toString()) + .toEqual(iterableChangesAsString({ + collection: ['b[1->0]', 'a[0->1]', 'c'], + previous: ['a[0->1]', 'b[1->0]', 'c'], + moves: ['b[1->0]', 'a[0->1]'] + })); + + ListWrapper.removeAt(l, 1); + ListWrapper.push(l, 'a'); + changes.check(l); + expect(changes.toString()) + .toEqual(iterableChangesAsString({ + collection: ['b', 'c[2->1]', 'a[1->2]'], + previous: ['b', 'a[1->2]', 'c[2->1]'], + moves: ['c[2->1]', 'a[1->2]'] + })); + }); + + it('should detect changes in list', () => { + l = []; + changes.check(l); + + ListWrapper.push(l, 'a'); + changes.check(l); + expect(changes.toString()) + .toEqual( + iterableChangesAsString({collection: ['a[null->0]'], additions: ['a[null->0]']})); + + ListWrapper.push(l, 'b'); + changes.check(l); + expect(changes.toString()) + .toEqual(iterableChangesAsString( + {collection: ['a', 'b[null->1]'], previous: ['a'], additions: ['b[null->1]']})); + + ListWrapper.push(l, 'c'); + ListWrapper.push(l, 'd'); + changes.check(l); + expect(changes.toString()) + .toEqual(iterableChangesAsString({ + collection: ['a', 'b', 'c[null->2]', 'd[null->3]'], + previous: ['a', 'b'], + additions: ['c[null->2]', 'd[null->3]'] + })); + + ListWrapper.removeAt(l, 2); + changes.check(l); + expect(changes.toString()) + .toEqual(iterableChangesAsString({ + collection: ['a', 'b', 'd[3->2]'], + previous: ['a', 'b', 'c[2->null]', 'd[3->2]'], + moves: ['d[3->2]'], + removals: ['c[2->null]'] + })); + + ListWrapper.clear(l); + ListWrapper.push(l, 'd'); + ListWrapper.push(l, 'c'); + ListWrapper.push(l, 'b'); + ListWrapper.push(l, 'a'); + changes.check(l); + expect(changes.toString()) + .toEqual(iterableChangesAsString({ + collection: ['d[2->0]', 'c[null->1]', 'b[1->2]', 'a[0->3]'], + previous: ['a[0->3]', 'b[1->2]', 'd[2->0]'], + additions: ['c[null->1]'], + moves: ['d[2->0]', 'b[1->2]', 'a[0->3]'] + })); + }); + + it('should test string by value rather than by reference (Dart)', () => { + l = ['a', 'boo']; + changes.check(l); + + var b = 'b'; + var oo = 'oo'; + ListWrapper.set(l, 1, b + oo); + changes.check(l); + expect(changes.toString()) + .toEqual(iterableChangesAsString({collection: ['a', 'boo'], previous: ['a', 'boo']})); + }); + + it('should ignore [NaN] != [NaN] (JS)', () => { + l = [NumberWrapper.NaN]; + changes.check(l); + changes.check(l); + expect(changes.toString()) + .toEqual(iterableChangesAsString( + {collection: [NumberWrapper.NaN], previous: [NumberWrapper.NaN]})); + }); + + it('should detect [NaN] moves', () => { + l = [NumberWrapper.NaN, NumberWrapper.NaN]; + changes.check(l); + + ListWrapper.insert(l, 0, 'foo'); + changes.check(l); + expect(changes.toString()) + .toEqual(iterableChangesAsString({ + collection: ['foo[null->0]', 'NaN[0->1]', 'NaN[1->2]'], + previous: ['NaN[0->1]', 'NaN[1->2]'], + additions: ['foo[null->0]'], + moves: ['NaN[0->1]', 'NaN[1->2]'] + })); + }); + + it('should remove and add same item', () => { + l = ['a', 'b', 'c']; + changes.check(l); + + ListWrapper.removeAt(l, 1); + changes.check(l); + expect(changes.toString()) + .toEqual(iterableChangesAsString({ + collection: ['a', 'c[2->1]'], + previous: ['a', 'b[1->null]', 'c[2->1]'], + moves: ['c[2->1]'], + removals: ['b[1->null]'] + })); + + ListWrapper.insert(l, 1, 'b'); + changes.check(l); + expect(changes.toString()) + .toEqual(iterableChangesAsString({ + collection: ['a', 'b[null->1]', 'c[1->2]'], + previous: ['a', 'c[1->2]'], + additions: ['b[null->1]'], + moves: ['c[1->2]'] + })); + }); + + it('should support duplicates', () => { + l = ['a', 'a', 'a', 'b', 'b']; + changes.check(l); + + ListWrapper.removeAt(l, 0); + changes.check(l); + expect(changes.toString()) + .toEqual(iterableChangesAsString({ + collection: ['a', 'a', 'b[3->2]', 'b[4->3]'], + previous: ['a', 'a', 'a[2->null]', 'b[3->2]', 'b[4->3]'], + moves: ['b[3->2]', 'b[4->3]'], + removals: ['a[2->null]'] + })); + }); + + it('should support insertions/moves', () => { + l = ['a', 'a', 'b', 'b']; + changes.check(l); + + ListWrapper.insert(l, 0, 'b'); + changes.check(l); + expect(changes.toString()) + .toEqual(iterableChangesAsString({ + collection: ['b[2->0]', 'a[0->1]', 'a[1->2]', 'b', 'b[null->4]'], + previous: ['a[0->1]', 'a[1->2]', 'b[2->0]', 'b'], + additions: ['b[null->4]'], + moves: ['b[2->0]', 'a[0->1]', 'a[1->2]'] + })); + }); + + it('should not report unnecessary moves', () => { + l = ['a', 'b', 'c']; + changes.check(l); + + ListWrapper.clear(l); + ListWrapper.push(l, 'b'); + ListWrapper.push(l, 'a'); + ListWrapper.push(l, 'c'); + changes.check(l); + expect(changes.toString()) + .toEqual(iterableChangesAsString({ + collection: ['b[1->0]', 'a[0->1]', 'c'], + previous: ['a[0->1]', 'b[1->0]', 'c'], + moves: ['b[1->0]', 'a[0->1]'] + })); + }); + }); + }); +} diff --git a/modules/angular2/test/change_detection/pipes/json_pipe_spec.js b/modules/angular2/test/change_detection/pipes/json_pipe_spec.js deleted file mode 100644 index c46f252edd..0000000000 --- a/modules/angular2/test/change_detection/pipes/json_pipe_spec.js +++ /dev/null @@ -1,97 +0,0 @@ -import {ddescribe, describe, it, iit, xit, expect, beforeEach, afterEach, - AsyncTestCompleter, inject, proxy, SpyObject, IS_DARTIUM} from 'angular2/test_lib'; -import {Json, RegExp, NumberWrapper, StringWrapper} from 'angular2/src/facade/lang'; - -import {JsonPipe} from 'angular2/src/change_detection/pipes/json_pipe'; - -export function main() { - describe("JsonPipe", () => { - var regNewLine = new RegExp('\n'); - var canHasUndefined; // because Dart doesn't like undefined; - var inceptionObj; - var inceptionObjString; - var catString; - var pipe; - - function normalize(obj: string): string { - return StringWrapper.replace(obj, regNewLine, ''); - } - - beforeEach(() => { - inceptionObj = { - dream: { - dream: { - dream: 'Limbo' - } - } - }; - inceptionObjString = "{\n" + - " \"dream\": {\n" + - " \"dream\": {\n" + - " \"dream\": \"Limbo\"\n" + - " }\n" + - " }\n" + - "}"; - - - catString = 'Inception Cat'; - pipe = new JsonPipe(); - }); - - describe("supports", () => { - it("should support objects", () => { - expect(pipe.supports(inceptionObj)).toBe(true); - }); - - it("should support strings", () => { - expect(pipe.supports(catString)).toBe(true); - }); - - it("should support null", () => { - expect(pipe.supports(null)).toBe(true); - }); - - it("should support NaN", () => { - expect(pipe.supports(NumberWrapper.NaN)).toBe(true); - }); - - if (!IS_DARTIUM) { - it("should support undefined", () => { - expect(pipe.supports(canHasUndefined)).toBe(true); - }); - } - - }); - - describe("transform", () => { - it("should return JSON-formatted string", () => { - expect(pipe.transform(inceptionObj)).toEqual(inceptionObjString); - }); - - it("should return JSON-formatted string even when normalized", () => { - var dream1 = normalize(pipe.transform(inceptionObj)); - var dream2 = normalize(inceptionObjString); - expect(dream1).toEqual(dream2); - }); - - it("should return JSON-formatted string similar to Json.stringify", () => { - var dream1 = normalize(pipe.transform(inceptionObj)); - var dream2 = normalize(Json.stringify(inceptionObj)); - expect(dream1).toEqual(dream2); - }); - - it("should return same value when nothing has changed since the last call", () => { - expect(pipe.transform(inceptionObj)).toEqual(inceptionObjString); - expect(pipe.transform(inceptionObj)).toEqual(inceptionObjString); - }); - - }); - - describe("onDestroy", () => { - it("should do nothing when no latest value", () => { - expect(() => pipe.onDestroy()).not.toThrow(); - }); - }); - - }); -} diff --git a/modules/angular2/test/change_detection/pipes/json_pipe_spec.ts b/modules/angular2/test/change_detection/pipes/json_pipe_spec.ts new file mode 100644 index 0000000000..a2c5d6d298 --- /dev/null +++ b/modules/angular2/test/change_detection/pipes/json_pipe_spec.ts @@ -0,0 +1,88 @@ +import { + ddescribe, + describe, + it, + iit, + xit, + expect, + beforeEach, + afterEach, + AsyncTestCompleter, + inject, + proxy, + SpyObject, + IS_DARTIUM +} from 'angular2/test_lib'; +import {Json, RegExp, NumberWrapper, StringWrapper} from 'angular2/src/facade/lang'; + +import {JsonPipe} from 'angular2/src/change_detection/pipes/json_pipe'; + +export function main() { + describe("JsonPipe", () => { + var regNewLine = '\n'; + var canHasUndefined; // because Dart doesn't like undefined; + var inceptionObj; + var inceptionObjString; + var catString; + var pipe; + + function normalize(obj: string): string { return StringWrapper.replace(obj, regNewLine, ''); } + + beforeEach(() => { + inceptionObj = { + dream: {dream: {dream: 'Limbo'}} + }; + inceptionObjString = "{\n" + " \"dream\": {\n" + " \"dream\": {\n" + + " \"dream\": \"Limbo\"\n" + " }\n" + " }\n" + "}"; + + + catString = 'Inception Cat'; + pipe = new JsonPipe(); + }); + + describe("supports", () => { + it("should support objects", () => { expect(pipe.supports(inceptionObj)).toBe(true); }); + + it("should support strings", () => { expect(pipe.supports(catString)).toBe(true); }); + + it("should support null", () => { expect(pipe.supports(null)).toBe(true); }); + + it("should support NaN", () => { expect(pipe.supports(NumberWrapper.NaN)).toBe(true); }); + + if (!IS_DARTIUM) { + it("should support undefined", + () => { expect(pipe.supports(canHasUndefined)).toBe(true); }); + } + + }); + + describe("transform", () => { + it("should return JSON-formatted string", + () => { expect(pipe.transform(inceptionObj)).toEqual(inceptionObjString); }); + + it("should return JSON-formatted string even when normalized", () => { + var dream1 = normalize(pipe.transform(inceptionObj)); + var dream2 = normalize(inceptionObjString); + expect(dream1).toEqual(dream2); + }); + + it("should return JSON-formatted string similar to Json.stringify", () => { + var dream1 = normalize(pipe.transform(inceptionObj)); + var dream2 = normalize(Json.stringify(inceptionObj)); + expect(dream1).toEqual(dream2); + }); + + it("should return same value when nothing has changed since the last call", () => { + expect(pipe.transform(inceptionObj)).toEqual(inceptionObjString); + expect(pipe.transform(inceptionObj)).toEqual(inceptionObjString); + }); + + }); + + describe("onDestroy", () => { + it("should do nothing when no latest value", + () => { expect(() => pipe.onDestroy()).not.toThrow(); }); + }); + + }); +} diff --git a/modules/angular2/test/change_detection/pipes/keyvalue_changes_spec.js b/modules/angular2/test/change_detection/pipes/keyvalue_changes_spec.ts similarity index 56% rename from modules/angular2/test/change_detection/pipes/keyvalue_changes_spec.js rename to modules/angular2/test/change_detection/pipes/keyvalue_changes_spec.ts index 3a7d18fa95..220c965a3c 100644 --- a/modules/angular2/test/change_detection/pipes/keyvalue_changes_spec.js +++ b/modules/angular2/test/change_detection/pipes/keyvalue_changes_spec.ts @@ -16,27 +16,21 @@ export function main() { m = MapWrapper.create(); }); - afterEach(() => { - changes = null; - }); + afterEach(() => { changes = null; }); it('should detect additions', () => { changes.check(m); MapWrapper.set(m, 'a', 1); changes.check(m); - expect(changes.toString()).toEqual(kvChangesAsString({ - map: ['a[null->1]'], - additions: ['a[null->1]'] - })); + expect(changes.toString()) + .toEqual(kvChangesAsString({map: ['a[null->1]'], additions: ['a[null->1]']})); MapWrapper.set(m, 'b', 2); changes.check(m); - expect(changes.toString()).toEqual(kvChangesAsString({ - map: ['a', 'b[null->2]'], - previous: ['a'], - additions: ['b[null->2]'] - })); + expect(changes.toString()) + .toEqual(kvChangesAsString( + {map: ['a', 'b[null->2]'], previous: ['a'], additions: ['b[null->2]']})); }); it('should handle changing key/values correctly', () => { @@ -47,11 +41,12 @@ export function main() { MapWrapper.set(m, 2, 10); MapWrapper.set(m, 1, 20); changes.check(m); - expect(changes.toString()).toEqual(kvChangesAsString({ - map: ['1[10->20]', '2[20->10]'], - previous: ['1[10->20]', '2[20->10]'], - changes: ['1[10->20]', '2[20->10]'] - })); + expect(changes.toString()) + .toEqual(kvChangesAsString({ + map: ['1[10->20]', '2[20->10]'], + previous: ['1[10->20]', '2[20->10]'], + changes: ['1[10->20]', '2[20->10]'] + })); }); it('should expose previous and current value', () => { @@ -63,12 +58,14 @@ export function main() { MapWrapper.set(m, 1, 20); changes.check(m); - changes.forEachChangedItem((record) => { - previous = record.previousValue; - current = record.currentValue; - }) + changes.forEachChangedItem((record) => + { + previous = record.previousValue; + current = record.currentValue; + }) - expect(previous).toEqual(10); + expect(previous) + .toEqual(10); expect(current).toEqual(20); }); @@ -77,43 +74,40 @@ export function main() { MapWrapper.set(m, 'a', 'A'); changes.check(m); - expect(changes.toString()).toEqual(kvChangesAsString({ - map: ['a[null->A]'], - additions: ['a[null->A]'] - })); + expect(changes.toString()) + .toEqual(kvChangesAsString({map: ['a[null->A]'], additions: ['a[null->A]']})); MapWrapper.set(m, 'b', 'B'); changes.check(m); - expect(changes.toString()).toEqual(kvChangesAsString({ - map: ['a', 'b[null->B]'], - previous: ['a'], - additions: ['b[null->B]'] - })); + expect(changes.toString()) + .toEqual(kvChangesAsString( + {map: ['a', 'b[null->B]'], previous: ['a'], additions: ['b[null->B]']})); MapWrapper.set(m, 'b', 'BB'); MapWrapper.set(m, 'd', 'D'); changes.check(m); - expect(changes.toString()).toEqual(kvChangesAsString({ - map: ['a', 'b[B->BB]', 'd[null->D]'], - previous: ['a', 'b[B->BB]'], - additions: ['d[null->D]'], - changes: ['b[B->BB]'] - })); + expect(changes.toString()) + .toEqual(kvChangesAsString({ + map: ['a', 'b[B->BB]', 'd[null->D]'], + previous: ['a', 'b[B->BB]'], + additions: ['d[null->D]'], + changes: ['b[B->BB]'] + })); MapWrapper.delete(m, 'b'); changes.check(m); - expect(changes.toString()).toEqual(kvChangesAsString({ - map: ['a', 'd'], - previous: ['a', 'b[BB->null]', 'd'], - removals: ['b[BB->null]'] - })); + expect(changes.toString()) + .toEqual(kvChangesAsString({ + map: ['a', 'd'], + previous: ['a', 'b[BB->null]', 'd'], + removals: ['b[BB->null]'] + })); MapWrapper.clear(m); changes.check(m); - expect(changes.toString()).toEqual(kvChangesAsString({ - previous: ['a[A->null]', 'd[D->null]'], - removals: ['a[A->null]', 'd[D->null]'] - })); + expect(changes.toString()) + .toEqual(kvChangesAsString( + {previous: ['a[A->null]', 'd[D->null]'], removals: ['a[A->null]', 'd[D->null]']})); }); it('should test string by value rather than by reference (DART)', () => { @@ -128,10 +122,7 @@ export function main() { MapWrapper.set(m, f + oo, b + ar); changes.check(m); - expect(changes.toString()).toEqual(kvChangesAsString({ - map: ['foo'], - previous: ['foo'] - })); + expect(changes.toString()).toEqual(kvChangesAsString({map: ['foo'], previous: ['foo']})); }); it('should not see a NaN value as a change (JS)', () => { @@ -139,10 +130,7 @@ export function main() { changes.check(m); changes.check(m); - expect(changes.toString()).toEqual(kvChangesAsString({ - map: ['foo'], - previous: ['foo'] - })); + expect(changes.toString()).toEqual(kvChangesAsString({map: ['foo'], previous: ['foo']})); }); // JS specific tests (JS Objects) @@ -161,45 +149,44 @@ export function main() { m['a'] = 'A'; changes.check(m); - expect(changes.toString()).toEqual(kvChangesAsString({ - map: ['a[null->A]'], - additions: ['a[null->A]'] - })); + expect(changes.toString()) + .toEqual(kvChangesAsString({map: ['a[null->A]'], additions: ['a[null->A]']})); m['b'] = 'B'; changes.check(m); - expect(changes.toString()).toEqual(kvChangesAsString({ - map: ['a', 'b[null->B]'], - previous: ['a'], - additions: ['b[null->B]'] - })); + expect(changes.toString()) + .toEqual(kvChangesAsString( + {map: ['a', 'b[null->B]'], previous: ['a'], additions: ['b[null->B]']})); m['b'] = 'BB'; m['d'] = 'D'; changes.check(m); - expect(changes.toString()).toEqual(kvChangesAsString({ - map: ['a', 'b[B->BB]', 'd[null->D]'], - previous: ['a', 'b[B->BB]'], - additions: ['d[null->D]'], - changes: ['b[B->BB]'] - })); + expect(changes.toString()) + .toEqual(kvChangesAsString({ + map: ['a', 'b[B->BB]', 'd[null->D]'], + previous: ['a', 'b[B->BB]'], + additions: ['d[null->D]'], + changes: ['b[B->BB]'] + })); m = {}; m['a'] = 'A'; m['d'] = 'D'; changes.check(m); - expect(changes.toString()).toEqual(kvChangesAsString({ - map: ['a', 'd'], - previous: ['a', 'b[BB->null]', 'd'], - removals: ['b[BB->null]'] - })); + expect(changes.toString()) + .toEqual(kvChangesAsString({ + map: ['a', 'd'], + previous: ['a', 'b[BB->null]', 'd'], + removals: ['b[BB->null]'] + })); m = {}; changes.check(m); - expect(changes.toString()).toEqual(kvChangesAsString({ - previous: ['a[A->null]', 'd[D->null]'], - removals: ['a[A->null]', 'd[D->null]'] - })); + expect(changes.toString()) + .toEqual(kvChangesAsString({ + previous: ['a[A->null]', 'd[D->null]'], + removals: ['a[A->null]', 'd[D->null]'] + })); }); }); } diff --git a/modules/angular2/test/change_detection/pipes/lowercase_pipe_spec.js b/modules/angular2/test/change_detection/pipes/lowercase_pipe_spec.ts similarity index 91% rename from modules/angular2/test/change_detection/pipes/lowercase_pipe_spec.js rename to modules/angular2/test/change_detection/pipes/lowercase_pipe_spec.ts index 10f76574d4..52637fa383 100644 --- a/modules/angular2/test/change_detection/pipes/lowercase_pipe_spec.js +++ b/modules/angular2/test/change_detection/pipes/lowercase_pipe_spec.ts @@ -17,9 +17,7 @@ export function main() { }); describe("supports", () => { - it("should support strings", () => { - expect(pipe.supports(str)).toBe(true); - }); + it("should support strings", () => { expect(pipe.supports(str)).toBe(true); }); it("should not support other objects", () => { expect(pipe.supports(new Object())).toBe(false); diff --git a/modules/angular2/test/change_detection/pipes/observable_pipe_spec.js b/modules/angular2/test/change_detection/pipes/observable_pipe_spec.js deleted file mode 100644 index 63433074f7..0000000000 --- a/modules/angular2/test/change_detection/pipes/observable_pipe_spec.js +++ /dev/null @@ -1,115 +0,0 @@ -import {ddescribe, describe, it, iit, xit, expect, beforeEach, afterEach, - AsyncTestCompleter, inject, proxy, SpyObject} from 'angular2/test_lib'; -import {IMPLEMENTS} from 'angular2/src/facade/lang'; - -import {WrappedValue} from 'angular2/src/change_detection/pipes/pipe'; -import {ObservablePipe} from 'angular2/src/change_detection/pipes/observable_pipe'; -import {ChangeDetectorRef} from 'angular2/src/change_detection/change_detector_ref'; -import {EventEmitter, Observable, ObservableWrapper, TimerWrapper} from 'angular2/src/facade/async'; - -export function main() { - describe("ObservablePipe", () => { - var emitter; - var pipe; - var ref; - var message = new Object(); - - beforeEach(() => { - emitter = new EventEmitter(); - ref = new SpyChangeDetectorRef(); - pipe = new ObservablePipe(ref); - }); - - describe("supports", () => { - it("should support observables", () => { - expect(pipe.supports(emitter)).toBe(true); - }); - - it("should not support other objects", () => { - expect(pipe.supports("string")).toBe(false); - expect(pipe.supports(null)).toBe(false); - }); - }); - - describe("transform", () => { - it("should return null when subscribing to an observable", () => { - expect(pipe.transform(emitter)).toBe(null); - }); - - it("should return the latest available value wrapped", inject([AsyncTestCompleter], (async) => { - pipe.transform(emitter); - - ObservableWrapper.callNext(emitter, message); - - TimerWrapper.setTimeout(() => { - expect(pipe.transform(emitter)).toEqual(new WrappedValue(message)); - async.done(); - }, 0) - })); - - it("should return same value when nothing has changed since the last call", - inject([AsyncTestCompleter], (async) => { - pipe.transform(emitter); - ObservableWrapper.callNext(emitter, message); - - TimerWrapper.setTimeout(() => { - pipe.transform(emitter); - expect(pipe.transform(emitter)).toBe(message); - async.done(); - }, 0) - })); - - it("should dispose of the existing subscription when subscribing to a new observable", - inject([AsyncTestCompleter], (async) => { - pipe.transform(emitter); - - var newEmitter = new EventEmitter(); - expect(pipe.transform(newEmitter)).toBe(null); - - // this should not affect the pipe - ObservableWrapper.callNext(emitter, message); - - TimerWrapper.setTimeout(() => { - expect(pipe.transform(newEmitter)).toBe(null); - async.done(); - }, 0) - })); - - it("should request a change detection check upon receiving a new value", - inject([AsyncTestCompleter], (async) => { - pipe.transform(emitter); - ObservableWrapper.callNext(emitter, message); - - TimerWrapper.setTimeout(() => { - expect(ref.spy('requestCheck')).toHaveBeenCalled(); - async.done(); - }, 0) - })); - }); - - describe("onDestroy", () => { - it("should do nothing when no subscription", () => { - expect(() => pipe.onDestroy()).not.toThrow(); - }); - - it("should dispose of the existing subscription", inject([AsyncTestCompleter], (async) => { - pipe.transform(emitter); - pipe.onDestroy(); - - ObservableWrapper.callNext(emitter, message); - - TimerWrapper.setTimeout(() => { - expect(pipe.transform(emitter)).toBe(null); - async.done(); - }, 0) - })); - }); - }); -} - -@proxy -@IMPLEMENTS(ChangeDetectorRef) -class SpyChangeDetectorRef extends SpyObject { - constructor(){super(ChangeDetectorRef);} - noSuchMethod(m){return super.noSuchMethod(m)} -} diff --git a/modules/angular2/test/change_detection/pipes/observable_pipe_spec.ts b/modules/angular2/test/change_detection/pipes/observable_pipe_spec.ts new file mode 100644 index 0000000000..cc32b55930 --- /dev/null +++ b/modules/angular2/test/change_detection/pipes/observable_pipe_spec.ts @@ -0,0 +1,125 @@ +import { + ddescribe, + describe, + it, + iit, + xit, + expect, + beforeEach, + afterEach, + AsyncTestCompleter, + inject, + proxy, + SpyObject +} from 'angular2/test_lib'; + +import {IMPLEMENTS} from 'angular2/src/facade/lang'; +import {WrappedValue} from 'angular2/src/change_detection/pipes/pipe'; +import {ObservablePipe} from 'angular2/src/change_detection/pipes/observable_pipe'; +import {ChangeDetectorRef} from 'angular2/src/change_detection/change_detector_ref'; +import {EventEmitter, ObservableWrapper, TimerWrapper} from 'angular2/src/facade/async'; + +export function main() { + describe("ObservablePipe", () => { + var emitter; + var pipe; + var ref; + var message = new Object(); + + beforeEach(() => { + emitter = new EventEmitter(); + ref = new SpyChangeDetectorRef(); + pipe = new ObservablePipe(ref); + }); + + describe("supports", () => { + it("should support observables", () => { expect(pipe.supports(emitter)).toBe(true); }); + + it("should not support other objects", () => { + expect(pipe.supports("string")).toBe(false); + expect(pipe.supports(null)).toBe(false); + }); + }); + + describe("transform", () => { + it("should return null when subscribing to an observable", + () => { expect(pipe.transform(emitter)).toBe(null); }); + + it("should return the latest available value wrapped", + inject([AsyncTestCompleter], (async) => { + pipe.transform(emitter); + + ObservableWrapper.callNext(emitter, message); + + TimerWrapper.setTimeout(() => { + expect(pipe.transform(emitter)).toEqual(new WrappedValue(message)); + async.done(); + }, 0) + })); + + + it("should return same value when nothing has changed since the last call", + inject([AsyncTestCompleter], (async) => { + pipe.transform(emitter); + ObservableWrapper.callNext(emitter, message); + + TimerWrapper.setTimeout(() => { + pipe.transform(emitter); + expect(pipe.transform(emitter)).toBe(message); + async.done(); + }, 0) + })); + + it("should dispose of the existing subscription when subscribing to a new observable", + inject([AsyncTestCompleter], (async) => { + pipe.transform(emitter); + + var newEmitter = new EventEmitter(); + expect(pipe.transform(newEmitter)).toBe(null); + + // this should not affect the pipe + ObservableWrapper.callNext(emitter, message); + + TimerWrapper.setTimeout(() => { + expect(pipe.transform(newEmitter)).toBe(null); + async.done(); + }, 0) + })); + + it("should request a change detection check upon receiving a new value", + inject([AsyncTestCompleter], (async) => { + pipe.transform(emitter); + ObservableWrapper.callNext(emitter, message); + + TimerWrapper.setTimeout(() => { + expect(ref.spy('requestCheck')).toHaveBeenCalled(); + async.done(); + }, 0) + })); + }); + + describe("onDestroy", () => { + it("should do nothing when no subscription", + () => { expect(() => pipe.onDestroy()).not.toThrow(); }); + + it("should dispose of the existing subscription", inject([AsyncTestCompleter], (async) => { + pipe.transform(emitter); + pipe.onDestroy(); + + ObservableWrapper.callNext(emitter, message); + + TimerWrapper.setTimeout(() => { + expect(pipe.transform(emitter)).toBe(null); + async.done(); + }, 0) + })); + }); + }); +} + +@proxy +@IMPLEMENTS(ChangeDetectorRef) +class SpyChangeDetectorRef extends SpyObject { + constructor() { super(ChangeDetectorRef); } + noSuchMethod(m) { return super.noSuchMethod(m) } +} diff --git a/modules/angular2/test/change_detection/pipes/pipe_registry_spec.js b/modules/angular2/test/change_detection/pipes/pipe_registry_spec.ts similarity index 52% rename from modules/angular2/test/change_detection/pipes/pipe_registry_spec.js rename to modules/angular2/test/change_detection/pipes/pipe_registry_spec.ts index 5afa496f5c..fe5a936dae 100644 --- a/modules/angular2/test/change_detection/pipes/pipe_registry_spec.js +++ b/modules/angular2/test/change_detection/pipes/pipe_registry_spec.ts @@ -9,49 +9,37 @@ export function main() { var secondPipe = new Pipe(); it("should return the first pipe supporting the data type", () => { - var r = new PipeRegistry({ - "type": [ - new PipeFactory(false, firstPipe), - new PipeFactory(true, secondPipe) - ] - }); + var r = new PipeRegistry( + {"type": [new PipeFactory(false, firstPipe), new PipeFactory(true, secondPipe)]}); expect(r.get("type", "some object", null)).toBe(secondPipe); }); it("should throw when no matching type", () => { var r = new PipeRegistry({}); - expect(() => r.get("unknown", "some object", null)).toThrowError( - `Cannot find 'unknown' pipe supporting object 'some object'` - ); + expect(() => r.get("unknown", "some object", null)) + .toThrowError(`Cannot find 'unknown' pipe supporting object 'some object'`); }); it("should throw when no matching pipe", () => { - var r = new PipeRegistry({ - "type" : [] - }); + var r = new PipeRegistry({"type": []}); - expect(() => r.get("type", "some object", null)).toThrowError( - `Cannot find 'type' pipe supporting object 'some object'` - ); + expect(() => r.get("type", "some object", null)) + .toThrowError(`Cannot find 'type' pipe supporting object 'some object'`); }); }); } class PipeFactory { - shouldSupport:boolean; - pipe:any; + shouldSupport: boolean; + pipe: any; - constructor(shouldSupport:boolean, pipe:any) { + constructor(shouldSupport: boolean, pipe: any) { this.shouldSupport = shouldSupport; this.pipe = pipe; } - supports(obj):boolean { - return this.shouldSupport; - } + supports(obj): boolean { return this.shouldSupport; } - create(cdRef):Pipe { - return this.pipe; - } + create(cdRef): Pipe { return this.pipe; } } diff --git a/modules/angular2/test/change_detection/pipes/promise_pipe_spec.js b/modules/angular2/test/change_detection/pipes/promise_pipe_spec.js deleted file mode 100644 index 36cb6d9a36..0000000000 --- a/modules/angular2/test/change_detection/pipes/promise_pipe_spec.js +++ /dev/null @@ -1,119 +0,0 @@ -import {ddescribe, describe, it, iit, xit, expect, beforeEach, afterEach, - AsyncTestCompleter, inject, proxy, SpyObject} from 'angular2/test_lib'; -import {IMPLEMENTS} from 'angular2/src/facade/lang'; -import {PromisePipe} from 'angular2/src/change_detection/pipes/promise_pipe'; -import {WrappedValue} from 'angular2/src/change_detection/pipes/pipe'; -import {ChangeDetectorRef} from 'angular2/src/change_detection/change_detector_ref'; -import {PromiseWrapper, TimerWrapper} from 'angular2/src/facade/async'; -import {DOM} from 'angular2/src/dom/dom_adapter'; - -export function main() { - describe("PromisePipe", () => { - var message = new Object(); - var pipe; - var completer; - var ref; - //adds longer timers for passing tests in IE - var timer = (DOM.getUserAgent().indexOf("Trident") > -1) ? 50 : 0; - - beforeEach(() => { - completer = PromiseWrapper.completer(); - ref = new SpyChangeDetectorRef(); - pipe = new PromisePipe(ref); - }); - - describe("supports", () => { - it("should support promises", () => { - expect(pipe.supports(completer.promise)).toBe(true); - }); - - it("should not support other objects", () => { - expect(pipe.supports("string")).toBe(false); - expect(pipe.supports(null)).toBe(false); - }); - }); - - describe("transform", () => { - it("should return null when subscribing to a promise", () => { - expect(pipe.transform(completer.promise)).toBe(null); - }); - - it("should return the latest available value", inject([AsyncTestCompleter], (async) => { - pipe.transform(completer.promise); - - completer.resolve(message); - - TimerWrapper.setTimeout(() => { - expect(pipe.transform(completer.promise)).toEqual(new WrappedValue(message)); - async.done(); - }, timer) - })); - - it("should return unwrapped value when nothing has changed since the last call", - inject([AsyncTestCompleter], (async) => { - pipe.transform(completer.promise); - completer.resolve(message); - - TimerWrapper.setTimeout(() => { - pipe.transform(completer.promise); - expect(pipe.transform(completer.promise)).toBe(message); - async.done(); - }, timer) - })); - - it("should dispose of the existing subscription when subscribing to a new promise", - inject([AsyncTestCompleter], (async) => { - pipe.transform(completer.promise); - - var newCompleter = PromiseWrapper.completer(); - expect(pipe.transform(newCompleter.promise)).toBe(null); - - // this should not affect the pipe, so it should return WrappedValue - completer.resolve(message); - - TimerWrapper.setTimeout(() => { - expect(pipe.transform(newCompleter.promise)).toBe(null); - async.done(); - }, timer) - })); - - it("should request a change detection check upon receiving a new value", - inject([AsyncTestCompleter], (async) => { - pipe.transform(completer.promise); - completer.resolve(message); - - TimerWrapper.setTimeout(() => { - expect(ref.spy('requestCheck')).toHaveBeenCalled(); - async.done(); - }, timer) - })); - - describe("onDestroy", () => { - it("should do nothing when no source", () => { - expect(() => pipe.onDestroy()).not.toThrow(); - }); - - it("should dispose of the existing source", inject([AsyncTestCompleter], (async) => { - pipe.transform(completer.promise); - expect(pipe.transform(completer.promise)).toBe(null); - completer.resolve(message) - - - TimerWrapper.setTimeout(() => { - expect(pipe.transform(completer.promise)).toEqual(new WrappedValue(message)); - pipe.onDestroy(); - expect(pipe.transform(completer.promise)).toBe(null); - async.done(); - }, timer); - })); - }); - }); - }); -} - -@proxy -@IMPLEMENTS(ChangeDetectorRef) -class SpyChangeDetectorRef extends SpyObject { - constructor(){super(ChangeDetectorRef);} - noSuchMethod(m){return super.noSuchMethod(m)} -} diff --git a/modules/angular2/test/change_detection/pipes/promise_pipe_spec.ts b/modules/angular2/test/change_detection/pipes/promise_pipe_spec.ts new file mode 100644 index 0000000000..db9b00c8a2 --- /dev/null +++ b/modules/angular2/test/change_detection/pipes/promise_pipe_spec.ts @@ -0,0 +1,127 @@ +import { + ddescribe, + describe, + it, + iit, + xit, + expect, + beforeEach, + afterEach, + AsyncTestCompleter, + inject, + proxy, + SpyObject +} from 'angular2/test_lib'; +import {IMPLEMENTS, isBlank} from 'angular2/src/facade/lang'; +import {PromisePipe} from 'angular2/src/change_detection/pipes/promise_pipe'; +import {WrappedValue} from 'angular2/src/change_detection/pipes/pipe'; +import {ChangeDetectorRef} from 'angular2/src/change_detection/change_detector_ref'; +import {PromiseWrapper, TimerWrapper} from 'angular2/src/facade/async'; +import {DOM} from 'angular2/src/dom/dom_adapter'; + +export function main() { + describe("PromisePipe", () => { + var message = new Object(); + var pipe; + var completer; + var ref; + // adds longer timers for passing tests in IE + var timer = (!isBlank(DOM) && DOM.getUserAgent().indexOf("Trident") > -1) ? 50 : 0; + + beforeEach(() => { + completer = PromiseWrapper.completer(); + ref = new SpyChangeDetectorRef(); + pipe = new PromisePipe(ref); + }); + + describe("supports", () => { + it("should support promises", () => { expect(pipe.supports(completer.promise)).toBe(true); }); + + it("should not support other objects", () => { + expect(pipe.supports("string")).toBe(false); + expect(pipe.supports(null)).toBe(false); + }); + }); + + describe("transform", () => { + it("should return null when subscribing to a promise", + () => { expect(pipe.transform(completer.promise)).toBe(null); }); + + it("should return the latest available value", inject([AsyncTestCompleter], (async) => { + pipe.transform(completer.promise); + + completer.resolve(message); + + TimerWrapper.setTimeout(() => { + expect(pipe.transform(completer.promise)).toEqual(new WrappedValue(message)); + async.done(); + }, timer) + })); + + it("should return unwrapped value when nothing has changed since the last call", + inject([AsyncTestCompleter], (async) => { + pipe.transform(completer.promise); + completer.resolve(message); + + TimerWrapper.setTimeout(() => { + pipe.transform(completer.promise); + expect(pipe.transform(completer.promise)).toBe(message); + async.done(); + }, timer) + })); + + it("should dispose of the existing subscription when subscribing to a new promise", + inject([AsyncTestCompleter], (async) => { + pipe.transform(completer.promise); + + var newCompleter = PromiseWrapper.completer(); + expect(pipe.transform(newCompleter.promise)).toBe(null); + + // this should not affect the pipe, so it should return WrappedValue + completer.resolve(message); + + TimerWrapper.setTimeout(() => { + expect(pipe.transform(newCompleter.promise)).toBe(null); + async.done(); + }, timer) + })); + + it("should request a change detection check upon receiving a new value", + inject([AsyncTestCompleter], (async) => { + pipe.transform(completer.promise); + completer.resolve(message); + + TimerWrapper.setTimeout(() => { + expect(ref.spy('requestCheck')).toHaveBeenCalled(); + async.done(); + }, timer) + })); + + describe("onDestroy", () => { + it("should do nothing when no source", + () => { expect(() => pipe.onDestroy()).not.toThrow(); }); + + it("should dispose of the existing source", inject([AsyncTestCompleter], (async) => { + pipe.transform(completer.promise); + expect(pipe.transform(completer.promise)).toBe(null); + completer.resolve(message) + + + TimerWrapper.setTimeout(() => { + expect(pipe.transform(completer.promise)).toEqual(new WrappedValue(message)); + pipe.onDestroy(); + expect(pipe.transform(completer.promise)).toBe(null); + async.done(); + }, timer); + })); + }); + }); + }); +} + +@proxy +@IMPLEMENTS(ChangeDetectorRef) +class SpyChangeDetectorRef extends SpyObject { + constructor() { super(ChangeDetectorRef); } + noSuchMethod(m) { return super.noSuchMethod(m) } +} diff --git a/modules/angular2/test/change_detection/pipes/uppercase_pipe_spec.js b/modules/angular2/test/change_detection/pipes/uppercase_pipe_spec.ts similarity index 91% rename from modules/angular2/test/change_detection/pipes/uppercase_pipe_spec.js rename to modules/angular2/test/change_detection/pipes/uppercase_pipe_spec.ts index 535baf054d..813e39eb26 100644 --- a/modules/angular2/test/change_detection/pipes/uppercase_pipe_spec.js +++ b/modules/angular2/test/change_detection/pipes/uppercase_pipe_spec.ts @@ -17,9 +17,7 @@ export function main() { }); describe("supports", () => { - it("should support strings", () => { - expect(pipe.supports(str)).toBe(true); - }); + it("should support strings", () => { expect(pipe.supports(str)).toBe(true); }); it("should not support other objects", () => { expect(pipe.supports(new Object())).toBe(false); diff --git a/modules/angular2/test/change_detection/util.js b/modules/angular2/test/change_detection/util.js deleted file mode 100644 index 9e59178b46..0000000000 --- a/modules/angular2/test/change_detection/util.js +++ /dev/null @@ -1,29 +0,0 @@ -import {isBlank} from 'angular2/src/facade/lang'; - -export function iterableChangesAsString({collection, previous, additions, moves, removals}) { - if (isBlank(collection)) collection = []; - if (isBlank(previous)) previous = []; - if (isBlank(additions)) additions = []; - if (isBlank(moves)) moves = []; - if (isBlank(removals)) removals = []; - - return "collection: " + collection.join(', ') + "\n" + - "previous: " + previous.join(', ') + "\n" + - "additions: " + additions.join(', ') + "\n" + - "moves: " + moves.join(', ') + "\n" + - "removals: " + removals.join(', ') + "\n"; -} - -export function kvChangesAsString({map, previous, additions, changes, removals}) { - if (isBlank(map)) map = []; - if (isBlank(previous)) previous = []; - if (isBlank(additions)) additions = []; - if (isBlank(changes)) changes = []; - if (isBlank(removals)) removals = []; - - return "map: " + map.join(', ') + "\n" + - "previous: " + previous.join(', ') + "\n" + - "additions: " + additions.join(', ') + "\n" + - "changes: " + changes.join(', ') + "\n" + - "removals: " + removals.join(', ') + "\n"; -} diff --git a/modules/angular2/test/change_detection/util.ts b/modules/angular2/test/change_detection/util.ts new file mode 100644 index 0000000000..75d385d776 --- /dev/null +++ b/modules/angular2/test/change_detection/util.ts @@ -0,0 +1,28 @@ +import {isBlank, CONST_EXPR} from 'angular2/src/facade/lang'; + +export function iterableChangesAsString({collection = CONST_EXPR([]), previous = CONST_EXPR([]), + additions = CONST_EXPR([]), moves = CONST_EXPR([]), + removals = CONST_EXPR([])}) { + return "collection: " + collection.join(', ') + "\n" + "previous: " + previous.join(', ') + "\n" + + "additions: " + additions.join(', ') + "\n" + "moves: " + moves.join(', ') + "\n" + + "removals: " + removals.join(', ') + "\n"; +} + +export function kvChangesAsString({map, previous, additions, changes, removals}: + { + map?:List, + previous?:List, + additions?: List, + changes?: List, + removals?: List + }):string { + if (isBlank(map)) map = []; + if (isBlank(previous)) previous = []; + if (isBlank(additions)) additions = []; + if (isBlank(changes)) changes = []; + if (isBlank(removals)) removals = []; + + return "map: " + map.join(', ') + "\n" + "previous: " + previous.join(', ') + "\n" + + "additions: " + additions.join(', ') + "\n" + "changes: " + changes.join(', ') + "\n" + + "removals: " + removals.join(', ') + "\n"; +} diff --git a/tools/broccoli/trees/node_tree.ts b/tools/broccoli/trees/node_tree.ts index d6b115015f..539be352b7 100644 --- a/tools/broccoli/trees/node_tree.ts +++ b/tools/broccoli/trees/node_tree.ts @@ -120,22 +120,19 @@ module.exports = function makeNodeTree(destinationPath) { // Transform all tests to make them runnable in node nodeTree = replace(nodeTree, { files: ['**/test/**/*_spec.js'], - patterns: [ - { - // Override the default DOM adapter with Parse5 for all tests - match: /"use strict";/, - replacement: - "'use strict'; var parse5Adapter = require('angular2/src/dom/parse5_adapter'); " + - "parse5Adapter.Parse5DomAdapter.makeCurrent();" - }, - { - // Append main() to all tests since all of our tests are wrapped in exported main fn - match: /$/g, - replacement: "\r\n main();" - } - ] + replaceWithPath: function(path, content) { + return "var parse5Adapter = require('angular2/src/dom/parse5_adapter'); " + + "parse5Adapter.Parse5DomAdapter.makeCurrent();" + content; + }, + patterns: [{ + // Append main() to all tests since all of our tests are wrapped in exported main fn + match: /$/g, + replacement: "\r\n main();" + }] }); + + // TODO(iminar): tree differ seems to have issues with trees created by mergeTrees, investigate! // ENOENT error is thrown while doing fs.readdirSync on inputRoot // in the meantime, we just do noop mv to create a new tree