feat(ivy): improve debugging experience for styles/classes (#32753)
This patch introduces the `printTable()` and `printSources()` methods to `DebugStylingContext` which can be used via the `window.ng.getDebugNode` helpers when debugging an application. PR Close #32753
This commit is contained in:
parent
f8f7c1540a
commit
32f4544f34
|
@ -20,7 +20,7 @@ import {TQueries} from '../interfaces/query';
|
||||||
import {RComment, RElement, RNode} from '../interfaces/renderer';
|
import {RComment, RElement, RNode} from '../interfaces/renderer';
|
||||||
import {TStylingContext} from '../interfaces/styling';
|
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 {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 {attachDebugObject} from '../util/debug_utils';
|
||||||
import {isStylingContext} from '../util/styling_utils';
|
import {isStylingContext} from '../util/styling_utils';
|
||||||
import {getTNode, unwrapRNode} from '../util/view_utils';
|
import {getTNode, unwrapRNode} from '../util/view_utils';
|
||||||
|
@ -341,8 +341,8 @@ export class LViewDebug {
|
||||||
export interface DebugNode {
|
export interface DebugNode {
|
||||||
html: string|null;
|
html: string|null;
|
||||||
native: Node;
|
native: Node;
|
||||||
styles: DebugNewStyling|null;
|
styles: DebugNodeStyling|null;
|
||||||
classes: DebugNewStyling|null;
|
classes: DebugNodeStyling|null;
|
||||||
nodes: DebugNode[]|null;
|
nodes: DebugNode[]|null;
|
||||||
component: LViewDebug|null;
|
component: LViewDebug|null;
|
||||||
}
|
}
|
||||||
|
@ -372,7 +372,7 @@ export function buildDebugNode(tNode: TNode, lView: LView, nodeIndex: number): D
|
||||||
const native = unwrapRNode(rawValue);
|
const native = unwrapRNode(rawValue);
|
||||||
const componentLViewDebug = toDebug(readLViewValue(rawValue));
|
const componentLViewDebug = toDebug(readLViewValue(rawValue));
|
||||||
const styles = isStylingContext(tNode.styles) ?
|
const styles = isStylingContext(tNode.styles) ?
|
||||||
new NodeStylingDebug(tNode.styles as any as TStylingContext, lView) :
|
new NodeStylingDebug(tNode.styles as any as TStylingContext, lView, false) :
|
||||||
null;
|
null;
|
||||||
const classes = isStylingContext(tNode.classes) ?
|
const classes = isStylingContext(tNode.classes) ?
|
||||||
new NodeStylingDebug(tNode.classes as any as TStylingContext, lView, true) :
|
new NodeStylingDebug(tNode.classes as any as TStylingContext, lView, true) :
|
||||||
|
|
|
@ -498,7 +498,7 @@ function getContext(tNode: TNode, isClassBased: boolean): TStylingContext {
|
||||||
if (!isStylingContext(context)) {
|
if (!isStylingContext(context)) {
|
||||||
context = allocTStylingContext(context as StylingMapArray | null);
|
context = allocTStylingContext(context as StylingMapArray | null);
|
||||||
if (ngDevMode) {
|
if (ngDevMode) {
|
||||||
attachStylingDebugObject(context as TStylingContext);
|
attachStylingDebugObject(context as TStylingContext, isClassBased);
|
||||||
}
|
}
|
||||||
if (isClassBased) {
|
if (isClassBased) {
|
||||||
tNode.classes = context;
|
tNode.classes = context;
|
||||||
|
|
|
@ -10,7 +10,7 @@ import {RElement} from '../interfaces/renderer';
|
||||||
import {ApplyStylingFn, LStylingData, TStylingConfig, TStylingContext, TStylingContextIndex} from '../interfaces/styling';
|
import {ApplyStylingFn, LStylingData, TStylingConfig, TStylingContext, TStylingContextIndex} from '../interfaces/styling';
|
||||||
import {getCurrentStyleSanitizer} from '../state';
|
import {getCurrentStyleSanitizer} from '../state';
|
||||||
import {attachDebugObject} from '../util/debug_utils';
|
import {attachDebugObject} from '../util/debug_utils';
|
||||||
import {allowDirectStyling as _allowDirectStyling, getDefaultValue, getGuardMask, getProp, getPropValuesStartPosition, getValuesCount, hasConfig, isContextLocked, isSanitizationRequired, isStylingContext} from '../util/styling_utils';
|
import {MAP_BASED_ENTRY_PROP_NAME, TEMPLATE_DIRECTIVE_INDEX, allowDirectStyling as _allowDirectStyling, getBindingValue, getDefaultValue, getGuardMask, getProp, getPropValuesStartPosition, getValuesCount, hasConfig, isContextLocked, isSanitizationRequired, isStylingContext} from '../util/styling_utils';
|
||||||
|
|
||||||
import {applyStylingViaContext} from './bindings';
|
import {applyStylingViaContext} from './bindings';
|
||||||
import {activateStylingMapFeature} from './map_based_bindings';
|
import {activateStylingMapFeature} from './map_based_bindings';
|
||||||
|
@ -41,6 +41,12 @@ export interface DebugStylingContext {
|
||||||
|
|
||||||
/** The associated TStylingContext instance */
|
/** The associated TStylingContext instance */
|
||||||
entries: {[prop: string]: DebugStylingContextEntry};
|
entries: {[prop: string]: DebugStylingContextEntry};
|
||||||
|
|
||||||
|
/** A status report of all the sources within the context */
|
||||||
|
printSources(): void;
|
||||||
|
|
||||||
|
/** A status report of all the entire context as a table */
|
||||||
|
printTable(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -63,10 +69,10 @@ export interface DebugStylingConfig {
|
||||||
* A debug/testing-oriented summary of all styling entries within a `TStylingContext`.
|
* A debug/testing-oriented summary of all styling entries within a `TStylingContext`.
|
||||||
*/
|
*/
|
||||||
export interface DebugStylingContextEntry {
|
export interface DebugStylingContextEntry {
|
||||||
/** The property (style or class property) that this tuple represents */
|
/** The property (style or class property) that this entry represents */
|
||||||
prop: string;
|
prop: string;
|
||||||
|
|
||||||
/** The total amount of styling entries a part of this tuple */
|
/** The total amount of styling entries a part of this entry */
|
||||||
valuesCount: number;
|
valuesCount: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -145,8 +151,8 @@ export interface DebugNodeStylingEntry {
|
||||||
/**
|
/**
|
||||||
* Instantiates and attaches an instance of `TStylingContextDebug` to the provided context
|
* Instantiates and attaches an instance of `TStylingContextDebug` to the provided context
|
||||||
*/
|
*/
|
||||||
export function attachStylingDebugObject(context: TStylingContext) {
|
export function attachStylingDebugObject(context: TStylingContext, isClassBased: boolean) {
|
||||||
const debug = new TStylingContextDebug(context);
|
const debug = new TStylingContextDebug(context, isClassBased);
|
||||||
attachDebugObject(context, debug);
|
attachDebugObject(context, debug);
|
||||||
return debug;
|
return debug;
|
||||||
}
|
}
|
||||||
|
@ -158,14 +164,14 @@ export function attachStylingDebugObject(context: TStylingContext) {
|
||||||
* application has `ngDevMode` activated.
|
* application has `ngDevMode` activated.
|
||||||
*/
|
*/
|
||||||
class TStylingContextDebug implements DebugStylingContext {
|
class TStylingContextDebug implements DebugStylingContext {
|
||||||
constructor(public readonly context: TStylingContext) {}
|
constructor(public readonly context: TStylingContext, private _isClassBased: boolean) {}
|
||||||
|
|
||||||
get config(): DebugStylingConfig { return buildConfig(this.context); }
|
get config(): DebugStylingConfig { return buildConfig(this.context); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a detailed summary of each styling entry in the context.
|
* Returns a detailed summary of each styling entry in the context.
|
||||||
*
|
*
|
||||||
* See `TStylingTupleSummary`.
|
* See `DebugStylingContextEntry`.
|
||||||
*/
|
*/
|
||||||
get entries(): {[prop: string]: DebugStylingContextEntry} {
|
get entries(): {[prop: string]: DebugStylingContextEntry} {
|
||||||
const context = this.context;
|
const context = this.context;
|
||||||
|
@ -202,6 +208,137 @@ class TStylingContextDebug implements DebugStylingContext {
|
||||||
}
|
}
|
||||||
return entries;
|
return entries;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prints a detailed summary of each styling source grouped together with each binding index in
|
||||||
|
* the context.
|
||||||
|
*/
|
||||||
|
printSources(): void {
|
||||||
|
let output = '\n';
|
||||||
|
|
||||||
|
const context = this.context;
|
||||||
|
const prefix = this._isClassBased ? 'class' : 'style';
|
||||||
|
const bindingsBySource: {
|
||||||
|
type: string,
|
||||||
|
entries: {binding: string, bindingIndex: number, value: any, bitMask: number}[]
|
||||||
|
}[] = [];
|
||||||
|
|
||||||
|
const totalColumns = getValuesCount(context);
|
||||||
|
const itemsPerRow = TStylingContextIndex.BindingsStartOffset + totalColumns;
|
||||||
|
|
||||||
|
for (let i = 0; i < totalColumns; i++) {
|
||||||
|
const isDefaultColumn = i === totalColumns - 1;
|
||||||
|
const hostBindingsMode = i !== TEMPLATE_DIRECTIVE_INDEX;
|
||||||
|
const type = getTypeFromColumn(i, totalColumns);
|
||||||
|
const entries: {binding: string, value: any, bindingIndex: number, bitMask: number}[] = [];
|
||||||
|
|
||||||
|
let j = TStylingContextIndex.ValuesStartPosition;
|
||||||
|
while (j < context.length) {
|
||||||
|
const value = getBindingValue(context, j, i);
|
||||||
|
if (isDefaultColumn || value > 0) {
|
||||||
|
const bitMask = getGuardMask(context, j, hostBindingsMode);
|
||||||
|
const bindingIndex = isDefaultColumn ? -1 : value as number;
|
||||||
|
const prop = getProp(context, j);
|
||||||
|
const isMapBased = prop === MAP_BASED_ENTRY_PROP_NAME;
|
||||||
|
const binding = `${prefix}${isMapBased ? '' : '.' + prop}`;
|
||||||
|
entries.push({binding, value, bindingIndex, bitMask});
|
||||||
|
}
|
||||||
|
j += itemsPerRow;
|
||||||
|
}
|
||||||
|
|
||||||
|
bindingsBySource.push(
|
||||||
|
{type, entries: entries.sort((a, b) => a.bindingIndex - b.bindingIndex)});
|
||||||
|
}
|
||||||
|
|
||||||
|
bindingsBySource.forEach(entry => {
|
||||||
|
output += `[${entry.type.toUpperCase()}]\n`;
|
||||||
|
output += repeat('-', entry.type.length + 2) + '\n';
|
||||||
|
|
||||||
|
let tab = ' ';
|
||||||
|
entry.entries.forEach(entry => {
|
||||||
|
const isDefault = typeof entry.value !== 'number';
|
||||||
|
const value = entry.value;
|
||||||
|
if (!isDefault || value !== null) {
|
||||||
|
output += `${tab}[${entry.binding}] = \`${value}\``;
|
||||||
|
output += '\n';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
output += '\n';
|
||||||
|
});
|
||||||
|
|
||||||
|
/* tslint:disable */
|
||||||
|
console.log(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prints a detailed table of the entire styling context.
|
||||||
|
*/
|
||||||
|
printTable(): void {
|
||||||
|
// IE (not Edge) is the only browser that doesn't support this feature. Because
|
||||||
|
// these debugging tools are not apart of the core of Angular (they are just
|
||||||
|
// extra tools) we can skip-out on older browsers.
|
||||||
|
if (!console.table) {
|
||||||
|
throw new Error('This feature is not supported in your browser');
|
||||||
|
}
|
||||||
|
|
||||||
|
const context = this.context;
|
||||||
|
const table: any[] = [];
|
||||||
|
const totalColumns = getValuesCount(context);
|
||||||
|
const itemsPerRow = TStylingContextIndex.BindingsStartOffset + totalColumns;
|
||||||
|
const totalProps = Math.floor(context.length / itemsPerRow);
|
||||||
|
|
||||||
|
let i = TStylingContextIndex.ValuesStartPosition;
|
||||||
|
while (i < context.length) {
|
||||||
|
const prop = getProp(context, i);
|
||||||
|
const isMapBased = prop === MAP_BASED_ENTRY_PROP_NAME;
|
||||||
|
const entry: {[key: string]: any} = {
|
||||||
|
prop,
|
||||||
|
'tpl mask': generateBitString(getGuardMask(context, i, false), isMapBased, totalProps),
|
||||||
|
'host mask': generateBitString(getGuardMask(context, i, true), isMapBased, totalProps),
|
||||||
|
};
|
||||||
|
|
||||||
|
for (let j = 0; j < totalColumns; j++) {
|
||||||
|
const key = getTypeFromColumn(j, totalColumns);
|
||||||
|
const value = getBindingValue(context, i, j);
|
||||||
|
entry[key] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
i += itemsPerRow;
|
||||||
|
table.push(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* tslint:disable */
|
||||||
|
console.table(table);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateBitString(value: number, isMapBased: boolean, totalProps: number) {
|
||||||
|
if (isMapBased || value > 1) {
|
||||||
|
return `0b${leftPad(value.toString(2), totalProps, '0')}`;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function leftPad(value: string, max: number, pad: string) {
|
||||||
|
return repeat(pad, max - value.length) + value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTypeFromColumn(index: number, totalColumns: number) {
|
||||||
|
if (index === TEMPLATE_DIRECTIVE_INDEX) {
|
||||||
|
return 'template';
|
||||||
|
} else if (index === totalColumns - 1) {
|
||||||
|
return 'defaults';
|
||||||
|
} else {
|
||||||
|
return `dir #${index}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function repeat(c: string, times: number) {
|
||||||
|
let s = '';
|
||||||
|
for (let i = 0; i < times; i++) {
|
||||||
|
s += c;
|
||||||
|
}
|
||||||
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -216,9 +353,9 @@ export class NodeStylingDebug implements DebugNodeStyling {
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
context: TStylingContext|DebugStylingContext, private _data: LStylingData,
|
context: TStylingContext|DebugStylingContext, private _data: LStylingData,
|
||||||
private _isClassBased?: boolean) {
|
private _isClassBased: boolean) {
|
||||||
this._debugContext = isStylingContext(context) ?
|
this._debugContext = isStylingContext(context) ?
|
||||||
new TStylingContextDebug(context as TStylingContext) :
|
new TStylingContextDebug(context as TStylingContext, _isClassBased) :
|
||||||
(context as DebugStylingContext);
|
(context as DebugStylingContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ import {DEFAULT_GUARD_MASK_VALUE, allocTStylingContext} from '../../../src/rende
|
||||||
|
|
||||||
describe('styling context', () => {
|
describe('styling context', () => {
|
||||||
it('should register a series of entries into the context', () => {
|
it('should register a series of entries into the context', () => {
|
||||||
const debug = makeContextWithDebug();
|
const debug = makeContextWithDebug(false);
|
||||||
const context = debug.context;
|
const context = debug.context;
|
||||||
expect(debug.entries).toEqual({});
|
expect(debug.entries).toEqual({});
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@ describe('styling context', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should only register the same binding index once per property', () => {
|
it('should only register the same binding index once per property', () => {
|
||||||
const debug = makeContextWithDebug();
|
const debug = makeContextWithDebug(false);
|
||||||
const context = debug.context;
|
const context = debug.context;
|
||||||
expect(debug.entries).toEqual({});
|
expect(debug.entries).toEqual({});
|
||||||
|
|
||||||
|
@ -70,7 +70,7 @@ describe('styling context', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should overwrite a default value for an entry only if it is non-null', () => {
|
it('should overwrite a default value for an entry only if it is non-null', () => {
|
||||||
const debug = makeContextWithDebug();
|
const debug = makeContextWithDebug(false);
|
||||||
const context = debug.context;
|
const context = debug.context;
|
||||||
|
|
||||||
registerBinding(context, 1, 0, 'width', null);
|
registerBinding(context, 1, 0, 'width', null);
|
||||||
|
@ -109,9 +109,9 @@ describe('styling context', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function makeContextWithDebug() {
|
function makeContextWithDebug(isClassBased: boolean) {
|
||||||
const ctx = allocTStylingContext();
|
const ctx = allocTStylingContext();
|
||||||
return attachStylingDebugObject(ctx);
|
return attachStylingDebugObject(ctx, isClassBased);
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildGuardMask(...bindingIndices: number[]) {
|
function buildGuardMask(...bindingIndices: number[]) {
|
||||||
|
|
|
@ -13,10 +13,10 @@ describe('styling debugging tools', () => {
|
||||||
describe('NodeStylingDebug', () => {
|
describe('NodeStylingDebug', () => {
|
||||||
it('should list out each of the values in the context paired together with the provided data',
|
it('should list out each of the values in the context paired together with the provided data',
|
||||||
() => {
|
() => {
|
||||||
const debug = makeContextWithDebug();
|
const debug = makeContextWithDebug(false);
|
||||||
const context = debug.context;
|
const context = debug.context;
|
||||||
const data: any[] = [];
|
const data: any[] = [];
|
||||||
const d = new NodeStylingDebug(context, data);
|
const d = new NodeStylingDebug(context, data, false);
|
||||||
|
|
||||||
registerBinding(context, 0, 0, 'width', null);
|
registerBinding(context, 0, 0, 'width', null);
|
||||||
expect(d.summary).toEqual({
|
expect(d.summary).toEqual({
|
||||||
|
@ -63,7 +63,7 @@ describe('styling debugging tools', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function makeContextWithDebug() {
|
function makeContextWithDebug(isClassBased: boolean) {
|
||||||
const ctx = allocTStylingContext();
|
const ctx = allocTStylingContext();
|
||||||
return attachStylingDebugObject(ctx);
|
return attachStylingDebugObject(ctx, isClassBased);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue