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']); }); });
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); expect(cd.hydrated()).toBe(true); cd.dehydrate(); expect(cd.hydrated()).toBe(false); cd.hydrate("other context", 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("handle children", () => { var parent, child; beforeEach(() => { var protoParent = createProtoChangeDetector(); parent = protoParent.instantiate(null, [], null); var protoChild = createProtoChangeDetector(); child = protoChild.instantiate(null, [], null); }); it("should add children", () => { parent.addChild(child); expect(parent.children.length).toEqual(1); expect(parent.children[0]).toBe(child); }); it("should remove children", () => { parent.addChild(child); parent.removeChild(child); expect(parent.children).toEqual([]); }); });
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']); }); });
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); }); });
describe('RuntimeComponentUrlMapper', () => { it('should return the registered URL', () => { var url = 'http://path/to/component'; var mapper = new RuntimeComponentUrlMapper(); mapper.setComponentUrl(SomeComponent, url); expect(mapper.getUrl(SomeComponent)).toEqual(url); }); it('should fallback to ComponentUrlMapper', () => { var mapper = new ComponentUrlMapper(); var runtimeMapper = new RuntimeComponentUrlMapper(); expect(runtimeMapper.getUrl(SomeComponent)).toEqual(mapper.getUrl(SomeComponent)); }); });
describe("markPathToRootAsCheckOnce", () => { function changeDetector(mode, parent) { var cd = createProtoChangeDetector().instantiate(null, [], 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("group changes", () => { it("should notify the dispatcher when a group of records changes", () => { var pcd = createProtoChangeDetector(); var dispatcher = new TestDispatcher(); var cd = pcd.instantiate(dispatcher, [ new BindingRecord(ast("1 + 2"), "memo", "1"), new BindingRecord(ast("10 + 20"), "memo", "1"), new BindingRecord(ast("100 + 200"), "memo", "2") ], null); cd.detectChanges(); expect(dispatcher.loggedValues).toEqual([[3, 30], [300]]); }); it("should notify the dispatcher before switching to the next group", () => { var pcd = createProtoChangeDetector(); var dispatcher = new TestDispatcher(); var cd = pcd.instantiate(dispatcher, [ new BindingRecord(ast("a()"), "a", "1"), new BindingRecord(ast("b()"), "b", "2"), new BindingRecord(ast("c()"), "c", "2") ], null); var tr = new TestRecord(); tr.a = () => { dispatcher.logValue('InvokeA'); return 'a' }; tr.b = () => { dispatcher.logValue('InvokeB'); return 'b' }; tr.c = () => { dispatcher.logValue('InvokeC'); return 'c' }; cd.hydrate(tr, null); cd.detectChanges(); expect(dispatcher.loggedValues).toEqual(['InvokeA', ['a'], 'InvokeB', 'InvokeC', ['b', 'c']]); }); });
describe('regression slope validator', () => { var validator; function createValidator({size, metric}) { validator = new Injector([ RegressionSlopeValidator.BINDINGS, bind(RegressionSlopeValidator.METRIC).toValue(metric), bind(RegressionSlopeValidator.SAMPLE_SIZE).toValue(size) ]).get(RegressionSlopeValidator); } it('should return sampleSize and metric as description', () => { createValidator({size: 2, metric: 'script'}); expect(validator.describe()).toEqual({ 'sampleSize': 2, 'regressionSlopeMetric': 'script' }); }); it('should return null while the completeSample is smaller than the given size', () => { createValidator({size: 2, metric: 'script'}); expect(validator.validate([])).toBe(null); expect(validator.validate([mv(0,0,{})])).toBe(null); }); it('should return null while the regression slope is < 0', () => { createValidator({size: 2, metric: 'script'}); expect(validator.validate([mv(0,0,{'script':2}), mv(1,1,{'script':1})])).toBe(null); }); it('should return the last sampleSize runs when the regression slope is ==0', () => { createValidator({size: 2, metric: 'script'}); var sample = [mv(0,0,{'script':1}), mv(1,1,{'script':1}), mv(2,2,{'script':1})]; expect(validator.validate(ListWrapper.slice(sample,0,2))).toEqual(ListWrapper.slice(sample,0,2)); expect(validator.validate(sample)).toEqual(ListWrapper.slice(sample,1,3)); }); it('should return the last sampleSize runs when the regression slope is >0', () => { createValidator({size: 2, metric: 'script'}); var sample = [mv(0,0,{'script':1}), mv(1,1,{'script':2}), mv(2,2,{'script':3})]; expect(validator.validate(ListWrapper.slice(sample,0,2))).toEqual(ListWrapper.slice(sample,0,2)); expect(validator.validate(sample)).toEqual(ListWrapper.slice(sample,1,3)); }); });
describe('elements with template attribute', () => { it('should replace the element with an empty <template> element', () => { var rootElement = el('<div><span template=""></span></div>'); var originalChild = rootElement.childNodes[0]; var results = createPipeline().process(rootElement); expect(results[0].element).toBe(rootElement); expect(DOM.getOuterHTML(results[0].element)).toEqual('<div><template></template></div>'); expect(DOM.getOuterHTML(results[2].element)).toEqual('<span template=""></span>') expect(results[2].element).toBe(originalChild); }); it('should mark the element as viewRoot', () => { var rootElement = el('<div><div template></div></div>'); var results = createPipeline().process(rootElement); expect(results[2].isViewRoot).toBe(true); }); it('should work with top-level template node', () => { var rootElement = DOM.createTemplate('<div template>x</div>'); var originalChild = DOM.content(rootElement).childNodes[0]; var results = createPipeline().process(rootElement); expect(results[0].element).toBe(rootElement); expect(results[0].isViewRoot).toBe(true); expect(results[2].isViewRoot).toBe(true); expect(DOM.getOuterHTML(results[0].element)).toEqual('<template><template></template></template>'); expect(results[2].element).toBe(originalChild); }); it('should add property bindings from the template attribute', () => { var rootElement = el('<div><div template="prop:expr"></div></div>'); var results = createPipeline().process(rootElement); expect(MapWrapper.get(results[1].propertyBindings, 'prop').source).toEqual('expr'); }); it('should add variable mappings from the template attribute', () => { var rootElement = el('<div><div template="var varName=mapName"></div></div>'); var results = createPipeline().process(rootElement); expect(results[1].variableBindings).toEqual(MapWrapper.createFromStringMap({'mapName': 'varName'})); }); it('should add entries without value as attribute to the element', () => { var rootElement = el('<div><div template="varname"></div></div>'); var results = createPipeline().process(rootElement); expect(results[1].attrs()).toEqual(MapWrapper.createFromStringMap({'varname': ''})); expect(results[1].propertyBindings).toBe(null); expect(results[1].variableBindings).toBe(null); }); it('should iterate properly after a template dom modification', () => { var rootElement = el('<div><div template></div><after></after></div>'); var results = createPipeline().process(rootElement); // 1 root + 2 initial + 1 generated template elements expect(results.length).toEqual(4); }); });
describe('<template> elements', () => { it('should move the content into a new <template> element and mark that as viewRoot', () => { var rootElement = el('<div><template if="true">a</template></div>'); var results = createPipeline().process(rootElement); expect(DOM.getOuterHTML(results[1].element)).toEqual('<template if="true"></template>'); expect(results[1].isViewRoot).toBe(false); expect(DOM.getOuterHTML(results[2].element)).toEqual('<template>a</template>'); expect(results[2].isViewRoot).toBe(true); }); it('should not wrap a root <template> element', () => { var rootElement = el('<div></div>'); var results = createPipeline().process(rootElement); expect(results.length).toBe(1); expect(DOM.getOuterHTML(rootElement)).toEqual('<div></div>'); }); });
describe("mode", () => { 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, [], null); cd.mode = CHECK_ONCE; cd.detectChanges(); expect(cd.mode).toEqual(CHECKED); }); it("should not change the CHECK_ALWAYS", () => { var cd = createProtoChangeDetector().instantiate(null, [], null); cd.mode = CHECK_ALWAYS; cd.detectChanges(); expect(cd.mode).toEqual(CHECK_ALWAYS); }); });
['RecalculateStyles', 'Layout', 'UpdateLayerTree', 'Paint', 'Rasterize', 'CompositeLayers'].forEach( (recordType) => { it(`should report ${recordType}`, inject([AsyncTestCompleter], (async) => { createExtension([ durationRecord(recordType, 0, 1) ]).readPerfLog().then( (events) => { expect(events).toEqual([ normEvents.start('render', 0), normEvents.end('render', 1), ]); async.done(); }); })); });
xdescribe('static class fields', function() { it('should fail when setting wrong type value', function() { expect(function() { WithFields.id = true; }).toThrowError(IS_DARTIUM ? // Dart "type 'bool' is not a subtype of type 'num' of 'id'" : // JavaScript // TODO(vojta): Better error, it's not first argument, it's setting a field. 'Invalid arguments given!\n' + ' - 1st argument has to be an instance of number, got true' ); }); });
describe('TextInterpolationParser', () => { function createPipeline(ignoreBindings = false) { return new CompilePipeline([ new MockStep((parent, current, control) => { current.ignoreBindings = ignoreBindings; }), new IgnoreChildrenStep(), new TextInterpolationParser(new Parser(new Lexer())) ]); } it('should not look for text interpolation when ignoreBindings is true', () => { var results = createPipeline(true).process(el('<div>{{expr1}}<span></span>{{expr2}}</div>')); expect(results[0].textNodeBindings).toBe(null); }); it('should find text interpolation in normal elements', () => { var results = createPipeline().process(el('<div>{{expr1}}<span></span>{{expr2}}</div>')); var bindings = results[0].textNodeBindings; expect(MapWrapper.get(bindings, 0).source).toEqual("{{expr1}}"); expect(MapWrapper.get(bindings, 2).source).toEqual("{{expr2}}"); }); it('should find text interpolation in template elements', () => { var results = createPipeline().process(el('<template>{{expr1}}<span></span>{{expr2}}</template>')); var bindings = results[0].textNodeBindings; expect(MapWrapper.get(bindings, 0).source).toEqual("{{expr1}}"); expect(MapWrapper.get(bindings, 2).source).toEqual("{{expr2}}"); }); it('should allow multiple expressions', () => { var results = createPipeline().process(el('<div>{{expr1}}{{expr2}}</div>')); var bindings = results[0].textNodeBindings; expect(MapWrapper.get(bindings, 0).source).toEqual("{{expr1}}{{expr2}}"); }); it('should not interpolate when compileChildren is false', () => { var results = createPipeline().process(el('<div>{{included}}<span ignore-children>{{excluded}}</span></div>')); var bindings = results[0].textNodeBindings; expect(MapWrapper.get(bindings, 0).source).toEqual("{{included}}"); expect(results[1].textNodeBindings).toBe(null); }); it('should allow fixed text before, in between and after expressions', () => { var results = createPipeline().process(el('<div>a{{expr1}}b{{expr2}}c</div>')); var bindings = results[0].textNodeBindings; expect(MapWrapper.get(bindings, 0).source).toEqual("a{{expr1}}b{{expr2}}c"); }); it('should escape quotes in fixed parts', () => { var results = createPipeline().process(el("<div>'\"a{{expr1}}</div>")); expect(MapWrapper.get(results[0].textNodeBindings, 0).source).toEqual("'\"a{{expr1}}"); }); });
describe('title service', () => { var initialTitle = DOM.getTitle(); var titleService = new Title(); afterEach(() => { DOM.setTitle(initialTitle); }); it('should allow reading initial title', () => { expect(titleService.getTitle()).toEqual(initialTitle); }); it('should set a title on the injected document', () => { titleService.setTitle('test title'); expect(DOM.getTitle()).toEqual('test title'); expect(titleService.getTitle()).toEqual('test title'); }); it('should reset title to empty string if title not provided', () => { titleService.setTitle(null); expect(DOM.getTitle()).toEqual(''); }); });
xdescribe('class fields', function() { it('should fail when setting wrong type value', function() { var wf = new WithFields(); expect(function() { wf.name = true; }).toThrowError(IS_DARTIUM ? // Dart "type 'bool' is not a subtype of type 'String' of 'value'" : // JavaScript // TODO(vojta): Better error, it's not first argument, it's setting a field. 'Invalid arguments given!\n' + ' - 1st argument has to be an instance of string, got true' ); }); });
describe('types', function() { it('should work', function() { // TODO(vojta): test this better. var f = new Foo(1, 2); assert(f.sum() == 3); assert(f instanceof Foo); f.typedVariables(); }); xdescribe('class fields', function() { it('should fail when setting wrong type value', function() { var wf = new WithFields(); expect(function() { wf.name = true; }).toThrowError(IS_DARTIUM ? // Dart "type 'bool' is not a subtype of type 'String' of 'value'" : // JavaScript // TODO(vojta): Better error, it's not first argument, it's setting a field. 'Invalid arguments given!\n' + ' - 1st argument has to be an instance of string, got true' ); }); }); xdescribe('static class fields', function() { it('should fail when setting wrong type value', function() { expect(function() { WithFields.id = true; }).toThrowError(IS_DARTIUM ? // Dart "type 'bool' is not a subtype of type 'num' of 'id'" : // JavaScript // TODO(vojta): Better error, it's not first argument, it's setting a field. 'Invalid arguments given!\n' + ' - 1st argument has to be an instance of number, got true' ); }); }); });
describe(`${name} change detection`, () => { 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][0]['z']).toEqual(1); c = createChangeDetector('map', '{z:a}', new TestData(1)); c["changeDetector"].detectChanges(); expect(c["dispatcher"].loggedValues[0][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']); 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 parser = new Parser(new Lexer()); var pcd = createProtoChangeDetector(); var ast = parser.parseInterpolation("B{{a}}A", "location"); var dispatcher = new TestDispatcher(); var cd = pcd.instantiate(dispatcher, [new BindingRecord(ast, "memo", "memo")], null); cd.hydrate(new TestData("value"), 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(); var changeRecord = dispatcher.changeRecords[0][0]; expect(changeRecord.bindingMemento).toEqual('name'); expect(changeRecord.change.currentValue).toEqual('bob'); expect(changeRecord.change.previousValue).toEqual(ChangeDetectionUtil.unitialized()); }); }); 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(); var changeRecord = dispatcher.changeRecords[0][0]; expect(changeRecord.bindingMemento).toEqual('name'); expect(changeRecord.change.currentValue).toEqual('bob state:0'); expect(changeRecord.change.previousValue).toEqual(ChangeDetectionUtil.unitialized()); }); }); describe("group changes", () => { it("should notify the dispatcher when a group of records changes", () => { var pcd = createProtoChangeDetector(); var dispatcher = new TestDispatcher(); var cd = pcd.instantiate(dispatcher, [ new BindingRecord(ast("1 + 2"), "memo", "1"), new BindingRecord(ast("10 + 20"), "memo", "1"), new BindingRecord(ast("100 + 200"), "memo", "2") ], null); cd.detectChanges(); expect(dispatcher.loggedValues).toEqual([[3, 30], [300]]); }); it("should notify the dispatcher before switching to the next group", () => { var pcd = createProtoChangeDetector(); var dispatcher = new TestDispatcher(); var cd = pcd.instantiate(dispatcher, [ new BindingRecord(ast("a()"), "a", "1"), new BindingRecord(ast("b()"), "b", "2"), new BindingRecord(ast("c()"), "c", "2") ], null); var tr = new TestRecord(); tr.a = () => { dispatcher.logValue('InvokeA'); return 'a' }; tr.b = () => { dispatcher.logValue('InvokeB'); return 'b' }; tr.c = () => { dispatcher.logValue('InvokeC'); return 'c' }; cd.hydrate(tr, null); cd.detectChanges(); expect(dispatcher.loggedValues).toEqual(['InvokeA', ['a'], 'InvokeB', 'InvokeC', ['b', 'c']]); }); }); }); describe("enforce no new changes", () => { it("should throw when a record gets changed after it has been checked", () => { var pcd = createProtoChangeDetector(); pcd.addAst(ast("a"), "a", 1); var dispatcher = new TestDispatcher(); var cd = pcd.instantiate(dispatcher, [ new BindingRecord(ast("a"), "a", 1) ], null); cd.hydrate(new TestData('value'), null); expect(() => { cd.checkNoChanges(); }).toThrowError(new RegExp("Expression 'a in location' has changed after it was checked")); }); }); //TODO vsavkin: implement it describe("error handling", () => { xit("should wrap exceptions into ChangeDetectionError", () => { var pcd = createProtoChangeDetector(); var cd = pcd.instantiate(new TestDispatcher(), [ new BindingRecord(ast("invalidProp", "someComponent"), "a", 1) ], 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']); }); }); describe("handle children", () => { var parent, child; beforeEach(() => { var protoParent = createProtoChangeDetector(); parent = protoParent.instantiate(null, [], null); var protoChild = createProtoChangeDetector(); child = protoChild.instantiate(null, [], null); }); it("should add children", () => { parent.addChild(child); expect(parent.children.length).toEqual(1); expect(parent.children[0]).toBe(child); }); it("should remove children", () => { parent.addChild(child); parent.removeChild(child); expect(parent.children).toEqual([]); }); }); });
describe('CssSelector.parse', () => { it('should detect element names', () => { var cssSelector = CssSelector.parse('sometag')[0]; expect(cssSelector.element).toEqual('sometag'); expect(cssSelector.toString()).toEqual('sometag'); }); it('should detect class names', () => { var cssSelector = CssSelector.parse('.someClass')[0]; expect(cssSelector.classNames).toEqual(['someclass']); expect(cssSelector.toString()).toEqual('.someclass'); }); it('should detect attr names', () => { var cssSelector = CssSelector.parse('[attrname]')[0]; expect(cssSelector.attrs).toEqual(['attrname', '']); expect(cssSelector.toString()).toEqual('[attrname]'); }); it('should detect attr values', () => { var cssSelector = CssSelector.parse('[attrname=attrvalue]')[0]; expect(cssSelector.attrs).toEqual(['attrname', 'attrvalue']); expect(cssSelector.toString()).toEqual('[attrname=attrvalue]'); }); it('should detect multiple parts', () => { var cssSelector = CssSelector.parse('sometag[attrname=attrvalue].someclass')[0]; expect(cssSelector.element).toEqual('sometag'); expect(cssSelector.attrs).toEqual(['attrname', 'attrvalue']); expect(cssSelector.classNames).toEqual(['someclass']); expect(cssSelector.toString()).toEqual('sometag.someclass[attrname=attrvalue]'); }); it('should detect :not', () => { var cssSelector = CssSelector.parse('sometag:not([attrname=attrvalue].someclass)')[0]; expect(cssSelector.element).toEqual('sometag'); expect(cssSelector.attrs.length).toEqual(0); expect(cssSelector.classNames.length).toEqual(0); var notSelector = cssSelector.notSelector; expect(notSelector.element).toEqual(null); expect(notSelector.attrs).toEqual(['attrname', 'attrvalue']); expect(notSelector.classNames).toEqual(['someclass']); expect(cssSelector.toString()).toEqual('sometag:not(.someclass[attrname=attrvalue])'); }); it('should detect :not without truthy', () => { var cssSelector = CssSelector.parse(':not([attrname=attrvalue].someclass)')[0]; expect(cssSelector.element).toEqual("*"); var notSelector = cssSelector.notSelector; expect(notSelector.attrs).toEqual(['attrname', 'attrvalue']); expect(notSelector.classNames).toEqual(['someclass']); expect(cssSelector.toString()).toEqual('*:not(.someclass[attrname=attrvalue])'); }); it('should throw when nested :not', () => { expect(() => { CssSelector.parse('sometag:not(:not([attrname=attrvalue].someclass))')[0]; }).toThrowError('Nesting :not is not allowed in a selector'); }); it('should detect lists of selectors', () => { var cssSelectors = CssSelector.parse('.someclass,[attrname=attrvalue], sometag'); expect(cssSelectors.length).toEqual(3); expect(cssSelectors[0].classNames).toEqual(['someclass']); expect(cssSelectors[1].attrs).toEqual(['attrname', 'attrvalue']); expect(cssSelectors[2].element).toEqual('sometag'); }); it('should detect lists of selectors with :not', () => { var cssSelectors = CssSelector.parse('input[type=text], :not(textarea), textbox:not(.special)'); expect(cssSelectors.length).toEqual(3); expect(cssSelectors[0].element).toEqual('input'); expect(cssSelectors[0].attrs).toEqual(['type', 'text']); expect(cssSelectors[1].element).toEqual('*'); expect(cssSelectors[1].notSelector.element).toEqual('textarea'); expect(cssSelectors[2].element).toEqual('textbox'); expect(cssSelectors[2].notSelector.classNames).toEqual(['special']); }); });
describe('SelectorMatcher', () => { var matcher, matched, selectableCollector, s1, s2, s3, s4; function reset() { matched = ListWrapper.create(); } beforeEach(() => { reset(); s1 = s2 = s3 = s4 = null; selectableCollector = (selector, context) => { ListWrapper.push(matched, selector); ListWrapper.push(matched, context); } matcher = new SelectorMatcher(); }); it('should select by element name case insensitive', () => { matcher.addSelectables(s1 = CssSelector.parse('someTag'), 1); expect(matcher.match(CssSelector.parse('SOMEOTHERTAG')[0], selectableCollector)).toEqual(false); expect(matched).toEqual([]); expect(matcher.match(CssSelector.parse('SOMETAG')[0], selectableCollector)).toEqual(true); expect(matched).toEqual([s1[0],1]); }); it('should select by class name case insensitive', () => { matcher.addSelectables(s1 = CssSelector.parse('.someClass'), 1); matcher.addSelectables(s2 = CssSelector.parse('.someClass.class2'), 2); expect(matcher.match(CssSelector.parse('.SOMEOTHERCLASS')[0], selectableCollector)).toEqual(false); expect(matched).toEqual([]); expect(matcher.match(CssSelector.parse('.SOMECLASS')[0], selectableCollector)).toEqual(true); expect(matched).toEqual([s1[0],1]); reset(); expect(matcher.match(CssSelector.parse('.someClass.class2')[0], selectableCollector)).toEqual(true); expect(matched).toEqual([s1[0],1,s2[0],2]); }); it('should select by attr name case insensitive independent of the value', () => { matcher.addSelectables(s1 = CssSelector.parse('[someAttr]'), 1); matcher.addSelectables(s2 = CssSelector.parse('[someAttr][someAttr2]'), 2); expect(matcher.match(CssSelector.parse('[SOMEOTHERATTR]')[0], selectableCollector)).toEqual(false); expect(matched).toEqual([]); expect(matcher.match(CssSelector.parse('[SOMEATTR]')[0], selectableCollector)).toEqual(true); expect(matched).toEqual([s1[0],1]); reset(); expect(matcher.match(CssSelector.parse('[SOMEATTR=someValue]')[0], selectableCollector)).toEqual(true); expect(matched).toEqual([s1[0],1]); reset(); expect(matcher.match(CssSelector.parse('[someAttr][someAttr2]')[0], selectableCollector)).toEqual(true); expect(matched).toEqual([s1[0],1,s2[0],2]); }); it('should select by attr name only once if the value is from the DOM', () => { matcher.addSelectables(s1 = CssSelector.parse('[some-decor]'), 1); var elementSelector = new CssSelector(); var element = el('<div attr></div>'); var empty = DOM.getAttribute(element, 'attr'); elementSelector.addAttribute('some-decor', empty); matcher.match(elementSelector, selectableCollector); expect(matched).toEqual([s1[0],1]); }); it('should select by attr name and value case insensitive', () => { matcher.addSelectables(s1 = CssSelector.parse('[someAttr=someValue]'), 1); expect(matcher.match(CssSelector.parse('[SOMEATTR=SOMEOTHERATTR]')[0], selectableCollector)).toEqual(false); expect(matched).toEqual([]); expect(matcher.match(CssSelector.parse('[SOMEATTR=SOMEVALUE]')[0], selectableCollector)).toEqual(true); expect(matched).toEqual([s1[0],1]); }); it('should select by element name, class name and attribute name with value', () => { matcher.addSelectables(s1 = CssSelector.parse('someTag.someClass[someAttr=someValue]'), 1); expect(matcher.match(CssSelector.parse('someOtherTag.someOtherClass[someOtherAttr]')[0], selectableCollector)).toEqual(false); expect(matched).toEqual([]); expect(matcher.match(CssSelector.parse('someTag.someOtherClass[someOtherAttr]')[0], selectableCollector)).toEqual(false); expect(matched).toEqual([]); expect(matcher.match(CssSelector.parse('someTag.someClass[someOtherAttr]')[0], selectableCollector)).toEqual(false); expect(matched).toEqual([]); expect(matcher.match(CssSelector.parse('someTag.someClass[someAttr]')[0], selectableCollector)).toEqual(false); expect(matched).toEqual([]); expect(matcher.match(CssSelector.parse('someTag.someClass[someAttr=someValue]')[0], selectableCollector)).toEqual(true); expect(matched).toEqual([s1[0],1]); }); it('should select independent of the order in the css selector', () => { matcher.addSelectables(s1 = CssSelector.parse('[someAttr].someClass'), 1); matcher.addSelectables(s2 = CssSelector.parse('.someClass[someAttr]'), 2); matcher.addSelectables(s3 = CssSelector.parse('.class1.class2'), 3); matcher.addSelectables(s4 = CssSelector.parse('.class2.class1'), 4); expect(matcher.match(CssSelector.parse('[someAttr].someClass')[0], selectableCollector)).toEqual(true); expect(matched).toEqual([s1[0],1,s2[0],2]); reset(); expect(matcher.match(CssSelector.parse('.someClass[someAttr]')[0], selectableCollector)).toEqual(true); expect(matched).toEqual([s1[0],1,s2[0],2]); reset(); expect(matcher.match(CssSelector.parse('.class1.class2')[0], selectableCollector)).toEqual(true); expect(matched).toEqual([s3[0],3,s4[0],4]); reset(); expect(matcher.match(CssSelector.parse('.class2.class1')[0], selectableCollector)).toEqual(true); expect(matched).toEqual([s4[0],4,s3[0],3]); }); it('should not select with a matching :not selector', () => { matcher.addSelectables(CssSelector.parse('p:not(.someClass)'), 1); matcher.addSelectables(CssSelector.parse('p:not([someAttr])'), 2); matcher.addSelectables(CssSelector.parse(':not(.someClass)'), 3); matcher.addSelectables(CssSelector.parse(':not(p)'), 4); matcher.addSelectables(CssSelector.parse(':not(p[someAttr])'), 5); expect(matcher.match(CssSelector.parse('p.someClass[someAttr]')[0], selectableCollector)).toEqual(false); expect(matched).toEqual([]); }); it('should select with a non matching :not selector', () => { matcher.addSelectables(s1 = CssSelector.parse('p:not(.someClass)'), 1); matcher.addSelectables(s2 = CssSelector.parse('p:not(.someOtherClass[someAttr])'), 2); matcher.addSelectables(s3 = CssSelector.parse(':not(.someClass)'), 3); matcher.addSelectables(s4 = CssSelector.parse(':not(.someOtherClass[someAttr])'), 4); expect(matcher.match(CssSelector.parse('p[someOtherAttr].someOtherClass')[0], selectableCollector)).toEqual(true); expect(matched).toEqual([s1[0],1,s2[0],2,s3[0],3,s4[0],4]); }); it('should select with one match in a list', () => { matcher.addSelectables(s1 = CssSelector.parse('input[type=text], textbox'), 1); expect(matcher.match(CssSelector.parse('textbox')[0], selectableCollector)).toEqual(true); expect(matched).toEqual([s1[1],1]); reset(); expect(matcher.match(CssSelector.parse('input[type=text]')[0], selectableCollector)).toEqual(true); expect(matched).toEqual([s1[0],1]); }); it('should not select twice with two matches in a list', () => { matcher.addSelectables(s1 = CssSelector.parse('input, .someClass'), 1); expect(matcher.match(CssSelector.parse('input.someclass')[0], selectableCollector)).toEqual(true); expect(matched.length).toEqual(2); expect(matched).toEqual([s1[0],1]); }); });
describe('runner', () => { var injector; var runner; function createRunner(defaultBindings = null) { if (isBlank(defaultBindings)) { defaultBindings = []; } runner = new Runner([ defaultBindings, bind(Sampler).toFactory( (_injector) => { injector = _injector; return new MockSampler(); }, [Injector] ), bind(Metric).toFactory( () => new MockMetric(), []), bind(Validator).toFactory( () => new MockValidator(), []), bind(WebDriverAdapter).toFactory( () => new MockWebDriverAdapter(), []) ]); return runner; } it('should set SampleDescription.id', inject([AsyncTestCompleter], (async) => { createRunner().sample({id: 'someId'}) .then( (_) => injector.asyncGet(SampleDescription) ) .then( (desc) => { expect(desc.id).toBe('someId'); async.done(); }); })); it('should merge SampleDescription.description', inject([AsyncTestCompleter], (async) => { createRunner([ bind(Options.DEFAULT_DESCRIPTION).toValue({'a': 1}) ]).sample({id: 'someId', bindings: [ bind(Options.SAMPLE_DESCRIPTION).toValue({'b': 2}) ]}).then( (_) => injector.asyncGet(SampleDescription) ) .then( (desc) => { expect(desc.description).toEqual({ 'forceGc': false, 'userAgent': 'someUserAgent', 'a': 1, 'b': 2, 'v': 11 }); async.done(); }); })); it('should fill SampleDescription.metrics from the Metric', inject([AsyncTestCompleter], (async) => { createRunner().sample({id: 'someId'}) .then( (_) => injector.asyncGet(SampleDescription) ) .then( (desc) => { expect(desc.metrics).toEqual({ 'm1': 'some metric' }); async.done(); }); })); it('should bind Options.EXECUTE', inject([AsyncTestCompleter], (async) => { var execute = () => {}; createRunner().sample({id: 'someId', execute: execute}).then( (_) => { expect(injector.get(Options.EXECUTE)).toEqual(execute); async.done(); }); })); it('should bind Options.PREPARE', inject([AsyncTestCompleter], (async) => { var prepare = () => {}; createRunner().sample({id: 'someId', prepare: prepare}).then( (_) => { expect(injector.get(Options.PREPARE)).toEqual(prepare); async.done(); }); })); it('should bind Options.MICRO_ITERATIONS', inject([AsyncTestCompleter], (async) => { createRunner().sample({id: 'someId', microIterations: 23}).then( (_) => { expect(injector.get(Options.MICRO_ITERATIONS)).toEqual(23); async.done(); }); })); it('should overwrite bindings per sample call', inject([AsyncTestCompleter], (async) => { createRunner([ bind(Options.DEFAULT_DESCRIPTION).toValue({'a': 1}), ]).sample({id: 'someId', bindings: [ bind(Options.DEFAULT_DESCRIPTION).toValue({'a': 2}), ]}).then( (_) => injector.asyncGet(SampleDescription) ) .then( (desc) => { expect(injector.get(SampleDescription).description['a']).toBe(2); async.done(); }); })); });
describe("enforce no new changes", () => { it("should throw when a record gets changed after it has been checked", () => { var pcd = createProtoChangeDetector(); pcd.addAst(ast("a"), "a", 1); var dispatcher = new TestDispatcher(); var cd = pcd.instantiate(dispatcher, [ new BindingRecord(ast("a"), "a", 1) ], null); cd.hydrate(new TestData('value'), null); expect(() => { cd.checkNoChanges(); }).toThrowError(new RegExp("Expression 'a in location' has changed after it was checked")); }); });
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(); var changeRecord = dispatcher.changeRecords[0][0]; expect(changeRecord.bindingMemento).toEqual('name'); expect(changeRecord.change.currentValue).toEqual('bob state:0'); expect(changeRecord.change.previousValue).toEqual(ChangeDetectionUtil.unitialized()); }); });
describe('ios driver extension', () => { var log; var extension; var normEvents = new TraceEventFactory('timeline', 'pid0'); function createExtension(perfRecords = null) { if (isBlank(perfRecords)) { perfRecords = []; } log = []; extension = new Injector([ IOsDriverExtension.BINDINGS, bind(WebDriverAdapter).toValue(new MockDriverAdapter(log, perfRecords)) ]).get(IOsDriverExtension); return extension; } it('should throw on forcing gc', () => { expect( () => createExtension().gc() ).toThrowError('Force GC is not supported on iOS'); }); it('should mark the timeline via console.time()', inject([AsyncTestCompleter], (async) => { createExtension().timeBegin('someName').then( (_) => { expect(log).toEqual([['executeScript', `console.time('someName');`]]); async.done(); }); })); it('should mark the timeline via console.timeEnd()', inject([AsyncTestCompleter], (async) => { createExtension().timeEnd('someName').then( (_) => { expect(log).toEqual([['executeScript', `console.timeEnd('someName');`]]); async.done(); }); })); it('should mark the timeline via console.time() and console.timeEnd()', inject([AsyncTestCompleter], (async) => { createExtension().timeEnd('name1', 'name2').then( (_) => { expect(log).toEqual([['executeScript', `console.timeEnd('name1');console.time('name2');`]]); async.done(); }); })); describe('readPerfLog', () => { it('should execute a dummy script before reading them', inject([AsyncTestCompleter], (async) => { // TODO(tbosch): This seems to be a bug in ChromeDriver: // Sometimes it does not report the newest events of the performance log // to the WebDriver client unless a script is executed... createExtension([]).readPerfLog().then( (_) => { expect(log).toEqual([ [ 'executeScript', '1+1' ], [ 'logs', 'performance' ] ]); async.done(); }); })); it('should report FunctionCall records as "script"', inject([AsyncTestCompleter], (async) => { createExtension([ durationRecord('FunctionCall', 1, 5) ]).readPerfLog().then( (events) => { expect(events).toEqual([ normEvents.start('script', 1), normEvents.end('script', 5) ]); async.done(); }); })); it('should ignore FunctionCalls from webdriver', inject([AsyncTestCompleter], (async) => { createExtension([ internalScriptRecord(1, 5) ]).readPerfLog().then( (events) => { expect(events).toEqual([]); async.done(); }); })); it('should report begin time', inject([AsyncTestCompleter], (async) => { createExtension([ timeBeginRecord('someName', 12) ]).readPerfLog().then( (events) => { expect(events).toEqual([ normEvents.markStart('someName', 12) ]); async.done(); }); })); it('should report end timestamps', inject([AsyncTestCompleter], (async) => { createExtension([ timeEndRecord('someName', 12) ]).readPerfLog().then( (events) => { expect(events).toEqual([ normEvents.markEnd('someName', 12) ]); async.done(); }); })); ['RecalculateStyles', 'Layout', 'UpdateLayerTree', 'Paint', 'Rasterize', 'CompositeLayers'].forEach( (recordType) => { it(`should report ${recordType}`, inject([AsyncTestCompleter], (async) => { createExtension([ durationRecord(recordType, 0, 1) ]).readPerfLog().then( (events) => { expect(events).toEqual([ normEvents.start('render', 0), normEvents.end('render', 1), ]); async.done(); }); })); }); it('should walk children', inject([AsyncTestCompleter], (async) => { createExtension([ durationRecord('FunctionCall', 1, 5, [ timeBeginRecord('someName', 2) ]) ]).readPerfLog().then( (events) => { expect(events).toEqual([ normEvents.start('script', 1), normEvents.markStart('someName', 2), normEvents.end('script', 5) ]); async.done(); }); })); it('should match safari browsers', () => { expect(createExtension().supports({ 'browserName': 'safari' })).toBe(true); expect(createExtension().supports({ 'browserName': 'Safari' })).toBe(true); }); }); });
describe('token', function() { it('should tokenize a simple identifier', function() { var tokens:List<int> = lex("j"); expect(tokens.length).toEqual(1); expectIdentifierToken(tokens[0], 0, 'j'); }); it('should tokenize a dotted identifier', function() { var tokens:List<int> = lex("j.k"); expect(tokens.length).toEqual(3); expectIdentifierToken(tokens[0], 0, 'j'); expectCharacterToken (tokens[1], 1, '.'); expectIdentifierToken(tokens[2], 2, 'k'); }); it('should tokenize an operator', function() { var tokens:List<int> = lex("j-k"); expect(tokens.length).toEqual(3); expectOperatorToken(tokens[1], 1, '-'); }); it('should tokenize an indexed operator', function() { var tokens:List<int> = lex("j[k]"); expect(tokens.length).toEqual(4); expectCharacterToken(tokens[1], 1, "["); expectCharacterToken(tokens[3], 3, "]"); }); it('should tokenize numbers', function() { var tokens:List<int> = 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 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 a string', function() { var tokens:List<Token> = 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'); expectCharacterToken(tokens[3], 3, '.'); expectIdentifierToken(tokens[4], 4, 'bc'); expectCharacterToken(tokens[5], 6, '['); expectNumberToken(tokens[6], 7, 22); expectCharacterToken(tokens[7], 9, ']'); expectOperatorToken(tokens[8], 10, '+'); expectNumberToken(tokens[9], 11, 1.3); expectOperatorToken(tokens[10], 14, '|'); expectIdentifierToken(tokens[11], 15, 'f'); expectCharacterToken(tokens[12], 16, ':'); expectStringToken(tokens[13], 17, "a'c"); expectCharacterToken(tokens[14], 23, ':'); expectStringToken(tokens[15], 24, 'd"e'); }); it('should tokenize undefined', function() { var tokens:List<Token> = lex("undefined"); expectKeywordToken(tokens[0], 0, "undefined"); expect(tokens[0].isKeywordUndefined()).toBe(true); }); it('should ignore whitespace', function() { var tokens:List<Token> = 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<Token> = 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<Token> = 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<Token> = lex('"\\u00A0"'); expect(tokens.length).toEqual(1); expect(tokens[0].toString()).toEqual('\u00a0'); }); it('should tokenize relation', function() { var tokens:List<Token> = lex("! == != < > <= >="); expectOperatorToken(tokens[0], 0, '!'); expectOperatorToken(tokens[1], 2, '=='); expectOperatorToken(tokens[2], 5, '!='); expectOperatorToken(tokens[3], 8, '<'); expectOperatorToken(tokens[4], 10, '>'); expectOperatorToken(tokens[5], 12, '<='); expectOperatorToken(tokens[6], 15, '>='); }); it('should tokenize statements', function() { var tokens:List<Token> = lex("a;b;"); expectIdentifierToken(tokens[0], 0, 'a'); expectCharacterToken(tokens[1], 1, ';'); expectIdentifierToken(tokens[2], 2, 'b'); expectCharacterToken(tokens[3], 3, ';'); }); it('should tokenize function invocation', function() { var tokens:List<Token> = 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<Token> = lex("a.method()"); expectIdentifierToken(tokens[2], 2, 'method'); }); it('should tokenize method invocation', function() { var tokens:List<Token> = lex("a.b.c (d) - e.f()"); expectIdentifierToken(tokens[0], 0, 'a'); expectCharacterToken(tokens[1], 1, '.'); expectIdentifierToken(tokens[2], 2, 'b'); expectCharacterToken(tokens[3], 3, '.'); expectIdentifierToken(tokens[4], 4, 'c'); expectCharacterToken(tokens[5], 6, '('); expectIdentifierToken(tokens[6], 7, 'd'); expectCharacterToken(tokens[7], 8, ')'); expectOperatorToken(tokens[8], 10, '-'); expectIdentifierToken(tokens[9], 12, 'e'); expectCharacterToken(tokens[10], 13, '.'); expectIdentifierToken(tokens[11], 14, 'f'); expectCharacterToken(tokens[12], 15, '('); expectCharacterToken(tokens[13], 16, ')'); }); it('should tokenize number', function() { var tokens:List<Token> = lex("0.5"); expectNumberToken(tokens[0], 0, 0.5); }); // NOTE(deboer): NOT A LEXER TEST // it('should tokenize negative number', function() { // var tokens:List<Token> = lex("-0.5"); // expectNumberToken(tokens[0], 0, -0.5); // }); it('should tokenize number with exponent', function() { var tokens:List<Token> = lex("0.5E-10"); expect(tokens.length).toEqual(1); expectNumberToken(tokens[0], 0, 0.5E-10); tokens = lex("0.5E+10"); expectNumberToken(tokens[0], 0, 0.5E+10); }); 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-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<Token> = 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']"); }); it('should tokenize hash as operator', function() { var tokens:List<Token> = lex("#"); expectOperatorToken(tokens[0], 0, '#'); }); });
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(); var changeRecord = dispatcher.changeRecords[0][0]; expect(changeRecord.bindingMemento).toEqual('name'); expect(changeRecord.change.currentValue).toEqual('bob'); expect(changeRecord.change.previousValue).toEqual(ChangeDetectionUtil.unitialized()); }); });
}, (createProtoChangeDetector, name) => { if (name == "JIT" && IS_DARTIUM) return; function ast(exp:string, location:string = 'location') { var parser = new Parser(new Lexer()); return parser.parseBinding(exp, location); } 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(memo:string, exp:string, context = null, locals = null, registry = null) { var pcd = createProtoChangeDetector(registry); var dispatcher = new TestDispatcher(); var variableBindings = convertLocalsToVariableBindings(locals); var cd = pcd.instantiate(dispatcher, [new BindingRecord(ast(exp), memo, memo)], variableBindings); cd.hydrate(context, locals); 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`, () => { 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][0]['z']).toEqual(1); c = createChangeDetector('map', '{z:a}', new TestData(1)); c["changeDetector"].detectChanges(); expect(c["dispatcher"].loggedValues[0][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']); 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 parser = new Parser(new Lexer()); var pcd = createProtoChangeDetector(); var ast = parser.parseInterpolation("B{{a}}A", "location"); var dispatcher = new TestDispatcher(); var cd = pcd.instantiate(dispatcher, [new BindingRecord(ast, "memo", "memo")], null); cd.hydrate(new TestData("value"), 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(); var changeRecord = dispatcher.changeRecords[0][0]; expect(changeRecord.bindingMemento).toEqual('name'); expect(changeRecord.change.currentValue).toEqual('bob'); expect(changeRecord.change.previousValue).toEqual(ChangeDetectionUtil.unitialized()); }); }); 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(); var changeRecord = dispatcher.changeRecords[0][0]; expect(changeRecord.bindingMemento).toEqual('name'); expect(changeRecord.change.currentValue).toEqual('bob state:0'); expect(changeRecord.change.previousValue).toEqual(ChangeDetectionUtil.unitialized()); }); }); describe("group changes", () => { it("should notify the dispatcher when a group of records changes", () => { var pcd = createProtoChangeDetector(); var dispatcher = new TestDispatcher(); var cd = pcd.instantiate(dispatcher, [ new BindingRecord(ast("1 + 2"), "memo", "1"), new BindingRecord(ast("10 + 20"), "memo", "1"), new BindingRecord(ast("100 + 200"), "memo", "2") ], null); cd.detectChanges(); expect(dispatcher.loggedValues).toEqual([[3, 30], [300]]); }); it("should notify the dispatcher before switching to the next group", () => { var pcd = createProtoChangeDetector(); var dispatcher = new TestDispatcher(); var cd = pcd.instantiate(dispatcher, [ new BindingRecord(ast("a()"), "a", "1"), new BindingRecord(ast("b()"), "b", "2"), new BindingRecord(ast("c()"), "c", "2") ], null); var tr = new TestRecord(); tr.a = () => { dispatcher.logValue('InvokeA'); return 'a' }; tr.b = () => { dispatcher.logValue('InvokeB'); return 'b' }; tr.c = () => { dispatcher.logValue('InvokeC'); return 'c' }; cd.hydrate(tr, null); cd.detectChanges(); expect(dispatcher.loggedValues).toEqual(['InvokeA', ['a'], 'InvokeB', 'InvokeC', ['b', 'c']]); }); }); }); describe("enforce no new changes", () => { it("should throw when a record gets changed after it has been checked", () => { var pcd = createProtoChangeDetector(); pcd.addAst(ast("a"), "a", 1); var dispatcher = new TestDispatcher(); var cd = pcd.instantiate(dispatcher, [ new BindingRecord(ast("a"), "a", 1) ], null); cd.hydrate(new TestData('value'), null); expect(() => { cd.checkNoChanges(); }).toThrowError(new RegExp("Expression 'a in location' has changed after it was checked")); }); }); //TODO vsavkin: implement it describe("error handling", () => { xit("should wrap exceptions into ChangeDetectionError", () => { var pcd = createProtoChangeDetector(); var cd = pcd.instantiate(new TestDispatcher(), [ new BindingRecord(ast("invalidProp", "someComponent"), "a", 1) ], 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']); }); }); describe("handle children", () => { var parent, child; beforeEach(() => { var protoParent = createProtoChangeDetector(); parent = protoParent.instantiate(null, [], null); var protoChild = createProtoChangeDetector(); child = protoChild.instantiate(null, [], null); }); it("should add children", () => { parent.addChild(child); expect(parent.children.length).toEqual(1); expect(parent.children[0]).toBe(child); }); it("should remove children", () => { parent.addChild(child); parent.removeChild(child); expect(parent.children).toEqual([]); }); }); }); describe("mode", () => { 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, [], null); cd.mode = CHECK_ONCE; cd.detectChanges(); expect(cd.mode).toEqual(CHECKED); }); it("should not change the CHECK_ALWAYS", () => { var cd = createProtoChangeDetector().instantiate(null, [], null); cd.mode = CHECK_ALWAYS; cd.detectChanges(); expect(cd.mode).toEqual(CHECK_ALWAYS); }); }); describe("markPathToRootAsCheckOnce", () => { function changeDetector(mode, parent) { var cd = createProtoChangeDetector().instantiate(null, [], 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); expect(cd.hydrated()).toBe(true); cd.dehydrate(); expect(cd.hydrated()).toBe(false); cd.hydrate("other context", 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 do nothing when returns 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(); cd.detectChanges(); expect(dispatcher.log).toEqual(['memo=Megatron']); ctx.name = "Optimus Prime"; dispatcher.clear(); cd.detectChanges(); expect(dispatcher.log).toEqual(['memo=Optimus Prime']); }); });
describe('readPerfLog', () => { it('should execute a dummy script before reading them', inject([AsyncTestCompleter], (async) => { // TODO(tbosch): This seems to be a bug in ChromeDriver: // Sometimes it does not report the newest events of the performance log // to the WebDriver client unless a script is executed... createExtension([]).readPerfLog().then( (_) => { expect(log).toEqual([ [ 'executeScript', '1+1' ], [ 'logs', 'performance' ] ]); async.done(); }); })); it('should report FunctionCall records as "script"', inject([AsyncTestCompleter], (async) => { createExtension([ durationRecord('FunctionCall', 1, 5) ]).readPerfLog().then( (events) => { expect(events).toEqual([ normEvents.start('script', 1), normEvents.end('script', 5) ]); async.done(); }); })); it('should ignore FunctionCalls from webdriver', inject([AsyncTestCompleter], (async) => { createExtension([ internalScriptRecord(1, 5) ]).readPerfLog().then( (events) => { expect(events).toEqual([]); async.done(); }); })); it('should report begin time', inject([AsyncTestCompleter], (async) => { createExtension([ timeBeginRecord('someName', 12) ]).readPerfLog().then( (events) => { expect(events).toEqual([ normEvents.markStart('someName', 12) ]); async.done(); }); })); it('should report end timestamps', inject([AsyncTestCompleter], (async) => { createExtension([ timeEndRecord('someName', 12) ]).readPerfLog().then( (events) => { expect(events).toEqual([ normEvents.markEnd('someName', 12) ]); async.done(); }); })); ['RecalculateStyles', 'Layout', 'UpdateLayerTree', 'Paint', 'Rasterize', 'CompositeLayers'].forEach( (recordType) => { it(`should report ${recordType}`, inject([AsyncTestCompleter], (async) => { createExtension([ durationRecord(recordType, 0, 1) ]).readPerfLog().then( (events) => { expect(events).toEqual([ normEvents.start('render', 0), normEvents.end('render', 1), ]); async.done(); }); })); }); it('should walk children', inject([AsyncTestCompleter], (async) => { createExtension([ durationRecord('FunctionCall', 1, 5, [ timeBeginRecord('someName', 2) ]) ]).readPerfLog().then( (events) => { expect(events).toEqual([ normEvents.start('script', 1), normEvents.markStart('someName', 2), normEvents.end('script', 5) ]); async.done(); }); })); it('should match safari browsers', () => { expect(createExtension().supports({ 'browserName': 'safari' })).toBe(true); expect(createExtension().supports({ 'browserName': 'Safari' })).toBe(true); }); });
describe('elements with *directive_name attribute', () => { it('should replace the element with an empty <template> element', () => { var rootElement = el('<div><span *if></span></div>'); var originalChild = rootElement.childNodes[0]; var results = createPipeline().process(rootElement); expect(results[0].element).toBe(rootElement); expect(DOM.getOuterHTML(results[0].element)).toEqual('<div><template if=""></template></div>'); expect(DOM.getOuterHTML(results[2].element)).toEqual('<span *if=""></span>') expect(results[2].element).toBe(originalChild); }); it('should mark the element as viewRoot', () => { var rootElement = el('<div><div *foo="bar"></div></div>'); var results = createPipeline().process(rootElement); expect(results[2].isViewRoot).toBe(true); }); it('should work with top-level template node', () => { var rootElement = DOM.createTemplate('<div *foo>x</div>'); var originalChild = DOM.content(rootElement).childNodes[0]; var results = createPipeline().process(rootElement); expect(results[0].element).toBe(rootElement); expect(results[0].isViewRoot).toBe(true); expect(results[2].isViewRoot).toBe(true); expect(DOM.getOuterHTML(results[0].element)).toEqual('<template><template foo=""></template></template>'); expect(results[2].element).toBe(originalChild); }); it('should add property bindings from the template attribute', () => { var rootElement = el('<div><div *prop="expr"></div></div>'); var results = createPipeline().process(rootElement); expect(MapWrapper.get(results[1].propertyBindings, 'prop').source).toEqual('expr'); }); it('should add variable mappings from the template attribute', () => { var rootElement = el('<div><div *foreach="var varName=mapName"></div></div>'); var results = createPipeline().process(rootElement); expect(results[1].variableBindings).toEqual(MapWrapper.createFromStringMap({'mapName': 'varName'})); }); it('should add entries without value as attribute to the element', () => { var rootElement = el('<div><div *varname></div></div>'); var results = createPipeline().process(rootElement); expect(results[1].attrs()).toEqual(MapWrapper.createFromStringMap({'varname': ''})); expect(results[1].propertyBindings).toBe(null); expect(results[1].variableBindings).toBe(null); }); it('should iterate properly after a template dom modification', () => { var rootElement = el('<div><div *foo></div><after></after></div>'); var results = createPipeline().process(rootElement); // 1 root + 2 initial + 1 generated template elements expect(results.length).toEqual(4); }); it('should not allow multiple template directives on the same element', () => { expect( () => { var rootElement = el('<div><div *foo *bar="blah"></div></div>'); createPipeline().process(rootElement); }).toThrowError('Only one template directive per element is allowed: foo and bar cannot be used simultaneously in <div *foo *bar="blah">'); }); it('should not allow template and star directives on the same element', () => { expect( () => { var rootElement = el('<div><div *foo template="bar"></div></div>'); createPipeline().process(rootElement); }).toThrowError('Only one template directive per element is allowed: bar and foo cannot be used simultaneously in <div *foo template="bar">'); }); });