Skip to content

Commit

Permalink
fix(Compiler): fix text nodes after content tags
Browse files Browse the repository at this point in the history
  • Loading branch information
vicb committed Jun 14, 2015
1 parent 32bbce9 commit f103d9b
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 29 deletions.
23 changes: 15 additions & 8 deletions modules/angular2/src/render/dom/shadow_dom/content_tag.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as ldModule from './light_dom';
import {DOM} from 'angular2/src/dom/dom_adapter';
import {isPresent} from 'angular2/src/facade/lang';
import {isPresent, isBlank} from 'angular2/src/facade/lang';
import {List, ListWrapper} from 'angular2/src/facade/collection';

class ContentStrategy {
Expand All @@ -20,23 +20,30 @@ class RenderedContent extends ContentStrategy {
constructor(contentEl) {
super();
this.beginScript = contentEl;
this.endScript = DOM.nextSibling(this.beginScript);
this.nodes = [];
}

// Inserts the nodes in between the start and end scripts.
// Previous content is removed.
insert(nodes: List</*node*/ any>) {
this.nodes = nodes;

if (isBlank(this.endScript)) {
// On first invocation, we need to create the end marker
this.endScript = DOM.createScriptTag('type', 'ng/contentEnd');
DOM.insertAfter(this.beginScript, this.endScript);
} else {
// On subsequent invocations, only remove all the nodes between the start end end markers
this._removeNodes();
}

DOM.insertAllBefore(this.endScript, nodes);
this._removeNodesUntil(ListWrapper.isEmpty(nodes) ? this.endScript : nodes[0]);
}

_removeNodesUntil(node) {
var p = DOM.parentElement(this.beginScript);
for (var next = DOM.nextSibling(this.beginScript); next !== node;
next = DOM.nextSibling(this.beginScript)) {
DOM.removeChild(p, next);
_removeNodes() {
for (var node = DOM.nextSibling(this.beginScript); node !== this.endScript;
node = DOM.nextSibling(this.beginScript)) {
DOM.remove(node);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,15 @@ export class ShadowDomCompileStep implements CompileStep {
var selector = MapWrapper.get(attrs, 'select');
selector = isPresent(selector) ? selector : '';

// The content tag should be replaced by a pair of marker tags (start & end).
// The end marker creation is delayed to keep the number of elements constant.
// Creating the end marker here would invalidate the parent's textNodeIndices for the subsequent
// text nodes
var contentStart = DOM.createScriptTag('type', 'ng/contentStart');
if (assertionsEnabled()) {
DOM.setAttribute(contentStart, 'select', selector);
}
var contentEnd = DOM.createScriptTag('type', 'ng/contentEnd');
DOM.insertBefore(current.element, contentStart);
DOM.insertBefore(current.element, contentEnd);
DOM.remove(current.element);

current.element = contentStart;
Expand Down
34 changes: 15 additions & 19 deletions modules/angular2/test/render/dom/shadow_dom/content_tag_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,43 +13,39 @@ import {DOM} from 'angular2/src/dom/dom_adapter';
import {Content} from 'angular2/src/render/dom/shadow_dom/content_tag';

var _scriptStart = `<script start=""></script>`;
var _scriptEnd = `<script end=""></script>`;

export function main() {
describe('Content', function() {
var parent;
var content;

beforeEach(() => {
parent = el(`<div>${_scriptStart}${_scriptEnd}`);
content = DOM.firstChild(parent);
parent = el(`<div>${_scriptStart}</div>`);
let contentStartMarker = DOM.firstChild(parent);
content = new Content(contentStartMarker, '');
});

it("should insert the nodes", () => {
var c = new Content(content, '');
c.init(null);
c.insert([el("<a></a>"), el("<b></b>")])
content.init(null);
content.insert([el("<a>A</a>"), el("<b>B</b>")]);

expect(DOM.getInnerHTML(parent))
.toEqual(`${_scriptStart}<a></a><b></b>${_scriptEnd}`);
expect(parent).toHaveText('AB');
});

it("should remove the nodes from the previous insertion", () => {
var c = new Content(content, '');
c.init(null);
c.insert([el("<a></a>")]);
c.insert([el("<b></b>")]);
content.init(null);
content.insert([el("<a>A</a>")]);
content.insert([el("<b>B</b>")]);

expect(DOM.getInnerHTML(parent)).toEqual(`${_scriptStart}<b></b>${_scriptEnd}`);
expect(parent).toHaveText('B');
});

it("should insert empty list", () => {
var c = new Content(content, '');
c.init(null);
c.insert([el("<a></a>")]);
c.insert([]);
it("should clear nodes on inserting an empty list", () => {
content.init(null);
content.insert([el("<a>A</a>")]);
content.insert([]);

expect(DOM.getInnerHTML(parent)).toEqual(`${_scriptStart}${_scriptEnd}`);
expect(parent).toHaveText('');
});
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
xit,
beforeEachBindings,
SpyObject,
TestComponentBuilder
} from 'angular2/test_lib';

import {bind} from 'angular2/di';
Expand All @@ -34,6 +35,10 @@ import {StyleInliner} from 'angular2/src/render/dom/shadow_dom/style_inliner';

import {DomTestbed} from './dom_testbed';

import {Injectable} from 'angular2/di';

import {Component, View} from 'angular2/annotations';

export function main() {
describe('ShadowDom integration tests', function() {
var strategies = {
Expand All @@ -60,6 +65,19 @@ export function main() {
beforeEachBindings(() => { return [strategyBinding, DomTestbed]; });

describe(`${name} shadow dom strategy`, () => {
// GH-2095 - https://github.com/angular/angular/issues/2095
it('should support text nodes after content tags',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb, async) => {

tcb.createAsync(TextAfterContentTag)
.then((rootTestComponent) => {
rootTestComponent.detectChanges();

expect(rootTestComponent.domElement).toHaveText('P,text');

async.done();
});
}));

it('should support simple components',
inject([AsyncTestCompleter, DomTestbed], (async, tb) => {
Expand Down Expand Up @@ -493,3 +511,9 @@ var tabTemplate = new ViewDefinition({
template: '<div><div *auto="cond">TAB(<content></content>)</div></div>',
directives: [autoViewportDirective]
});

@Component({selector: 'textAfterContent'})
@View({template: `<content></content><p>P,</p>{{ 'text' }}`})
@Injectable()
class TextAfterContentTag {
}

0 comments on commit f103d9b

Please sign in to comment.