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:
parent
a0a01f1e1e
commit
112431db69
|
@ -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();
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
```
|
|
@ -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,
|
||||
|
|
|
@ -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>');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue