Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions integration/ng_elements/e2e/app.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ describe('Element E2E Tests', function () {
browser.get('hello-world.html');
const helloWorldEl = element(by.css('hello-world-el'));
const input = element(by.css('input[type=text]'));
input.sendKeys('F', 'o', 'o');
expect(helloWorldEl.getText()).toEqual('Hello Foo!');

@pkozlowski-opensource pkozlowski-opensource Oct 9, 2018 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes to this file are due to the flaky e2e tests that seem to be unrelated to this PR as I saw it on other, completely different PRs, ex.:

Seems like getText() is async and we wait for on it in other tests:

div.getText().then(t => expect(t).toEqual(`Hello world!`));

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe @robwormald has additional input here - my Protractor kung-fu is too weak here...

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getText() is indeed async, but (theoretically at least) waiting shouldn't make any difference (because Protractor will internally wait for async WebDriver methods before checking assertions).
This test is indeed quite flakey lately. If this change makes it less flakey, \o/

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apparently, many people have found this workaround useful. Maybe worth trying (if promise chaining doesn't make a difference).

['f', 'o', 'o'].forEach((key) => input.sendKeys(key));
expect(helloWorldEl.getText()).toEqual('Hello foo!');
});
});
});
83 changes: 30 additions & 53 deletions packages/core/src/render3/debug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,10 @@ import {Renderer2, RendererType2} from '../render/api';
import {DebugContext} from '../view';
import {DebugRenderer2, DebugRendererFactory2} from '../view/services';

import * as di from './di';
import {_getViewData} from './instructions';
import {TNodeFlags} from './interfaces/node';
import {CONTEXT, LViewData, TVIEW} from './interfaces/view';

import {getHostComponent, getInjector, getLocalRefs, loadContext} from './discovery_utils';
import {DirectiveDef} from './interfaces/definition';
import {TNode, TNodeFlags} from './interfaces/node';
import {TVIEW} from './interfaces/view';

/**
* Adapts the DebugRendererFactory2 to create a DebugRenderer2 specific for IVY.
Expand All @@ -25,7 +24,7 @@ import {CONTEXT, LViewData, TVIEW} from './interfaces/view';
export class Render3DebugRendererFactory2 extends DebugRendererFactory2 {
createRenderer(element: any, renderData: RendererType2|null): Renderer2 {
const renderer = super.createRenderer(element, renderData) as DebugRenderer2;
renderer.debugContextFactory = () => new Render3DebugContext(_getViewData());
renderer.debugContextFactory = (nativeElement: any) => new Render3DebugContext(nativeElement);
return renderer;
}
}
Expand All @@ -36,68 +35,46 @@ export class Render3DebugRendererFactory2 extends DebugRendererFactory2 {
* Used in tests to retrieve information those nodes.
*/
class Render3DebugContext implements DebugContext {
readonly nodeIndex: number|null;

constructor(private viewData: LViewData) {
// The LNode will be created next and appended to viewData
this.nodeIndex = viewData ? viewData.length : null;
}
constructor(private _nativeNode: any) {}

get view(): any { return this.viewData; }
get nodeIndex(): number|null { return loadContext(this._nativeNode).nodeIndex; }

get injector(): Injector {
if (this.nodeIndex !== null) {
const tNode = this.view[TVIEW].data[this.nodeIndex];
return new di.NodeInjector(tNode, this.view);
}
return Injector.NULL;
}
get view(): any { return loadContext(this._nativeNode).lViewData; }

get component(): any {
// TODO(vicb): why/when
if (this.nodeIndex === null) {
return null;
}
get injector(): Injector { return getInjector(this._nativeNode); }

const tView = this.view[TVIEW];
const components: number[]|null = tView.components;
get component(): any { return getHostComponent(this._nativeNode); }

return (components && components.indexOf(this.nodeIndex) == -1) ?
null :
this.view[this.nodeIndex].data[CONTEXT];
}

// TODO(vicb): add view providers when supported
get providerTokens(): any[] {
// TODO(vicb): why/when
const directiveDefs = this.view[TVIEW].data;
if (this.nodeIndex === null || directiveDefs == null) {
return [];
const lDebugCtx = loadContext(this._nativeNode);
const lViewData = lDebugCtx.lViewData;
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);
}

const currentTNode = this.view[TVIEW].data[this.nodeIndex];
const dirStart = currentTNode >> TNodeFlags.DirectiveStartingIndexShift;
const dirEnd = dirStart + (currentTNode & TNodeFlags.DirectiveCountMask);
return directiveDefs.slice(dirStart, dirEnd);
return [];
}

get references(): {[key: string]: any} {
// TODO(vicb): implement retrieving references
throw new Error('Not implemented yet in ivy');
}
get references(): {[key: string]: any} { return getLocalRefs(this._nativeNode); }

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 context(): any { throw new Error('Not implemented in ivy'); }

// TODO(pk): check previous implementation and re-implement
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'); }

// TODO(vicb): check previous implementation
// TODO(pk): check previous implementation and re-implement
logError(console: Console, ...values: any[]): void { console.error(...values); }
}
6 changes: 5 additions & 1 deletion packages/core/src/render3/discovery_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,11 @@ export function getDirectives(target: {}): Array<{}> {
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);
if (!context) {
throw new Error(
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/render3/node_manipulation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -395,8 +395,8 @@ export function detachView(lContainer: LContainer, removeIndex: number, detached
export function removeView(
lContainer: LContainer, tContainer: TContainerNode, removeIndex: number) {
const view = lContainer[VIEWS][removeIndex];
destroyLView(view);
detachView(lContainer, removeIndex, !!tContainer.detached);
destroyLView(view);
}

/** Gets the child of the given LViewData */
Expand Down
15 changes: 8 additions & 7 deletions packages/core/src/view/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {InjectableDef, getInjectableDef} from '../di/defs';
import {InjectableType} from '../di/injectable';
import {ErrorHandler} from '../error_handler';
import {isDevMode} from '../is_dev_mode';
import {ivyEnabled} from '../ivy_switch/compiler/index';
import {ComponentFactory} from '../linker/component_factory';
import {NgModuleRef} from '../linker/ng_module_factory';
import {Renderer2, RendererFactory2, RendererStyleFlags2, RendererType2} from '../render/api';
Expand Down Expand Up @@ -694,6 +695,8 @@ export class DebugRendererFactory2 implements RendererFactory2 {
export class DebugRenderer2 implements Renderer2 {
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.
*
Expand All @@ -702,9 +705,7 @@ export class DebugRenderer2 implements Renderer2 {
* The factory is configurable so that the `DebugRenderer2` could instantiate either a View Engine
* or a Render context.
*/
debugContextFactory: () => DebugContext | null = getCurrentDebugContext;

private get debugContext() { return this.debugContextFactory(); }
debugContextFactory: (nativeElement?: any) => DebugContext | null = getCurrentDebugContext;

constructor(private delegate: Renderer2) { this.data = this.delegate.data; }

Expand All @@ -719,7 +720,7 @@ export class DebugRenderer2 implements Renderer2 {

createElement(name: string, namespace?: string): any {
const el = this.delegate.createElement(name, namespace);
const debugCtx = this.debugContext;
const debugCtx = this.createDebugContext(el);
if (debugCtx) {
const debugEl = new DebugElement(el, null, debugCtx);
debugEl.name = name;
Expand All @@ -730,7 +731,7 @@ export class DebugRenderer2 implements Renderer2 {

createComment(value: string): any {
const comment = this.delegate.createComment(value);
const debugCtx = this.debugContext;
const debugCtx = this.createDebugContext(comment);
if (debugCtx) {
indexDebugNode(new DebugNode(comment, null, debugCtx));
}
Expand All @@ -739,7 +740,7 @@ export class DebugRenderer2 implements Renderer2 {

createText(value: string): any {
const text = this.delegate.createText(value);
const debugCtx = this.debugContext;
const debugCtx = this.createDebugContext(text);
if (debugCtx) {
indexDebugNode(new DebugNode(text, null, debugCtx));
}
Expand Down Expand Up @@ -777,7 +778,7 @@ export class DebugRenderer2 implements Renderer2 {

selectRootElement(selectorOrNode: string|any, preserveContent?: boolean): any {
const el = this.delegate.selectRootElement(selectorOrNode, preserveContent);
const debugCtx = getCurrentDebugContext();
const debugCtx = getCurrentDebugContext() || (ivyEnabled ? this.createDebugContext(el) : null);
if (debugCtx) {
indexDebugNode(new DebugElement(el, null, debugCtx));
}
Expand Down
34 changes: 30 additions & 4 deletions packages/core/test/test_bed_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,12 @@ export class GreetingModule {
export class SimpleCmp {
}

@Component({selector: 'with-refs-cmp', template: '<div #firstDiv></div>'})
export class WithRefsCmp {
}

@NgModule({
declarations: [HelloWorld, SimpleCmp],
declarations: [HelloWorld, SimpleCmp, WithRefsCmp],
imports: [GreetingModule],
providers: [
{provide: NAME, useValue: 'World!'},
Expand Down Expand Up @@ -94,11 +98,27 @@ describe('TestBed', () => {
expect(greetingByCss.nativeElement).toHaveText('Hello TestBed!');
});


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);
hello.detectChanges();
const injector = hello.debugElement.query(By.css('greeting-cmp')).injector;
const injector = hello.debugElement.injector;

// from the node injector
const helloInjected = injector.get(HelloWorld);
Expand All @@ -109,6 +129,13 @@ describe('TestBed', () => {
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', () => {
const hello = TestBed.createComponent(HelloWorld);
hello.detectChanges();
Expand All @@ -117,7 +144,6 @@ describe('TestBed', () => {
expect(greetingByDirective.componentInstance).toBeAnInstanceOf(GreetingCmp);
});


it('allow to override a template', () => {
// use original template when there is no override
let hello = TestBed.createComponent(HelloWorld);
Expand Down