refactor(ivy): use context discovery in TestBed implementation (#26211)
PR Close #26211
This commit is contained in:
parent
39f42bad1c
commit
053bf27fb3
|
@ -13,8 +13,8 @@ describe('Element E2E Tests', function () {
|
||||||
browser.get('hello-world.html');
|
browser.get('hello-world.html');
|
||||||
const helloWorldEl = element(by.css('hello-world-el'));
|
const helloWorldEl = element(by.css('hello-world-el'));
|
||||||
const input = element(by.css('input[type=text]'));
|
const input = element(by.css('input[type=text]'));
|
||||||
input.sendKeys('F', 'o', 'o');
|
['f', 'o', 'o'].forEach((key) => input.sendKeys(key));
|
||||||
expect(helloWorldEl.getText()).toEqual('Hello Foo!');
|
expect(helloWorldEl.getText()).toEqual('Hello foo!');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -11,11 +11,10 @@ import {Renderer2, RendererType2} from '../render/api';
|
||||||
import {DebugContext} from '../view';
|
import {DebugContext} from '../view';
|
||||||
import {DebugRenderer2, DebugRendererFactory2} from '../view/services';
|
import {DebugRenderer2, DebugRendererFactory2} from '../view/services';
|
||||||
|
|
||||||
import * as di from './di';
|
import {getHostComponent, getInjector, getLocalRefs, loadContext} from './discovery_utils';
|
||||||
import {_getViewData} from './instructions';
|
import {DirectiveDef} from './interfaces/definition';
|
||||||
import {TNodeFlags} from './interfaces/node';
|
import {TNode, TNodeFlags} from './interfaces/node';
|
||||||
import {CONTEXT, LViewData, TVIEW} from './interfaces/view';
|
import {TVIEW} from './interfaces/view';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adapts the DebugRendererFactory2 to create a DebugRenderer2 specific for IVY.
|
* Adapts the DebugRendererFactory2 to create a DebugRenderer2 specific for IVY.
|
||||||
|
@ -25,7 +24,7 @@ import {CONTEXT, LViewData, TVIEW} from './interfaces/view';
|
||||||
export class Render3DebugRendererFactory2 extends DebugRendererFactory2 {
|
export class Render3DebugRendererFactory2 extends DebugRendererFactory2 {
|
||||||
createRenderer(element: any, renderData: RendererType2|null): Renderer2 {
|
createRenderer(element: any, renderData: RendererType2|null): Renderer2 {
|
||||||
const renderer = super.createRenderer(element, renderData) as DebugRenderer2;
|
const renderer = super.createRenderer(element, renderData) as DebugRenderer2;
|
||||||
renderer.debugContextFactory = () => new Render3DebugContext(_getViewData());
|
renderer.debugContextFactory = (nativeElement: any) => new Render3DebugContext(nativeElement);
|
||||||
return renderer;
|
return renderer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -36,68 +35,46 @@ export class Render3DebugRendererFactory2 extends DebugRendererFactory2 {
|
||||||
* Used in tests to retrieve information those nodes.
|
* Used in tests to retrieve information those nodes.
|
||||||
*/
|
*/
|
||||||
class Render3DebugContext implements DebugContext {
|
class Render3DebugContext implements DebugContext {
|
||||||
readonly nodeIndex: number|null;
|
constructor(private _nativeNode: any) {}
|
||||||
|
|
||||||
constructor(private viewData: LViewData) {
|
get nodeIndex(): number|null { return loadContext(this._nativeNode).nodeIndex; }
|
||||||
// The LNode will be created next and appended to viewData
|
|
||||||
this.nodeIndex = viewData ? viewData.length : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
get view(): any { return this.viewData; }
|
get view(): any { return loadContext(this._nativeNode).lViewData; }
|
||||||
|
|
||||||
get injector(): Injector {
|
get injector(): Injector { return getInjector(this._nativeNode); }
|
||||||
if (this.nodeIndex !== null) {
|
|
||||||
const tNode = this.view[TVIEW].data[this.nodeIndex];
|
|
||||||
return new di.NodeInjector(tNode, this.view);
|
|
||||||
}
|
|
||||||
return Injector.NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
get component(): any {
|
get component(): any { return getHostComponent(this._nativeNode); }
|
||||||
// TODO(vicb): why/when
|
|
||||||
if (this.nodeIndex === null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const tView = this.view[TVIEW];
|
|
||||||
const components: number[]|null = tView.components;
|
|
||||||
|
|
||||||
return (components && components.indexOf(this.nodeIndex) == -1) ?
|
|
||||||
null :
|
|
||||||
this.view[this.nodeIndex].data[CONTEXT];
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(vicb): add view providers when supported
|
|
||||||
get providerTokens(): any[] {
|
get providerTokens(): any[] {
|
||||||
// TODO(vicb): why/when
|
const lDebugCtx = loadContext(this._nativeNode);
|
||||||
const directiveDefs = this.view[TVIEW].data;
|
const lViewData = lDebugCtx.lViewData;
|
||||||
if (this.nodeIndex === null || directiveDefs == null) {
|
const tNode = lViewData[TVIEW].data[lDebugCtx.nodeIndex] as TNode;
|
||||||
|
const directivesCount = tNode.flags & TNodeFlags.DirectiveCountMask;
|
||||||
|
|
||||||
|
if (directivesCount > 0) {
|
||||||
|
const directiveIdxStart = tNode.flags >> TNodeFlags.DirectiveStartingIndexShift;
|
||||||
|
const directiveIdxEnd = directiveIdxStart + directivesCount;
|
||||||
|
const viewDirectiveDefs = this.view[TVIEW].data;
|
||||||
|
const directiveDefs =
|
||||||
|
viewDirectiveDefs.slice(directiveIdxStart, directiveIdxEnd) as DirectiveDef<any>[];
|
||||||
|
|
||||||
|
return directiveDefs.map(directiveDef => directiveDef.type);
|
||||||
|
}
|
||||||
|
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentTNode = this.view[TVIEW].data[this.nodeIndex];
|
get references(): {[key: string]: any} { return getLocalRefs(this._nativeNode); }
|
||||||
const dirStart = currentTNode >> TNodeFlags.DirectiveStartingIndexShift;
|
|
||||||
const dirEnd = dirStart + (currentTNode & TNodeFlags.DirectiveCountMask);
|
|
||||||
return directiveDefs.slice(dirStart, dirEnd);
|
|
||||||
}
|
|
||||||
|
|
||||||
get references(): {[key: string]: any} {
|
// TODO(pk): check previous implementation and re-implement
|
||||||
// TODO(vicb): implement retrieving references
|
get context(): any { throw new Error('Not implemented in ivy'); }
|
||||||
throw new Error('Not implemented yet in ivy');
|
|
||||||
}
|
|
||||||
|
|
||||||
get context(): any {
|
|
||||||
if (this.nodeIndex === null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const lNode = this.view[this.nodeIndex];
|
|
||||||
return lNode.view[CONTEXT];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// TODO(pk): check previous implementation and re-implement
|
||||||
get componentRenderElement(): any { throw new Error('Not implemented in ivy'); }
|
get componentRenderElement(): any { throw new Error('Not implemented in ivy'); }
|
||||||
|
|
||||||
|
// TODO(pk): check previous implementation and re-implement
|
||||||
get renderNode(): any { throw new Error('Not implemented in ivy'); }
|
get renderNode(): any { throw new Error('Not implemented in ivy'); }
|
||||||
|
|
||||||
// TODO(vicb): check previous implementation
|
// TODO(pk): check previous implementation and re-implement
|
||||||
logError(console: Console, ...values: any[]): void { console.error(...values); }
|
logError(console: Console, ...values: any[]): void { console.error(...values); }
|
||||||
}
|
}
|
||||||
|
|
|
@ -110,7 +110,11 @@ export function getDirectives(target: {}): Array<{}> {
|
||||||
return context.directives || [];
|
return context.directives || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadContext(target: {}): LContext {
|
/**
|
||||||
|
* Returns LContext associated with a target passed as an argument.
|
||||||
|
* Throws if a given target doesn't have associated LContext.
|
||||||
|
*/
|
||||||
|
export function loadContext(target: {}): LContext {
|
||||||
const context = getContext(target);
|
const context = getContext(target);
|
||||||
if (!context) {
|
if (!context) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
|
|
@ -395,8 +395,8 @@ export function detachView(lContainer: LContainer, removeIndex: number, detached
|
||||||
export function removeView(
|
export function removeView(
|
||||||
lContainer: LContainer, tContainer: TContainerNode, removeIndex: number) {
|
lContainer: LContainer, tContainer: TContainerNode, removeIndex: number) {
|
||||||
const view = lContainer[VIEWS][removeIndex];
|
const view = lContainer[VIEWS][removeIndex];
|
||||||
destroyLView(view);
|
|
||||||
detachView(lContainer, removeIndex, !!tContainer.detached);
|
detachView(lContainer, removeIndex, !!tContainer.detached);
|
||||||
|
destroyLView(view);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Gets the child of the given LViewData */
|
/** Gets the child of the given LViewData */
|
||||||
|
|
|
@ -12,6 +12,7 @@ import {InjectableDef, getInjectableDef} from '../di/defs';
|
||||||
import {InjectableType} from '../di/injectable';
|
import {InjectableType} from '../di/injectable';
|
||||||
import {ErrorHandler} from '../error_handler';
|
import {ErrorHandler} from '../error_handler';
|
||||||
import {isDevMode} from '../is_dev_mode';
|
import {isDevMode} from '../is_dev_mode';
|
||||||
|
import {ivyEnabled} from '../ivy_switch/compiler/index';
|
||||||
import {ComponentFactory} from '../linker/component_factory';
|
import {ComponentFactory} from '../linker/component_factory';
|
||||||
import {NgModuleRef} from '../linker/ng_module_factory';
|
import {NgModuleRef} from '../linker/ng_module_factory';
|
||||||
import {Renderer2, RendererFactory2, RendererStyleFlags2, RendererType2} from '../render/api';
|
import {Renderer2, RendererFactory2, RendererStyleFlags2, RendererType2} from '../render/api';
|
||||||
|
@ -694,6 +695,8 @@ export class DebugRendererFactory2 implements RendererFactory2 {
|
||||||
export class DebugRenderer2 implements Renderer2 {
|
export class DebugRenderer2 implements Renderer2 {
|
||||||
readonly data: {[key: string]: any};
|
readonly data: {[key: string]: any};
|
||||||
|
|
||||||
|
private createDebugContext(nativeElement: any) { return this.debugContextFactory(nativeElement); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Factory function used to create a `DebugContext` when a node is created.
|
* Factory function used to create a `DebugContext` when a node is created.
|
||||||
*
|
*
|
||||||
|
@ -702,9 +705,7 @@ export class DebugRenderer2 implements Renderer2 {
|
||||||
* The factory is configurable so that the `DebugRenderer2` could instantiate either a View Engine
|
* The factory is configurable so that the `DebugRenderer2` could instantiate either a View Engine
|
||||||
* or a Render context.
|
* or a Render context.
|
||||||
*/
|
*/
|
||||||
debugContextFactory: () => DebugContext | null = getCurrentDebugContext;
|
debugContextFactory: (nativeElement?: any) => DebugContext | null = getCurrentDebugContext;
|
||||||
|
|
||||||
private get debugContext() { return this.debugContextFactory(); }
|
|
||||||
|
|
||||||
constructor(private delegate: Renderer2) { this.data = this.delegate.data; }
|
constructor(private delegate: Renderer2) { this.data = this.delegate.data; }
|
||||||
|
|
||||||
|
@ -719,7 +720,7 @@ export class DebugRenderer2 implements Renderer2 {
|
||||||
|
|
||||||
createElement(name: string, namespace?: string): any {
|
createElement(name: string, namespace?: string): any {
|
||||||
const el = this.delegate.createElement(name, namespace);
|
const el = this.delegate.createElement(name, namespace);
|
||||||
const debugCtx = this.debugContext;
|
const debugCtx = this.createDebugContext(el);
|
||||||
if (debugCtx) {
|
if (debugCtx) {
|
||||||
const debugEl = new DebugElement(el, null, debugCtx);
|
const debugEl = new DebugElement(el, null, debugCtx);
|
||||||
debugEl.name = name;
|
debugEl.name = name;
|
||||||
|
@ -730,7 +731,7 @@ export class DebugRenderer2 implements Renderer2 {
|
||||||
|
|
||||||
createComment(value: string): any {
|
createComment(value: string): any {
|
||||||
const comment = this.delegate.createComment(value);
|
const comment = this.delegate.createComment(value);
|
||||||
const debugCtx = this.debugContext;
|
const debugCtx = this.createDebugContext(comment);
|
||||||
if (debugCtx) {
|
if (debugCtx) {
|
||||||
indexDebugNode(new DebugNode(comment, null, debugCtx));
|
indexDebugNode(new DebugNode(comment, null, debugCtx));
|
||||||
}
|
}
|
||||||
|
@ -739,7 +740,7 @@ export class DebugRenderer2 implements Renderer2 {
|
||||||
|
|
||||||
createText(value: string): any {
|
createText(value: string): any {
|
||||||
const text = this.delegate.createText(value);
|
const text = this.delegate.createText(value);
|
||||||
const debugCtx = this.debugContext;
|
const debugCtx = this.createDebugContext(text);
|
||||||
if (debugCtx) {
|
if (debugCtx) {
|
||||||
indexDebugNode(new DebugNode(text, null, debugCtx));
|
indexDebugNode(new DebugNode(text, null, debugCtx));
|
||||||
}
|
}
|
||||||
|
@ -777,7 +778,7 @@ export class DebugRenderer2 implements Renderer2 {
|
||||||
|
|
||||||
selectRootElement(selectorOrNode: string|any, preserveContent?: boolean): any {
|
selectRootElement(selectorOrNode: string|any, preserveContent?: boolean): any {
|
||||||
const el = this.delegate.selectRootElement(selectorOrNode, preserveContent);
|
const el = this.delegate.selectRootElement(selectorOrNode, preserveContent);
|
||||||
const debugCtx = getCurrentDebugContext();
|
const debugCtx = getCurrentDebugContext() || (ivyEnabled ? this.createDebugContext(el) : null);
|
||||||
if (debugCtx) {
|
if (debugCtx) {
|
||||||
indexDebugNode(new DebugElement(el, null, debugCtx));
|
indexDebugNode(new DebugElement(el, null, debugCtx));
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,8 +43,12 @@ export class GreetingModule {
|
||||||
export class SimpleCmp {
|
export class SimpleCmp {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Component({selector: 'with-refs-cmp', template: '<div #firstDiv></div>'})
|
||||||
|
export class WithRefsCmp {
|
||||||
|
}
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [HelloWorld, SimpleCmp],
|
declarations: [HelloWorld, SimpleCmp, WithRefsCmp],
|
||||||
imports: [GreetingModule],
|
imports: [GreetingModule],
|
||||||
providers: [
|
providers: [
|
||||||
{provide: NAME, useValue: 'World!'},
|
{provide: NAME, useValue: 'World!'},
|
||||||
|
@ -94,11 +98,27 @@ describe('TestBed', () => {
|
||||||
expect(greetingByCss.nativeElement).toHaveText('Hello TestBed!');
|
expect(greetingByCss.nativeElement).toHaveText('Hello TestBed!');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should give access to the node injector', () => {
|
it('should give access to the node injector', () => {
|
||||||
|
const fixture = TestBed.createComponent(HelloWorld);
|
||||||
|
fixture.detectChanges();
|
||||||
|
const injector = fixture.debugElement.query(By.css('greeting-cmp')).injector;
|
||||||
|
|
||||||
|
// from the node injector
|
||||||
|
const greetingCmp = injector.get(GreetingCmp);
|
||||||
|
expect(greetingCmp.constructor).toBe(GreetingCmp);
|
||||||
|
|
||||||
|
// from the node injector (inherited from a parent node)
|
||||||
|
const helloWorldCmp = injector.get(HelloWorld);
|
||||||
|
expect(fixture.componentInstance).toBe(helloWorldCmp);
|
||||||
|
|
||||||
|
const nameInjected = injector.get(NAME);
|
||||||
|
expect(nameInjected).toEqual('World!');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should give access to the node injector for root node', () => {
|
||||||
const hello = TestBed.createComponent(HelloWorld);
|
const hello = TestBed.createComponent(HelloWorld);
|
||||||
hello.detectChanges();
|
hello.detectChanges();
|
||||||
const injector = hello.debugElement.query(By.css('greeting-cmp')).injector;
|
const injector = hello.debugElement.injector;
|
||||||
|
|
||||||
// from the node injector
|
// from the node injector
|
||||||
const helloInjected = injector.get(HelloWorld);
|
const helloInjected = injector.get(HelloWorld);
|
||||||
|
@ -109,6 +129,13 @@ describe('TestBed', () => {
|
||||||
expect(nameInjected).toEqual('World!');
|
expect(nameInjected).toEqual('World!');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should give access to local refs on a node', () => {
|
||||||
|
const withRefsCmp = TestBed.createComponent(WithRefsCmp);
|
||||||
|
const firstDivDebugEl = withRefsCmp.debugElement.query(By.css('div'));
|
||||||
|
// assert that a native element is referenced by a local ref
|
||||||
|
expect(firstDivDebugEl.references.firstDiv.tagName.toLowerCase()).toBe('div');
|
||||||
|
});
|
||||||
|
|
||||||
it('should give the ability to query by directive', () => {
|
it('should give the ability to query by directive', () => {
|
||||||
const hello = TestBed.createComponent(HelloWorld);
|
const hello = TestBed.createComponent(HelloWorld);
|
||||||
hello.detectChanges();
|
hello.detectChanges();
|
||||||
|
@ -117,7 +144,6 @@ describe('TestBed', () => {
|
||||||
expect(greetingByDirective.componentInstance).toBeAnInstanceOf(GreetingCmp);
|
expect(greetingByDirective.componentInstance).toBeAnInstanceOf(GreetingCmp);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('allow to override a template', () => {
|
it('allow to override a template', () => {
|
||||||
// use original template when there is no override
|
// use original template when there is no override
|
||||||
let hello = TestBed.createComponent(HelloWorld);
|
let hello = TestBed.createComponent(HelloWorld);
|
||||||
|
|
Loading…
Reference in New Issue