refactor(ivy): Switch styling to new reconcile algorithm (#34616)
NOTE: This change must be reverted with previous deletes so that it code remains in build-able state. This change deletes old styling code and replaces it with a simplified styling algorithm. The mental model for the new algorithm is: - Create a linked list of styling bindings in the order of priority. All styling bindings ere executed in compiled order and than a linked list of bindings is created in priority order. - Flush the style bindings at the end of `advance()` instruction. This implies that there are two flush events. One at the end of template `advance` instruction in the template. Second one at the end of `hostBindings` `advance` instruction when processing host bindings (if any). - Each binding instructions effectively updates the string to represent the string at that location. Because most of the bindings are additive, this is a cheap strategy in most cases. In rare cases the strategy requires removing tokens from the styling up to this point. (We expect that to be rare case)S Because, the bindings are presorted in the order of priority, it is safe to resume the processing of the concatenated string from the last change binding. PR Close #34616
This commit is contained in:
@ -1536,16 +1536,15 @@ describe('change detection', () => {
|
||||
});
|
||||
|
||||
it('should include style prop name in case of style binding', () => {
|
||||
const message = ivyEnabled ?
|
||||
`Previous value for 'style.color': 'red'. Current value: 'green'` :
|
||||
`Previous value: 'color: red'. Current value: 'color: green'`;
|
||||
const message = ivyEnabled ? `Previous value for 'color': 'red'. Current value: 'green'` :
|
||||
`Previous value: 'color: red'. Current value: 'color: green'`;
|
||||
expect(() => initWithTemplate('<div [style.color]="unstableColorExpression"></div>'))
|
||||
.toThrowError(new RegExp(message));
|
||||
});
|
||||
|
||||
it('should include class name in case of class binding', () => {
|
||||
const message = ivyEnabled ?
|
||||
`Previous value for 'class.someClass': 'true'. Current value: 'false'` :
|
||||
`Previous value for 'someClass': 'true'. Current value: 'false'` :
|
||||
`Previous value: 'someClass: true'. Current value: 'someClass: false'`;
|
||||
expect(() => initWithTemplate('<div [class.someClass]="unstableBooleanExpression"></div>'))
|
||||
.toThrowError(new RegExp(message));
|
||||
@ -1574,16 +1573,15 @@ describe('change detection', () => {
|
||||
});
|
||||
|
||||
it('should include style prop name in case of host style bindings', () => {
|
||||
const message = ivyEnabled ?
|
||||
`Previous value for 'style.color': 'red'. Current value: 'green'` :
|
||||
`Previous value: 'color: red'. Current value: 'color: green'`;
|
||||
const message = ivyEnabled ? `Previous value for 'color': 'red'. Current value: 'green'` :
|
||||
`Previous value: 'color: red'. Current value: 'color: green'`;
|
||||
expect(() => initWithHostBindings({'[style.color]': 'unstableColorExpression'}))
|
||||
.toThrowError(new RegExp(message));
|
||||
});
|
||||
|
||||
it('should include class name in case of host class bindings', () => {
|
||||
const message = ivyEnabled ?
|
||||
`Previous value for 'class.someClass': 'true'. Current value: 'false'` :
|
||||
`Previous value for 'someClass': 'true'. Current value: 'false'` :
|
||||
`Previous value: 'someClass: true'. Current value: 'someClass: false'`;
|
||||
expect(() => initWithHostBindings({'[class.someClass]': 'unstableBooleanExpression'}))
|
||||
.toThrowError(new RegExp(message));
|
||||
|
@ -10,6 +10,8 @@ import {Component, Directive, HostBinding, InjectionToken, ViewChild} from '@ang
|
||||
import {isLView} from '@angular/core/src/render3/interfaces/type_checks';
|
||||
import {CONTEXT} from '@angular/core/src/render3/interfaces/view';
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
import {getElementStyles} from '@angular/core/testing/src/styling';
|
||||
import {expect} from '@angular/core/testing/src/testing_internal';
|
||||
import {onlyInIvy} from '@angular/private/testing';
|
||||
|
||||
import {getHostElement, markDirty} from '../../src/render3/index';
|
||||
@ -473,11 +475,10 @@ onlyInIvy('Ivy-specific utilities').describe('discovery utils deprecated', () =>
|
||||
const childDebug = getDebugNode(child) !;
|
||||
|
||||
expect(childDebug.native).toBe(child);
|
||||
expect(childDebug.styles).toBeTruthy();
|
||||
|
||||
const styles = childDebug.styles !.values;
|
||||
expect(styles['width']).toEqual('200px');
|
||||
expect(styles['height']).toEqual('400px');
|
||||
expect(getElementStyles(child)).toEqual({
|
||||
width: '200px',
|
||||
height: '400px',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -254,7 +254,7 @@ describe('host bindings', () => {
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule(
|
||||
{declarations: [MyApp, ParentDir, ChildDir, SiblingDir]});
|
||||
{declarations: [MyApp, ParentDir, SiblingDir, ChildDir]});
|
||||
const fixture = TestBed.createComponent(MyApp);
|
||||
const element = fixture.nativeElement;
|
||||
fixture.detectChanges();
|
||||
@ -262,10 +262,9 @@ describe('host bindings', () => {
|
||||
const childElement = element.querySelector('div');
|
||||
|
||||
// width/height values were set in all directives, but the sub-class directive
|
||||
// (ChildDir)
|
||||
// had priority over the parent directive (ParentDir) which is why its value won. It
|
||||
// also
|
||||
// won over Dir because the SiblingDir directive was evaluated later on.
|
||||
// (ChildDir) had priority over the parent directive (ParentDir) which is why its
|
||||
// value won. It also won over Dir because the SiblingDir directive was declared
|
||||
// later in `declarations`.
|
||||
expect(childElement.style.width).toEqual('200px');
|
||||
expect(childElement.style.height).toEqual('200px');
|
||||
|
||||
|
@ -116,10 +116,10 @@ describe('inheritance', () => {
|
||||
'Base.backgroundColor', 'Super.color', 'Sub2.width', //
|
||||
]);
|
||||
if (ivyEnabled) {
|
||||
expect(getDirectiveDef(BaseDirective) !.hostVars).toEqual(1);
|
||||
expect(getDirectiveDef(SuperDirective) !.hostVars).toEqual(2);
|
||||
expect(getDirectiveDef(Sub1Directive) !.hostVars).toEqual(3);
|
||||
expect(getDirectiveDef(Sub2Directive) !.hostVars).toEqual(3);
|
||||
expect(getDirectiveDef(BaseDirective) !.hostVars).toEqual(2);
|
||||
expect(getDirectiveDef(SuperDirective) !.hostVars).toEqual(4);
|
||||
expect(getDirectiveDef(Sub1Directive) !.hostVars).toEqual(6);
|
||||
expect(getDirectiveDef(Sub2Directive) !.hostVars).toEqual(6);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -1032,7 +1032,7 @@ describe('acceptance integration tests', () => {
|
||||
|
||||
fixture.componentInstance.value = false;
|
||||
fixture.detectChanges();
|
||||
expect(structuralCompEl.getAttribute('class')).toEqual('');
|
||||
expect(structuralCompEl.getAttribute('class')).toBeFalsy();
|
||||
});
|
||||
|
||||
@Directive({selector: '[DirWithClass]'})
|
||||
@ -1071,7 +1071,7 @@ describe('acceptance integration tests', () => {
|
||||
|
||||
it('should delegate initial styles to a [style] input binding if present on a directive on the same element',
|
||||
() => {
|
||||
@Component({template: '<div style="width:100px;height:200px" DirWithStyle></div>'})
|
||||
@Component({template: '<div style="width: 100px; height: 200px" DirWithStyle></div>'})
|
||||
class App {
|
||||
@ViewChild(DirWithStyleDirective)
|
||||
mockStyleDirective !: DirWithStyleDirective;
|
||||
@ -1084,8 +1084,8 @@ describe('acceptance integration tests', () => {
|
||||
const styles = fixture.componentInstance.mockStyleDirective.stylesVal;
|
||||
|
||||
// Use `toContain` since Ivy and ViewEngine have some slight differences in formatting.
|
||||
expect(styles).toContain('width:100px');
|
||||
expect(styles).toContain('height:200px');
|
||||
expect(styles).toContain('width: 100px');
|
||||
expect(styles).toContain('height: 200px');
|
||||
});
|
||||
|
||||
it('should update `[class]` and bindings in the provided directive if the input is matched',
|
||||
@ -1122,7 +1122,7 @@ describe('acceptance integration tests', () => {
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(fixture.componentInstance.mockStyleDirective.stylesVal)
|
||||
.toEqual({'width': '200px', 'height': '500px'});
|
||||
.toEqual({width: '200px', height: '500px'});
|
||||
});
|
||||
|
||||
onlyInIvy('Style binding merging works differently in Ivy')
|
||||
@ -1177,8 +1177,8 @@ describe('acceptance integration tests', () => {
|
||||
}
|
||||
})
|
||||
class DirWithSingleStylingBindings {
|
||||
width: null|string = null;
|
||||
height: null|string = null;
|
||||
width: string|null|undefined = undefined;
|
||||
height: string|null|undefined = undefined;
|
||||
activateXYZClass: boolean = false;
|
||||
}
|
||||
|
||||
@ -1214,8 +1214,8 @@ describe('acceptance integration tests', () => {
|
||||
expect(target.classList.contains('def')).toBeTruthy();
|
||||
expect(target.classList.contains('xyz')).toBeTruthy();
|
||||
|
||||
dirInstance.width = null;
|
||||
dirInstance.height = null;
|
||||
dirInstance.width = undefined;
|
||||
dirInstance.height = undefined;
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(target.style.getPropertyValue('width')).toEqual('100px');
|
||||
@ -1230,7 +1230,7 @@ describe('acceptance integration tests', () => {
|
||||
() => {
|
||||
@Directive({selector: '[Dir1WithStyle]', host: {'[style.width]': 'width'}})
|
||||
class Dir1WithStyle {
|
||||
width: null|string = null;
|
||||
width: null|string|undefined = undefined;
|
||||
}
|
||||
|
||||
@Directive({
|
||||
@ -1238,7 +1238,7 @@ describe('acceptance integration tests', () => {
|
||||
host: {'style': 'width: 111px', '[style.width]': 'width'}
|
||||
})
|
||||
class Dir2WithStyle {
|
||||
width: null|string = null;
|
||||
width: null|string|undefined = undefined;
|
||||
}
|
||||
|
||||
@Component(
|
||||
@ -1246,10 +1246,10 @@ describe('acceptance integration tests', () => {
|
||||
class App {
|
||||
@ViewChild(Dir1WithStyle) dir1Instance !: Dir1WithStyle;
|
||||
@ViewChild(Dir2WithStyle) dir2Instance !: Dir2WithStyle;
|
||||
width: string|null = null;
|
||||
width: string|null|undefined = undefined;
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({declarations: [App, Dir1WithStyle, Dir2WithStyle]});
|
||||
TestBed.configureTestingModule({declarations: [App, Dir2WithStyle, Dir1WithStyle]});
|
||||
const fixture = TestBed.createComponent(App);
|
||||
fixture.detectChanges();
|
||||
const {dir1Instance, dir2Instance} = fixture.componentInstance;
|
||||
@ -1263,15 +1263,15 @@ describe('acceptance integration tests', () => {
|
||||
fixture.detectChanges();
|
||||
expect(target.style.getPropertyValue('width')).toEqual('999px');
|
||||
|
||||
fixture.componentInstance.width = null;
|
||||
fixture.componentInstance.width = undefined;
|
||||
fixture.detectChanges();
|
||||
expect(target.style.getPropertyValue('width')).toEqual('222px');
|
||||
|
||||
dir1Instance.width = null;
|
||||
dir1Instance.width = undefined;
|
||||
fixture.detectChanges();
|
||||
expect(target.style.getPropertyValue('width')).toEqual('333px');
|
||||
|
||||
dir2Instance.width = null;
|
||||
dir2Instance.width = undefined;
|
||||
fixture.detectChanges();
|
||||
expect(target.style.getPropertyValue('width')).toEqual('111px');
|
||||
|
||||
@ -1316,7 +1316,7 @@ describe('acceptance integration tests', () => {
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule(
|
||||
{declarations: [App, Dir1WithStyling, Dir2WithStyling]});
|
||||
{declarations: [App, Dir2WithStyling, Dir1WithStyling]});
|
||||
const fixture = TestBed.createComponent(App);
|
||||
fixture.detectChanges();
|
||||
const {dir1Instance, dir2Instance} = fixture.componentInstance;
|
||||
@ -1325,7 +1325,7 @@ describe('acceptance integration tests', () => {
|
||||
expect(target.style.getPropertyValue('width')).toEqual('111px');
|
||||
|
||||
const compInstance = fixture.componentInstance;
|
||||
compInstance.stylesExp = {width: '999px', height: null};
|
||||
compInstance.stylesExp = {width: '999px', height: undefined};
|
||||
compInstance.classesExp = {one: true, two: false};
|
||||
dir1Instance.stylesExp = {width: '222px'};
|
||||
dir1Instance.classesExp = {two: true, three: false};
|
||||
|
@ -73,7 +73,6 @@ describe('renderer factory lifecycle', () => {
|
||||
fixture.detectChanges();
|
||||
expect(logs).toEqual(
|
||||
['create', 'create', 'begin', 'some_component create', 'some_component update', 'end']);
|
||||
|
||||
logs = [];
|
||||
fixture.detectChanges();
|
||||
expect(logs).toEqual(['begin', 'some_component update', 'end']);
|
||||
|
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user