feat(compiler): parse5 DOM adapter

Closes #841
This commit is contained in:
Marc Laval
2015-03-11 14:42:54 +01:00
parent 537f943f57
commit 1d4ff9bcdc
13 changed files with 647 additions and 140 deletions
+3 -2
View File
@@ -45,8 +45,9 @@ If you don't already have `npm`, get it by installing [node.js](http://nodejs.or
### Unit tests
1. `gulp test.unit.js`: JS tests
2. `gulp test.unit.dart`: Dart tests
1. `gulp test.unit.js`: JS tests in a browser
2. `gulp test.unit.cjs`: JS tests in NodeJS (requires a build before)
3. `gulp test.unit.dart`: Dart tests
Notes for transpiler tests:
+9 -1
View File
@@ -19,6 +19,7 @@ var karma = require('karma').server;
var minimist = require('minimist');
var es5build = require('./tools/build/es5build');
var runServerDartTests = require('./tools/build/run_server_dart_tests');
var transformCJSTests = require('./tools/build/transformCJSTests');
var util = require('./tools/build/util');
var DART_SDK = require('./tools/build/dartdetect')(gulp);
@@ -299,12 +300,15 @@ gulp.task('build/transpile.js.prod', function(done) {
});
gulp.task('build/transpile.js.cjs', transpile(gulp, gulpPlugins, {
src: CONFIG.transpile.src.js,
src: CONFIG.transpile.src.js.concat(['modules/**/*.cjs']),
dest: CONFIG.dest.js.cjs,
outputExt: 'js',
options: CONFIG.transpile.options.js.cjs,
srcFolderInsertion: CONFIG.srcFolderInsertion.js
}));
gulp.task('build/transformCJSTests', function() {
return gulp.src(CONFIG.dest.js.cjs + '/angular2/test/**/*_spec.js').pipe(transformCJSTests()).pipe(gulp.dest(CONFIG.dest.js.cjs + '/angular2/test/'));
});
gulp.task('build/transpile.dart', transpile(gulp, gulpPlugins, {
src: CONFIG.transpile.src.dart,
@@ -548,6 +552,9 @@ gulp.task('test.unit.dart/ci', function (done) {
karma.start({configFile: __dirname + '/karma-dart.conf.js',
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}));
});
// ------------------
// server tests
@@ -616,6 +623,7 @@ gulp.task('build.js.cjs', function(done) {
runSequence(
['build/transpile.js.cjs', 'build/copy.js.cjs', 'build/multicopy.js.cjs'],
['build/linknodemodules.js.cjs'],
'build/transformCJSTests',
done
);
});
@@ -184,7 +184,9 @@ export class ElementBinderBuilder extends CompileStep {
} else {
property = this._resolvePropertyName(property);
//TODO(pk): special casing innerHtml, see: https://github.com/angular/angular/issues/789
if (DOM.hasProperty(compileElement.element, property) || StringWrapper.equals(property, 'innerHtml')) {
if (StringWrapper.equals(property, 'innerHTML')) {
setterFn = (element, value) => DOM.setInnerHTML(element, value);
} else if (DOM.hasProperty(compileElement.element, property) || StringWrapper.equals(property, 'innerHtml')) {
setterFn = reflector.setter(property);
}
}
File diff suppressed because it is too large Load Diff
@@ -11,6 +11,7 @@ import 'package:collection/equality.dart';
import 'package:angular2/src/dom/dom_adapter.dart' show DOM;
bool IS_DARTIUM = true;
bool IS_NODEJS = false;
Expect expect(actual, [matcher]) {
final expect = new Expect(actual);
+59 -23
View File
@@ -1,23 +1,26 @@
import {DOM} from 'angular2/src/dom/dom_adapter';
var _global = typeof window === 'undefined' ? global : window;
export {proxy} from 'rtts_assert/rtts_assert';
export var describe = window.describe;
export var xdescribe = window.xdescribe;
export var ddescribe = window.ddescribe;
export var it = window.it;
export var xit = window.xit;
export var iit = window.iit;
export var beforeEach = window.beforeEach;
export var afterEach = window.afterEach;
export var expect = window.expect;
export var describe = _global.describe;
export var xdescribe = _global.xdescribe;
export var ddescribe = _global.ddescribe;
export var it = _global.it;
export var xit = _global.xit;
export var iit = _global.iit;
export var beforeEach = _global.beforeEach;
export var afterEach = _global.afterEach;
export var expect = _global.expect;
export var IS_DARTIUM = false;
export var IS_NODEJS = typeof window === 'undefined';
// To make testing consistent between dart and js
window.print = function(msg) {
if (window.dump) {
window.dump(msg);
_global.print = function(msg) {
if (_global.dump) {
_global.dump(msg);
} else {
window.console.log(msg);
_global.console.log(msg);
}
};
@@ -25,7 +28,7 @@ window.print = function(msg) {
// gives us bad error messages in tests.
// The only way to do this in Jasmine is to monkey patch a method
// to the object :-(
window.Map.prototype.jasmineToString = function() {
_global.Map.prototype.jasmineToString = function() {
var m = this;
if (!m) {
return ''+m;
@@ -37,7 +40,7 @@ window.Map.prototype.jasmineToString = function() {
return `{ ${res.join(',')} }`;
}
window.beforeEach(function() {
_global.beforeEach(function() {
jasmine.addMatchers({
// Custom handler for Map as Jasmine does not support it yet
toEqual: function(util, customEqualityTesters) {
@@ -150,15 +153,48 @@ export class SpyObject {
function elementText(n) {
var hasNodes = (n) => {var children = DOM.childNodes(n); return children && children.length > 0;}
if (!IS_NODEJS) {
var hasNodes = (n) => {var children = DOM.childNodes(n); return children && children.length > 0;}
if (n instanceof Comment) return '';
if (n instanceof Comment) return '';
if (n instanceof Array) return n.map((nn) => elementText(nn)).join("");
if (n instanceof Element && DOM.tagName(n) == 'CONTENT')
return elementText(Array.prototype.slice.apply(n.getDistributedNodes()));
if (DOM.hasShadowRoot(n)) return elementText(DOM.childNodesAsList(n.shadowRoot));
if (hasNodes(n)) return elementText(DOM.childNodesAsList(n));
if (n instanceof Array) return n.map((nn) => elementText(nn)).join("");
if (n instanceof Element && DOM.tagName(n) == 'CONTENT')
return elementText(Array.prototype.slice.apply(n.getDistributedNodes()));
if (DOM.hasShadowRoot(n)) return elementText(DOM.childNodesAsList(n.shadowRoot));
if (hasNodes(n)) return elementText(DOM.childNodesAsList(n));
return n.textContent;
return n.textContent;
} else {
if (DOM.hasShadowRoot(n)) {
return elementText(DOM.getShadowRoot(n).childNodes);
} else if (n instanceof Array) {
return n.map((nn) => elementText(nn)).join("");
} else if (DOM.tagName(n) == 'content') {
//minimal implementation of getDistributedNodes()
var host = null;
var temp = n;
while (temp.parent) {
if (DOM.hasShadowRoot(temp)) {
host = temp;
}
temp = temp.parent;
}
if (host) {
var list = [];
var select = DOM.getAttribute(n, "select");
var selectClass = select? select.substring(1): null;
DOM.childNodes(host).forEach((child) => {
var classList = DOM.classList(child);
if (selectClass && classList.indexOf(selectClass) > -1 || selectClass == null && classList.length == 0) {
list.push(child);
}
});
return list.length > 0? elementText(list): "";
}
return "";
} else {
return DOM.getText(n);
}
}
}
+2 -21
View File
@@ -19,10 +19,6 @@ import {UrlResolver} from 'angular2/src/core/compiler/url_resolver';
import {StyleUrlResolver} from 'angular2/src/core/compiler/style_url_resolver';
import {CssProcessor} from 'angular2/src/core/compiler/css_processor';
import {EventManager, DomEventsPlugin} from 'angular2/src/core/events/event_manager';
import {VmTurnZone} from 'angular2/src/core/zone/vm_turn_zone';
import {Decorator, Component, Viewport} from 'angular2/src/core/annotations/annotations';
import {Template} from 'angular2/src/core/annotations/template';
import {Parent, Ancestor} from 'angular2/src/core/annotations/visibility';
@@ -64,7 +60,7 @@ export function main() {
ctx = new MyComp();
view = pv.instantiate(
null,
new EventManager([new DomEventsPlugin()], new FakeVmTurnZone()),
null,
reflector
);
view.hydrate(new Injector([]), null, ctx);
@@ -354,7 +350,7 @@ export function main() {
var value = view.contextWithLocals.get('alice');
expect(value).not.toBe(null);
expect(value.tagName).toEqual('DIV');
expect(value.tagName.toLowerCase()).toEqual('div');
done();
})
@@ -724,18 +720,3 @@ class DecoratorListeningEvent {
this.msg = msg;
}
}
class FakeVmTurnZone extends VmTurnZone {
constructor() {
super({enableLongStackTrace: false});
}
run(fn) {
fn();
}
runOutsideAngular(fn) {
fn();
}
}
@@ -1,4 +1,4 @@
import {describe, beforeEach, it, expect, ddescribe, iit, SpyObject, el} from 'angular2/test_lib';
import {describe, beforeEach, it, expect, ddescribe, iit, SpyObject, el, IS_NODEJS} from 'angular2/test_lib';
import {ShadowCss} from 'angular2/src/core/compiler/shadow_dom_emulation/shadow_css';
import {RegExpWrapper, StringWrapper} from 'angular2/src/facade/lang';
@@ -91,13 +91,16 @@ export function main() {
expect(StringWrapper.contains(css, '#menu > .bar {;background: blue;}')).toBeTruthy();
});
it('should support polyfill-rule', () => {
var css = s("polyfill-rule {content: ':host.foo .bar';background: blue;}", 'a', 'a-host');
expect(css).toEqual('[a-host].foo .bar {background: blue;}');
if (!IS_NODEJS) {
//TODO: reactivate once CSS parser is fixed: https://github.com/reworkcss/css/issues/65
it('should support polyfill-rule', () => {
var css = s("polyfill-rule {content: ':host.foo .bar';background: blue;}", 'a', 'a-host');
expect(css).toEqual('[a-host].foo .bar {background: blue;}');
css = s('polyfill-rule {content: ":host.foo .bar";background: blue;}', 'a', 'a-host');
expect(css).toEqual('[a-host].foo .bar {background: blue;}');
});
css = s('polyfill-rule {content: ":host.foo .bar";background: blue;}', 'a', 'a-host');
expect(css).toEqual('[a-host].foo .bar {background: blue;}');
});
}
it('should handle ::shadow', () => {
var css = s('x::shadow > y {}', 'a');
@@ -87,7 +87,7 @@ export function main() {
strategy.attachTemplate(host, view);
var firstChild = DOM.firstChild(host);
expect(DOM.tagName(firstChild)).toEqual('DIV');
expect(DOM.tagName(firstChild).toLowerCase()).toEqual('div');
expect(firstChild).toHaveText('view');
expect(host).toHaveText('view');
});
@@ -222,7 +222,7 @@ export function main() {
strategy.attachTemplate(host, view);
var firstChild = DOM.firstChild(host);
expect(DOM.tagName(firstChild)).toEqual('DIV');
expect(DOM.tagName(firstChild).toLowerCase()).toEqual('div');
expect(firstChild).toHaveText('view');
expect(host).toHaveText('view');
});
+85 -83
View File
@@ -1,4 +1,4 @@
import {describe, xit, it, expect, beforeEach, ddescribe, iit, el, proxy} from 'angular2/test_lib';
import {describe, xit, it, expect, beforeEach, ddescribe, iit, el, proxy, IS_NODEJS} from 'angular2/test_lib';
import {ProtoView, ElementPropertyMemento, DirectivePropertyMemento} from 'angular2/src/core/compiler/view';
import {ProtoElementInjector, ElementInjector, DirectiveBinding} from 'angular2/src/core/compiler/element_injector';
import {EmulatedScopedShadowDomStrategy, NativeShadowDomStrategy} from 'angular2/src/core/compiler/shadow_dom_strategy';
@@ -444,96 +444,98 @@ export function main() {
});
});
describe('event handlers', () => {
var view, ctx, called, receivedEvent, dispatchedEvent;
if (!IS_NODEJS) {
describe('event handlers', () => {
var view, ctx, called, receivedEvent, dispatchedEvent;
function createViewAndContext(protoView) {
view = createView(protoView,
new EventManager([new DomEventsPlugin()], new FakeVmTurnZone()));
ctx = view.context;
called = 0;
receivedEvent = null;
ctx.callMe = (event) => {
called += 1;
receivedEvent = event;
function createViewAndContext(protoView) {
view = createView(protoView,
new EventManager([new DomEventsPlugin()], new FakeVmTurnZone()));
ctx = view.context;
called = 0;
receivedEvent = null;
ctx.callMe = (event) => {
called += 1;
receivedEvent = event;
}
}
}
function dispatchClick(el) {
dispatchedEvent = DOM.createMouseEvent('click');
DOM.dispatchEvent(el, dispatchedEvent);
}
function dispatchClick(el) {
dispatchedEvent = DOM.createMouseEvent('click');
DOM.dispatchEvent(el, dispatchedEvent);
}
function createProtoView() {
var pv = new ProtoView(el('<div class="ng-binding"><div></div></div>'),
new DynamicProtoChangeDetector(null), null);
pv.bindElement(new TestProtoElementInjector(null, 0, []));
pv.bindEvent('click', parser.parseBinding('callMe($event)', null));
return pv;
}
function createProtoView() {
var pv = new ProtoView(el('<div class="ng-binding"><div></div></div>'),
new DynamicProtoChangeDetector(null), null);
pv.bindElement(new TestProtoElementInjector(null, 0, []));
pv.bindEvent('click', parser.parseBinding('callMe($event)', null));
return pv;
}
it('should fire on non-bubbling native events', () => {
createViewAndContext(createProtoView());
it('should fire on non-bubbling native events', () => {
createViewAndContext(createProtoView());
dispatchClick(view.nodes[0]);
dispatchClick(view.nodes[0]);
expect(called).toEqual(1);
expect(receivedEvent).toBe(dispatchedEvent);
expect(called).toEqual(1);
expect(receivedEvent).toBe(dispatchedEvent);
});
it('should not fire on a bubbled native events', () => {
createViewAndContext(createProtoView());
dispatchClick(view.nodes[0].firstChild);
// This test passes trivially on webkit browsers due to
// https://bugs.webkit.org/show_bug.cgi?id=122755
expect(called).toEqual(0);
});
it('should not throw if the view is dehydrated', () => {
createViewAndContext(createProtoView());
view.dehydrate();
expect(() => dispatchClick(view.nodes[0])).not.toThrow();
expect(called).toEqual(0);
});
it('should support custom event emitters', () => {
var pv = new ProtoView(el('<div class="ng-binding"><div></div></div>'),
new DynamicProtoChangeDetector(null), null);
pv.bindElement(new TestProtoElementInjector(null, 0, [EventEmitterDirective]));
pv.bindEvent('click', parser.parseBinding('callMe($event)', null));
createViewAndContext(pv);
var dir = view.elementInjectors[0].get(EventEmitterDirective);
var dispatchedEvent = new Object();
dir.click(dispatchedEvent);
expect(receivedEvent).toBe(dispatchedEvent);
expect(called).toEqual(1);
// Should not eval the binding, because custom emitter takes over.
dispatchClick(view.nodes[0]);
expect(called).toEqual(1);
});
it('should bind to directive events', () => {
var pv = new ProtoView(el('<div class="ng-binding"></div>'),
new DynamicProtoChangeDetector(null), null);
pv.bindElement(new ProtoElementInjector(null, 0, [SomeDirectiveWithEventHandler]));
pv.bindEvent('click', parser.parseAction('onEvent($event)', null), 0);
view = createView(pv, new EventManager([new DomEventsPlugin()], new FakeVmTurnZone()));
var directive = view.elementInjectors[0].get(SomeDirectiveWithEventHandler);
expect(directive.event).toEqual(null);
dispatchClick(view.nodes[0]);
expect(directive.event).toBe(dispatchedEvent);
});
});
it('should not fire on a bubbled native events', () => {
createViewAndContext(createProtoView());
dispatchClick(view.nodes[0].firstChild);
// This test passes trivially on webkit browsers due to
// https://bugs.webkit.org/show_bug.cgi?id=122755
expect(called).toEqual(0);
});
it('should not throw if the view is dehydrated', () => {
createViewAndContext(createProtoView());
view.dehydrate();
expect(() => dispatchClick(view.nodes[0])).not.toThrow();
expect(called).toEqual(0);
});
it('should support custom event emitters', () => {
var pv = new ProtoView(el('<div class="ng-binding"><div></div></div>'),
new DynamicProtoChangeDetector(null), null);
pv.bindElement(new TestProtoElementInjector(null, 0, [EventEmitterDirective]));
pv.bindEvent('click', parser.parseBinding('callMe($event)', null));
createViewAndContext(pv);
var dir = view.elementInjectors[0].get(EventEmitterDirective);
var dispatchedEvent = new Object();
dir.click(dispatchedEvent);
expect(receivedEvent).toBe(dispatchedEvent);
expect(called).toEqual(1);
// Should not eval the binding, because custom emitter takes over.
dispatchClick(view.nodes[0]);
expect(called).toEqual(1);
});
it('should bind to directive events', () => {
var pv = new ProtoView(el('<div class="ng-binding"></div>'),
new DynamicProtoChangeDetector(null), null);
pv.bindElement(new ProtoElementInjector(null, 0, [SomeDirectiveWithEventHandler]));
pv.bindEvent('click', parser.parseAction('onEvent($event)', null), 0);
view = createView(pv, new EventManager([new DomEventsPlugin()], new FakeVmTurnZone()));
var directive = view.elementInjectors[0].get(SomeDirectiveWithEventHandler);
expect(directive.event).toEqual(null);
dispatchClick(view.nodes[0]);
expect(directive.event).toBe(dispatchedEvent);
});
});
}
describe('react to record changes', () => {
var view, cd, ctx;
+2
View File
@@ -36,6 +36,7 @@
"angular": "1.3.5",
"bower": "^1.3.12",
"canonical-path": "0.0.2",
"css-parse": "2.0.0",
"del": "~1",
"dgeni": "^0.4.1",
"dgeni-packages": "^0.10.10",
@@ -63,6 +64,7 @@
"merge": "^1.2.0",
"minimatch": "^2.0.1",
"minimist": "1.1.x",
"parse5": "1.3.2",
"protractor": "1.7.x",
"q": "^1.0.1",
"run-sequence": "^0.3.6",
+1
View File
@@ -10,3 +10,4 @@ cd $SCRIPT_DIR/../..
./node_modules/.bin/gulp test.transpiler.unittest
./node_modules/.bin/gulp docs/test
./node_modules/.bin/gulp test.unit.js/ci --browsers=$KARMA_BROWSERS
./node_modules/.bin/gulp test.unit.cjs
+16
View File
@@ -0,0 +1,16 @@
var through2 = require('through2');
module.exports = function() {
return through2.obj(function(file, encoding, done) {
if (file.relative.substring(file.relative.length - 8) == "_spec.js") {
var content =
"var parse5Adapter = require('angular2/src/dom/parse5_adapter');\r\n"
+ "parse5Adapter.Parse5DomAdapter.makeCurrent();\r\n"
+ String(file.contents)
+ "\r\n main();";
file.contents = new Buffer(content);
}
this.push(file);
done();
});
}