From e8965656a47dedd34db377af6228b284fcc0df63 Mon Sep 17 00:00:00 2001 From: Marc Laval Date: Mon, 9 Mar 2015 17:41:49 +0100 Subject: [PATCH] feat(directives/forms): run tests in NodeJS Closes #921 --- gulpfile.js | 12 +- modules/angular2/src/dom/parse5_adapter.cjs | 53 +++-- modules/angular2/src/forms/form_builder.js | 16 +- modules/angular2/src/forms/validators.js | 10 +- modules/angular2/test/directives/if_spec.js | 28 +-- .../angular2/test/forms/integration_spec.js | 203 +++++++++--------- 6 files changed, 180 insertions(+), 142 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index e28a438cee..eb5a4dbe41 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -229,8 +229,18 @@ var CONFIG = { formatDart: { packageName: 'dart_style', args: ['dart_style:format', '-w', 'dist/dart'] + }, + test: { + js: { + cjs: [ + '/angular2/test/core/compiler/**/*_spec.js', + '/angular2/test/directives/**/*_spec.js', + '/angular2/test/forms/**/*_spec.js' + ] + } } }; +CONFIG.test.js.cjs = CONFIG.test.js.cjs.map(function(s) {return CONFIG.dest.js.cjs + s}); // ------------ // clean @@ -553,7 +563,7 @@ gulp.task('test.unit.dart/ci', function (done) { singleRun: true, reporters: ['dots'], browsers: getBrowsersFromCLI()}, done); }); gulp.task('test.unit.cjs', function (done) { - return gulp.src(CONFIG.dest.js.cjs + '/angular2/test/core/compiler/**/*_spec.js').pipe(jasmine({verbose: true, includeStackTrace: true})); + return gulp.src(CONFIG.test.js.cjs).pipe(jasmine(/*{verbose: true, includeStackTrace: true}*/)); }); // ------------------ diff --git a/modules/angular2/src/dom/parse5_adapter.cjs b/modules/angular2/src/dom/parse5_adapter.cjs index 3db796ff71..6b3b0c6b02 100644 --- a/modules/angular2/src/dom/parse5_adapter.cjs +++ b/modules/angular2/src/dom/parse5_adapter.cjs @@ -10,6 +10,7 @@ var url = require('url'); import {List, MapWrapper, ListWrapper, StringMapWrapper} from 'angular2/src/facade/collection'; import {DomAdapter, setRootDomAdapter} from './dom_adapter'; import {BaseException, isPresent, isBlank} from 'angular2/src/facade/lang'; +import {SelectorMatcher, CssSelector} from 'angular2/src/core/compiler/selector'; var _attrToPropMap = { 'inner-html': 'innerHTML', @@ -35,27 +36,55 @@ export class Parse5DomAdapter extends DomAdapter { throw _notImplemented('query'); } querySelector(el, selector:string) { - throw _notImplemented('querySelector'); + return this.querySelectorAll(el, selector)[0]; } querySelectorAll(el, selector:string) { - //TODO: use selector class from core. For now, only works for .classname ... var res = ListWrapper.create(); - var _recursive = (result, node, className) => { - if (this.hasClass(node, className)) { + var _recursive = (result, node, selector, matcher) => { + if (this.elementMatches(node, selector, matcher)) { ListWrapper.push(result, node); } var cNodes = node.childNodes; if (cNodes && cNodes.length > 0) { for (var i = 0; i < cNodes.length; i++) { - _recursive(result, cNodes[i], className); + _recursive(result, cNodes[i], selector, matcher); } } }; - _recursive(res, el, selector.substring(1)); + var matcher = new SelectorMatcher(); + matcher.addSelectable(CssSelector.parse(selector)); + _recursive(res, el, selector, matcher); return res; } + elementMatches(node, selector:string, matcher = null):boolean { + var result = false; + if (selector && selector.charAt(0) == "#") { + result = this.getAttribute(node, 'id') == selector.substring(1); + } else if (selector) { + var result = false; + if (matcher == null) { + matcher = new SelectorMatcher(); + matcher.addSelectable(CssSelector.parse(selector)); + } + + var cssSelector = new CssSelector(); + cssSelector.setElement(this.tagName(node)); + if (node.attribs) { + for (var attrName in node.attribs) { + cssSelector.addAttribute(attrName, node.attribs[attrName]); + } + } + var classList = this.classList(node); + for (var i = 0; i < classList.length; i++) { + cssSelector.addClassName(classList[i]); + } + + matcher.match(cssSelector, function(selector, cb) {result = true;}); + } + return result; + } on(el, evt, listener) { - throw _notImplemented('on'); + //Do nothing, in order to not break forms integration tests } dispatchEvent(el, evt) { throw _notImplemented('dispatchEvent'); @@ -316,7 +345,7 @@ export class Parse5DomAdapter extends DomAdapter { return res; } getAttribute(element, attribute:string) { - return element.attribs.hasOwnProperty(attribute) ? element.attribs[attribute] : null; + return element.attribs && element.attribs.hasOwnProperty(attribute)? element.attribs[attribute]: null; } setAttribute(element, attribute:string, value:string) { if (attribute) { @@ -341,14 +370,6 @@ export class Parse5DomAdapter extends DomAdapter { } return defDoc; } - elementMatches(n, selector:string):boolean { - //TODO: use selector class from core. - if (selector && selector.charAt(0) == ".") { - return this.hasClass(n, selector.substring(1)); - } else { - return n.tagName == selector; - } - } isTemplateElement(el:any):boolean { return this.isElementNode(el) && this.tagName(el) === "template"; } diff --git a/modules/angular2/src/forms/form_builder.js b/modules/angular2/src/forms/form_builder.js index c6e796e565..2180560ca0 100644 --- a/modules/angular2/src/forms/form_builder.js +++ b/modules/angular2/src/forms/form_builder.js @@ -1,26 +1,26 @@ import {StringMapWrapper, ListWrapper} from 'angular2/src/facade/collection'; import {isPresent} from 'angular2/src/facade/lang'; -import {ControlGroup, Control, OptionalControl, OptionalControlGroup} from 'angular2/forms'; +import * as modelModule from './model'; export class FormBuilder { - group(controlsConfig, extra = null):ControlGroup { + group(controlsConfig, extra = null):modelModule.ControlGroup { var controls = this._reduceControls(controlsConfig); var optionals = isPresent(extra) ? StringMapWrapper.get(extra, "optionals") : null; var validator = isPresent(extra) ? StringMapWrapper.get(extra, "validator") : null; if (isPresent(validator)) { - return new ControlGroup(controls, optionals, validator); + return new modelModule.ControlGroup(controls, optionals, validator); } else { - return new ControlGroup(controls, optionals); + return new modelModule.ControlGroup(controls, optionals); } } - control(value, validator:Function = null):Control { + control(value, validator:Function = null):modelModule.Control { if (isPresent(validator)) { - return new Control(value, validator); + return new modelModule.Control(value, validator); } else { - return new Control(value); + return new modelModule.Control(value); } } @@ -33,7 +33,7 @@ export class FormBuilder { } _createControl(controlConfig) { - if (controlConfig instanceof Control || controlConfig instanceof ControlGroup) { + if (controlConfig instanceof modelModule.Control || controlConfig instanceof modelModule.ControlGroup) { return controlConfig; } else if (ListWrapper.isList(controlConfig)) { diff --git a/modules/angular2/src/forms/validators.js b/modules/angular2/src/forms/validators.js index 9888f12648..773ac8ba34 100644 --- a/modules/angular2/src/forms/validators.js +++ b/modules/angular2/src/forms/validators.js @@ -1,18 +1,18 @@ import {isBlank, isPresent} from 'angular2/src/facade/lang'; import {List, ListWrapper, StringMapWrapper} from 'angular2/src/facade/collection'; -import {ControlGroup, Control} from 'angular2/forms'; +import * as modelModule from './model'; -export function required(c:Control) { +export function required(c:modelModule.Control) { return isBlank(c.value) || c.value == "" ? {"required" : true} : null; } -export function nullValidator(c:Control) { +export function nullValidator(c:modelModule.Control) { return null; } export function compose(validators:List):Function { - return function(c:Control) { + return function(c:modelModule.Control) { var res = ListWrapper.reduce(validators, (res, validator) => { var errors = validator(c); return isPresent(errors) ? StringMapWrapper.merge(res, errors) : res; @@ -21,7 +21,7 @@ export function compose(validators:List):Function { } } -export function controlGroupValidator(c:ControlGroup) { +export function controlGroupValidator(c:modelModule.ControlGroup) { var res = {}; StringMapWrapper.forEach(c.controls, (control, name) => { if (c.contains(name) && isPresent(control.errors)) { diff --git a/modules/angular2/test/directives/if_spec.js b/modules/angular2/test/directives/if_spec.js index 9b088cb96f..ffc143c621 100644 --- a/modules/angular2/test/directives/if_spec.js +++ b/modules/angular2/test/directives/if_spec.js @@ -75,7 +75,7 @@ export function main() { createView(pv); cd.detectChanges(); - expect(view.nodes[0].querySelectorAll('copy-me').length).toEqual(1); + expect(DOM.querySelectorAll(view.nodes[0], 'copy-me').length).toEqual(1); expect(DOM.getText(view.nodes[0])).toEqual('hello'); async.done(); }); @@ -86,7 +86,7 @@ export function main() { createView(pv); cd.detectChanges(); - expect(view.nodes[0].querySelectorAll('copy-me').length).toEqual(1); + expect(DOM.querySelectorAll(view.nodes[0], 'copy-me').length).toEqual(1); expect(DOM.getText(view.nodes[0])).toEqual('hello2'); async.done(); }); @@ -98,18 +98,18 @@ export function main() { component.booleanCondition = false; cd.detectChanges(); - expect(view.nodes[0].querySelectorAll('copy-me').length).toEqual(0); + expect(DOM.querySelectorAll(view.nodes[0], 'copy-me').length).toEqual(0); expect(DOM.getText(view.nodes[0])).toEqual(''); component.booleanCondition = true; cd.detectChanges(); - expect(view.nodes[0].querySelectorAll('copy-me').length).toEqual(1); + expect(DOM.querySelectorAll(view.nodes[0], 'copy-me').length).toEqual(1); expect(DOM.getText(view.nodes[0])).toEqual('hello'); component.booleanCondition = false; cd.detectChanges(); - expect(view.nodes[0].querySelectorAll('copy-me').length).toEqual(0); + expect(DOM.querySelectorAll(view.nodes[0], 'copy-me').length).toEqual(0); expect(DOM.getText(view.nodes[0])).toEqual(''); async.done(); @@ -127,18 +127,18 @@ export function main() { createView(pv); cd.detectChanges(); - expect(view.nodes[0].querySelectorAll('copy-me').length).toEqual(3); + expect(DOM.querySelectorAll(view.nodes[0], 'copy-me').length).toEqual(3); expect(DOM.getText(view.nodes[0])).toEqual('helloNumberhelloStringhelloFunction'); component.numberCondition = 0; cd.detectChanges(); - expect(view.nodes[0].querySelectorAll('copy-me').length).toEqual(1); + expect(DOM.querySelectorAll(view.nodes[0], 'copy-me').length).toEqual(1); expect(DOM.getText(view.nodes[0])).toEqual('helloString'); component.numberCondition = 1; component.stringCondition = "bar"; cd.detectChanges(); - expect(view.nodes[0].querySelectorAll('copy-me').length).toEqual(1); + expect(DOM.querySelectorAll(view.nodes[0], 'copy-me').length).toEqual(1); expect(DOM.getText(view.nodes[0])).toEqual('helloNumber'); async.done(); }); @@ -151,7 +151,7 @@ export function main() { createView(pv); cd.detectChanges(); - expect(view.nodes[0].querySelectorAll('copy-me').length).toEqual(1); + expect(DOM.querySelectorAll(view.nodes[0], 'copy-me').length).toEqual(1); expect(DOM.getText(view.nodes[0])).toEqual('hello'); async.done(); }); @@ -162,7 +162,7 @@ export function main() { createView(pv); cd.detectChanges(); - expect(view.nodes[0].querySelectorAll('copy-me').length).toEqual(1); + expect(DOM.querySelectorAll(view.nodes[0], 'copy-me').length).toEqual(1); expect(DOM.getText(view.nodes[0])).toEqual('hello'); async.done(); }); @@ -173,7 +173,7 @@ export function main() { createView(pv); cd.detectChanges(); - expect(view.nodes[0].querySelectorAll('copy-me').length).toEqual(0); + expect(DOM.querySelectorAll(view.nodes[0], 'copy-me').length).toEqual(0); expect(DOM.getText(view.nodes[0])).toEqual(''); async.done(); }); @@ -184,12 +184,12 @@ export function main() { createView(pv); cd.detectChanges(); - expect(view.nodes[0].querySelectorAll('copy-me').length).toEqual(1); + expect(DOM.querySelectorAll(view.nodes[0], 'copy-me').length).toEqual(1); expect(DOM.getText(view.nodes[0])).toEqual('hello'); component.numberCondition = 2; cd.detectChanges(); - expect(view.nodes[0].querySelectorAll('copy-me').length).toEqual(1); + expect(DOM.querySelectorAll(view.nodes[0], 'copy-me').length).toEqual(1); expect(DOM.getText(view.nodes[0])).toEqual('hello'); async.done(); @@ -215,7 +215,7 @@ export function main() { compileWithTemplate('
hello
').then((pv) => { createView(pv); expect(function(){cd.detectChanges();}).toThrowError(); - expect(view.nodes[0].querySelectorAll('copy-me').length).toEqual(0); + expect(DOM.querySelectorAll(view.nodes[0], 'copy-me').length).toEqual(0); expect(DOM.getText(view.nodes[0])).toEqual(''); async.done(); }); diff --git a/modules/angular2/test/forms/integration_spec.js b/modules/angular2/test/forms/integration_spec.js index c0ac2ff817..5a535a45f3 100644 --- a/modules/angular2/test/forms/integration_spec.js +++ b/modules/angular2/test/forms/integration_spec.js @@ -12,6 +12,7 @@ import { it, queryView, xit, + IS_NODEJS } from 'angular2/test_lib'; import {Lexer, Parser, ChangeDetector, dynamicChangeDetection} from 'angular2/change_detection'; @@ -85,26 +86,28 @@ export function main() { }); })); - it("should update the control group values on DOM change", inject([AsyncTestCompleter], (async) => { - var form = new ControlGroup({ - "login": new Control("oldValue") - }); - var ctx = new MyComp(form); + if (!IS_NODEJS) { + it("should update the control group values on DOM change", inject([AsyncTestCompleter], (async) => { + var form = new ControlGroup({ + "login": new Control("oldValue") + }); + var ctx = new MyComp(form); - var t = `
- -
`; + var t = `
+ +
`; - compile(MyComp, t, ctx, (view) => { - var input = queryView(view, "input") + compile(MyComp, t, ctx, (view) => { + var input = queryView(view, "input") - input.value = "updatedValue"; - dispatchEvent(input, "change"); + input.value = "updatedValue"; + dispatchEvent(input, "change"); - expect(form.value).toEqual({"login": "updatedValue"}); - async.done(); - }); - })); + expect(form.value).toEqual({"login": "updatedValue"}); + async.done(); + }); + })); + } it("should update DOM elements when rebinding the control group", inject([AsyncTestCompleter], (async) => { var form = new ControlGroup({ @@ -150,89 +153,91 @@ export function main() { }); })); - describe("different control types", () => { - it("should support type=checkbox", inject([AsyncTestCompleter], (async) => { - var ctx = new MyComp(new ControlGroup({"checkbox": new Control(true)})); + if (!IS_NODEJS) { + describe("different control types", () => { + it("should support type=checkbox", inject([AsyncTestCompleter], (async) => { + var ctx = new MyComp(new ControlGroup({"checkbox": new Control(true)})); - var t = `
- -
`; + var t = `
+ +
`; - compile(MyComp, t, ctx, (view) => { - var input = queryView(view, "input") - expect(input.checked).toBe(true); + compile(MyComp, t, ctx, (view) => { + var input = queryView(view, "input") + expect(input.checked).toBe(true); - input.checked = false; - dispatchEvent(input, "change"); + input.checked = false; + dispatchEvent(input, "change"); - expect(ctx.form.value).toEqual({"checkbox" : false}); - async.done(); - }); - })); + expect(ctx.form.value).toEqual({"checkbox" : false}); + async.done(); + }); + })); - it("should support custom value accessors", inject([AsyncTestCompleter], (async) => { - var ctx = new MyComp(new ControlGroup({"name": new Control("aa")})); + it("should support custom value accessors", inject([AsyncTestCompleter], (async) => { + var ctx = new MyComp(new ControlGroup({"name": new Control("aa")})); - var t = `
- -
`; + var t = `
+ +
`; - compile(MyComp, t, ctx, (view) => { - var input = queryView(view, "input") - expect(input.value).toEqual("!aa!"); + compile(MyComp, t, ctx, (view) => { + var input = queryView(view, "input") + expect(input.value).toEqual("!aa!"); - input.value = "!bb!"; - dispatchEvent(input, "change"); + input.value = "!bb!"; + dispatchEvent(input, "change"); - expect(ctx.form.value).toEqual({"name" : "bb"}); - async.done(); - }); - })); - }); + expect(ctx.form.value).toEqual({"name" : "bb"}); + async.done(); + }); + })); + }); - describe("validations", () => { - it("should use validators defined in html", inject([AsyncTestCompleter], (async) => { - var form = new ControlGroup({"login": new Control("aa")}); - var ctx = new MyComp(form); + describe("validations", () => { + it("should use validators defined in html", inject([AsyncTestCompleter], (async) => { + var form = new ControlGroup({"login": new Control("aa")}); + var ctx = new MyComp(form); - var t = `
- -
`; + var t = `
+ +
`; - compile(MyComp, t, ctx, (view) => { - expect(form.valid).toEqual(true); + compile(MyComp, t, ctx, (view) => { + expect(form.valid).toEqual(true); - var input = queryView(view, "input"); + var input = queryView(view, "input"); - input.value = ""; - dispatchEvent(input, "change"); + input.value = ""; + dispatchEvent(input, "change"); - expect(form.valid).toEqual(false); - async.done(); - }); - })); + expect(form.valid).toEqual(false); + async.done(); + }); + })); - it("should use validators defined in the model", inject([AsyncTestCompleter], (async) => { - var form = new ControlGroup({"login": new Control("aa", validators.required)}); - var ctx = new MyComp(form); + it("should use validators defined in the model", inject([AsyncTestCompleter], (async) => { + var form = new ControlGroup({"login": new Control("aa", validators.required)}); + var ctx = new MyComp(form); - var t = `
- -
`; + var t = `
+ +
`; - compile(MyComp, t, ctx, (view) => { - expect(form.valid).toEqual(true); + compile(MyComp, t, ctx, (view) => { + expect(form.valid).toEqual(true); - var input = queryView(view, "input"); + var input = queryView(view, "input"); - input.value = ""; - dispatchEvent(input, "change"); + input.value = ""; + dispatchEvent(input, "change"); - expect(form.valid).toEqual(false); - async.done(); - }); - })); - }); + expect(form.valid).toEqual(false); + async.done(); + }); + })); + }); + } describe("nested forms", () => { it("should init DOM with the given form object",inject([AsyncTestCompleter], (async) => { @@ -256,30 +261,32 @@ export function main() { }); })); - it("should update the control group values on DOM change", inject([AsyncTestCompleter], (async) => { - var form = new ControlGroup({ - "nested": new ControlGroup({ - "login": new Control("value") - }) - }); - var ctx = new MyComp(form); + if (!IS_NODEJS) { + it("should update the control group values on DOM change", inject([AsyncTestCompleter], (async) => { + var form = new ControlGroup({ + "nested": new ControlGroup({ + "login": new Control("value") + }) + }); + var ctx = new MyComp(form); - var t = `
-
- -
-
`; + var t = `
+
+ +
+
`; - compile(MyComp, t, ctx, (view) => { - var input = queryView(view, "input") + compile(MyComp, t, ctx, (view) => { + var input = queryView(view, "input") - input.value = "updatedValue"; - dispatchEvent(input, "change"); + input.value = "updatedValue"; + dispatchEvent(input, "change"); - expect(form.value).toEqual({"nested" : {"login" : "updatedValue"}}); - async.done(); - }); - })); + expect(form.value).toEqual({"nested" : {"login" : "updatedValue"}}); + async.done(); + }); + })); + } }); }); }