feat(ivy): expose a series of helpful application inspection tools (#25919)
PR Close #25919
This commit is contained in:
parent
cf095d982d
commit
0c344715e5
|
@ -3,13 +3,13 @@
|
|||
"version": "0.0.0",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"build": "ng build --prod --progress false",
|
||||
"build": "ng build --prod --progress=false",
|
||||
"e2e": "ng e2e",
|
||||
"lint": "ng lint",
|
||||
"ng": "ng",
|
||||
"postinstall": "webdriver-manager update --gecko false --standalone false $CHROMEDRIVER_VERSION_ARG",
|
||||
"postinstall": "webdriver-manager update --gecko=false --standalone=false $CHROMEDRIVER_VERSION_ARG",
|
||||
"start": "ng serve",
|
||||
"test": "ng test && ng e2e --webdriver-update=false && ng e2e --prod --webdriver-update=false"
|
||||
"test": "ng test --progress=false && ng e2e --webdriver-update=false && ng e2e --prod --webdriver-update=false"
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
|
@ -28,7 +28,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "~0.5.0",
|
||||
"@angular/cli": "^6.0.0-rc.5",
|
||||
"@angular/cli": "7.0.0-beta.4",
|
||||
"@angular/compiler-cli": "file:../../dist/packages-dist/compiler-cli",
|
||||
"@angular/language-service": "file:../../dist/packages-dist/language-service",
|
||||
"@types/jasmine": "~2.8.3",
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -352,7 +352,8 @@ function getLNodeFromViewData(lViewData: LViewData, lElementIndex: number): LEle
|
|||
* Returns a collection of directive index values that are used on the element
|
||||
* (which is referenced by the lNodeIndex)
|
||||
*/
|
||||
function discoverDirectiveIndices(lViewData: LViewData, lNodeIndex: number): number[]|null {
|
||||
export function discoverDirectiveIndices(
|
||||
lViewData: LViewData, lNodeIndex: number, includeComponents?: boolean): number[]|null {
|
||||
const directivesAcrossView = lViewData[DIRECTIVES];
|
||||
const tNode = lViewData[TVIEW].data[lNodeIndex] as TNode;
|
||||
if (directivesAcrossView && directivesAcrossView.length) {
|
||||
|
@ -373,12 +374,20 @@ function discoverDirectiveIndices(lViewData: LViewData, lNodeIndex: number): num
|
|||
return null;
|
||||
}
|
||||
|
||||
function discoverDirectives(lViewData: LViewData, directiveIndices: number[]): number[]|null {
|
||||
/**
|
||||
* Returns a list of directives extracted from the given view based on the
|
||||
* provided list of directive index values.
|
||||
*
|
||||
* @param lViewData The target view data
|
||||
* @param indices A collection of directive index values which will be used to
|
||||
* figure out the directive instances
|
||||
*/
|
||||
export function discoverDirectives(lViewData: LViewData, indices: number[]): number[]|null {
|
||||
const directives: any[] = [];
|
||||
const directiveInstances = lViewData[DIRECTIVES];
|
||||
if (directiveInstances) {
|
||||
for (let i = 0; i < directiveIndices.length; i++) {
|
||||
const directiveIndex = directiveIndices[i];
|
||||
for (let i = 0; i < indices.length; i++) {
|
||||
const directiveIndex = indices[i];
|
||||
const directive = directiveInstances[directiveIndex];
|
||||
directives.push(directive);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,143 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {Injector} from '../di/injector';
|
||||
|
||||
import {assertDefined} from './assert';
|
||||
import {LContext, discoverDirectiveIndices, discoverDirectives, getContext, isComponentInstance, readPatchedLViewData} from './context_discovery';
|
||||
import {LElementNode, TNode, TNodeFlags} from './interfaces/node';
|
||||
import {CONTEXT, FLAGS, INJECTOR, LViewData, LViewFlags, PARENT, RootContext, TVIEW} from './interfaces/view';
|
||||
|
||||
|
||||
/**
|
||||
* NOTE: The following functions might not be ideal for core usage in Angular...
|
||||
*
|
||||
* Each function below is designed
|
||||
*/
|
||||
|
||||
/**
|
||||
* Returns the component instance associated with the target.
|
||||
*
|
||||
* If a DOM is used then it will return the component that
|
||||
* owns the view where the element is situated.
|
||||
* If a component instance is used then it will return the
|
||||
* instance of the parent component depending on where
|
||||
* the component instance is exists in a template.
|
||||
* If a directive instance is used then it will return the
|
||||
* component that contains that directive in it's template.
|
||||
*/
|
||||
export function getComponent<T = {}>(target: {}): T|null {
|
||||
const context = loadContext(target) !;
|
||||
|
||||
if (context.component === undefined) {
|
||||
let lViewData = context.lViewData;
|
||||
while (lViewData) {
|
||||
const ctx = lViewData ![CONTEXT] !as{};
|
||||
if (ctx && isComponentInstance(ctx)) {
|
||||
context.component = ctx;
|
||||
break;
|
||||
}
|
||||
lViewData = lViewData ![PARENT] !;
|
||||
}
|
||||
if (context.component === undefined) {
|
||||
context.component = null;
|
||||
}
|
||||
}
|
||||
|
||||
return context.component as T;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the host component instance associated with the target.
|
||||
*
|
||||
* This will only return a component instance of the DOM node
|
||||
* contains an instance of a component on it.
|
||||
*/
|
||||
export function getHostComponent<T = {}>(target: {}): T|null {
|
||||
const context = loadContext(target);
|
||||
const tNode = context.lViewData[TVIEW].data[context.lNodeIndex] as TNode;
|
||||
if (tNode.flags & TNodeFlags.isComponent) {
|
||||
const lNode = context.lViewData[context.lNodeIndex] as LElementNode;
|
||||
return lNode.data ![CONTEXT] as any as T;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the `RootContext` instance that is associated with
|
||||
* the application where the target is situated.
|
||||
*/
|
||||
export function getRootContext(target: {}): RootContext {
|
||||
const context = loadContext(target) !;
|
||||
const rootLViewData = getRootView(context.lViewData);
|
||||
return rootLViewData[CONTEXT] as RootContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all the components in the application
|
||||
* that are have been bootstrapped.
|
||||
*/
|
||||
export function getRootComponents(target: {}): any[] {
|
||||
return [...getRootContext(target).components];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the injector instance that is associated with
|
||||
* the element, component or directive.
|
||||
*/
|
||||
export function getInjector(target: {}): Injector|null {
|
||||
const context = loadContext(target) !;
|
||||
return context.lViewData[INJECTOR] || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all the directives that are associated
|
||||
* with the underlying target element.
|
||||
*/
|
||||
export function getDirectives(target: {}): Array<{}> {
|
||||
const context = loadContext(target) !;
|
||||
|
||||
if (context.directives === undefined) {
|
||||
context.directiveIndices = discoverDirectiveIndices(context.lViewData, context.lNodeIndex);
|
||||
context.directives = context.directiveIndices ?
|
||||
discoverDirectives(context.lViewData, context.directiveIndices) :
|
||||
null;
|
||||
}
|
||||
|
||||
return context.directives || [];
|
||||
}
|
||||
|
||||
function loadContext(target: {}): LContext {
|
||||
const context = getContext(target);
|
||||
if (!context) {
|
||||
throw new Error(
|
||||
ngDevMode ? 'Unable to find the given context data for the given target' :
|
||||
'Invalid ng target');
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the root view from any component by walking the parent `LViewData` until
|
||||
* reaching the root `LViewData`.
|
||||
*
|
||||
* @param componentOrView any component or view
|
||||
*/
|
||||
export function getRootView(componentOrView: LViewData | {}): LViewData {
|
||||
let lViewData: LViewData;
|
||||
if (Array.isArray(componentOrView)) {
|
||||
ngDevMode && assertDefined(componentOrView, 'lViewData');
|
||||
lViewData = componentOrView as LViewData;
|
||||
} else {
|
||||
ngDevMode && assertDefined(componentOrView, 'component');
|
||||
lViewData = readPatchedLViewData(componentOrView) !;
|
||||
}
|
||||
while (lViewData && !(lViewData[FLAGS] & LViewFlags.IsRoot)) {
|
||||
lViewData = lViewData[PARENT] !;
|
||||
}
|
||||
return lViewData;
|
||||
}
|
|
@ -11,9 +11,9 @@ import './ng_dev_mode';
|
|||
import {QueryList} from '../linker';
|
||||
import {Sanitizer} from '../sanitization/security';
|
||||
import {StyleSanitizeFn} from '../sanitization/style_sanitizer';
|
||||
|
||||
import {assertDefined, assertEqual, assertLessThan, assertNotEqual} from './assert';
|
||||
import {attachPatchData, getLElementFromComponent, readElementValue, readPatchedLViewData} from './context_discovery';
|
||||
import {getRootView} from './discovery_utils';
|
||||
import {throwCyclicDependencyError, throwErrorIfNoChangesMode, throwMultipleComponentError} from './errors';
|
||||
import {executeHooks, executeInitHooks, queueInitHooks, queueLifecycleHooks} from './hooks';
|
||||
import {ACTIVE_INDEX, LContainer, RENDER_PARENT, VIEWS} from './interfaces/container';
|
||||
|
@ -28,9 +28,7 @@ import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert';
|
|||
import {appendChild, appendProjectedNode, createTextNode, findComponentView, getContainerNode, getHostElementNode, getLViewChild, getParentOrContainerNode, getRenderParent, insertView, removeView} from './node_manipulation';
|
||||
import {isNodeMatchingSelectorList, matchingSelectorIndex} from './node_selector_matcher';
|
||||
import {StylingContext, allocStylingContext, createStylingContextTemplate, renderStyling as renderElementStyles, updateClassProp as updateElementClassProp, updateStyleProp as updateElementStyleProp, updateStylingMap} from './styling';
|
||||
import {assertDataInRangeInternal, getLNode, getRootContext, getRootView, isContentQueryHost, isDifferent, loadElementInternal, loadInternal, stringify} from './util';
|
||||
import {ViewRef} from './view_ref';
|
||||
|
||||
import {assertDataInRangeInternal, getLNode, isContentQueryHost, isDifferent, loadElementInternal, loadInternal, stringify} from './util';
|
||||
|
||||
|
||||
/**
|
||||
|
|
|
@ -654,10 +654,10 @@
|
|||
"name": "getRendererFactory"
|
||||
},
|
||||
{
|
||||
"name": "getRootContext"
|
||||
"name": "getRootContext$1"
|
||||
},
|
||||
{
|
||||
"name": "getRootView"
|
||||
"name": "getRootView$1"
|
||||
},
|
||||
{
|
||||
"name": "getStyleSanitizer"
|
||||
|
|
|
@ -0,0 +1,269 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {StaticInjector} from '../../src/di/injector';
|
||||
import {getComponent, getDirectives, getHostComponent, getInjector, getRootComponents} from '../../src/render3/discovery_utils';
|
||||
import {RenderFlags, defineComponent, defineDirective} from '../../src/render3/index';
|
||||
import {element} from '../../src/render3/instructions';
|
||||
|
||||
import {ComponentFixture} from './render_util';
|
||||
|
||||
describe('discovery utils', () => {
|
||||
describe('getComponent()', () => {
|
||||
it('should return the component instance for a DOM element', () => {
|
||||
class InnerComp {
|
||||
static ngComponentDef = defineComponent({
|
||||
type: InnerComp,
|
||||
selectors: [['inner-comp']],
|
||||
factory: () => new InnerComp(),
|
||||
consts: 1,
|
||||
vars: 0,
|
||||
template: (rf: RenderFlags, ctx: InnerComp) => {
|
||||
if (rf & RenderFlags.Create) {
|
||||
element(0, 'div');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
class Comp {
|
||||
static ngComponentDef = defineComponent({
|
||||
type: Comp,
|
||||
selectors: [['comp']],
|
||||
factory: () => new Comp(),
|
||||
consts: 1,
|
||||
vars: 0,
|
||||
template: (rf: RenderFlags, ctx: Comp) => {
|
||||
if (rf & RenderFlags.Create) {
|
||||
element(0, 'inner-comp');
|
||||
}
|
||||
},
|
||||
directives: [InnerComp]
|
||||
});
|
||||
}
|
||||
|
||||
const fixture = new ComponentFixture(Comp);
|
||||
fixture.update();
|
||||
|
||||
const hostElm = fixture.hostElement;
|
||||
const innerCompElm = hostElm.querySelector('inner-comp');
|
||||
const component = fixture.component;
|
||||
|
||||
expect(getComponent(innerCompElm !) !).toBe(component);
|
||||
expect(getComponent(hostElm) !).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getRootComponents()', () => {
|
||||
it('should return a list of the root components of the application from an element', () => {
|
||||
let innerComp: InnerComp;
|
||||
|
||||
class InnerComp {
|
||||
static ngComponentDef = defineComponent({
|
||||
type: InnerComp,
|
||||
selectors: [['inner-comp']],
|
||||
factory: () => innerComp = new InnerComp(),
|
||||
consts: 1,
|
||||
vars: 0,
|
||||
template: (rf: RenderFlags, ctx: InnerComp) => {
|
||||
if (rf & RenderFlags.Create) {
|
||||
element(0, 'div');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
class Comp {
|
||||
static ngComponentDef = defineComponent({
|
||||
type: Comp,
|
||||
selectors: [['comp']],
|
||||
factory: () => new Comp(),
|
||||
consts: 1,
|
||||
vars: 0,
|
||||
template: (rf: RenderFlags, ctx: Comp) => {
|
||||
if (rf & RenderFlags.Create) {
|
||||
element(0, 'inner-comp');
|
||||
}
|
||||
},
|
||||
directives: [InnerComp]
|
||||
});
|
||||
}
|
||||
|
||||
const fixture = new ComponentFixture(Comp);
|
||||
fixture.update();
|
||||
|
||||
const hostElm = fixture.hostElement;
|
||||
const innerElm = hostElm.querySelector('inner-comp') !;
|
||||
const divElm = hostElm.querySelector('div') !;
|
||||
const component = fixture.component;
|
||||
|
||||
expect(getRootComponents(hostElm) !).toEqual([component]);
|
||||
expect(getRootComponents(innerElm) !).toEqual([component]);
|
||||
expect(getRootComponents(divElm) !).toEqual([component]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getDirectives()', () => {
|
||||
it('should return a list of the directives that are on the given element', () => {
|
||||
let myDir1Instance: MyDir1|null = null;
|
||||
let myDir2Instance: MyDir2|null = null;
|
||||
let myDir3Instance: MyDir2|null = null;
|
||||
|
||||
class MyDir1 {
|
||||
static ngDirectiveDef = defineDirective({
|
||||
type: MyDir1,
|
||||
selectors: [['', 'my-dir-1', '']],
|
||||
factory: () => myDir1Instance = new MyDir1()
|
||||
});
|
||||
}
|
||||
|
||||
class MyDir2 {
|
||||
static ngDirectiveDef = defineDirective({
|
||||
type: MyDir2,
|
||||
selectors: [['', 'my-dir-2', '']],
|
||||
factory: () => myDir2Instance = new MyDir2()
|
||||
});
|
||||
}
|
||||
|
||||
class MyDir3 {
|
||||
static ngDirectiveDef = defineDirective({
|
||||
type: MyDir3,
|
||||
selectors: [['', 'my-dir-3', '']],
|
||||
factory: () => myDir3Instance = new MyDir2()
|
||||
});
|
||||
}
|
||||
|
||||
class Comp {
|
||||
static ngComponentDef = defineComponent({
|
||||
type: Comp,
|
||||
selectors: [['comp']],
|
||||
factory: () => new Comp(),
|
||||
consts: 2,
|
||||
vars: 0,
|
||||
template: (rf: RenderFlags, ctx: Comp) => {
|
||||
if (rf & RenderFlags.Create) {
|
||||
element(0, 'div', ['my-dir-1', '', 'my-dir-2', '']);
|
||||
element(1, 'div', ['my-dir-3']);
|
||||
}
|
||||
},
|
||||
directives: [MyDir1, MyDir2, MyDir3]
|
||||
});
|
||||
}
|
||||
|
||||
const fixture = new ComponentFixture(Comp);
|
||||
fixture.update();
|
||||
|
||||
const hostElm = fixture.hostElement;
|
||||
const elements = hostElm.querySelectorAll('div');
|
||||
|
||||
const elm1 = elements[0];
|
||||
const elm1Dirs = getDirectives(elm1);
|
||||
expect(elm1Dirs).toContain(myDir1Instance !);
|
||||
expect(elm1Dirs).toContain(myDir2Instance !);
|
||||
|
||||
const elm2 = elements[1];
|
||||
const elm2Dirs = getDirectives(elm2);
|
||||
expect(elm2Dirs).toContain(myDir3Instance !);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getHostComponent()', () => {
|
||||
it('should return the component instance for a DOM element', () => {
|
||||
let innerComp: InnerComp;
|
||||
|
||||
class InnerComp {
|
||||
static ngComponentDef = defineComponent({
|
||||
type: InnerComp,
|
||||
selectors: [['inner-comp']],
|
||||
factory: () => innerComp = new InnerComp(),
|
||||
consts: 1,
|
||||
vars: 0,
|
||||
template: (rf: RenderFlags, ctx: InnerComp) => {
|
||||
if (rf & RenderFlags.Create) {
|
||||
element(0, 'div');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
class Comp {
|
||||
static ngComponentDef = defineComponent({
|
||||
type: Comp,
|
||||
selectors: [['comp']],
|
||||
factory: () => new Comp(),
|
||||
consts: 1,
|
||||
vars: 0,
|
||||
template: (rf: RenderFlags, ctx: Comp) => {
|
||||
if (rf & RenderFlags.Create) {
|
||||
element(0, 'inner-comp');
|
||||
}
|
||||
},
|
||||
directives: [InnerComp]
|
||||
});
|
||||
}
|
||||
|
||||
const fixture = new ComponentFixture(Comp);
|
||||
fixture.update();
|
||||
|
||||
const hostElm = fixture.hostElement;
|
||||
const innerElm = hostElm.querySelector('inner-comp') !;
|
||||
const divElm = hostElm.querySelector('div') !;
|
||||
const component = fixture.component;
|
||||
|
||||
expect(getHostComponent(hostElm) !).toBe(component);
|
||||
expect(getHostComponent(innerElm) !).toBe(innerComp !);
|
||||
expect(getHostComponent(divElm) !).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getInjector()', () => {
|
||||
it('should return the instance of the injector that was passed into the component', () => {
|
||||
class Comp {
|
||||
static ngComponentDef = defineComponent({
|
||||
type: Comp,
|
||||
selectors: [['comp']],
|
||||
factory: () => new Comp(),
|
||||
consts: 1,
|
||||
vars: 0,
|
||||
template: (rf: RenderFlags, ctx: Comp) => {
|
||||
if (rf & RenderFlags.Create) {
|
||||
element(0, 'div');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const injector = new StaticInjector([]);
|
||||
const fixture = new ComponentFixture(Comp, {injector});
|
||||
fixture.update();
|
||||
|
||||
expect(getInjector(fixture.hostElement) !).toBe(injector);
|
||||
});
|
||||
|
||||
it('should return null when there is no injector passed into a component', () => {
|
||||
class Comp {
|
||||
static ngComponentDef = defineComponent({
|
||||
type: Comp,
|
||||
selectors: [['comp']],
|
||||
factory: () => new Comp(),
|
||||
consts: 1,
|
||||
vars: 0,
|
||||
template: (rf: RenderFlags, ctx: Comp) => {
|
||||
if (rf & RenderFlags.Create) {
|
||||
element(0, 'div');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const fixture = new ComponentFixture(Comp);
|
||||
fixture.update();
|
||||
|
||||
expect(getInjector(fixture.hostElement)).toEqual(null);
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue