feat(ivy): enable ng-reflect debug attributes (#27268)

PR Close #27268
This commit is contained in:
Olivier Combe 2018-11-21 13:43:00 +01:00 committed by Jason Aden
parent 32157115da
commit 2fce701ced
5 changed files with 65 additions and 44 deletions

View File

@ -15,8 +15,8 @@ import {QueryList} from '../linker';
import {Sanitizer} from '../sanitization/security';
import {StyleSanitizeFn} from '../sanitization/style_sanitizer';
import {Type} from '../type';
import {normalizeDebugBindingName, normalizeDebugBindingValue} from '../util/ng_reflect';
import {noop} from '../util/noop';
import {assertDefined, assertEqual, assertLessThan, assertNotEqual} from './assert';
import {attachPatchData, getComponentViewByInstance} from './context_discovery';
import {diPublicInInjector, getNodeInjectable, getOrCreateInjectable, getOrCreateNodeInjectorForNode, injectAttributeImpl} from './di';
@ -956,6 +956,9 @@ export function elementProperty<T>(
if (inputData && (dataValue = inputData[propName])) {
setInputsForProperty(viewData, dataValue, value);
if (isComponent(tNode)) markDirtyIfOnPush(viewData, index + HEADER_OFFSET);
if (ngDevMode && tNode.type === TNodeType.Element) {
setNgReflectProperties(element as RElement, propName, value);
}
} else if (tNode.type === TNodeType.Element) {
const renderer = getRenderer();
// It is assumed that the sanitizer is only added when the compiler determines that the property
@ -1025,6 +1028,15 @@ function setInputsForProperty(viewData: LViewData, inputs: PropertyAliasValue, v
}
}
function setNgReflectProperties(element: RElement, propName: string, value: any) {
const renderer = getRenderer() as ProceduralRenderer3;
const isProcedural = isProceduralRenderer(renderer);
const attrName = normalizeDebugBindingName(propName);
const debugValue = normalizeDebugBindingValue(value);
isProcedural ? renderer.setAttribute(element, attrName, debugValue) :
element.setAttribute(attrName, debugValue);
}
/**
* Consolidates all inputs or outputs of all directives on this logical node.
*

View File

@ -0,0 +1,28 @@
/**
* @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
*/
export function normalizeDebugBindingName(name: string) {
// Attribute names with `$` (eg `x-y$`) are valid per spec, but unsupported by some browsers
name = camelCaseToDashCase(name.replace(/[$@]/g, '_'));
return `ng-reflect-${name}`;
}
const CAMEL_CASE_REGEXP = /([A-Z])/g;
function camelCaseToDashCase(input: string): string {
return input.replace(CAMEL_CASE_REGEXP, (...m: any[]) => '-' + m[1].toLowerCase());
}
export function normalizeDebugBindingValue(value: any): string {
try {
// Limit the size of the value as otherwise the DOM just gets polluted.
return value != null ? value.toString().slice(0, 30) : value;
} catch (e) {
return '[ERROR] Exception while trying to serialize the value';
}
}

View File

@ -18,14 +18,13 @@ import {NgModuleRef} from '../linker/ng_module_factory';
import {Renderer2, RendererFactory2, RendererStyleFlags2, RendererType2} from '../render/api';
import {Sanitizer} from '../sanitization/security';
import {Type} from '../type';
import {tokenKey} from '../view/util';
import {normalizeDebugBindingName, normalizeDebugBindingValue} from '../util/ng_reflect';
import {isViewDebugError, viewDestroyedError, viewWrappedDebugError} from './errors';
import {resolveDep} from './provider';
import {dirtyParentQueries, getQueryValue} from './query';
import {createInjector, createNgModuleRef, getComponentViewDefinitionFactory} from './refs';
import {ArgumentType, BindingFlags, CheckType, DebugContext, ElementData, NgModuleDefinition, NodeDef, NodeFlags, NodeLogger, ProviderOverride, RootData, Services, ViewData, ViewDefinition, ViewState, asElementData, asPureExpressionData} from './types';
import {NOOP, isComponentView, renderNode, resolveDefinition, splitDepsDsl, viewParentEl} from './util';
import {NOOP, isComponentView, renderNode, resolveDefinition, splitDepsDsl, tokenKey, viewParentEl} from './util';
import {checkAndUpdateNode, checkAndUpdateView, checkNoChangesNode, checkNoChangesView, createComponentView, createEmbeddedView, createRootView, destroyView} from './view';
@ -467,27 +466,6 @@ function debugCheckNoChangesNode(
(<any>checkNoChangesNode)(view, nodeDef, argStyle, ...values);
}
function normalizeDebugBindingName(name: string) {
// Attribute names with `$` (eg `x-y$`) are valid per spec, but unsupported by some browsers
name = camelCaseToDashCase(name.replace(/[$@]/g, '_'));
return `ng-reflect-${name}`;
}
const CAMEL_CASE_REGEXP = /([A-Z])/g;
function camelCaseToDashCase(input: string): string {
return input.replace(CAMEL_CASE_REGEXP, (...m: any[]) => '-' + m[1].toLowerCase());
}
function normalizeDebugBindingValue(value: any): string {
try {
// Limit the size of the value as otherwise the DOM just gets polluted.
return value != null ? value.toString().slice(0, 30) : value;
} catch (e) {
return '[ERROR] Exception while trying to serialize the value';
}
}
function nextDirectiveWithBinding(view: ViewData, nodeIndex: number): number|null {
for (let i = nodeIndex; i < view.def.nodes.length; i++) {
const nodeDef = view.def.nodes[i];

View File

@ -218,11 +218,11 @@ export function resetDOM() {
export function renderToHtml(
template: ComponentTemplate<any>, ctx: any, consts: number = 0, vars: number = 0,
directives?: DirectiveTypesOrFactory | null, pipes?: PipeTypesOrFactory | null,
providedRendererFactory?: RendererFactory3 | null) {
providedRendererFactory?: RendererFactory3 | null, keepNgReflect = false) {
hostView = renderTemplate(
containerEl, template, consts, vars, ctx, providedRendererFactory || testRendererFactory,
hostView, toDefs(directives, extractDirectiveDef), toDefs(pipes, extractPipeDef));
return toHtml(containerEl);
return toHtml(containerEl, keepNgReflect);
}
function toDefs(
@ -263,7 +263,7 @@ export function renderComponent<T>(type: ComponentType<T>, opts?: CreateComponen
/**
* @deprecated use `TemplateFixture` or `ComponentFixture`
*/
export function toHtml<T>(componentOrElement: T | RElement): string {
export function toHtml<T>(componentOrElement: T | RElement, keepNgReflect = false): string {
let element: any;
if (isComponentInstance(componentOrElement)) {
const context = getContext(componentOrElement);
@ -273,13 +273,17 @@ export function toHtml<T>(componentOrElement: T | RElement): string {
}
if (element) {
return 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) {
html = html.replace(/\sng-reflect-\S*="[^"]*"/g, '');
}
return html;
} else {
return '';
}

View File

@ -614,7 +614,6 @@ class HiddenModule {
});
}));
fixmeIvy('to investigate') &&
it('should handle false values on attributes', async(() => {
renderModule(FalseAttributesModule, {document: doc}).then(output => {
expect(output).toBe(