feat(ivy): add ng-reflect debug text for containers (#27350)

PR Close #27350
This commit is contained in:
Olivier Combe 2018-11-29 16:39:43 +01:00 committed by Igor Minar
parent 20cef5078d
commit 1279a503a1
5 changed files with 53 additions and 41 deletions

View File

@ -16,7 +16,6 @@ import {Sanitizer} from '../sanitization/security';
import {StyleSanitizeFn} from '../sanitization/style_sanitizer'; import {StyleSanitizeFn} from '../sanitization/style_sanitizer';
import {Type} from '../type'; import {Type} from '../type';
import {normalizeDebugBindingName, normalizeDebugBindingValue} from '../util/ng_reflect'; import {normalizeDebugBindingName, normalizeDebugBindingValue} from '../util/ng_reflect';
import {assertDataInRange, assertDefined, assertEqual, assertHasParent, assertLessThan, assertNotEqual, assertPreviousIsParent} from './assert'; import {assertDataInRange, assertDefined, assertEqual, assertHasParent, assertLessThan, assertNotEqual, assertPreviousIsParent} from './assert';
import {bindingUpdated, bindingUpdated2, bindingUpdated3, bindingUpdated4} from './bindings'; import {bindingUpdated, bindingUpdated2, bindingUpdated3, bindingUpdated4} from './bindings';
import {attachPatchData, getComponentViewByInstance} from './context_discovery'; import {attachPatchData, getComponentViewByInstance} from './context_discovery';
@ -30,7 +29,7 @@ import {AttributeMarker, InitialInputData, InitialInputs, LocalRefExtractor, Pro
import {PlayerFactory} from './interfaces/player'; import {PlayerFactory} from './interfaces/player';
import {CssSelectorList, NG_PROJECT_AS_ATTR_NAME} from './interfaces/projection'; import {CssSelectorList, NG_PROJECT_AS_ATTR_NAME} from './interfaces/projection';
import {LQueries} from './interfaces/query'; import {LQueries} from './interfaces/query';
import {ProceduralRenderer3, RComment, RElement, RNode, RText, Renderer3, RendererFactory3, isProceduralRenderer} from './interfaces/renderer'; import {ProceduralRenderer3, RComment, RElement, RText, Renderer3, RendererFactory3, isProceduralRenderer} from './interfaces/renderer';
import {SanitizerFn} from './interfaces/sanitization'; import {SanitizerFn} from './interfaces/sanitization';
import {StylingIndex} from './interfaces/styling'; import {StylingIndex} from './interfaces/styling';
import {BINDING_INDEX, CLEANUP, CONTAINER_INDEX, CONTENT_QUERIES, CONTEXT, DECLARATION_VIEW, FLAGS, HEADER_OFFSET, HOST, HOST_NODE, INJECTOR, LView, LViewFlags, NEXT, OpaqueViewState, PARENT, QUERIES, RENDERER, RENDERER_FACTORY, RootContext, RootContextFlags, SANITIZER, TAIL, TVIEW, TView} from './interfaces/view'; import {BINDING_INDEX, CLEANUP, CONTAINER_INDEX, CONTENT_QUERIES, CONTEXT, DECLARATION_VIEW, FLAGS, HEADER_OFFSET, HOST, HOST_NODE, INJECTOR, LView, LViewFlags, NEXT, OpaqueViewState, PARENT, QUERIES, RENDERER, RENDERER_FACTORY, RootContext, RootContextFlags, SANITIZER, TAIL, TVIEW, TView} from './interfaces/view';
@ -957,8 +956,10 @@ export function elementProperty<T>(
if (inputData && (dataValue = inputData[propName])) { if (inputData && (dataValue = inputData[propName])) {
setInputsForProperty(lView, dataValue, value); setInputsForProperty(lView, dataValue, value);
if (isComponent(tNode)) markDirtyIfOnPush(lView, index + HEADER_OFFSET); if (isComponent(tNode)) markDirtyIfOnPush(lView, index + HEADER_OFFSET);
if (ngDevMode && tNode.type === TNodeType.Element) { if (ngDevMode) {
setNgReflectProperties(lView, element as RElement, propName, value); if (tNode.type === TNodeType.Element || tNode.type === TNodeType.Container) {
setNgReflectProperties(lView, element, tNode.type, propName, value);
}
} }
} else if (tNode.type === TNodeType.Element) { } else if (tNode.type === TNodeType.Element) {
const renderer = lView[RENDERER]; const renderer = lView[RENDERER];
@ -1031,12 +1032,23 @@ function setInputsForProperty(lView: LView, inputs: PropertyAliasValue, value: a
} }
} }
function setNgReflectProperties(lView: LView, element: RElement, propName: string, value: any) { function setNgReflectProperties(
lView: LView, element: RElement | RComment, type: TNodeType, propName: string, value: any) {
const renderer = lView[RENDERER]; const renderer = lView[RENDERER];
const attrName = normalizeDebugBindingName(propName); const attrName = normalizeDebugBindingName(propName);
const debugValue = normalizeDebugBindingValue(value); const debugValue = normalizeDebugBindingValue(value);
isProceduralRenderer(renderer) ? renderer.setAttribute(element, attrName, debugValue) : if (type === TNodeType.Element) {
element.setAttribute(attrName, debugValue); isProceduralRenderer(renderer) ?
renderer.setAttribute((element as RElement), attrName, debugValue) :
(element as RElement).setAttribute(attrName, debugValue);
} else if (value !== undefined) {
const value = `bindings=${JSON.stringify({[attrName]: debugValue}, null, 2)}`;
if (isProceduralRenderer(renderer)) {
renderer.setValue((element as RComment), value);
} else {
(element as RComment).textContent = value;
}
}
} }
/** /**

View File

@ -83,7 +83,7 @@ export interface ProceduralRenderer3 {
flags?: RendererStyleFlags2|RendererStyleFlags3): void; flags?: RendererStyleFlags2|RendererStyleFlags3): void;
removeStyle(el: RElement, style: string, flags?: RendererStyleFlags2|RendererStyleFlags3): void; removeStyle(el: RElement, style: string, flags?: RendererStyleFlags2|RendererStyleFlags3): void;
setProperty(el: RElement, name: string, value: any): void; setProperty(el: RElement, name: string, value: any): void;
setValue(node: RText, value: string): void; setValue(node: RText|RComment, value: string): void;
// TODO(misko): Deprecate in favor of addEventListener/removeEventListener // TODO(misko): Deprecate in favor of addEventListener/removeEventListener
listen(target: RNode, eventName: string, callback: (event: any) => boolean | void): () => void; listen(target: RNode, eventName: string, callback: (event: any) => boolean | void): () => void;
@ -152,7 +152,7 @@ export interface RDomTokenList {
export interface RText extends RNode { textContent: string|null; } export interface RText extends RNode { textContent: string|null; }
export interface RComment extends RNode {} export interface RComment extends RNode { textContent: string|null; }
// Note: This hack is necessary so we don't erroneously get a circular dependency // Note: This hack is necessary so we don't erroneously get a circular dependency
// failure based on types. // failure based on types.

View File

@ -1688,7 +1688,7 @@ function declareTests(config?: {useJit: boolean}) {
}); });
describe('logging property updates', () => { describe('logging property updates', () => {
fixmeIvy('FW-664: ng-reflect-* is not supported') fixmeIvy('FW-587: Inputs with aliases in component decorators don\'t work')
.it('should reflect property values as attributes', () => { .it('should reflect property values as attributes', () => {
TestBed.configureTestingModule({declarations: [MyComp, MyDir]}); TestBed.configureTestingModule({declarations: [MyComp, MyDir]});
const template = '<div>' + const template = '<div>' +
@ -1712,21 +1712,19 @@ function declareTests(config?: {useJit: boolean}) {
expect(getDOM().getInnerHTML(fixture.nativeElement)).toContain('ng-reflect-test_="hello"'); expect(getDOM().getInnerHTML(fixture.nativeElement)).toContain('ng-reflect-test_="hello"');
}); });
fixmeIvy('FW-664: ng-reflect-* is not supported') it('should reflect property values on template comments', () => {
.it('should reflect property values on template comments', () => { const fixture =
const fixture = TestBed.configureTestingModule({declarations: [MyComp]})
TestBed.configureTestingModule({declarations: [MyComp]}) .overrideComponent(
.overrideComponent( MyComp, {set: {template: '<ng-template [ngIf]="ctxBoolProp"></ng-template>'}})
MyComp, .createComponent(MyComp);
{set: {template: '<ng-template [ngIf]="ctxBoolProp"></ng-template>'}})
.createComponent(MyComp);
fixture.componentInstance.ctxBoolProp = true; fixture.componentInstance.ctxBoolProp = true;
fixture.detectChanges(); fixture.detectChanges();
expect(getDOM().getInnerHTML(fixture.nativeElement)) expect(getDOM().getInnerHTML(fixture.nativeElement))
.toContain('"ng\-reflect\-ng\-if"\: "true"'); .toContain('"ng\-reflect\-ng\-if"\: "true"');
}); });
// also affected by FW-587: Inputs with aliases in component decorators don't work // also affected by FW-587: Inputs with aliases in component decorators don't work
fixmeIvy('FW-664: ng-reflect-* is not supported') fixmeIvy('FW-664: ng-reflect-* is not supported')

View File

@ -273,16 +273,19 @@ export function toHtml<T>(componentOrElement: T | RElement, keepNgReflect = fals
} }
if (element) { if (element) {
let html = stringifyElement(element) let html = stringifyElement(element);
.replace(/^<div host="">(.*)<\/div>$/, '$1')
.replace(/^<div fixture="mark">(.*)<\/div>$/, '$1')
.replace(/^<div host="mark">(.*)<\/div>$/, '$1')
.replace(' style=""', '')
.replace(/<!--container-->/g, '')
.replace(/<!--ng-container-->/g, '');
if (!keepNgReflect) { if (!keepNgReflect) {
html = html.replace(/\sng-reflect-\S*="[^"]*"/g, ''); html = html.replace(/\sng-reflect-\S*="[^"]*"/g, '')
.replace(/<!--bindings=\{(\W.*\W\s*)?\}-->/g, '');
} }
html = html.replace(/^<div host="">(.*)<\/div>$/, '$1')
.replace(/^<div fixture="mark">(.*)<\/div>$/, '$1')
.replace(/^<div host="mark">(.*)<\/div>$/, '$1')
.replace(' style=""', '')
.replace(/<!--container-->/g, '')
.replace(/<!--ng-container-->/g, '');
return html; return html;
} else { } else {
return ''; return '';

View File

@ -131,16 +131,15 @@ let lastCreatedRenderer: Renderer2;
checkSetters(fixture.componentRef, fixture.debugElement.children[0].nativeElement); checkSetters(fixture.componentRef, fixture.debugElement.children[0].nativeElement);
}); });
fixmeIvy('#FW-664 ng-reflect-* is not supported') it('should update any template comment property/attributes', () => {
.it('should update any template comment property/attributes', () => { const fixture =
const fixture = TestBed.overrideTemplate(MyComp2, '<ng-container *ngIf="ctxBoolProp"></ng-container>')
TestBed.overrideTemplate(MyComp2, '<ng-container *ngIf="ctxBoolProp"></ng-container>') .createComponent(MyComp2);
.createComponent(MyComp2); fixture.componentInstance.ctxBoolProp = true;
fixture.componentInstance.ctxBoolProp = true; fixture.detectChanges();
fixture.detectChanges(); const el = getRenderElement(fixture.nativeElement);
const el = getRenderElement(fixture.nativeElement); expect(getDOM().getInnerHTML(el)).toContain('"ng-reflect-ng-if": "true"');
expect(getDOM().getInnerHTML(el)).toContain('"ng-reflect-ng-if": "true"'); });
});
it('should add and remove fragments', () => { it('should add and remove fragments', () => {
const fixture = const fixture =