feat(ivy): support context discovery for containers & ICU expressions (#27644)
Context discovery was only available on elements. This PR adds support for containers and ICU expressions. FW-378 #resolve FW-665 #comment linker integration tests PR Close #27644
This commit is contained in:
parent
062c7af4f3
commit
4c1cd1bb78
|
@ -6,15 +6,13 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import './ng_dev_mode';
|
||||
|
||||
import {assertDomNode} from './assert';
|
||||
import {EMPTY_ARRAY} from './definition';
|
||||
import {LContext, MONKEY_PATCH_KEY_NAME} from './interfaces/context';
|
||||
import {TNode, TNodeFlags} from './interfaces/node';
|
||||
import {RElement} from './interfaces/renderer';
|
||||
import {CONTEXT, HEADER_OFFSET, HOST, LView, TVIEW} from './interfaces/view';
|
||||
import {getComponentViewByIndex, getNativeByTNode, readElementValue, readPatchedData} from './util';
|
||||
|
||||
import {getComponentViewByIndex, getNativeByTNode, getTNode, readElementValue, readPatchedData} from './util';
|
||||
|
||||
|
||||
/** Returns the matching `LContext` data for a given DOM node, directive or component instance.
|
||||
|
@ -67,8 +65,8 @@ export function getLContext(target: any): LContext|null {
|
|||
}
|
||||
|
||||
// the goal is not to fill the entire context full of data because the lookups
|
||||
// are expensive. Instead, only the target data (the element, compontent or
|
||||
// directive details) are filled into the context. If called multiple times
|
||||
// are expensive. Instead, only the target data (the element, component, container, ICU
|
||||
// expression or directive details) are filled into the context. If called multiple times
|
||||
// with different target values then the missing target data will be filled in.
|
||||
const native = readElementValue(lView[nodeIndex]);
|
||||
const existingCtx = readPatchedData(native);
|
||||
|
@ -208,10 +206,15 @@ function traverseNextElement(tNode: TNode): TNode|null {
|
|||
return tNode.child;
|
||||
} else if (tNode.next) {
|
||||
return tNode.next;
|
||||
} else if (tNode.parent) {
|
||||
return tNode.parent.next || null;
|
||||
} else {
|
||||
// Let's take the following template: <div><span>text</span></div><component/>
|
||||
// After checking the text node, we need to find the next parent that has a "next" TNode,
|
||||
// in this case the parent `div`, so that we can find the component.
|
||||
while (tNode.parent && !tNode.parent.next) {
|
||||
tNode = tNode.parent;
|
||||
}
|
||||
return tNode.parent && tNode.parent.next;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
import {SRCSET_ATTRS, URI_ATTRS, VALID_ATTRS, VALID_ELEMENTS, getTemplateContent} from '../sanitization/html_sanitizer';
|
||||
import {InertBodyHelper} from '../sanitization/inert_body';
|
||||
import {_sanitizeUrl, sanitizeSrcset} from '../sanitization/url_sanitizer';
|
||||
|
||||
import {assertDefined, assertEqual, assertGreaterThan} from './assert';
|
||||
import {attachPatchData} from './context_discovery';
|
||||
import {allocExpando, createNodeAtIndex, elementAttribute, load, textBinding} from './instructions';
|
||||
import {LContainer, NATIVE, RENDER_PARENT} from './interfaces/container';
|
||||
import {COMMENT_MARKER, ELEMENT_MARKER, I18nMutateOpCode, I18nMutateOpCodes, I18nUpdateOpCode, I18nUpdateOpCodes, IcuType, TI18n, TIcu} from './interfaces/i18n';
|
||||
|
@ -337,9 +337,7 @@ const parentIndexStack: number[] = [];
|
|||
export function i18nStart(index: number, message: string, subTemplateIndex?: number): void {
|
||||
const tView = getLView()[TVIEW];
|
||||
ngDevMode && assertDefined(tView, `tView should be defined`);
|
||||
ngDevMode &&
|
||||
assertEqual(
|
||||
tView.firstTemplatePass, true, `You should only call i18nEnd on first template pass`);
|
||||
i18nIndexStack[++i18nIndexStackPointer] = index;
|
||||
if (tView.firstTemplatePass && tView.data[index + HEADER_OFFSET] === null) {
|
||||
i18nStartFirstPass(tView, index, message, subTemplateIndex);
|
||||
}
|
||||
|
@ -350,7 +348,6 @@ export function i18nStart(index: number, message: string, subTemplateIndex?: num
|
|||
*/
|
||||
function i18nStartFirstPass(
|
||||
tView: TView, index: number, message: string, subTemplateIndex?: number) {
|
||||
i18nIndexStack[++i18nIndexStackPointer] = index;
|
||||
const viewData = getLView();
|
||||
const expandoStartIndex = tView.blueprint.length - HEADER_OFFSET;
|
||||
const previousOrParentTNode = getPreviousOrParentTNode();
|
||||
|
@ -484,7 +481,6 @@ function appendI18nNode(tNode: TNode, parentTNode: TNode, previousTNode: TNode |
|
|||
// Nodes that inject ViewContainerRef also have a comment node that should be moved
|
||||
appendChild(slotValue[NATIVE], tNode, viewData);
|
||||
}
|
||||
|
||||
return tNode;
|
||||
}
|
||||
|
||||
|
@ -566,12 +562,7 @@ export function i18nPostprocess(
|
|||
export function i18nEnd(): void {
|
||||
const tView = getLView()[TVIEW];
|
||||
ngDevMode && assertDefined(tView, `tView should be defined`);
|
||||
ngDevMode &&
|
||||
assertEqual(
|
||||
tView.firstTemplatePass, true, `You should only call i18nEnd on first template pass`);
|
||||
if (tView.firstTemplatePass) {
|
||||
i18nEndFirstPass(tView);
|
||||
}
|
||||
i18nEndFirstPass(tView);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -675,6 +666,7 @@ function readCreateOpCodes(
|
|||
previousTNode = currentTNode;
|
||||
currentTNode = createNodeAtIndex(
|
||||
expandoStartIndex++, TNodeType.IcuContainer, commentRNode, null, null);
|
||||
attachPatchData(commentRNode, viewData);
|
||||
(currentTNode as TIcuContainerNode).activeCaseIndex = null;
|
||||
// We will add the case nodes later, during the update phase
|
||||
setIsParent(false);
|
||||
|
@ -1510,4 +1502,4 @@ function parseNodes(
|
|||
nestedIcuNodeIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
|
||||
import {resolveForwardRef} from '../di/forward_ref';
|
||||
import {InjectionToken} from '../di/injection_token';
|
||||
import {Injector} from '../di/injector';
|
||||
|
@ -44,7 +43,6 @@ import {NO_CHANGE} from './tokens';
|
|||
import {getComponentViewByIndex, getNativeByIndex, getNativeByTNode, getRootContext, getRootView, getTNode, isComponent, isComponentDef, loadInternal, readElementValue, readPatchedLView, stringify} from './util';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A permanent marker promise which signifies that the current CD tree is
|
||||
* clean.
|
||||
|
@ -212,24 +210,26 @@ export function createNodeAtIndex(
|
|||
|
||||
let tNode = tView.data[adjustedIndex] as TNode;
|
||||
if (tNode == null) {
|
||||
const previousOrParentTNode = getPreviousOrParentTNode();
|
||||
const isParent = getIsParent();
|
||||
// TODO(misko): Refactor createTNode so that it does not depend on LView.
|
||||
tNode = tView.data[adjustedIndex] = createTNode(lView, type, adjustedIndex, name, attrs, null);
|
||||
}
|
||||
|
||||
// Now link ourselves into the tree.
|
||||
if (previousOrParentTNode) {
|
||||
if (isParent && previousOrParentTNode.child == null &&
|
||||
(tNode.parent !== null || previousOrParentTNode.type === TNodeType.View)) {
|
||||
// We are in the same view, which means we are adding content node to the parent view.
|
||||
previousOrParentTNode.child = tNode;
|
||||
} else if (!isParent) {
|
||||
previousOrParentTNode.next = tNode;
|
||||
}
|
||||
// Now link ourselves into the tree.
|
||||
// We need this even if tNode exists, otherwise we might end up pointing to unexisting tNodes when
|
||||
// we use i18n (especially with ICU expressions that update the DOM during the update phase).
|
||||
const previousOrParentTNode = getPreviousOrParentTNode();
|
||||
const isParent = getIsParent();
|
||||
if (previousOrParentTNode) {
|
||||
if (isParent && previousOrParentTNode.child == null &&
|
||||
(tNode.parent !== null || previousOrParentTNode.type === TNodeType.View)) {
|
||||
// We are in the same view, which means we are adding content node to the parent view.
|
||||
previousOrParentTNode.child = tNode;
|
||||
} else if (!isParent) {
|
||||
previousOrParentTNode.next = tNode;
|
||||
}
|
||||
}
|
||||
|
||||
if (tView.firstChild == null && type === TNodeType.Element) {
|
||||
if (tView.firstChild == null) {
|
||||
tView.firstChild = tNode;
|
||||
}
|
||||
|
||||
|
@ -504,6 +504,7 @@ export function elementContainerStart(
|
|||
|
||||
appendChild(native, tNode, lView);
|
||||
createDirectivesAndLocals(tView, lView, localRefs);
|
||||
attachPatchData(native, lView);
|
||||
}
|
||||
|
||||
/** Mark the end of the <ng-container>. */
|
||||
|
@ -1921,6 +1922,8 @@ export function template(
|
|||
createDirectivesAndLocals(tView, lView, localRefs, localRefExtractor);
|
||||
const currentQueries = lView[QUERIES];
|
||||
const previousOrParentTNode = getPreviousOrParentTNode();
|
||||
const native = getNativeByTNode(previousOrParentTNode, lView);
|
||||
attachPatchData(native, lView);
|
||||
if (currentQueries) {
|
||||
lView[QUERIES] = currentQueries.addNode(previousOrParentTNode as TContainerNode);
|
||||
}
|
||||
|
|
|
@ -284,7 +284,7 @@ export interface TView {
|
|||
/**
|
||||
* Pointer to the `TNode` that represents the root of the view.
|
||||
*
|
||||
* If this is a `TNode` for an `LViewNode`, this is an embedded view of a container.
|
||||
* If this is a `TViewNode` for an `LViewNode`, this is an embedded view of a container.
|
||||
* We need this pointer to be able to efficiently find this node when inserting the view
|
||||
* into an anchor.
|
||||
*
|
||||
|
|
|
@ -399,29 +399,30 @@ function declareTests(config?: {useJit: boolean}) {
|
|||
expect(fixture.nativeElement).toHaveText('baz');
|
||||
});
|
||||
|
||||
fixmeIvy(
|
||||
'FW-665: Discovery util fails with "Unable to find the given context data for the given target"')
|
||||
.it('should not detach views in ViewContainers when the parent view is destroyed.', () => {
|
||||
TestBed.configureTestingModule({declarations: [MyComp, SomeViewport]});
|
||||
const template =
|
||||
'<div *ngIf="ctxBoolProp"><ng-template some-viewport let-greeting="someTmpl"><span>{{greeting}}</span></ng-template></div>';
|
||||
TestBed.overrideComponent(MyComp, {set: {template}});
|
||||
const fixture = TestBed.createComponent(MyComp);
|
||||
it('should not detach views in ViewContainers when the parent view is destroyed.', () => {
|
||||
TestBed.configureTestingModule({declarations: [MyComp, SomeViewport]});
|
||||
const template =
|
||||
'<div *ngIf="ctxBoolProp"><ng-template some-viewport let-greeting="someTmpl"><span>{{greeting}}</span></ng-template></div>';
|
||||
TestBed.overrideComponent(MyComp, {set: {template}});
|
||||
const fixture = TestBed.createComponent(MyComp);
|
||||
|
||||
fixture.componentInstance.ctxBoolProp = true;
|
||||
fixture.detectChanges();
|
||||
fixture.componentInstance.ctxBoolProp = true;
|
||||
fixture.detectChanges();
|
||||
|
||||
const ngIfEl = fixture.debugElement.children[0];
|
||||
const someViewport: SomeViewport = ngIfEl.childNodes[0].injector.get(SomeViewport);
|
||||
expect(someViewport.container.length).toBe(2);
|
||||
expect(ngIfEl.children.length).toBe(2);
|
||||
const ngIfEl = fixture.debugElement.children[0];
|
||||
const someViewport: SomeViewport =
|
||||
ngIfEl.childNodes
|
||||
.find(debugElement => debugElement.nativeNode.nodeType === Node.COMMENT_NODE)
|
||||
.injector.get(SomeViewport);
|
||||
expect(someViewport.container.length).toBe(2);
|
||||
expect(ngIfEl.children.length).toBe(2);
|
||||
|
||||
fixture.componentInstance.ctxBoolProp = false;
|
||||
fixture.detectChanges();
|
||||
fixture.componentInstance.ctxBoolProp = false;
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(someViewport.container.length).toBe(2);
|
||||
expect(fixture.debugElement.children.length).toBe(0);
|
||||
});
|
||||
expect(someViewport.container.length).toBe(2);
|
||||
expect(fixture.debugElement.children.length).toBe(0);
|
||||
});
|
||||
|
||||
it('should use a comment while stamping out `<ng-template>` elements.', () => {
|
||||
const fixture =
|
||||
|
@ -798,40 +799,37 @@ function declareTests(config?: {useJit: boolean}) {
|
|||
emitter.fireEvent('fired !');
|
||||
}));
|
||||
|
||||
fixmeIvy(
|
||||
'FW-665: Discovery util fails with Unable to find the given context data for the given target')
|
||||
.it('should support events via EventEmitter on template elements', async(() => {
|
||||
const fixture =
|
||||
TestBed
|
||||
.configureTestingModule({
|
||||
declarations: [MyComp, DirectiveEmittingEvent, DirectiveListeningEvent]
|
||||
})
|
||||
.overrideComponent(MyComp, {
|
||||
set: {
|
||||
template:
|
||||
'<ng-template emitter listener (event)="ctxProp=$event"></ng-template>'
|
||||
}
|
||||
})
|
||||
.createComponent(MyComp);
|
||||
it('should support events via EventEmitter on template elements', async(() => {
|
||||
const fixture =
|
||||
TestBed
|
||||
.configureTestingModule(
|
||||
{declarations: [MyComp, DirectiveEmittingEvent, DirectiveListeningEvent]})
|
||||
.overrideComponent(MyComp, {
|
||||
set: {
|
||||
template:
|
||||
'<ng-template emitter listener (event)="ctxProp=$event"></ng-template>'
|
||||
}
|
||||
})
|
||||
.createComponent(MyComp);
|
||||
const tc = fixture.debugElement.childNodes.find(
|
||||
debugElement => debugElement.nativeNode.nodeType === Node.COMMENT_NODE);
|
||||
|
||||
const tc = fixture.debugElement.childNodes[0];
|
||||
const emitter = tc.injector.get(DirectiveEmittingEvent);
|
||||
const myComp = fixture.debugElement.injector.get(MyComp);
|
||||
const listener = tc.injector.get(DirectiveListeningEvent);
|
||||
|
||||
const emitter = tc.injector.get(DirectiveEmittingEvent);
|
||||
const myComp = fixture.debugElement.injector.get(MyComp);
|
||||
const listener = tc.injector.get(DirectiveListeningEvent);
|
||||
myComp.ctxProp = '';
|
||||
expect(listener.msg).toEqual('');
|
||||
|
||||
myComp.ctxProp = '';
|
||||
expect(listener.msg).toEqual('');
|
||||
emitter.event.subscribe({
|
||||
next: () => {
|
||||
expect(listener.msg).toEqual('fired !');
|
||||
expect(myComp.ctxProp).toEqual('fired !');
|
||||
}
|
||||
});
|
||||
|
||||
emitter.event.subscribe({
|
||||
next: () => {
|
||||
expect(listener.msg).toEqual('fired !');
|
||||
expect(myComp.ctxProp).toEqual('fired !');
|
||||
}
|
||||
});
|
||||
|
||||
emitter.fireEvent('fired !');
|
||||
}));
|
||||
emitter.fireEvent('fired !');
|
||||
}));
|
||||
|
||||
it('should support [()] syntax', async(() => {
|
||||
TestBed.configureTestingModule({declarations: [MyComp, DirectiveWithTwoWayBinding]});
|
||||
|
|
|
@ -8,17 +8,15 @@
|
|||
import {createInjector} from '@angular/core';
|
||||
|
||||
import {StaticInjector} from '../../src/di/injector';
|
||||
import {getComponent, getContext, getDirectives, getInjectionTokens, getInjector, getListeners, getLocalRefs, getRootComponents, getViewComponent} from '../../src/render3/discovery_utils';
|
||||
import {ProvidersFeature, RenderFlags, defineComponent, defineDirective, getHostElement} from '../../src/render3/index';
|
||||
|
||||
import {getComponent, getContext, getDirectives, getInjectionTokens, getInjector, getListeners, getLocalRefs, getRootComponents, getViewComponent, loadLContext} from '../../src/render3/discovery_utils';
|
||||
import {ProvidersFeature, RenderFlags, defineComponent, defineDirective, elementContainerEnd, elementContainerStart, getHostElement, i18n, i18nApply, i18nExp} from '../../src/render3/index';
|
||||
import {element, elementEnd, elementStart, elementStyling, elementStylingApply, template, bind, elementProperty, text, textBinding, markDirty, listener} from '../../src/render3/instructions';
|
||||
|
||||
import {ComponentFixture} from './render_util';
|
||||
import {NgIf} from './common_with_def';
|
||||
|
||||
describe('discovery utils', () => {
|
||||
let fixture: ComponentFixture<MyApp>;
|
||||
let myApp: MyApp[];
|
||||
let myApp: MyApp;
|
||||
let dirA: DirectiveA[];
|
||||
let childComponent: DirectiveA[];
|
||||
let child: NodeListOf<Element>;
|
||||
|
@ -29,7 +27,6 @@ describe('discovery utils', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
log = [];
|
||||
myApp = [];
|
||||
dirA = [];
|
||||
childComponent = [];
|
||||
fixture = new ComponentFixture(
|
||||
|
@ -63,6 +60,7 @@ describe('discovery utils', () => {
|
|||
* <p></p>
|
||||
* <VIEW>
|
||||
* </child>
|
||||
* <i18n>ICU expression</i18n>
|
||||
* </#VIEW>
|
||||
* </my-app>
|
||||
* ```
|
||||
|
@ -96,15 +94,19 @@ describe('discovery utils', () => {
|
|||
});
|
||||
}
|
||||
|
||||
const MSG_DIV = `{<7B>0<EFBFBD>, select,
|
||||
other {ICU expression}
|
||||
}`;
|
||||
|
||||
class MyApp {
|
||||
text: string = 'INIT';
|
||||
constructor() { myApp.push(this); }
|
||||
constructor() { myApp = this; }
|
||||
|
||||
static ngComponentDef = defineComponent({
|
||||
type: MyApp,
|
||||
selectors: [['my-app']],
|
||||
factory: () => new MyApp(),
|
||||
consts: 9,
|
||||
consts: 13,
|
||||
vars: 1,
|
||||
directives: [Child, DirectiveA, NgIf],
|
||||
template: (rf: RenderFlags, ctx: MyApp) => {
|
||||
|
@ -121,10 +123,18 @@ describe('discovery utils', () => {
|
|||
element(0, 'child');
|
||||
}
|
||||
}, 1, 0, 'child', ['ngIf', '']);
|
||||
elementStart(9, 'i18n');
|
||||
i18n(10, MSG_DIV);
|
||||
elementEnd();
|
||||
elementContainerStart(11);
|
||||
{ text(12, 'content'); }
|
||||
elementContainerEnd();
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
textBinding(1, bind(ctx.text));
|
||||
elementProperty(8, 'ngIf', bind(true));
|
||||
i18nExp(bind(ctx.text));
|
||||
i18nApply(10);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -141,7 +151,7 @@ describe('discovery utils', () => {
|
|||
expect(() => getComponent(dirA[1] as any)).toThrowError(/Expecting instance of DOM Node/);
|
||||
});
|
||||
it('should return component from element', () => {
|
||||
expect(getComponent<MyApp>(fixture.hostElement)).toEqual(myApp[0]);
|
||||
expect(getComponent<MyApp>(fixture.hostElement)).toEqual(myApp);
|
||||
expect(getComponent<Child>(child[0])).toEqual(childComponent[0]);
|
||||
expect(getComponent<Child>(child[1])).toEqual(childComponent[1]);
|
||||
});
|
||||
|
@ -153,7 +163,7 @@ describe('discovery utils', () => {
|
|||
expect(() => getContext(dirA[1] as any)).toThrowError(/Expecting instance of DOM Node/);
|
||||
});
|
||||
it('should return context from element', () => {
|
||||
expect(getContext<MyApp>(child[0])).toEqual(myApp[0]);
|
||||
expect(getContext<MyApp>(child[0])).toEqual(myApp);
|
||||
expect(getContext<{$implicit: boolean}>(child[2]) !.$implicit).toEqual(true);
|
||||
expect(getContext<Child>(p[0])).toEqual(childComponent[0]);
|
||||
});
|
||||
|
@ -161,7 +171,7 @@ describe('discovery utils', () => {
|
|||
|
||||
describe('getHostElement', () => {
|
||||
it('should return element on component', () => {
|
||||
expect(getHostElement(myApp[0])).toEqual(fixture.hostElement);
|
||||
expect(getHostElement(myApp)).toEqual(fixture.hostElement);
|
||||
expect(getHostElement(childComponent[0])).toEqual(child[0]);
|
||||
expect(getHostElement(childComponent[1])).toEqual(child[1]);
|
||||
});
|
||||
|
@ -181,7 +191,7 @@ describe('discovery utils', () => {
|
|||
expect(getInjector(p[0]).get(String)).toEqual('Child');
|
||||
});
|
||||
it('should return node-injector from component with providers', () => {
|
||||
expect(getInjector(myApp[0]).get(String)).toEqual('Module');
|
||||
expect(getInjector(myApp).get(String)).toEqual('Module');
|
||||
expect(getInjector(childComponent[0]).get(String)).toEqual('Child');
|
||||
expect(getInjector(childComponent[1]).get(String)).toEqual('Child');
|
||||
});
|
||||
|
@ -206,34 +216,34 @@ describe('discovery utils', () => {
|
|||
describe('getViewComponent', () => {
|
||||
it('should return null when called on root component', () => {
|
||||
expect(getViewComponent(fixture.hostElement)).toEqual(null);
|
||||
expect(getViewComponent(myApp[0])).toEqual(null);
|
||||
expect(getViewComponent(myApp)).toEqual(null);
|
||||
});
|
||||
it('should return containing component of child component', () => {
|
||||
expect(getViewComponent<MyApp>(child[0])).toEqual(myApp[0]);
|
||||
expect(getViewComponent<MyApp>(child[1])).toEqual(myApp[0]);
|
||||
expect(getViewComponent<MyApp>(child[2])).toEqual(myApp[0]);
|
||||
expect(getViewComponent<MyApp>(child[0])).toEqual(myApp);
|
||||
expect(getViewComponent<MyApp>(child[1])).toEqual(myApp);
|
||||
expect(getViewComponent<MyApp>(child[2])).toEqual(myApp);
|
||||
|
||||
expect(getViewComponent<MyApp>(childComponent[0])).toEqual(myApp[0]);
|
||||
expect(getViewComponent<MyApp>(childComponent[1])).toEqual(myApp[0]);
|
||||
expect(getViewComponent<MyApp>(childComponent[2])).toEqual(myApp[0]);
|
||||
expect(getViewComponent<MyApp>(childComponent[0])).toEqual(myApp);
|
||||
expect(getViewComponent<MyApp>(childComponent[1])).toEqual(myApp);
|
||||
expect(getViewComponent<MyApp>(childComponent[2])).toEqual(myApp);
|
||||
});
|
||||
it('should return containing component of any view element', () => {
|
||||
expect(getViewComponent<MyApp>(span[0])).toEqual(myApp[0]);
|
||||
expect(getViewComponent<MyApp>(div[0])).toEqual(myApp[0]);
|
||||
expect(getViewComponent<MyApp>(span[0])).toEqual(myApp);
|
||||
expect(getViewComponent<MyApp>(div[0])).toEqual(myApp);
|
||||
expect(getViewComponent<Child>(p[0])).toEqual(childComponent[0]);
|
||||
expect(getViewComponent<Child>(p[1])).toEqual(childComponent[1]);
|
||||
expect(getViewComponent<Child>(p[2])).toEqual(childComponent[2]);
|
||||
});
|
||||
it('should return containing component of child directive', () => {
|
||||
expect(getViewComponent<MyApp>(dirA[0])).toEqual(myApp[0]);
|
||||
expect(getViewComponent<MyApp>(dirA[1])).toEqual(myApp[0]);
|
||||
expect(getViewComponent<MyApp>(dirA[0])).toEqual(myApp);
|
||||
expect(getViewComponent<MyApp>(dirA[1])).toEqual(myApp);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getLocalRefs', () => {
|
||||
it('should retrieve empty map', () => {
|
||||
expect(getLocalRefs(fixture.hostElement)).toEqual({});
|
||||
expect(getLocalRefs(myApp[0])).toEqual({});
|
||||
expect(getLocalRefs(myApp)).toEqual({});
|
||||
expect(getLocalRefs(span[0])).toEqual({});
|
||||
expect(getLocalRefs(child[0])).toEqual({});
|
||||
});
|
||||
|
@ -249,8 +259,8 @@ describe('discovery utils', () => {
|
|||
|
||||
describe('getRootComponents', () => {
|
||||
it('should return root components from component', () => {
|
||||
const rootComponents = [myApp[0]];
|
||||
expect(getRootComponents(myApp[0])).toEqual(rootComponents);
|
||||
const rootComponents = [myApp];
|
||||
expect(getRootComponents(myApp)).toEqual(rootComponents);
|
||||
expect(getRootComponents(childComponent[0])).toEqual(rootComponents);
|
||||
expect(getRootComponents(childComponent[1])).toEqual(rootComponents);
|
||||
expect(getRootComponents(dirA[0])).toEqual(rootComponents);
|
||||
|
@ -289,12 +299,46 @@ describe('discovery utils', () => {
|
|||
describe('markDirty', () => {
|
||||
it('should re-render component', () => {
|
||||
expect(span[0].textContent).toEqual('INIT');
|
||||
myApp[0].text = 'WORKS';
|
||||
markDirty(myApp[0]);
|
||||
myApp.text = 'WORKS';
|
||||
markDirty(myApp);
|
||||
fixture.requestAnimationFrame.flush();
|
||||
expect(span[0].textContent).toEqual('WORKS');
|
||||
});
|
||||
});
|
||||
|
||||
describe('loadLContext', () => {
|
||||
it('should work on components', () => {
|
||||
const lContext = loadLContext(child[0]);
|
||||
expect(lContext).toBeDefined();
|
||||
expect(lContext.native as any).toBe(child[0]);
|
||||
});
|
||||
|
||||
it('should work on templates', () => {
|
||||
const templateComment = Array.from(fixture.hostElement.childNodes)
|
||||
.find((node: ChildNode) => node.nodeType === Node.COMMENT_NODE);
|
||||
const lContext = loadLContext(templateComment);
|
||||
expect(lContext).toBeDefined();
|
||||
expect(lContext.native as any).toBe(templateComment);
|
||||
});
|
||||
|
||||
it('should work on ICU expressions', () => {
|
||||
const icuComment = Array.from(fixture.hostElement.querySelector('i18n') !.childNodes)
|
||||
.find((node: ChildNode) => node.nodeType === Node.COMMENT_NODE);
|
||||
const lContext = loadLContext(icuComment);
|
||||
expect(lContext).toBeDefined();
|
||||
expect(lContext.native as any).toBe(icuComment);
|
||||
});
|
||||
|
||||
it('should work on ng-container', () => {
|
||||
const ngContainerComment = Array.from(fixture.hostElement.childNodes)
|
||||
.find(
|
||||
(node: ChildNode) => node.nodeType === Node.COMMENT_NODE &&
|
||||
node.textContent === `ng-container`);
|
||||
const lContext = loadLContext(ngContainerComment);
|
||||
expect(lContext).toBeDefined();
|
||||
expect(lContext.native as any).toBe(ngContainerComment);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('discovery utils deprecated', () => {
|
||||
|
|
Loading…
Reference in New Issue