feat(directives/forms): run tests in NodeJS

Closes #921
This commit is contained in:
Marc Laval 2015-03-09 17:41:49 +01:00
parent 7322ed721e
commit e8965656a4
6 changed files with 180 additions and 142 deletions

View File

@ -229,8 +229,18 @@ var CONFIG = {
formatDart: { formatDart: {
packageName: 'dart_style', packageName: 'dart_style',
args: ['dart_style:format', '-w', 'dist/dart'] 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 // clean
@ -553,7 +563,7 @@ gulp.task('test.unit.dart/ci', function (done) {
singleRun: true, reporters: ['dots'], browsers: getBrowsersFromCLI()}, done); singleRun: true, reporters: ['dots'], browsers: getBrowsersFromCLI()}, done);
}); });
gulp.task('test.unit.cjs', function (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}*/));
}); });
// ------------------ // ------------------

View File

@ -10,6 +10,7 @@ var url = require('url');
import {List, MapWrapper, ListWrapper, StringMapWrapper} from 'angular2/src/facade/collection'; import {List, MapWrapper, ListWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
import {DomAdapter, setRootDomAdapter} from './dom_adapter'; import {DomAdapter, setRootDomAdapter} from './dom_adapter';
import {BaseException, isPresent, isBlank} from 'angular2/src/facade/lang'; import {BaseException, isPresent, isBlank} from 'angular2/src/facade/lang';
import {SelectorMatcher, CssSelector} from 'angular2/src/core/compiler/selector';
var _attrToPropMap = { var _attrToPropMap = {
'inner-html': 'innerHTML', 'inner-html': 'innerHTML',
@ -35,27 +36,55 @@ export class Parse5DomAdapter extends DomAdapter {
throw _notImplemented('query'); throw _notImplemented('query');
} }
querySelector(el, selector:string) { querySelector(el, selector:string) {
throw _notImplemented('querySelector'); return this.querySelectorAll(el, selector)[0];
} }
querySelectorAll(el, selector:string) { querySelectorAll(el, selector:string) {
//TODO: use selector class from core. For now, only works for .classname ...
var res = ListWrapper.create(); var res = ListWrapper.create();
var _recursive = (result, node, className) => { var _recursive = (result, node, selector, matcher) => {
if (this.hasClass(node, className)) { if (this.elementMatches(node, selector, matcher)) {
ListWrapper.push(result, node); ListWrapper.push(result, node);
} }
var cNodes = node.childNodes; var cNodes = node.childNodes;
if (cNodes && cNodes.length > 0) { if (cNodes && cNodes.length > 0) {
for (var i = 0; i < cNodes.length; i++) { 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; 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) { on(el, evt, listener) {
throw _notImplemented('on'); //Do nothing, in order to not break forms integration tests
} }
dispatchEvent(el, evt) { dispatchEvent(el, evt) {
throw _notImplemented('dispatchEvent'); throw _notImplemented('dispatchEvent');
@ -316,7 +345,7 @@ export class Parse5DomAdapter extends DomAdapter {
return res; return res;
} }
getAttribute(element, attribute:string) { 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) { setAttribute(element, attribute:string, value:string) {
if (attribute) { if (attribute) {
@ -341,14 +370,6 @@ export class Parse5DomAdapter extends DomAdapter {
} }
return defDoc; 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 { isTemplateElement(el:any):boolean {
return this.isElementNode(el) && this.tagName(el) === "template"; return this.isElementNode(el) && this.tagName(el) === "template";
} }

View File

@ -1,26 +1,26 @@
import {StringMapWrapper, ListWrapper} from 'angular2/src/facade/collection'; import {StringMapWrapper, ListWrapper} from 'angular2/src/facade/collection';
import {isPresent} from 'angular2/src/facade/lang'; import {isPresent} from 'angular2/src/facade/lang';
import {ControlGroup, Control, OptionalControl, OptionalControlGroup} from 'angular2/forms'; import * as modelModule from './model';
export class FormBuilder { export class FormBuilder {
group(controlsConfig, extra = null):ControlGroup { group(controlsConfig, extra = null):modelModule.ControlGroup {
var controls = this._reduceControls(controlsConfig); var controls = this._reduceControls(controlsConfig);
var optionals = isPresent(extra) ? StringMapWrapper.get(extra, "optionals") : null; var optionals = isPresent(extra) ? StringMapWrapper.get(extra, "optionals") : null;
var validator = isPresent(extra) ? StringMapWrapper.get(extra, "validator") : null; var validator = isPresent(extra) ? StringMapWrapper.get(extra, "validator") : null;
if (isPresent(validator)) { if (isPresent(validator)) {
return new ControlGroup(controls, optionals, validator); return new modelModule.ControlGroup(controls, optionals, validator);
} else { } 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)) { if (isPresent(validator)) {
return new Control(value, validator); return new modelModule.Control(value, validator);
} else { } else {
return new Control(value); return new modelModule.Control(value);
} }
} }
@ -33,7 +33,7 @@ export class FormBuilder {
} }
_createControl(controlConfig) { _createControl(controlConfig) {
if (controlConfig instanceof Control || controlConfig instanceof ControlGroup) { if (controlConfig instanceof modelModule.Control || controlConfig instanceof modelModule.ControlGroup) {
return controlConfig; return controlConfig;
} else if (ListWrapper.isList(controlConfig)) { } else if (ListWrapper.isList(controlConfig)) {

View File

@ -1,18 +1,18 @@
import {isBlank, isPresent} from 'angular2/src/facade/lang'; import {isBlank, isPresent} from 'angular2/src/facade/lang';
import {List, ListWrapper, StringMapWrapper} from 'angular2/src/facade/collection'; 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; return isBlank(c.value) || c.value == "" ? {"required" : true} : null;
} }
export function nullValidator(c:Control) { export function nullValidator(c:modelModule.Control) {
return null; return null;
} }
export function compose(validators:List<Function>):Function { export function compose(validators:List<Function>):Function {
return function(c:Control) { return function(c:modelModule.Control) {
var res = ListWrapper.reduce(validators, (res, validator) => { var res = ListWrapper.reduce(validators, (res, validator) => {
var errors = validator(c); var errors = validator(c);
return isPresent(errors) ? StringMapWrapper.merge(res, errors) : res; return isPresent(errors) ? StringMapWrapper.merge(res, errors) : res;
@ -21,7 +21,7 @@ export function compose(validators:List<Function>):Function {
} }
} }
export function controlGroupValidator(c:ControlGroup) { export function controlGroupValidator(c:modelModule.ControlGroup) {
var res = {}; var res = {};
StringMapWrapper.forEach(c.controls, (control, name) => { StringMapWrapper.forEach(c.controls, (control, name) => {
if (c.contains(name) && isPresent(control.errors)) { if (c.contains(name) && isPresent(control.errors)) {

View File

@ -75,7 +75,7 @@ export function main() {
createView(pv); createView(pv);
cd.detectChanges(); 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'); expect(DOM.getText(view.nodes[0])).toEqual('hello');
async.done(); async.done();
}); });
@ -86,7 +86,7 @@ export function main() {
createView(pv); createView(pv);
cd.detectChanges(); 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'); expect(DOM.getText(view.nodes[0])).toEqual('hello2');
async.done(); async.done();
}); });
@ -98,18 +98,18 @@ export function main() {
component.booleanCondition = false; component.booleanCondition = false;
cd.detectChanges(); 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(''); expect(DOM.getText(view.nodes[0])).toEqual('');
component.booleanCondition = true; component.booleanCondition = true;
cd.detectChanges(); 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'); expect(DOM.getText(view.nodes[0])).toEqual('hello');
component.booleanCondition = false; component.booleanCondition = false;
cd.detectChanges(); 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(''); expect(DOM.getText(view.nodes[0])).toEqual('');
async.done(); async.done();
@ -127,18 +127,18 @@ export function main() {
createView(pv); createView(pv);
cd.detectChanges(); 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'); expect(DOM.getText(view.nodes[0])).toEqual('helloNumberhelloStringhelloFunction');
component.numberCondition = 0; component.numberCondition = 0;
cd.detectChanges(); 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'); expect(DOM.getText(view.nodes[0])).toEqual('helloString');
component.numberCondition = 1; component.numberCondition = 1;
component.stringCondition = "bar"; component.stringCondition = "bar";
cd.detectChanges(); 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'); expect(DOM.getText(view.nodes[0])).toEqual('helloNumber');
async.done(); async.done();
}); });
@ -151,7 +151,7 @@ export function main() {
createView(pv); createView(pv);
cd.detectChanges(); 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'); expect(DOM.getText(view.nodes[0])).toEqual('hello');
async.done(); async.done();
}); });
@ -162,7 +162,7 @@ export function main() {
createView(pv); createView(pv);
cd.detectChanges(); 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'); expect(DOM.getText(view.nodes[0])).toEqual('hello');
async.done(); async.done();
}); });
@ -173,7 +173,7 @@ export function main() {
createView(pv); createView(pv);
cd.detectChanges(); 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(''); expect(DOM.getText(view.nodes[0])).toEqual('');
async.done(); async.done();
}); });
@ -184,12 +184,12 @@ export function main() {
createView(pv); createView(pv);
cd.detectChanges(); 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'); expect(DOM.getText(view.nodes[0])).toEqual('hello');
component.numberCondition = 2; component.numberCondition = 2;
cd.detectChanges(); 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'); expect(DOM.getText(view.nodes[0])).toEqual('hello');
async.done(); async.done();
@ -215,7 +215,7 @@ export function main() {
compileWithTemplate('<div><copy-me template="if numberCondition">hello</copy-me></div>').then((pv) => { compileWithTemplate('<div><copy-me template="if numberCondition">hello</copy-me></div>').then((pv) => {
createView(pv); createView(pv);
expect(function(){cd.detectChanges();}).toThrowError(); 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(''); expect(DOM.getText(view.nodes[0])).toEqual('');
async.done(); async.done();
}); });

View File

@ -12,6 +12,7 @@ import {
it, it,
queryView, queryView,
xit, xit,
IS_NODEJS
} from 'angular2/test_lib'; } from 'angular2/test_lib';
import {Lexer, Parser, ChangeDetector, dynamicChangeDetection} from 'angular2/change_detection'; 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) => { if (!IS_NODEJS) {
var form = new ControlGroup({ it("should update the control group values on DOM change", inject([AsyncTestCompleter], (async) => {
"login": new Control("oldValue") var form = new ControlGroup({
}); "login": new Control("oldValue")
var ctx = new MyComp(form); });
var ctx = new MyComp(form);
var t = `<div [control-group]="form"> var t = `<div [control-group]="form">
<input type="text" control="login"> <input type="text" control="login">
</div>`; </div>`;
compile(MyComp, t, ctx, (view) => { compile(MyComp, t, ctx, (view) => {
var input = queryView(view, "input") var input = queryView(view, "input")
input.value = "updatedValue"; input.value = "updatedValue";
dispatchEvent(input, "change"); dispatchEvent(input, "change");
expect(form.value).toEqual({"login": "updatedValue"}); expect(form.value).toEqual({"login": "updatedValue"});
async.done(); async.done();
}); });
})); }));
}
it("should update DOM elements when rebinding the control group", inject([AsyncTestCompleter], (async) => { it("should update DOM elements when rebinding the control group", inject([AsyncTestCompleter], (async) => {
var form = new ControlGroup({ var form = new ControlGroup({
@ -150,89 +153,91 @@ export function main() {
}); });
})); }));
describe("different control types", () => { if (!IS_NODEJS) {
it("should support type=checkbox", inject([AsyncTestCompleter], (async) => { describe("different control types", () => {
var ctx = new MyComp(new ControlGroup({"checkbox": new Control(true)})); it("should support type=checkbox", inject([AsyncTestCompleter], (async) => {
var ctx = new MyComp(new ControlGroup({"checkbox": new Control(true)}));
var t = `<div [control-group]="form"> var t = `<div [control-group]="form">
<input type="checkbox" control="checkbox"> <input type="checkbox" control="checkbox">
</div>`; </div>`;
compile(MyComp, t, ctx, (view) => { compile(MyComp, t, ctx, (view) => {
var input = queryView(view, "input") var input = queryView(view, "input")
expect(input.checked).toBe(true); expect(input.checked).toBe(true);
input.checked = false; input.checked = false;
dispatchEvent(input, "change"); dispatchEvent(input, "change");
expect(ctx.form.value).toEqual({"checkbox" : false}); expect(ctx.form.value).toEqual({"checkbox" : false});
async.done(); async.done();
}); });
})); }));
it("should support custom value accessors", inject([AsyncTestCompleter], (async) => { it("should support custom value accessors", inject([AsyncTestCompleter], (async) => {
var ctx = new MyComp(new ControlGroup({"name": new Control("aa")})); var ctx = new MyComp(new ControlGroup({"name": new Control("aa")}));
var t = `<div [control-group]="form"> var t = `<div [control-group]="form">
<input type="text" control="name" wrapped-value> <input type="text" control="name" wrapped-value>
</div>`; </div>`;
compile(MyComp, t, ctx, (view) => { compile(MyComp, t, ctx, (view) => {
var input = queryView(view, "input") var input = queryView(view, "input")
expect(input.value).toEqual("!aa!"); expect(input.value).toEqual("!aa!");
input.value = "!bb!"; input.value = "!bb!";
dispatchEvent(input, "change"); dispatchEvent(input, "change");
expect(ctx.form.value).toEqual({"name" : "bb"}); expect(ctx.form.value).toEqual({"name" : "bb"});
async.done(); async.done();
}); });
})); }));
}); });
describe("validations", () => { describe("validations", () => {
it("should use validators defined in html", inject([AsyncTestCompleter], (async) => { it("should use validators defined in html", inject([AsyncTestCompleter], (async) => {
var form = new ControlGroup({"login": new Control("aa")}); var form = new ControlGroup({"login": new Control("aa")});
var ctx = new MyComp(form); var ctx = new MyComp(form);
var t = `<div [control-group]="form"> var t = `<div [control-group]="form">
<input type="text" control="login" required> <input type="text" control="login" required>
</div>`; </div>`;
compile(MyComp, t, ctx, (view) => { compile(MyComp, t, ctx, (view) => {
expect(form.valid).toEqual(true); expect(form.valid).toEqual(true);
var input = queryView(view, "input"); var input = queryView(view, "input");
input.value = ""; input.value = "";
dispatchEvent(input, "change"); dispatchEvent(input, "change");
expect(form.valid).toEqual(false); expect(form.valid).toEqual(false);
async.done(); async.done();
}); });
})); }));
it("should use validators defined in the model", inject([AsyncTestCompleter], (async) => { it("should use validators defined in the model", inject([AsyncTestCompleter], (async) => {
var form = new ControlGroup({"login": new Control("aa", validators.required)}); var form = new ControlGroup({"login": new Control("aa", validators.required)});
var ctx = new MyComp(form); var ctx = new MyComp(form);
var t = `<div [control-group]="form"> var t = `<div [control-group]="form">
<input type="text" control="login"> <input type="text" control="login">
</div>`; </div>`;
compile(MyComp, t, ctx, (view) => { compile(MyComp, t, ctx, (view) => {
expect(form.valid).toEqual(true); expect(form.valid).toEqual(true);
var input = queryView(view, "input"); var input = queryView(view, "input");
input.value = ""; input.value = "";
dispatchEvent(input, "change"); dispatchEvent(input, "change");
expect(form.valid).toEqual(false); expect(form.valid).toEqual(false);
async.done(); async.done();
}); });
})); }));
}); });
}
describe("nested forms", () => { describe("nested forms", () => {
it("should init DOM with the given form object",inject([AsyncTestCompleter], (async) => { 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) => { if (!IS_NODEJS) {
var form = new ControlGroup({ it("should update the control group values on DOM change", inject([AsyncTestCompleter], (async) => {
"nested": new ControlGroup({ var form = new ControlGroup({
"login": new Control("value") "nested": new ControlGroup({
}) "login": new Control("value")
}); })
var ctx = new MyComp(form); });
var ctx = new MyComp(form);
var t = `<div [control-group]="form"> var t = `<div [control-group]="form">
<div control-group="nested"> <div control-group="nested">
<input type="text" control="login"> <input type="text" control="login">
</div> </div>
</div>`; </div>`;
compile(MyComp, t, ctx, (view) => { compile(MyComp, t, ctx, (view) => {
var input = queryView(view, "input") var input = queryView(view, "input")
input.value = "updatedValue"; input.value = "updatedValue";
dispatchEvent(input, "change"); dispatchEvent(input, "change");
expect(form.value).toEqual({"nested" : {"login" : "updatedValue"}}); expect(form.value).toEqual({"nested" : {"login" : "updatedValue"}});
async.done(); async.done();
}); });
})); }));
}
}); });
}); });
} }