diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts index 590f2ed6a2..ba853e58f1 100644 --- a/packages/core/src/render3/instructions.ts +++ b/packages/core/src/render3/instructions.ts @@ -1161,8 +1161,9 @@ export function elementStyling( */ export function elementStylingApply(index: number): void { const viewData = getViewData(); - const totalPlayersQueued = - renderStyleAndClassBindings(getStylingContext(index, viewData), getRenderer(), viewData); + const isFirstRender = (viewData[FLAGS] & LViewFlags.CreationMode) !== 0; + const totalPlayersQueued = renderStyleAndClassBindings( + getStylingContext(index, viewData), getRenderer(), viewData, isFirstRender); if (totalPlayersQueued > 0) { const rootContext = getRootContext(viewData); scheduleTick(rootContext, RootContextFlags.FlushPlayers); diff --git a/packages/core/src/render3/interfaces/player.ts b/packages/core/src/render3/interfaces/player.ts index 465edf1387..518aba8d28 100644 --- a/packages/core/src/render3/interfaces/player.ts +++ b/packages/core/src/render3/interfaces/player.ts @@ -21,8 +21,8 @@ export interface Player { export const enum BindingType { Unset = 0, - Class = 2, - Style = 3, + Class = 1, + Style = 2, } export interface BindingStore { setValue(prop: string, value: any): void; } @@ -35,7 +35,7 @@ export interface BindingStore { setValue(prop: string, value: any): void; } * to be used with `PlayerFactory`. */ export interface PlayerFactoryBuildFn { - (element: HTMLElement, type: BindingType, values: {[key: string]: any}, + (element: HTMLElement, type: BindingType, values: {[key: string]: any}, isFirstRender: boolean, currentPlayer: Player|null): Player|null; } @@ -53,7 +53,7 @@ export interface PlayerFactoryBuildFn { export interface PlayerFactory { '__brand__': 'Brand for PlayerFactory that nothing will match'; } export interface PlayerBuilder extends BindingStore { - buildPlayer(currentPlayer: Player|null): Player|undefined|null; + buildPlayer(currentPlayer: Player|null, isFirstRender: boolean): Player|undefined|null; } /** diff --git a/packages/core/src/render3/interfaces/styling.ts b/packages/core/src/render3/interfaces/styling.ts index 9259448a46..7f1c6349a8 100644 --- a/packages/core/src/render3/interfaces/styling.ts +++ b/packages/core/src/render3/interfaces/styling.ts @@ -196,7 +196,7 @@ export const enum StylingFlags { // The max amount of bits used to represent these configuration values BitCountSize = 5, // There are only five bits here - BitMask = 0b1111 + BitMask = 0b11111 } /** Used as numeric pointer values to determine what cells to update in the `StylingContext` */ diff --git a/packages/core/src/render3/styling/class_and_style_bindings.ts b/packages/core/src/render3/styling/class_and_style_bindings.ts index 86339f1102..55f0290111 100644 --- a/packages/core/src/render3/styling/class_and_style_bindings.ts +++ b/packages/core/src/render3/styling/class_and_style_bindings.ts @@ -480,8 +480,10 @@ export function updateClassProp( */ export function renderStyleAndClassBindings( context: StylingContext, renderer: Renderer3, rootOrView: RootContext | LViewData, - classesStore?: BindingStore | null, stylesStore?: BindingStore | null): number { + isFirstRender: boolean, classesStore?: BindingStore | null, + stylesStore?: BindingStore | null): number { let totalPlayersQueued = 0; + if (isContextDirty(context)) { const flushPlayerBuilders: any = context[StylingIndex.MasterFlagPosition] & StylingFlags.PlayerBuildersDirty; @@ -523,15 +525,23 @@ export function renderStyleAndClassBindings( valueToApply = getInitialValue(context, flag); } - if (isClassBased) { - setClass( - native, prop, valueToApply ? true : false, renderer, classesStore, playerBuilder); - } else { - const sanitizer = (flag & StylingFlags.Sanitize) ? styleSanitizer : null; - setStyle( - native, prop, valueToApply as string | null, renderer, sanitizer, stylesStore, - playerBuilder); + // if the first render is true then we do not want to start applying falsy + // values to the DOM element's styling. Otherwise then we know there has + // been a change and even if it's falsy then it's removing something that + // was truthy before. + const doApplyValue = isFirstRender ? valueToApply : true; + if (doApplyValue) { + if (isClassBased) { + setClass( + native, prop, valueToApply ? true : false, renderer, classesStore, playerBuilder); + } else { + const sanitizer = (flag & StylingFlags.Sanitize) ? styleSanitizer : null; + setStyle( + native, prop, valueToApply as string | null, renderer, sanitizer, stylesStore, + playerBuilder); + } } + setDirty(context, i, false); } } @@ -547,7 +557,7 @@ export function renderStyleAndClassBindings( const playerInsertionIndex = i + PlayerIndex.PlayerOffsetPosition; const oldPlayer = playerContext[playerInsertionIndex] as Player | null; if (builder) { - const player = builder.buildPlayer(oldPlayer); + const player = builder.buildPlayer(oldPlayer, isFirstRender); if (player !== undefined) { if (player != null) { const wasQueued = addPlayerInternal( @@ -924,13 +934,13 @@ export class ClassAndStylePlayerBuilder implements PlayerBuilder { } } - buildPlayer(currentPlayer?: Player|null): Player|undefined|null { + buildPlayer(currentPlayer: Player|null, isFirstRender: boolean): Player|undefined|null { // if no values have been set here then this means the binding didn't // change and therefore the binding values were not updated through // `setValue` which means no new player will be provided. if (this._dirty) { - const player = - this._factory.fn(this._element, this._type, this._values !, currentPlayer || null); + const player = this._factory.fn( + this._element, this._type, this._values !, isFirstRender, currentPlayer || null); this._values = {}; this._dirty = false; return player; diff --git a/packages/core/test/bundling/animation_world/index.ts b/packages/core/test/bundling/animation_world/index.ts index 01c07774e1..1d4cca61cb 100644 --- a/packages/core/test/bundling/animation_world/index.ts +++ b/packages/core/test/bundling/animation_world/index.ts @@ -147,11 +147,14 @@ renderComponent(AnimationWorldComponent, {playerHandler}); function animateStyleFactory(keyframes: any[], duration: number, easing: string) { const limit = keyframes.length - 1; const finalKeyframe = keyframes[limit]; - return bindPlayerFactory((element: HTMLElement, type: number, values: {[key: string]: any}) => { - const kf = keyframes.slice(0, limit); - kf.push(values); - return new WebAnimationsPlayer(element, keyframes, duration, easing); - }, finalKeyframe); + return bindPlayerFactory( + (element: HTMLElement, type: number, values: {[key: string]: any}, + isFirstRender: boolean) => { + const kf = keyframes.slice(0, limit); + kf.push(values); + return new WebAnimationsPlayer(element, keyframes, duration, easing); + }, + finalKeyframe); } class WebAnimationsPlayer implements Player { diff --git a/packages/core/test/render3/styling/class_and_style_bindings_spec.ts b/packages/core/test/render3/styling/class_and_style_bindings_spec.ts index 65af1babf7..3af5d0b625 100644 --- a/packages/core/test/render3/styling/class_and_style_bindings_spec.ts +++ b/packages/core/test/render3/styling/class_and_style_bindings_spec.ts @@ -48,48 +48,55 @@ describe('style and class based bindings', () => { return lViewData[CONTEXT] as RootContext; } - function renderStyles(context: StylingContext, renderer?: Renderer3, lViewData?: LViewData) { + function renderStyles( + context: StylingContext, firstRender?: boolean, renderer?: Renderer3, lViewData?: LViewData) { const store = new MockStylingStore(element as HTMLElement, BindingType.Style); const handler = new CorePlayerHandler(); _renderStyling( context, (renderer || {}) as Renderer3, - getRootContextInternal(lViewData || createMockViewData(handler, context)), null, store); + getRootContextInternal(lViewData || createMockViewData(handler, context)), !!firstRender, + null, store); return store.getValues(); } - function trackStylesFactory() { - const store = new MockStylingStore(element as HTMLElement, BindingType.Style); + function trackStylesFactory(store?: MockStylingStore) { + store = store || new MockStylingStore(element as HTMLElement, BindingType.Style); const handler = new CorePlayerHandler(); - return function(context: StylingContext, renderer?: Renderer3): {[key: string]: any} { - const lViewData = createMockViewData(handler, context); - _renderStyling( - context, (renderer || {}) as Renderer3, getRootContextInternal(lViewData), null, store); - return store.getValues(); - }; + return function(context: StylingContext, firstRender?: boolean, renderer?: Renderer3): + {[key: string]: any} { + const lViewData = createMockViewData(handler, context); + _renderStyling( + context, (renderer || {}) as Renderer3, getRootContextInternal(lViewData), + !!firstRender, null, store); + return store !.getValues(); + }; } - function trackClassesFactory() { - const store = new MockStylingStore(element as HTMLElement, BindingType.Class); + function trackClassesFactory(store?: MockStylingStore) { + store = store || new MockStylingStore(element as HTMLElement, BindingType.Class); const handler = new CorePlayerHandler(); - return function(context: StylingContext, renderer?: Renderer3): {[key: string]: any} { - const lViewData = createMockViewData(handler, context); - _renderStyling( - context, (renderer || {}) as Renderer3, getRootContextInternal(lViewData), store); - return store.getValues(); - }; + return function(context: StylingContext, firstRender?: boolean, renderer?: Renderer3): + {[key: string]: any} { + const lViewData = createMockViewData(handler, context); + _renderStyling( + context, (renderer || {}) as Renderer3, getRootContextInternal(lViewData), + !!firstRender, store); + return store !.getValues(); + }; } function trackStylesAndClasses() { const classStore = new MockStylingStore(element as HTMLElement, BindingType.Class); const styleStore = new MockStylingStore(element as HTMLElement, BindingType.Style); const handler = new CorePlayerHandler(); - return function(context: StylingContext, renderer?: Renderer3): {[key: string]: any} { - const lViewData = createMockViewData(handler, context); - _renderStyling( - context, (renderer || {}) as Renderer3, getRootContextInternal(lViewData), classStore, - styleStore); - return [classStore.getValues(), styleStore.getValues()]; - }; + return function(context: StylingContext, firstRender?: boolean, renderer?: Renderer3): + {[key: string]: any} { + const lViewData = createMockViewData(handler, context); + _renderStyling( + context, (renderer || {}) as Renderer3, getRootContextInternal(lViewData), + !!firstRender, classStore, styleStore); + return [classStore.getValues(), styleStore.getValues()]; + }; } function updateClasses(context: StylingContext, classes: string | {[key: string]: any} | null) { @@ -234,7 +241,7 @@ describe('style and class based bindings', () => { height: '100px', }); updateStyles(stylingContext, {height: '200px'}); - expect(getStyles(stylingContext)).toEqual({width: null, height: '200px'}); + expect(getStyles(stylingContext, true)).toEqual({height: '200px'}); }); it('should evaluate the delta between style changes when rendering occurs', () => { @@ -1029,6 +1036,32 @@ describe('style and class based bindings', () => { }); }); + it('should skip issuing style updates if there is nothing to update upon first render', () => { + const stylingContext = initContext([InitialStylingFlags.VALUES_MODE, 'color', '']); + const store = new MockStylingStore(element as HTMLElement, BindingType.Class); + const getStyles = trackStylesFactory(store); + + let styles: any = {fontSize: ''}; + updateStyleProp(stylingContext, 0, ''); + updateStylingMap(stylingContext, null, styles); + + getStyles(stylingContext, true); + expect(store.getValues()).toEqual({}); + + styles = {fontSize: '20px'}; + updateStyleProp(stylingContext, 0, 'red'); + updateStylingMap(stylingContext, null, styles); + + getStyles(stylingContext); + expect(store.getValues()).toEqual({fontSize: '20px', color: 'red'}); + + styles = {}; + updateStyleProp(stylingContext, 0, ''); + updateStylingMap(stylingContext, null, styles); + + getStyles(stylingContext); + expect(store.getValues()).toEqual({fontSize: null, color: ''}); + }); }); describe('classes', () => { @@ -1447,6 +1480,34 @@ describe('style and class based bindings', () => { // apply the styles expect(getClasses(stylingContext)).toEqual({apple: true, orange: true, banana: true}); }); + + it('should skip issuing class updates if there is nothing to update upon first render', () => { + const stylingContext = initContext(null, [InitialStylingFlags.VALUES_MODE, 'blue', false]); + const store = new MockStylingStore(element as HTMLElement, BindingType.Class); + const getClasses = trackClassesFactory(store); + + let classes: any = {red: false}; + updateClassProp(stylingContext, 0, false); + updateStylingMap(stylingContext, classes); + + // apply the styles + getClasses(stylingContext, true); + expect(store.getValues()).toEqual({}); + + classes = {red: true}; + updateClassProp(stylingContext, 0, true); + updateStylingMap(stylingContext, classes); + + getClasses(stylingContext); + expect(store.getValues()).toEqual({red: true, blue: true}); + + classes = {red: false}; + updateClassProp(stylingContext, 0, false); + updateStylingMap(stylingContext, classes); + + getClasses(stylingContext); + expect(store.getValues()).toEqual({red: false, blue: false}); + }); }); describe('players', () => { @@ -1457,20 +1518,22 @@ describe('style and class based bindings', () => { const classes = 'foo bar'; let classResult: any; - const classFactory = - bindPlayerFactory((element: HTMLElement, type: BindingType, value: any) => { + const classFactory = bindPlayerFactory( + (element: HTMLElement, type: BindingType, value: any, firstRender: boolean) => { const player = new MockPlayer(); classResult = {player, element, type, value}; return player; - }, classes); + }, + classes); let styleResult: any; - const styleFactory = - bindPlayerFactory((element: HTMLElement, type: BindingType, value: any) => { + const styleFactory = bindPlayerFactory( + (element: HTMLElement, type: BindingType, value: any, firstRender: boolean) => { const player = new MockPlayer(); styleResult = {player, element, type, value}; return player; - }, styles); + }, + styles); updateStylingMap(context, classFactory, styleFactory); expect(classResult).toBeFalsy(); @@ -1562,7 +1625,7 @@ describe('style and class based bindings', () => { 5, classPlayerBuilder, null, stylePlayerBuilder, null ]); - renderStyles(context, undefined, lViewData); + renderStyles(context, false, undefined, lViewData); expect(context[StylingIndex.PlayerContext]).toEqual([ 5, classPlayerBuilder, currentClassPlayer !, stylePlayerBuilder, currentStylePlayer ! ]); @@ -1638,7 +1701,7 @@ describe('style and class based bindings', () => { barPlayerBuilder, null ]); - renderStyles(context, undefined, lViewData); + renderStyles(context, false, undefined, lViewData); const classMapPlayer = capturedClassPlayers.shift() !; const barPlayer = capturedClassPlayers.shift() !; const styleMapPlayer = capturedStylePlayers.shift() !; @@ -1671,7 +1734,7 @@ describe('style and class based bindings', () => { bazPlayerBuilder, null ]); - renderStyles(context, undefined, lViewData); + renderStyles(context, false, undefined, lViewData); const heightPlayer = capturedStylePlayers.shift() !; const bazPlayer = capturedClassPlayers.shift() !; @@ -1698,7 +1761,7 @@ describe('style and class based bindings', () => { const players: MockPlayer[] = []; const buildFn = - (element: HTMLElement, type: BindingType, value: any, + (element: HTMLElement, type: BindingType, value: any, firstRender: boolean, oldPlayer: MockPlayer | null) => { const player = new MockPlayer(value); players.push(player); @@ -1709,7 +1772,7 @@ describe('style and class based bindings', () => { let mapFactory = bindPlayerFactory(buildFn, {width: '200px'}); updateStylingMap(context, null, mapFactory); - renderStyles(context, undefined, lViewData); + renderStyles(context, false, undefined, lViewData); expect(players.length).toEqual(1); const p1 = players.pop() !; @@ -1717,7 +1780,7 @@ describe('style and class based bindings', () => { mapFactory = bindPlayerFactory(buildFn, {width: '100px'}); updateStylingMap(context, null, mapFactory); - renderStyles(context, undefined, lViewData); + renderStyles(context, false, undefined, lViewData); expect(players.length).toEqual(1); const p2 = players.pop() !; @@ -1792,7 +1855,7 @@ describe('style and class based bindings', () => { const fooPlayerBuilder = makePlayerBuilder(fooWithPlayerFactory, true); updateStyleProp(context, 0, colorWithPlayerFactory as any); updateClassProp(context, 0, fooWithPlayerFactory as any); - renderStyles(context, undefined, lViewData); + renderStyles(context, false, undefined, lViewData); const p1 = classPlayers.shift(); const p2 = stylePlayers.shift(); @@ -1856,7 +1919,7 @@ describe('style and class based bindings', () => { const fooWithoutPlayerFactory = false; updateStyleProp(context, 0, colorWithoutPlayerFactory); updateClassProp(context, 0, fooWithoutPlayerFactory); - renderStyles(context, undefined, lViewData); + renderStyles(context, false, undefined, lViewData); expect(context).toEqual([ ([9, null, null, null, null, null, null, null, null] as any), @@ -1930,28 +1993,28 @@ describe('style and class based bindings', () => { expect(styleCalls).toEqual(0); expect(classCalls).toEqual(0); - renderStyles(context, undefined, lViewData); + renderStyles(context, false, undefined, lViewData); expect(styleCalls).toEqual(1); expect(classCalls).toEqual(1); - renderStyles(context, undefined, lViewData); + renderStyles(context, false, undefined, lViewData); expect(styleCalls).toEqual(1); expect(classCalls).toEqual(1); styleFactory.value = {opacity: '0.5'}; updateStylingMap(context, classFactory, styleFactory); - renderStyles(context, undefined, lViewData); + renderStyles(context, false, undefined, lViewData); expect(styleCalls).toEqual(2); expect(classCalls).toEqual(1); classFactory.value = 'foo'; updateStylingMap(context, classFactory, styleFactory); - renderStyles(context, undefined, lViewData); + renderStyles(context, false, undefined, lViewData); expect(styleCalls).toEqual(2); expect(classCalls).toEqual(2); updateStylingMap(context, 'foo', {opacity: '0.5'}); - renderStyles(context, undefined, lViewData); + renderStyles(context, false, undefined, lViewData); expect(styleCalls).toEqual(2); expect(classCalls).toEqual(2); }); @@ -1975,14 +2038,14 @@ describe('style and class based bindings', () => { const mapFactory = bindPlayerFactory(mapBuildFn, {color: 'black'}); updateStylingMap(context, null, mapFactory); updateStyleProp(context, 0, 'green'); - renderStyles(context, undefined, lViewData); + renderStyles(context, false, undefined, lViewData); expect(propPlayer).toBeFalsy(); expect(styleMapPlayer).toBeFalsy(); const propFactory = bindPlayerFactory(propBuildFn, 'orange'); updateStyleProp(context, 0, propFactory as any); - renderStyles(context, undefined, lViewData); + renderStyles(context, false, undefined, lViewData); expect(propPlayer).toBeTruthy(); expect(styleMapPlayer).toBeFalsy(); @@ -1990,7 +2053,7 @@ describe('style and class based bindings', () => { propPlayer = styleMapPlayer = null; updateStyleProp(context, 0, null); - renderStyles(context, undefined, lViewData); + renderStyles(context, false, undefined, lViewData); expect(propPlayer).toBeFalsy(); expect(styleMapPlayer).toBeTruthy(); @@ -1998,7 +2061,7 @@ describe('style and class based bindings', () => { propPlayer = styleMapPlayer = null; updateStylingMap(context, null, null); - renderStyles(context, undefined, lViewData); + renderStyles(context, false, undefined, lViewData); expect(propPlayer).toBeFalsy(); expect(styleMapPlayer).toBeFalsy(); @@ -2012,14 +2075,15 @@ describe('style and class based bindings', () => { let previousPlayer: MockPlayer|null = null; let currentPlayer: MockPlayer|null = null; const buildFn = - (element: HTMLElement, type: BindingType, value: any, existingPlayer: MockPlayer) => { + (element: HTMLElement, type: BindingType, value: any, firstRender: boolean, + existingPlayer: MockPlayer) => { previousPlayer = existingPlayer; return currentPlayer = new MockPlayer(value); }; let factory = bindPlayerFactory<{[key: string]: any}>(buildFn, {width: '200px'}); updateStylingMap(context, null, factory); - renderStyles(context, undefined, lViewData); + renderStyles(context, false, undefined, lViewData); expect(previousPlayer).toEqual(null); expect(currentPlayer !.value).toEqual({width: '200px'}); @@ -2027,7 +2091,7 @@ describe('style and class based bindings', () => { factory = bindPlayerFactory(buildFn, {height: '200px'}); updateStylingMap(context, null, factory); - renderStyles(context, undefined, lViewData); + renderStyles(context, false, undefined, lViewData); expect(previousPlayer !.value).toEqual({width: '200px'}); expect(currentPlayer !.value).toEqual({width: null, height: '200px'}); @@ -2041,7 +2105,7 @@ describe('style and class based bindings', () => { let currentPlayer: MockPlayer|null = null; let previousPlayer: MockPlayer|null = null; const buildFn = - (element: HTMLElement, type: BindingType, value: any, + (element: HTMLElement, type: BindingType, value: any, firstRender: boolean, existingPlayer: MockPlayer | null) => { previousPlayer = existingPlayer; return currentPlayer = new MockPlayer(value); @@ -2049,7 +2113,7 @@ describe('style and class based bindings', () => { let factory = bindPlayerFactory(buildFn, {foo: true}); updateStylingMap(context, null, factory); - renderStyles(context, undefined, lViewData); + renderStyles(context, false, undefined, lViewData); expect(currentPlayer).toBeTruthy(); expect(previousPlayer).toBeFalsy(); @@ -2059,7 +2123,7 @@ describe('style and class based bindings', () => { factory = bindPlayerFactory(buildFn, {bar: true}); updateStylingMap(context, null, factory); - renderStyles(context, undefined, lViewData); + renderStyles(context, false, undefined, lViewData); expect(currentPlayer).toBeTruthy(); expect(previousPlayer).toBeTruthy(); @@ -2081,21 +2145,22 @@ describe('style and class based bindings', () => { const lViewData = createMockViewData(handler, context); let values: {[key: string]: any}|null = null; - const buildFn = (element: HTMLElement, type: BindingType, value: any) => { - values = value; - return new MockPlayer(); - }; + const buildFn = + (element: HTMLElement, type: BindingType, value: any, isFirstRender: boolean) => { + values = value; + return new MockPlayer(); + }; let factory = bindPlayerFactory<{[key: string]: any}>( buildFn, {width: '200px', height: '100px', opacity: '1'}); updateStylingMap(context, null, factory); - renderStyles(context, undefined, lViewData); + renderStyles(context, false, undefined, lViewData); expect(values !).toEqual({width: '200px-safe!', height: '100px-safe!', opacity: '1'}); factory = bindPlayerFactory(buildFn, {width: 'auto'}); updateStylingMap(context, null, factory); - renderStyles(context, undefined, lViewData); + renderStyles(context, false, undefined, lViewData); expect(values !).toEqual({width: 'auto-safe!', height: null, opacity: null}); }); @@ -2127,7 +2192,7 @@ describe('style and class based bindings', () => { updateStyleProp(context, 0, bindPlayerFactory(styleBuildFn, '100px') as any); updateClassProp(context, 0, bindPlayerFactory(classBuildFn, true) as any); updateClassProp(context, 1, bindPlayerFactory(classBuildFn, true) as any); - renderStyles(context, undefined, lViewData); + renderStyles(context, false, undefined, lViewData); handler.flushPlayers(); const [p1, p2, p3, p4, p5] = players; @@ -2145,7 +2210,7 @@ describe('style and class based bindings', () => { expect(p4.state).toEqual(PlayState.Running); expect(p5.state).toEqual(PlayState.Running); - renderStyles(context, undefined, lViewData); + renderStyles(context, false, undefined, lViewData); expect(p1.state).toEqual(PlayState.Destroyed); expect(p2.state).toEqual(PlayState.Destroyed); expect(p3.state).toEqual(PlayState.Destroyed); @@ -2226,6 +2291,74 @@ describe('style and class based bindings', () => { expect(getPlayers(target)).toEqual([p1, p2, p4, p6]); }); + + it('should build a player and signal that the first render is active', () => { + const firstRenderCaptures: any[] = []; + const otherRenderCaptures: any[] = []; + const buildFn = + (element: HTMLElement, type: BindingType, value: any, isFirstRender: boolean) => { + if (isFirstRender) { + firstRenderCaptures.push({type, value}); + } else { + otherRenderCaptures.push({type, value}); + } + return new MockPlayer(); + }; + + const styleMapFactory = + bindPlayerFactory(buildFn, {height: '200px'}) as BoundPlayerFactory; + const classMapFactory = bindPlayerFactory(buildFn, {bar: true}) as BoundPlayerFactory; + const widthFactory = bindPlayerFactory(buildFn, '100px') as BoundPlayerFactory; + const fooFactory = bindPlayerFactory(buildFn, true) as BoundPlayerFactory; + + class Comp { + static ngComponentDef = defineComponent({ + type: Comp, + selectors: [['comp']], + directives: [Comp], + factory: () => new Comp(), + consts: 1, + vars: 0, + template: (rf: RenderFlags, ctx: Comp) => { + if (rf & RenderFlags.Create) { + elementStart(0, 'div'); + elementStyling(['foo'], ['width']); + elementEnd(); + } + if (rf & RenderFlags.Update) { + elementStylingMap(0, classMapFactory, styleMapFactory); + elementStyleProp(0, 0, widthFactory); + elementClassProp(0, 0, fooFactory); + elementStylingApply(0); + } + } + }); + } + + const fixture = new ComponentFixture(Comp); + + expect(firstRenderCaptures.length).toEqual(4); + expect(firstRenderCaptures[0]).toEqual({type: BindingType.Class, value: {bar: true}}); + expect(firstRenderCaptures[1]).toEqual({type: BindingType.Style, value: {height: '200px'}}); + expect(firstRenderCaptures[2]).toEqual({type: BindingType.Style, value: {width: '100px'}}); + expect(firstRenderCaptures[3]).toEqual({type: BindingType.Class, value: {foo: true}}); + expect(otherRenderCaptures.length).toEqual(0); + + firstRenderCaptures.length = 0; + styleMapFactory.value = {height: '100px'}; + classMapFactory.value = {bar: false}; + widthFactory.value = '50px'; + fooFactory.value = false; + + fixture.update(); + + expect(firstRenderCaptures.length).toEqual(0); + expect(otherRenderCaptures.length).toEqual(4); + expect(otherRenderCaptures[0]).toEqual({type: BindingType.Class, value: {bar: false}}); + expect(otherRenderCaptures[1]).toEqual({type: BindingType.Style, value: {height: '100px'}}); + expect(otherRenderCaptures[2]).toEqual({type: BindingType.Style, value: {width: '50px'}}); + expect(otherRenderCaptures[3]).toEqual({type: BindingType.Class, value: {foo: false}}); + }); }); });