feat(ivy): properly apply class="", [class], [class.foo] and [attr.class] bindings (#24822)

PR Close #24822
This commit is contained in:
Matias Niemelä
2018-07-11 09:56:47 -07:00
parent c8ad9657c9
commit ba3eb8b654
24 changed files with 1806 additions and 928 deletions

View File

@ -15,7 +15,6 @@ import {ComponentDefInternal, InitialStylingFlags} from '../../../src/render3/in
import {ComponentFixture, renderComponent, toHtml} from '../render_util';
/// See: `normative.md`
describe('elements', () => {
// Saving type as $any$, etc to simplify testing for compiler, as types aren't saved
@ -271,6 +270,7 @@ describe('elements', () => {
});
it('should bind to a specific class', () => {
const c1: (string | InitialStylingFlags | boolean)[] = ['foo'];
type $MyComponent$ = MyComponent;
@Component({selector: 'my-component', template: `<div [class.foo]="someFlag"></div>`})
@ -283,10 +283,13 @@ describe('elements', () => {
factory: function MyComponent_Factory() { return new MyComponent(); },
template: function MyComponent_Template(rf: $RenderFlags$, ctx: $MyComponent$) {
if (rf & 1) {
$r3$.ɵEe(0, 'div');
$r3$.ɵE(0, 'div');
$r3$.ɵs(null, c1);
$r3$.ɵe();
}
if (rf & 2) {
$r3$.ɵkn(0, 'foo', $r3$.ɵb(ctx.someFlag));
$r3$.ɵcp(0, 0, ctx.someFlag);
$r3$.ɵsa(0);
}
}
});
@ -320,13 +323,13 @@ describe('elements', () => {
template: function MyComponent_Template(rf: $RenderFlags$, ctx: $MyComponent$) {
if (rf & 1) {
$r3$.ɵE(0, 'div');
$r3$.ɵs(1, c0);
$r3$.ɵs(c0);
$r3$.ɵe();
}
if (rf & 2) {
$r3$.ɵsp(1, 0, ctx.someColor);
$r3$.ɵsp(1, 1, ctx.someWidth, 'px');
$r3$.ɵsa(1);
$r3$.ɵsp(0, 0, ctx.someColor);
$r3$.ɵsp(0, 1, ctx.someWidth, 'px');
$r3$.ɵsa(0);
}
}
});
@ -353,7 +356,9 @@ describe('elements', () => {
it('should bind to many and keep order', () => {
type $MyComponent$ = MyComponent;
const c0 = ['color', InitialStylingFlags.INITIAL_STYLES, 'color', 'red'];
const c0 = ['color', InitialStylingFlags.VALUES_MODE, 'color', 'red'];
const c1 = ['foo'];
@Component({
selector: 'my-component',
template:
@ -369,12 +374,13 @@ describe('elements', () => {
template: function MyComponent_Template(rf: $RenderFlags$, ctx: $MyComponent$) {
if (rf & 1) {
$r3$.ɵE(0, 'div');
$r3$.ɵs(1, c0);
$r3$.ɵs(c0, c1);
$r3$.ɵe();
}
if (rf & 2) {
$r3$.ɵp(0, 'id', $r3$.ɵb(ctx.someString + 1));
$r3$.ɵkn(0, 'foo', $r3$.ɵb(ctx.someString == 'initial'));
$r3$.ɵcp(0, 0, ctx.someString == 'initial');
$r3$.ɵsa(0);
}
}
});
@ -406,13 +412,12 @@ describe('elements', () => {
template: function StyleComponent_Template(rf: $RenderFlags$, ctx: $StyleComponent$) {
if (rf & 1) {
$r3$.ɵE(0, 'div');
$r3$.ɵs(1);
$r3$.ɵs();
$r3$.ɵe();
}
if (rf & 2) {
$r3$.ɵk(0, $r3$.ɵb(ctx.classExp));
$r3$.ɵsm(1, ctx.styleExp);
$r3$.ɵsa(1);
$r3$.ɵsm(0, ctx.styleExp, ctx.classExp);
$r3$.ɵsa(0);
}
}
});

View File

@ -44,17 +44,17 @@ describe('compiler sanitization', () => {
template: function MyComponent_Template(rf: $RenderFlags$, ctx: $MyComponent$) {
if (rf & 1) {
$r3$.ɵE(0, 'div');
$r3$.ɵs(1, ['background-image']);
$r3$.ɵs(['background-image']);
$r3$.ɵe();
$r3$.ɵEe(2, 'img');
$r3$.ɵEe(1, 'img');
}
if (rf & 2) {
$r3$.ɵp(0, 'innerHTML', $r3$.ɵb(ctx.innerHTML), $r3$.ɵsanitizeHtml);
$r3$.ɵp(0, 'hidden', $r3$.ɵb(ctx.hidden));
$r3$.ɵsp(1, 0, ctx.style, $r3$.ɵsanitizeStyle);
$r3$.ɵsa(1);
$r3$.ɵp(2, 'src', $r3$.ɵb(ctx.url), $r3$.ɵsanitizeUrl);
$r3$.ɵa(2, 'srcset', $r3$.ɵb(ctx.url), $r3$.ɵsanitizeUrl);
$r3$.ɵsp(0, 0, ctx.style, $r3$.ɵsanitizeStyle);
$r3$.ɵsa(0);
$r3$.ɵp(1, 'src', $r3$.ɵb(ctx.url), $r3$.ɵsanitizeUrl);
$r3$.ɵa(1, 'srcset', $r3$.ɵb(ctx.url), $r3$.ɵsanitizeUrl);
}
}
});

View File

@ -7,8 +7,9 @@
*/
import {defineComponent, defineDirective} from '../../src/render3/index';
import {bind, container, containerRefreshEnd, containerRefreshStart, elementAttribute, elementClassNamed, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, load, text, textBinding} from '../../src/render3/instructions';
import {RenderFlags} from '../../src/render3/interfaces/definition';
import {bind, container, containerRefreshEnd, containerRefreshStart, elementAttribute, elementClassProp, elementEnd, elementProperty, elementStart, elementStyling, elementStylingApply, embeddedViewEnd, embeddedViewStart, load, text, textBinding} from '../../src/render3/instructions';
import {InitialStylingFlags, RenderFlags} from '../../src/render3/interfaces/definition';
import {ComponentFixture, createComponent, renderToHtml} from './render_util';
describe('exports', () => {
@ -212,13 +213,15 @@ describe('exports', () => {
function Template(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementStart(0, 'div');
elementStyling(null, [InitialStylingFlags.VALUES_MODE, 'red', true]);
elementEnd();
elementStart(1, 'input', ['type', 'checkbox', 'checked', 'true'], ['myInput', '']);
elementEnd();
}
const tmp = load(2) as any;
if (rf & RenderFlags.Update) {
elementClassNamed(0, 'red', bind(tmp.checked));
elementClassProp(0, 0, tmp.checked);
elementStylingApply(0);
}
}

View File

@ -10,7 +10,7 @@ import {NgForOfContext} from '@angular/common';
import {RenderFlags, directiveInject} from '../../src/render3';
import {defineComponent} from '../../src/render3/definition';
import {bind, container, element, elementAttribute, elementClass, elementEnd, elementProperty, elementStart, elementStyle, elementStyleProp, elementStyling, elementStylingApply, interpolation1, renderTemplate, text, textBinding} from '../../src/render3/instructions';
import {bind, container, element, elementAttribute, elementEnd, elementProperty, elementStart, elementStyleProp, elementStyling, elementStylingApply, elementStylingMap, interpolation1, renderTemplate, text, textBinding} from '../../src/render3/instructions';
import {InitialStylingFlags} from '../../src/render3/interfaces/definition';
import {AttributeMarker, LElementNode, LNode} from '../../src/render3/interfaces/node';
import {RElement, domRendererFactory3} from '../../src/render3/interfaces/renderer';
@ -23,13 +23,13 @@ import {ComponentFixture, TemplateFixture} from './render_util';
describe('instructions', () => {
function createAnchor() {
elementStart(0, 'a');
elementStyling(1);
elementStyling();
elementEnd();
}
function createDiv(initialStyles?: (string | number)[]) {
elementStart(0, 'div');
elementStyling(1, initialStyles && Array.isArray(initialStyles) ? initialStyles : null);
elementStyling(initialStyles && Array.isArray(initialStyles) ? initialStyles : null);
elementEnd();
}
@ -193,15 +193,15 @@ describe('instructions', () => {
it('should use sanitizer function', () => {
const t = new TemplateFixture(() => { return createDiv(['background-image']); });
t.update(() => {
elementStyleProp(1, 0, 'url("http://server")', sanitizeStyle);
elementStylingApply(1);
elementStyleProp(0, 0, 'url("http://server")', sanitizeStyle);
elementStylingApply(0);
});
// nothing is set because sanitizer suppresses it.
expect(t.html).toEqual('<div></div>');
t.update(() => {
elementStyleProp(1, 0, bypassSanitizationTrustStyle('url("http://server")'), sanitizeStyle);
elementStylingApply(1);
elementStyleProp(0, 0, bypassSanitizationTrustStyle('url("http://server")'), sanitizeStyle);
elementStylingApply(0);
});
expect((t.hostElement.firstChild as HTMLElement).style.getPropertyValue('background-image'))
.toEqual('url("http://server")');
@ -211,25 +211,33 @@ describe('instructions', () => {
describe('elementStyleMap', () => {
function createDivWithStyle() {
elementStart(0, 'div');
elementStyling(1, ['height', InitialStylingFlags.INITIAL_STYLES, 'height', '10px']);
elementStyling(['height', InitialStylingFlags.VALUES_MODE, 'height', '10px']);
elementEnd();
}
it('should add style', () => {
const fixture = new TemplateFixture(createDivWithStyle);
fixture.update(() => {
elementStyle(1, {'background-color': 'red'});
elementStylingApply(1);
elementStylingMap(0, {'background-color': 'red'});
elementStylingApply(0);
});
expect(fixture.html).toEqual('<div style="height: 10px; background-color: red;"></div>');
});
});
describe('elementClass', () => {
function createDivWithStyling() {
elementStart(0, 'div');
elementStyling();
elementEnd();
}
it('should add class', () => {
const fixture = new TemplateFixture(createDiv);
fixture.update(() => elementClass(0, 'multiple classes'));
const fixture = new TemplateFixture(createDivWithStyling);
fixture.update(() => {
elementStylingMap(0, null, 'multiple classes');
elementStylingApply(0);
});
expect(fixture.html).toEqual('<div class="multiple classes"></div>');
});
});

View File

@ -9,7 +9,8 @@
import {RenderFlags} from '@angular/core/src/render3';
import {defineComponent, defineDirective} from '../../src/render3/index';
import {NO_CHANGE, bind, container, containerRefreshEnd, containerRefreshStart, elementAttribute, elementClassNamed, elementEnd, elementProperty, elementStart, elementStyleProp, elementStyling, elementStylingApply, embeddedViewEnd, embeddedViewStart, interpolation1, interpolation2, interpolation3, interpolation4, interpolation5, interpolation6, interpolation7, interpolation8, interpolationV, load, loadDirective, projection, projectionDef, text, textBinding} from '../../src/render3/instructions';
import {NO_CHANGE, bind, container, containerRefreshEnd, containerRefreshStart, elementAttribute, elementClassProp, elementEnd, elementProperty, elementStart, elementStyleProp, elementStyling, elementStylingApply, embeddedViewEnd, embeddedViewStart, interpolation1, interpolation2, interpolation3, interpolation4, interpolation5, interpolation6, interpolation7, interpolation8, interpolationV, load, loadDirective, projection, projectionDef, text, textBinding} from '../../src/render3/instructions';
import {InitialStylingFlags} from '../../src/render3/interfaces/definition';
import {HEADER_OFFSET} from '../../src/render3/interfaces/view';
import {sanitizeUrl} from '../../src/sanitization/sanitization';
import {Sanitizer, SecurityContext} from '../../src/sanitization/security';
@ -747,12 +748,12 @@ describe('render3 integration test', () => {
function Template(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementStart(0, 'span');
elementStyling(1, ['border-color']);
elementStyling(['border-color']);
elementEnd();
}
if (rf & RenderFlags.Update) {
elementStyleProp(1, 0, ctx);
elementStylingApply(1);
elementStyleProp(0, 0, ctx);
elementStylingApply(0);
}
}
@ -766,12 +767,12 @@ describe('render3 integration test', () => {
function Template(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementStart(0, 'span');
elementStyling(1, ['font-size']);
elementStyling(['font-size']);
elementEnd();
}
if (rf & RenderFlags.Update) {
elementStyleProp(1, 0, ctx, 'px');
elementStylingApply(1);
elementStyleProp(0, 0, ctx, 'px');
elementStylingApply(0);
}
}
@ -787,10 +788,12 @@ describe('render3 integration test', () => {
function Template(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementStart(0, 'span');
elementStyling(null, ['active']);
elementEnd();
}
if (rf & RenderFlags.Update) {
elementClassNamed(0, 'active', bind(ctx));
elementClassProp(0, 0, ctx);
elementStylingApply(0);
}
}
@ -809,11 +812,14 @@ describe('render3 integration test', () => {
it('should work correctly with existing static classes', () => {
function Template(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementStart(0, 'span', ['class', 'existing']);
elementStart(0, 'span');
elementStyling(
null, ['existing', 'active', InitialStylingFlags.VALUES_MODE, 'existing', true]);
elementEnd();
}
if (rf & RenderFlags.Update) {
elementClassNamed(0, 'active', bind(ctx));
elementClassProp(0, 1, ctx);
elementStylingApply(0);
}
}

File diff suppressed because it is too large Load Diff