refactor(ivy): ensure `StylingDebug` instances provide context debug info (#32856)

This patch enables a styling debug instance (which is apart of the
`debugNode.styles` or `debugNode.classes` data structures) to expose
its context value so that it can be easily debugged.

PR Close #32856
This commit is contained in:
Matias Niemelä 2019-09-25 11:05:07 -07:00 committed by Miško Hevery
parent 728cd8446f
commit 35a95a8a7e
4 changed files with 118 additions and 88 deletions

View File

@ -20,7 +20,7 @@ import {TQueries} from '../interfaces/query';
import {RComment, RElement, RNode} from '../interfaces/renderer';
import {TStylingContext} from '../interfaces/styling';
import {BINDING_INDEX, CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DECLARATION_VIEW, ExpandoInstructions, FLAGS, HEADER_OFFSET, HOST, HookData, INJECTOR, LView, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, RENDERER_FACTORY, SANITIZER, TData, TVIEW, TView as ITView, TView, T_HOST} from '../interfaces/view';
import {DebugStyling as DebugNewStyling, NodeStylingDebug} from '../styling/styling_debug';
import {DebugNodeStyling, NodeStylingDebug} from '../styling/styling_debug';
import {attachDebugObject} from '../util/debug_utils';
import {isStylingContext} from '../util/styling_utils';
import {getTNode, unwrapRNode} from '../util/view_utils';
@ -343,8 +343,8 @@ export class LViewDebug {
export interface DebugNode {
html: string|null;
native: Node;
styles: DebugNewStyling|null;
classes: DebugNewStyling|null;
styles: DebugNodeStyling|null;
classes: DebugNodeStyling|null;
nodes: DebugNode[]|null;
component: LViewDebug|null;
}

View File

@ -10,7 +10,7 @@ import {RElement} from '../interfaces/renderer';
import {ApplyStylingFn, LStylingData, TStylingConfig, TStylingContext, TStylingContextIndex} from '../interfaces/styling';
import {getCurrentStyleSanitizer} from '../state';
import {attachDebugObject} from '../util/debug_utils';
import {allowDirectStyling as _allowDirectStyling, getDefaultValue, getGuardMask, getProp, getPropValuesStartPosition, getValuesCount, hasConfig, isContextLocked, isSanitizationRequired} from '../util/styling_utils';
import {allowDirectStyling as _allowDirectStyling, getDefaultValue, getGuardMask, getProp, getPropValuesStartPosition, getValuesCount, hasConfig, isContextLocked, isSanitizationRequired, isStylingContext} from '../util/styling_utils';
import {applyStylingViaContext} from './bindings';
import {activateStylingMapFeature} from './map_based_bindings';
@ -27,65 +27,42 @@ import {activateStylingMapFeature} from './map_based_bindings';
* --------
*/
/**
* A debug/testing-oriented summary of a styling entry.
* A debug-friendly version of `TStylingContext`.
*
* A value such as this is generated as an artifact of the `DebugStyling`
* summary.
* An instance of this is attached to `tStylingContext.debug` when `ngDevMode` is active.
*/
export interface LStylingSummary {
/** The style/class property that the summary is attached to */
prop: string;
export interface DebugStylingContext {
/** The configuration settings of the associated `TStylingContext` */
config: DebugStylingConfig;
/** The last applied value for the style/class property */
value: string|boolean|null;
/** The binding index of the last applied style/class property */
bindingIndex: number|null;
}
/**
* A debug/testing-oriented summary of all styling entries for a `DebugNode` instance.
*/
export interface DebugStyling {
/** The associated TStylingContext instance */
context: TStylingContext;
/** Which configuration flags are active (see `TStylingContextConfig`) */
config: {
hasMapBindings: boolean; //
hasPropBindings: boolean; //
hasCollisions: boolean; //
hasTemplateBindings: boolean; //
hasHostBindings: boolean; //
templateBindingsLocked: boolean; //
hostBindingsLocked: boolean; //
allowDirectStyling: boolean; //
};
/**
* A summarization of each style/class property
* present in the context
*/
summary: {[propertyName: string]: LStylingSummary};
/**
* A key/value map of all styling properties and their
* runtime values
*/
values: {[propertyName: string]: string | number | null | boolean};
/**
* Overrides the sanitizer used to process styles
*/
overrideSanitizer(sanitizer: StyleSanitizeFn|null): void;
/** The associated TStylingContext instance */
entries: {[prop: string]: DebugStylingContextEntry};
}
/**
* A debug/testing-oriented summary of `TStylingConfig`.
*/
export interface DebugStylingConfig {
hasMapBindings: boolean; //
hasPropBindings: boolean; //
hasCollisions: boolean; //
hasTemplateBindings: boolean; //
hasHostBindings: boolean; //
templateBindingsLocked: boolean; //
hostBindingsLocked: boolean; //
allowDirectStyling: boolean; //
}
/**
* A debug/testing-oriented summary of all styling entries within a `TStylingContext`.
*/
export interface TStylingTupleSummary {
export interface DebugStylingContextEntry {
/** The property (style or class property) that this tuple represents */
prop: string;
@ -120,6 +97,51 @@ export interface TStylingTupleSummary {
sources: (number|null|string)[];
}
/**
* A debug/testing-oriented summary of all styling entries for a `DebugNode` instance.
*/
export interface DebugNodeStyling {
/** The associated debug context of the TStylingContext instance */
context: DebugStylingContext;
/**
* A summarization of each style/class property
* present in the context
*/
summary: {[propertyName: string]: DebugNodeStylingEntry};
/**
* A key/value map of all styling properties and their
* runtime values
*/
values: {[propertyName: string]: string | number | null | boolean};
/**
* Overrides the sanitizer used to process styles
*/
overrideSanitizer(sanitizer: StyleSanitizeFn|null): void;
}
/**
* A debug/testing-oriented summary of a styling entry.
*
* A value such as this is generated as an artifact of the `DebugStyling`
* summary.
*/
export interface DebugNodeStylingEntry {
/** The style/class property that the summary is attached to */
prop: string;
/** The last applied value for the style/class property */
value: string|boolean|null;
/** The binding index of the last applied style/class property */
bindingIndex: number|null;
}
/**
* Instantiates and attaches an instance of `TStylingContextDebug` to the provided context
*/
@ -135,21 +157,20 @@ export function attachStylingDebugObject(context: TStylingContext) {
* This class is designed to be used within testing code or when an
* application has `ngDevMode` activated.
*/
class TStylingContextDebug {
class TStylingContextDebug implements DebugStylingContext {
constructor(public readonly context: TStylingContext) {}
get isTemplateLocked() { return isContextLocked(this.context, true); }
get isHostBindingsLocked() { return isContextLocked(this.context, false); }
get config(): DebugStylingConfig { return buildConfig(this.context); }
/**
* Returns a detailed summary of each styling entry in the context.
*
* See `TStylingTupleSummary`.
*/
get entries(): {[prop: string]: TStylingTupleSummary} {
get entries(): {[prop: string]: DebugStylingContextEntry} {
const context = this.context;
const totalColumns = getValuesCount(context);
const entries: {[prop: string]: TStylingTupleSummary} = {};
const entries: {[prop: string]: DebugStylingContextEntry} = {};
const start = getPropValuesStartPosition(context);
let i = start;
while (i < context.length) {
@ -189,12 +210,19 @@ class TStylingContextDebug {
* This class is designed to be used within testing code or when an
* application has `ngDevMode` activated.
*/
export class NodeStylingDebug implements DebugStyling {
export class NodeStylingDebug implements DebugNodeStyling {
private _sanitizer: StyleSanitizeFn|null = null;
private _debugContext: DebugStylingContext;
constructor(
public context: TStylingContext, private _data: LStylingData,
private _isClassBased?: boolean) {}
context: TStylingContext|DebugStylingContext, private _data: LStylingData,
private _isClassBased?: boolean) {
this._debugContext = isStylingContext(context) ?
new TStylingContextDebug(context as TStylingContext) :
(context as DebugStylingContext);
}
get context() { return this._debugContext; }
/**
* Overrides the sanitizer used to process styles.
@ -207,36 +235,15 @@ export class NodeStylingDebug implements DebugStyling {
*
* See `LStylingSummary`.
*/
get summary(): {[key: string]: LStylingSummary} {
const entries: {[key: string]: LStylingSummary} = {};
get summary(): {[key: string]: DebugNodeStylingEntry} {
const entries: {[key: string]: DebugNodeStylingEntry} = {};
this._mapValues((prop: string, value: any, bindingIndex: number | null) => {
entries[prop] = {prop, value, bindingIndex};
});
return entries;
}
get config() {
const hasMapBindings = hasConfig(this.context, TStylingConfig.HasMapBindings);
const hasPropBindings = hasConfig(this.context, TStylingConfig.HasPropBindings);
const hasCollisions = hasConfig(this.context, TStylingConfig.HasCollisions);
const hasTemplateBindings = hasConfig(this.context, TStylingConfig.HasTemplateBindings);
const hasHostBindings = hasConfig(this.context, TStylingConfig.HasHostBindings);
const templateBindingsLocked = hasConfig(this.context, TStylingConfig.TemplateBindingsLocked);
const hostBindingsLocked = hasConfig(this.context, TStylingConfig.HostBindingsLocked);
const allowDirectStyling =
_allowDirectStyling(this.context, false) || _allowDirectStyling(this.context, true);
return {
hasMapBindings, //
hasPropBindings, //
hasCollisions, //
hasTemplateBindings, //
hasHostBindings, //
templateBindingsLocked, //
hostBindingsLocked, //
allowDirectStyling, //
};
}
get config() { return buildConfig(this.context.context); }
/**
* Returns a key/value map of all the styles/classes that were last applied to the element.
@ -252,7 +259,7 @@ export class NodeStylingDebug implements DebugStyling {
// element is only used when the styling algorithm attempts to
// style the value (and we mock out the stylingApplyFn anyway).
const mockElement = {} as any;
const hasMaps = hasConfig(this.context, TStylingConfig.HasMapBindings);
const hasMaps = hasConfig(this.context.context, TStylingConfig.HasMapBindings);
if (hasMaps) {
activateStylingMapFeature();
}
@ -265,10 +272,33 @@ export class NodeStylingDebug implements DebugStyling {
// run the template bindings
applyStylingViaContext(
this.context, null, mockElement, this._data, true, mapFn, sanitizer, false);
this.context.context, null, mockElement, this._data, true, mapFn, sanitizer, false);
// and also the host bindings
applyStylingViaContext(
this.context, null, mockElement, this._data, true, mapFn, sanitizer, true);
this.context.context, null, mockElement, this._data, true, mapFn, sanitizer, true);
}
}
function buildConfig(context: TStylingContext) {
const hasMapBindings = hasConfig(context, TStylingConfig.HasMapBindings);
const hasPropBindings = hasConfig(context, TStylingConfig.HasPropBindings);
const hasCollisions = hasConfig(context, TStylingConfig.HasCollisions);
const hasTemplateBindings = hasConfig(context, TStylingConfig.HasTemplateBindings);
const hasHostBindings = hasConfig(context, TStylingConfig.HasHostBindings);
const templateBindingsLocked = hasConfig(context, TStylingConfig.TemplateBindingsLocked);
const hostBindingsLocked = hasConfig(context, TStylingConfig.HostBindingsLocked);
const allowDirectStyling =
_allowDirectStyling(context, false) || _allowDirectStyling(context, true);
return {
hasMapBindings, //
hasPropBindings, //
hasCollisions, //
hasTemplateBindings, //
hasHostBindings, //
templateBindingsLocked, //
hostBindingsLocked, //
allowDirectStyling, //
};
}

View File

@ -240,7 +240,7 @@ export function getStylingMapArray(value: TStylingContext | StylingMapArray | nu
value as StylingMapArray;
}
export function isStylingContext(value: TStylingContext | StylingMapArray | null): boolean {
export function isStylingContext(value: any): boolean {
// the StylingMapArray is in the format of [initial, prop, string, prop, string]
// and this is the defining value to distinguish between arrays
return Array.isArray(value) && value.length >= TStylingContextIndex.ValuesStartPosition &&

View File

@ -863,7 +863,7 @@ describe('styling', () => {
const node = getDebugNode(element) !;
const styles = node.styles !;
const config = styles.config;
const config = styles.context.config;
expect(config.hasCollisions).toBeFalsy();
expect(config.hasMapBindings).toBeFalsy();
expect(config.hasPropBindings).toBeTruthy();