test(ivy): add canonical compiler spec for class/style (#22719)

Adds a stub for `elementStyle` and `elementClass` instruction
with a canonical spec for the compiler. The spec shows the the
compiler should be using `elementStyle` and `elementClass` instruction
in place of `[class]` and `[style]` bindings respectively.
PR Close #22719
This commit is contained in:
Miško Hevery
2018-03-08 13:57:56 -08:00
committed by Kara Erickson
parent a0a01f1e1e
commit 112431db69
9 changed files with 140 additions and 13 deletions
@@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {ɵC as C, ɵE as E, ɵT as T, ɵV as V, ɵb as b, ɵcR as cR, ɵcr as cr, ɵdefineComponent as defineComponent, ɵdetectChanges as detectChanges, ɵe as e, ɵs as s, ɵt as t, ɵv as v} from '@angular/core';
import {ɵC as C, ɵE as E, ɵT as T, ɵV as V, ɵb as b, ɵcR as cR, ɵcr as cr, ɵdefineComponent as defineComponent, ɵdetectChanges as detectChanges, ɵe as e, ɵsn as sn, ɵt as t, ɵv as v} from '@angular/core';
import {ComponentDef} from '@angular/core/src/render3/interfaces/definition';
import {TableCell, buildTable, emptyTable} from '../util';
@@ -48,7 +48,7 @@ export class LargeTableComponent {
{ T(1); }
e();
}
s(0, 'background-color', b(cell.row % 2 ? '' : 'grey'));
sn(0, 'background-color', b(cell.row % 2 ? '' : 'grey'));
t(1, b(cell.value));
}
v();
+3 -3
View File
@@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {ɵC as C, ɵE as E, ɵT as T, ɵV as V, ɵb as b, ɵcR as cR, ɵcr as cr, ɵdefineComponent as defineComponent, ɵdetectChanges as _detectChanges, ɵe as e, ɵi1 as i1, ɵp as p, ɵs as s, ɵt as t, ɵv as v} from '@angular/core';
import {ɵC as C, ɵE as E, ɵT as T, ɵV as V, ɵb as b, ɵcR as cR, ɵcr as cr, ɵdefineComponent as defineComponent, ɵdetectChanges as _detectChanges, ɵe as e, ɵi1 as i1, ɵp as p, ɵsn as sn, ɵt as t, ɵv as v} from '@angular/core';
import {ComponentDef} from '@angular/core/src/render3/interfaces/definition';
import {TreeNode, buildTree, emptyTree} from '../util';
@@ -46,7 +46,7 @@ export class TreeComponent {
C(2);
C(3);
}
s(0, 'background-color', b(ctx.data.depth % 2 ? '' : 'grey'));
sn(0, 'background-color', b(ctx.data.depth % 2 ? '' : 'grey'));
t(1, i1(' ', ctx.data.value, ' '));
cR(2);
{
@@ -114,7 +114,7 @@ export function TreeTpl(ctx: TreeNode, cm: boolean) {
}
e();
}
s(1, 'background-color', b(ctx.depth % 2 ? '' : 'grey'));
sn(1, 'background-color', b(ctx.depth % 2 ? '' : 'grey'));
t(2, i1(' ', ctx.value, ' '));
cR(3);
{
@@ -63,7 +63,9 @@ export {
p as ɵp,
pD as ɵpD,
a as ɵa,
s as ɵs,
sn as ɵsn,
k as ɵk,
kn as ɵkn,
t as ɵt,
v as ɵv,
+12 -1
View File
@@ -50,4 +50,15 @@ export function getExported() { return exported; }
export function setExported(v) { exported = v; }
```
Also writing to a property of `exports` might change its hidden class resulting in megamorphic access.
Also writing to a property of `exports` might change its hidden class resulting in megamorphic access.
## Iterating over Keys of an Object.
https://jsperf.com/object-keys-vs-for-in-with-closure/3 implies that `Object.keys` is the fastest way of iterating
over properties of an object.
```
for (var i = 0, keys = Object.keys(obj); i < keys.length; i++) {
const key = keys[i];
}
```
+2
View File
@@ -45,10 +45,12 @@ export {
containerRefreshEnd as cr,
elementAttribute as a,
elementClass as k,
elementClassNamed as kn,
elementEnd as e,
elementProperty as p,
elementStart as E,
elementStyle as s,
elementStyleNamed as sn,
listener as L,
+61 -4
View File
@@ -866,7 +866,7 @@ function generatePropertyAliases(lNodeFlags: number, direction: BindingDirection
}
/**
* Add or remove a class in a classList.
* Add or remove a class in a `classList` on a DOM element.
*
* This instruction is meant to handle the [class.foo]="exp" case
*
@@ -889,6 +889,29 @@ export function elementClassNamed<T>(index: number, className: string, value: T
}
}
/**
* Set the `className` property on a DOM element.
*
* This instruction is meant to handle the `[class]="exp"` usage.
*
* `elementClass` instruction writes the value to the "element's" `className` property.
*
* @param index The index of the element to update in the data array
* @param value A value indicating a set of classes which should be applied. The method overrides
* any existing classes. The value is stringified (`toString`) before it is applied to the
* element.
*/
export function elementClass<T>(index: number, value: T | NO_CHANGE): void {
if (value !== NO_CHANGE) {
// TODO: This is a naive implementation which simply writes value to the `className`. In the
// future
// we will add logic here which would work with the animation code.
const lElement: LElementNode = data[index];
isProceduralRenderer(renderer) ? renderer.setProperty(lElement.native, 'className', value) :
lElement.native['className'] = stringify(value);
}
}
/**
* Update a given style on an Element.
*
@@ -908,22 +931,56 @@ export function elementStyleNamed<T>(
index: number, styleName: string, value: T | NO_CHANGE,
suffixOrSanitizer?: string | Sanitizer): void {
if (value !== NO_CHANGE) {
const lElement = data[index] as LElementNode;
const lElement: LElementNode = data[index];
if (value == null) {
isProceduralRenderer(renderer) ?
renderer.removeStyle(lElement.native, styleName, RendererStyleFlags3.DashCase) :
lElement.native.style.removeProperty(styleName);
lElement.native['style'].removeProperty(styleName);
} else {
let strValue =
typeof suffixOrSanitizer == 'function' ? suffixOrSanitizer(value) : stringify(value);
if (typeof suffixOrSanitizer == 'string') strValue = strValue + suffixOrSanitizer;
isProceduralRenderer(renderer) ?
renderer.setStyle(lElement.native, styleName, strValue, RendererStyleFlags3.DashCase) :
lElement.native.style.setProperty(styleName, strValue);
lElement.native['style'].setProperty(styleName, strValue);
}
}
}
/**
* Set the `style` property on a DOM element.
*
* This instruction is meant to handle the `[style]="exp"` usage.
*
*
* @param index The index of the element to update in the data array
* @param value A value indicating if a given style should be added or removed.
* The expected shape of `value` is an object where keys are style names and the values
* are their corresponding values to set. If value is falsy than the style is remove. An absence
* of style does not cause that style to be removed. `NO_CHANGE` implies that no update should be
* performed.
*/
export function elementStyle<T>(
index: number, value: {[styleName: string]: any} | NO_CHANGE): void {
if (value !== NO_CHANGE) {
// TODO: This is a naive implementation which simply writes value to the `style`. In the future
// we will add logic here which would work with the animation code.
const lElement = data[index] as LElementNode;
if (isProceduralRenderer(renderer)) {
renderer.setProperty(lElement.native, 'style', value);
} else {
const style = lElement.native['style'];
for (let i = 0, keys = Object.keys(value); i < keys.length; i++) {
const styleName: string = keys[i];
const styleValue: any = (value as any)[styleName];
styleValue == null ? style.removeProperty(styleName) :
style.setProperty(styleName, styleValue);
}
}
}
}
//////////////////////////
//// Text
@@ -121,6 +121,7 @@ export interface RNode {
export interface RElement extends RNode {
style: RCssStyleDeclaration;
classList: RDomTokenList;
className: string;
setAttribute(name: string, value: string): void;
removeAttribute(name: string): void;
setAttributeNS(namespaceURI: string, qualifiedName: string, value: string): void;
@@ -9,7 +9,7 @@
import {browserDetection} from '@angular/platform-browser/testing/src/browser_util';
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChild, ContentChildren, Directive, HostBinding, HostListener, Injectable, Input, NgModule, OnDestroy, Optional, Pipe, PipeTransform, QueryList, SimpleChanges, TemplateRef, ViewChild, ViewChildren, ViewContainerRef} from '../../../src/core';
import * as $r3$ from '../../../src/core_render3_private_export';
import {renderComponent, toHtml} from '../render_util';
import {ComponentFixture, renderComponent, toHtml} from '../render_util';
/// See: `normative.md`
describe('elements', () => {
@@ -264,5 +264,36 @@ describe('elements', () => {
$r3$.ɵdetectChanges(comp);
expect(toHtml(comp)).toEqual('<div class="" id="changed1" style="color: red;"></div>');
});
it('should bind [class] and [style] to the element', () => {
type $StyleComponent$ = StyleComponent;
@Component(
{selector: 'style-comp', template: `<div [class]="classExp" [style]="styleExp"></div>`})
class StyleComponent {
classExp: string[]|string = 'some-name';
styleExp: {[name: string]: string} = {'background-color': 'red'};
// NORMATIVE
static ngComponentDef = $r3$.ɵdefineComponent({
type: StyleComponent,
tag: 'style-comp',
factory: function StyleComponent_Factory() { return new StyleComponent(); },
template: function StyleComponent_Template(ctx: $StyleComponent$, cm: $boolean$) {
if (cm) {
$r3$.ɵE(0, 'div');
$r3$.ɵe();
}
$r3$.ɵk(0, $r3$.ɵb(ctx.classExp));
$r3$.ɵs(0, $r3$.ɵb(ctx.styleExp));
}
});
// /NORMATIVE
}
const styleFixture = new ComponentFixture(StyleComponent);
expect(styleFixture.html)
.toEqual(`<div class="some-name" style="background-color: red;"></div>`);
});
});
});
@@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {elementAttribute, elementEnd, elementProperty, elementStart, elementStyleNamed, renderTemplate} from '../../src/render3/instructions';
import {elementAttribute, elementClass, elementEnd, elementProperty, elementStart, elementStyle, elementStyleNamed, renderTemplate} from '../../src/render3/instructions';
import {LElementNode, LNode} from '../../src/render3/interfaces/node';
import {RElement, domRendererFactory3} from '../../src/render3/interfaces/renderer';
import {bypassSanitizationTrustStyle, bypassSanitizationTrustUrl, sanitizeStyle, sanitizeUrl} from '../../src/sanitization/sanitization';
@@ -58,7 +58,8 @@ describe('instructions', () => {
describe('elementStyleNamed', () => {
it('should use sanitizer function', () => {
const t = new TemplateFixture(createDiv);
t.update(() => elementStyleNamed(0, 'background-image', 'url("http://server")', sanitizeStyle));
t.update(
() => elementStyleNamed(0, 'background-image', 'url("http://server")', sanitizeStyle));
// nothing is set because sanitizer suppresses it.
expect(t.html).toEqual('<div></div>');
@@ -70,4 +71,26 @@ describe('instructions', () => {
.toEqual('url("http://server")');
});
});
describe('elementStyle', () => {
function createDivWithStyle() {
elementStart(0, 'div', ['style', 'height: 10px']);
elementEnd();
}
const fixture = new TemplateFixture(createDivWithStyle);
it('should add style', () => {
fixture.update(() => elementStyle(0, {'background-color': 'red'}));
expect(fixture.html).toEqual('<div style="height: 10px; background-color: red;"></div>');
});
});
describe('elementClass', () => {
const fixture = new TemplateFixture(createDiv);
it('should add class', () => {
fixture.update(() => elementClass(0, 'multiple classes'));
expect(fixture.html).toEqual('<div class="multiple classes"></div>');
});
});
});