refactor(core): remove testing-only DOM manipulation utils from DomAdapters (#32291)

PR Close #32291
This commit is contained in:
Kara Erickson 2019-08-23 13:28:33 -07:00 committed by Miško Hevery
parent ede5786d1e
commit 30dabdf8fc
14 changed files with 53 additions and 106 deletions

View File

@ -103,8 +103,7 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
fixture.detectChanges(); fixture.detectChanges();
expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(3); expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(3);
expect(getDOM().getText(fixture.nativeElement)) expect(fixture.nativeElement.textContent).toEqual('helloNumberhelloStringhelloFunction');
.toEqual('helloNumberhelloStringhelloFunction');
getComponent().numberCondition = 0; getComponent().numberCondition = 0;
fixture.detectChanges(); fixture.detectChanges();

View File

@ -15,7 +15,7 @@ import {ComponentRef} from '@angular/core/src/linker/component_factory';
import {getLocaleId} from '@angular/core/src/render3'; import {getLocaleId} from '@angular/core/src/render3';
import {BrowserModule} from '@angular/platform-browser'; import {BrowserModule} from '@angular/platform-browser';
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
import {dispatchEvent, getContent} from '@angular/platform-browser/testing/src/browser_util'; import {createTemplate, dispatchEvent, getContent} from '@angular/platform-browser/testing/src/browser_util';
import {expect} from '@angular/platform-browser/testing/src/matchers'; import {expect} from '@angular/platform-browser/testing/src/matchers';
import {onlyInIvy} from '@angular/private/testing'; import {onlyInIvy} from '@angular/private/testing';
@ -35,7 +35,7 @@ class SomeComponent {
function createRootEl(selector = 'bootstrap-app') { function createRootEl(selector = 'bootstrap-app') {
const doc = TestBed.get(DOCUMENT); const doc = TestBed.get(DOCUMENT);
const rootEl = <HTMLElement>getDOM().firstChild( const rootEl = <HTMLElement>getDOM().firstChild(
getContent(getDOM().createTemplate(`<${selector}></${selector}>`))); getContent(createTemplate(`<${selector}></${selector}>`)));
const oldRoots = getDOM().querySelectorAll(doc, selector); const oldRoots = getDOM().querySelectorAll(doc, selector);
for (let i = 0; i < oldRoots.length; i++) { for (let i = 0; i < oldRoots.length; i++) {
getDOM().remove(oldRoots[i]); getDOM().remove(oldRoots[i]);

View File

@ -17,37 +17,6 @@ import {el, isTextNode, stringifyElement} from '@angular/platform-browser/testin
defaultDoc = getDOM().supportsDOMEvents() ? document : getDOM().createHtmlDocument(); defaultDoc = getDOM().supportsDOMEvents() ? document : getDOM().createHtmlDocument();
}); });
it('should not coalesque text nodes', () => {
const el1 = el('<div>a</div>');
const el2 = el('<div>b</div>');
getDOM().appendChild(el2, getDOM().firstChild(el1));
expect(getDOM().childNodes(el2).length).toBe(2);
const el2Clone = getDOM().clone(el2);
expect(getDOM().childNodes(el2Clone).length).toBe(2);
});
it('should clone correctly', () => {
const el1 = el('<div x="y">a<span>b</span></div>');
const clone = getDOM().clone(el1);
expect(clone).not.toBe(el1);
getDOM().setAttribute(clone, 'test', '1');
expect(stringifyElement(clone)).toEqual('<div test="1" x="y">a<span>b</span></div>');
expect(getDOM().getAttribute(el1, 'test')).toBeFalsy();
const cNodes = getDOM().childNodes(clone);
const firstChild = cNodes[0];
const secondChild = cNodes[1];
expect(getDOM().parentElement(firstChild)).toBe(clone);
expect(getDOM().nextSibling(firstChild)).toBe(secondChild);
expect(isTextNode(firstChild)).toBe(true);
expect(getDOM().parentElement(secondChild)).toBe(clone);
expect(getDOM().nextSibling(secondChild)).toBeFalsy();
expect(getDOM().isElementNode(secondChild)).toBe(true);
});
it('should be able to create text nodes and use them with the other APIs', () => { it('should be able to create text nodes and use them with the other APIs', () => {
const t = getDOM().createTextNode('hello'); const t = getDOM().createTextNode('hello');
expect(isTextNode(t)).toBe(true); expect(isTextNode(t)).toBe(true);

View File

@ -1006,8 +1006,8 @@ function declareTests(config?: {useJit: boolean}) {
getDOM().dispatchEvent(fixture.debugElement.children[1].nativeElement, dispatchedEvent2); getDOM().dispatchEvent(fixture.debugElement.children[1].nativeElement, dispatchedEvent2);
expect(getDOM().isPrevented(dispatchedEvent)).toBe(true); expect(getDOM().isPrevented(dispatchedEvent)).toBe(true);
expect(getDOM().isPrevented(dispatchedEvent2)).toBe(false); expect(getDOM().isPrevented(dispatchedEvent2)).toBe(false);
expect(getDOM().getChecked(fixture.debugElement.children[0].nativeElement)).toBeFalsy(); expect(fixture.debugElement.children[0].nativeElement.checked).toBeFalsy();
expect(getDOM().getChecked(fixture.debugElement.children[1].nativeElement)).toBeTruthy(); expect(fixture.debugElement.children[1].nativeElement.checked).toBeTruthy();
}); });
} }
@ -2689,15 +2689,14 @@ class ComponentWithoutView {
@Directive({selector: '[no-duplicate]'}) @Directive({selector: '[no-duplicate]'})
class DuplicateDir { class DuplicateDir {
constructor(elRef: ElementRef) { constructor(elRef: ElementRef) {
getDOM().setText(elRef.nativeElement, getDOM().getText(elRef.nativeElement) + 'noduplicate'); getDOM().setText(elRef.nativeElement, elRef.nativeElement.textContent + 'noduplicate');
} }
} }
@Directive({selector: '[no-duplicate]'}) @Directive({selector: '[no-duplicate]'})
class OtherDuplicateDir { class OtherDuplicateDir {
constructor(elRef: ElementRef) { constructor(elRef: ElementRef) {
getDOM().setText( getDOM().setText(elRef.nativeElement, elRef.nativeElement.textContent + 'othernoduplicate');
elRef.nativeElement, getDOM().getText(elRef.nativeElement) + 'othernoduplicate');
} }
} }

View File

@ -19,7 +19,7 @@ import {ARG_TYPE_VALUES, checkNodeInlineOrDynamic, compViewDef, createAndGetRoot
it('should create text nodes without parents', () => { it('should create text nodes without parents', () => {
const rootNodes = createAndGetRootNodes(compViewDef([textDef(0, null, ['a'])])).rootNodes; const rootNodes = createAndGetRootNodes(compViewDef([textDef(0, null, ['a'])])).rootNodes;
expect(rootNodes.length).toBe(1); expect(rootNodes.length).toBe(1);
expect(getDOM().getText(rootNodes[0])).toBe('a'); expect(rootNodes[0].textContent).toBe('a');
}); });
it('should create views with multiple root text nodes', () => { it('should create views with multiple root text nodes', () => {
@ -36,8 +36,8 @@ import {ARG_TYPE_VALUES, checkNodeInlineOrDynamic, compViewDef, createAndGetRoot
textDef(1, null, ['a']), textDef(1, null, ['a']),
])).rootNodes; ])).rootNodes;
expect(rootNodes.length).toBe(1); expect(rootNodes.length).toBe(1);
const textNode = getDOM().firstChild(rootNodes[0]); const textNode = getDOM().firstChild(rootNodes[0]) as Element;
expect(getDOM().getText(textNode)).toBe('a'); expect(textNode.textContent).toBe('a');
}); });
it('should add debug information to the renderer', () => { it('should add debug information to the renderer', () => {
@ -61,7 +61,7 @@ import {ARG_TYPE_VALUES, checkNodeInlineOrDynamic, compViewDef, createAndGetRoot
Services.checkAndUpdateView(view); Services.checkAndUpdateView(view);
expect(getDOM().getText(rootNodes[0])).toBe('0a1b2'); expect(rootNodes[0].textContent).toBe('0a1b2');
}); });
}); });

View File

@ -19,8 +19,9 @@ export class DOMTestComponentRenderer extends TestComponentRenderer {
constructor(@Inject(DOCUMENT) private _doc: any) { super(); } constructor(@Inject(DOCUMENT) private _doc: any) { super(); }
insertRootElement(rootElId: string) { insertRootElement(rootElId: string) {
const rootEl = <HTMLElement>getDOM().firstChild( const template = getDOM().getDefaultDocument().createElement('template');
getContent(getDOM().createTemplate(`<div id="${rootElId}"></div>`))); template.innerHTML = `<div id="${rootElId}"></div>`;
const rootEl = <HTMLElement>getDOM().firstChild(getContent(template));
// TODO(juliemr): can/should this be optional? // TODO(juliemr): can/should this be optional?
const oldRoots = getDOM().querySelectorAll(this._doc, '[id^=root]'); const oldRoots = getDOM().querySelectorAll(this._doc, '[id^=root]');

View File

@ -146,14 +146,6 @@ export class BrowserDomAdapter extends GenericBrowserDomAdapter {
nextSibling(el: Node): Node|null { return el.nextSibling; } nextSibling(el: Node): Node|null { return el.nextSibling; }
parentElement(el: Node): Node|null { return el.parentNode; } parentElement(el: Node): Node|null { return el.parentNode; }
childNodes(el: any): Node[] { return el.childNodes; } childNodes(el: any): Node[] { return el.childNodes; }
childNodesAsList(el: Node): any[] {
const childNodes = el.childNodes;
const res = [];
for (let i = 0; i < childNodes.length; i++) {
res[i] = childNodes[i];
}
return res;
}
clearNodes(el: Node) { clearNodes(el: Node) {
while (el.firstChild) { while (el.firstChild) {
el.removeChild(el.firstChild); el.removeChild(el.firstChild);
@ -168,17 +160,9 @@ export class BrowserDomAdapter extends GenericBrowserDomAdapter {
return node; return node;
} }
insertBefore(parent: Node, ref: Node, node: Node) { parent.insertBefore(node, ref); } insertBefore(parent: Node, ref: Node, node: Node) { parent.insertBefore(node, ref); }
getText(el: Node): string|null { return el.textContent; }
setText(el: Node, value: string) { el.textContent = value; } setText(el: Node, value: string) { el.textContent = value; }
getValue(el: any): string { return el.value; } getValue(el: any): string { return el.value; }
setValue(el: any, value: string) { el.value = value; }
getChecked(el: any): boolean { return el.checked; }
createComment(text: string): Comment { return this.getDefaultDocument().createComment(text); } createComment(text: string): Comment { return this.getDefaultDocument().createComment(text); }
createTemplate(html: any): HTMLElement {
const t = this.getDefaultDocument().createElement('template');
t.innerHTML = html;
return t;
}
createElement(tagName: string, doc?: Document): HTMLElement { createElement(tagName: string, doc?: Document): HTMLElement {
doc = doc || this.getDefaultDocument(); doc = doc || this.getDefaultDocument();
return doc.createElement(tagName); return doc.createElement(tagName);
@ -192,7 +176,6 @@ export class BrowserDomAdapter extends GenericBrowserDomAdapter {
return doc.createTextNode(text); return doc.createTextNode(text);
} }
getHost(el: HTMLElement): HTMLElement { return (<any>el).host; } getHost(el: HTMLElement): HTMLElement { return (<any>el).host; }
clone(node: Node): Node { return node.cloneNode(true); }
getElementsByTagName(element: any, name: string): HTMLElement[] { getElementsByTagName(element: any, name: string): HTMLElement[] {
return element.getElementsByTagName(name); return element.getElementsByTagName(name);
} }

View File

@ -19,6 +19,5 @@ import {DomAdapter} from '../dom/dom_adapter';
export abstract class GenericBrowserDomAdapter extends DomAdapter { export abstract class GenericBrowserDomAdapter extends DomAdapter {
constructor() { super(); } constructor() { super(); }
getDistributedNodes(el: HTMLElement): Node[] { return (<any>el).getDistributedNodes(); }
supportsDOMEvents(): boolean { return true; } supportsDOMEvents(): boolean { return true; }
} }

View File

@ -60,31 +60,22 @@ export abstract class DomAdapter {
abstract nextSibling(el: any): Node|null; abstract nextSibling(el: any): Node|null;
abstract parentElement(el: any): Node|null; abstract parentElement(el: any): Node|null;
abstract childNodes(el: any): Node[]; abstract childNodes(el: any): Node[];
abstract childNodesAsList(el: any): Node[];
abstract clearNodes(el: any): any;
abstract appendChild(el: any, node: any): any;
abstract removeChild(el: any, node: any): any;
abstract remove(el: any): Node;
abstract insertBefore(parent: any, ref: any, node: any): any;
abstract getText(el: any): string|null;
abstract setText(el: any, value: string): any;
abstract getValue(el: any): string;
abstract setValue(el: any, value: string): any;
abstract getChecked(el: any): boolean;
abstract createComment(text: string): any;
abstract createTemplate(html: any): HTMLElement;
abstract createElement(tagName: any, doc?: any): HTMLElement;
abstract createElementNS(ns: string, tagName: string, doc?: any): Element;
abstract createTextNode(text: string, doc?: any): Text;
abstract getHost(el: any): any;
abstract getDistributedNodes(el: any): Node[];
abstract clone /*<T extends Node>*/ (node: Node /*T*/): Node /*T*/;
abstract getElementsByTagName(element: any, name: string): HTMLElement[];
// Used by Meta // Used by Meta
abstract remove(el: any): Node;
abstract getAttribute(element: any, attribute: string): string|null; abstract getAttribute(element: any, attribute: string): string|null;
// Used by platform-server // Used by platform-server
abstract clearNodes(el: any): any;
abstract appendChild(el: any, node: any): any;
abstract removeChild(el: any, node: any): any;
abstract insertBefore(parent: any, ref: any, node: any): any;
abstract setText(el: any, value: string): any;
abstract createComment(text: string): any;
abstract createElement(tagName: any, doc?: any): HTMLElement;
abstract createElementNS(ns: string, tagName: string, doc?: any): Element;
abstract createTextNode(text: string, doc?: any): Text;
abstract getElementsByTagName(element: any, name: string): HTMLElement[];
abstract addClass(element: any, className: string): any; abstract addClass(element: any, className: string): any;
abstract removeClass(element: any, className: string): any; abstract removeClass(element: any, className: string): any;
abstract getStyle(element: any, styleName: string): any; abstract getStyle(element: any, styleName: string): any;
@ -107,6 +98,7 @@ export abstract class DomAdapter {
// Used by Testability // Used by Testability
abstract isShadowRoot(node: any): boolean; abstract isShadowRoot(node: any): boolean;
abstract getHost(el: any): any;
// Used by KeyEventsPlugin // Used by KeyEventsPlugin
abstract getEventKey(event: any): string; abstract getEventKey(event: any): string;

View File

@ -95,7 +95,7 @@ export function dispatchEvent(element: any, eventType: any): void {
} }
export function el(html: string): HTMLElement { export function el(html: string): HTMLElement {
return <HTMLElement>getDOM().firstChild(getContent(getDOM().createTemplate(html))); return <HTMLElement>getDOM().firstChild(getContent(createTemplate(html)));
} }
export function normalizeCSS(css: string): string { export function normalizeCSS(css: string): string {
@ -160,7 +160,7 @@ export function stringifyElement(el: any /** TODO #9100 */): string {
} else if (isCommentNode(el)) { } else if (isCommentNode(el)) {
result += `<!--${el.nodeValue}-->`; result += `<!--${el.nodeValue}-->`;
} else { } else {
result += getDOM().getText(el); result += el.textContent;
} }
return result; return result;
@ -212,3 +212,18 @@ export function hasClass(element: any, className: string): boolean {
export function sortedClassList(element: any): any[] { export function sortedClassList(element: any): any[] {
return Array.prototype.slice.call(element.classList, 0).sort(); return Array.prototype.slice.call(element.classList, 0).sort();
} }
export function createTemplate(html: any): HTMLElement {
const t = getDOM().getDefaultDocument().createElement('template');
t.innerHTML = html;
return t;
}
export function childNodesAsList(el: Node): any[] {
const childNodes = el.childNodes;
const res = [];
for (let i = 0; i < childNodes.length; i++) {
res[i] = childNodes[i];
}
return res;
}

View File

@ -11,7 +11,7 @@ import {Type, ɵglobal as global} from '@angular/core';
import {ComponentFixture} from '@angular/core/testing'; import {ComponentFixture} from '@angular/core/testing';
import {By, ɵgetDOM as getDOM} from '@angular/platform-browser'; import {By, ɵgetDOM as getDOM} from '@angular/platform-browser';
import {hasClass, hasStyle, isCommentNode} from './browser_util'; import {childNodesAsList, hasClass, hasStyle, isCommentNode} from './browser_util';
@ -293,18 +293,18 @@ function elementText(n: any): string {
} }
if (getDOM().isElementNode(n) && (n as Element).tagName == 'CONTENT') { if (getDOM().isElementNode(n) && (n as Element).tagName == 'CONTENT') {
return elementText(Array.prototype.slice.apply(getDOM().getDistributedNodes(n))); return elementText(Array.prototype.slice.apply((<any>n).getDistributedNodes()));
} }
if (hasShadowRoot(n)) { if (hasShadowRoot(n)) {
return elementText(getDOM().childNodesAsList((<any>n).shadowRoot)); return elementText(childNodesAsList((<any>n).shadowRoot));
} }
if (hasNodes(n)) { if (hasNodes(n)) {
return elementText(getDOM().childNodesAsList(n)); return elementText(childNodesAsList(n));
} }
return getDOM().getText(n) !; return (n as any).textContent;
} }
function hasShadowRoot(node: any): boolean { function hasShadowRoot(node: any): boolean {

View File

@ -197,8 +197,6 @@ export class DominoAdapter extends BrowserDomAdapter {
performanceNow(): number { return Date.now(); } performanceNow(): number { return Date.now(); }
getDistributedNodes(el: any): Node[] { throw _notImplemented('getDistributedNodes'); }
supportsCookies(): boolean { return false; } supportsCookies(): boolean { return false; }
getCookie(name: string): string { throw _notImplemented('getCookie'); } getCookie(name: string): string { throw _notImplemented('getCookie'); }
} }

View File

@ -412,7 +412,7 @@ class HiddenModule {
expect(doc.head).toBe(getDOM().querySelector(doc, 'head')); expect(doc.head).toBe(getDOM().querySelector(doc, 'head'));
expect(doc.body).toBe(getDOM().querySelector(doc, 'body')); expect(doc.body).toBe(getDOM().querySelector(doc, 'body'));
expect(getDOM().getText(doc.documentElement)).toEqual('Works!'); expect(doc.documentElement.textContent).toEqual('Works!');
platform.destroy(); platform.destroy();
}); });
@ -428,13 +428,13 @@ class HiddenModule {
platform.bootstrapModule(ExampleModule).then((moduleRef) => { platform.bootstrapModule(ExampleModule).then((moduleRef) => {
const doc = moduleRef.injector.get(DOCUMENT); const doc = moduleRef.injector.get(DOCUMENT);
expect(getDOM().getText(doc.documentElement)).toEqual('Works!'); expect(doc.documentElement.textContent).toEqual('Works!');
platform.destroy(); platform.destroy();
}); });
platform2.bootstrapModule(ExampleModule2).then((moduleRef) => { platform2.bootstrapModule(ExampleModule2).then((moduleRef) => {
const doc = moduleRef.injector.get(DOCUMENT); const doc = moduleRef.injector.get(DOCUMENT);
expect(getDOM().getText(doc.documentElement)).toEqual('Works too!'); expect(doc.documentElement.textContent).toEqual('Works too!');
platform2.destroy(); platform2.destroy();
}); });
})); }));
@ -449,7 +449,7 @@ class HiddenModule {
const state = ref.injector.get(PlatformState); const state = ref.injector.get(PlatformState);
const doc = ref.injector.get(DOCUMENT); const doc = ref.injector.get(DOCUMENT);
const title = getDOM().querySelector(doc, 'title'); const title = getDOM().querySelector(doc, 'title');
expect(getDOM().getText(title)).toBe('Test App Title'); expect(title.textContent).toBe('Test App Title');
expect(state.renderToString()).toContain('<title>Test App Title</title>'); expect(state.renderToString()).toContain('<title>Test App Title</title>');
}); });
})); }));
@ -477,7 +477,7 @@ class HiddenModule {
const head = getDOM().getElementsByTagName(doc, 'head')[0]; const head = getDOM().getElementsByTagName(doc, 'head')[0];
const styles: any[] = head.children as any; const styles: any[] = head.children as any;
expect(styles.length).toBe(1); expect(styles.length).toBe(1);
expect(getDOM().getText(styles[0])).toContain('color: red'); expect(styles[0].textContent).toContain('color: red');
expect(getDOM().getAttribute(styles[0], 'ng-transition')).toBe('example-styles'); expect(getDOM().getAttribute(styles[0], 'ng-transition')).toBe('example-styles');
}); });
})); }));

View File

@ -69,25 +69,17 @@ export class WorkerDomAdapter extends DomAdapter {
nextSibling(el: any): Node { throw 'not implemented'; } nextSibling(el: any): Node { throw 'not implemented'; }
parentElement(el: any): Node { throw 'not implemented'; } parentElement(el: any): Node { throw 'not implemented'; }
childNodes(el: any): Node[] { throw 'not implemented'; } childNodes(el: any): Node[] { throw 'not implemented'; }
childNodesAsList(el: any): Node[] { throw 'not implemented'; }
clearNodes(el: any) { throw 'not implemented'; } clearNodes(el: any) { throw 'not implemented'; }
appendChild(el: any, node: any) { throw 'not implemented'; } appendChild(el: any, node: any) { throw 'not implemented'; }
removeChild(el: any, node: any) { throw 'not implemented'; } removeChild(el: any, node: any) { throw 'not implemented'; }
remove(el: any): Node { throw 'not implemented'; } remove(el: any): Node { throw 'not implemented'; }
insertBefore(parent: any, el: any, node: any) { throw 'not implemented'; } insertBefore(parent: any, el: any, node: any) { throw 'not implemented'; }
getText(el: any): string { throw 'not implemented'; }
setText(el: any, value: string) { throw 'not implemented'; } setText(el: any, value: string) { throw 'not implemented'; }
getValue(el: any): string { throw 'not implemented'; }
setValue(el: any, value: string) { throw 'not implemented'; }
getChecked(el: any): boolean { throw 'not implemented'; }
createComment(text: string): any { throw 'not implemented'; } createComment(text: string): any { throw 'not implemented'; }
createTemplate(html: any): HTMLElement { throw 'not implemented'; }
createElement(tagName: any, doc?: any): HTMLElement { throw 'not implemented'; } createElement(tagName: any, doc?: any): HTMLElement { throw 'not implemented'; }
createElementNS(ns: string, tagName: string, doc?: any): Element { throw 'not implemented'; } createElementNS(ns: string, tagName: string, doc?: any): Element { throw 'not implemented'; }
createTextNode(text: string, doc?: any): Text { throw 'not implemented'; } createTextNode(text: string, doc?: any): Text { throw 'not implemented'; }
getHost(el: any): any { throw 'not implemented'; } getHost(el: any): any { throw 'not implemented'; }
getDistributedNodes(el: any): Node[] { throw 'not implemented'; }
clone(node: Node): Node { throw 'not implemented'; }
getElementsByTagName(element: any, name: string): HTMLElement[] { throw 'not implemented'; } getElementsByTagName(element: any, name: string): HTMLElement[] { throw 'not implemented'; }
addClass(element: any, className: string) { throw 'not implemented'; } addClass(element: any, className: string) { throw 'not implemented'; }
removeClass(element: any, className: string) { throw 'not implemented'; } removeClass(element: any, className: string) { throw 'not implemented'; }