fix(ivy): ensure component/directive class
selectors are properly understood (#27849)
Angular allows for `<ng-content>` elements to include a selector which filters which content-projected entries are inserted into the container depending on whether or not the selector is matched. With Ivy this feature has not fully worked due to the massive changes that took place inside of Ivy's styling algorithm code (which is responsible for assigning classes and styles to an element). This fix ensures that content-projection can correctly identify which slot an element should be placed into when class-based selectors are used. PR Close #27849
This commit is contained in:

committed by
Andrew Kushnir

parent
06e5bf1661
commit
e62eeed7d4
@ -81,22 +81,35 @@ describe('projection', () => {
|
||||
expect(main.nativeElement).toHaveText('');
|
||||
});
|
||||
|
||||
fixmeIvy('FW-833: Directive / projected node matching against class name')
|
||||
.it('should support multiple content tags', () => {
|
||||
TestBed.configureTestingModule({declarations: [MultipleContentTagsComponent]});
|
||||
TestBed.overrideComponent(MainComp, {
|
||||
set: {
|
||||
template: '<multiple-content-tags>' +
|
||||
'<div>B</div>' +
|
||||
'<div>C</div>' +
|
||||
'<div class="left">A</div>' +
|
||||
'</multiple-content-tags>'
|
||||
}
|
||||
});
|
||||
const main = TestBed.createComponent(MainComp);
|
||||
it('should project a single class-based tag', () => {
|
||||
TestBed.configureTestingModule({declarations: [SingleContentTagComponent]});
|
||||
TestBed.overrideComponent(MainComp, {
|
||||
set: {
|
||||
template: '<single-content-tag>' +
|
||||
'<div class="target">I AM PROJECTED</div>' +
|
||||
'</single-content-tag>'
|
||||
}
|
||||
});
|
||||
const main = TestBed.createComponent(MainComp);
|
||||
|
||||
expect(main.nativeElement).toHaveText('(A, BC)');
|
||||
});
|
||||
expect(main.nativeElement).toHaveText('I AM PROJECTED');
|
||||
});
|
||||
|
||||
fixmeIvy('unknown').it('should support multiple content tags', () => {
|
||||
TestBed.configureTestingModule({declarations: [MultipleContentTagsComponent]});
|
||||
TestBed.overrideComponent(MainComp, {
|
||||
set: {
|
||||
template: '<multiple-content-tags>' +
|
||||
'<div>B</div>' +
|
||||
'<div>C</div>' +
|
||||
'<div class="left">A</div>' +
|
||||
'</multiple-content-tags>'
|
||||
}
|
||||
});
|
||||
const main = TestBed.createComponent(MainComp);
|
||||
|
||||
expect(main.nativeElement).toHaveText('(A, BC)');
|
||||
});
|
||||
|
||||
it('should redistribute only direct children', () => {
|
||||
TestBed.configureTestingModule({declarations: [MultipleContentTagsComponent]});
|
||||
@ -182,35 +195,34 @@ describe('projection', () => {
|
||||
expect(main.nativeElement).toHaveText('OUTER(INNER(INNERINNER(A,BC)))');
|
||||
});
|
||||
|
||||
fixmeIvy('FW-833: Directive / projected node matching against class name')
|
||||
.it('should redistribute when the shadow dom changes', () => {
|
||||
TestBed.configureTestingModule(
|
||||
{declarations: [ConditionalContentComponent, ManualViewportDirective]});
|
||||
TestBed.overrideComponent(MainComp, {
|
||||
set: {
|
||||
template: '<conditional-content>' +
|
||||
'<div class="left">A</div>' +
|
||||
'<div>B</div>' +
|
||||
'<div>C</div>' +
|
||||
'</conditional-content>'
|
||||
}
|
||||
});
|
||||
const main = TestBed.createComponent(MainComp);
|
||||
fixmeIvy('unknown').it('should redistribute when the shadow dom changes', () => {
|
||||
TestBed.configureTestingModule(
|
||||
{declarations: [ConditionalContentComponent, ManualViewportDirective]});
|
||||
TestBed.overrideComponent(MainComp, {
|
||||
set: {
|
||||
template: '<conditional-content>' +
|
||||
'<div class="left">A</div>' +
|
||||
'<div>B</div>' +
|
||||
'<div>C</div>' +
|
||||
'</conditional-content>'
|
||||
}
|
||||
});
|
||||
const main = TestBed.createComponent(MainComp);
|
||||
|
||||
const viewportDirective =
|
||||
main.debugElement.queryAllNodes(By.directive(ManualViewportDirective))[0].injector.get(
|
||||
ManualViewportDirective);
|
||||
const viewportDirective =
|
||||
main.debugElement.queryAllNodes(By.directive(ManualViewportDirective))[0].injector.get(
|
||||
ManualViewportDirective);
|
||||
|
||||
expect(main.nativeElement).toHaveText('(, BC)');
|
||||
expect(main.nativeElement).toHaveText('(, BC)');
|
||||
|
||||
viewportDirective.show();
|
||||
main.detectChanges();
|
||||
expect(main.nativeElement).toHaveText('(A, BC)');
|
||||
viewportDirective.show();
|
||||
main.detectChanges();
|
||||
expect(main.nativeElement).toHaveText('(A, BC)');
|
||||
|
||||
viewportDirective.hide();
|
||||
main.detectChanges();
|
||||
expect(main.nativeElement).toHaveText('(, BC)');
|
||||
});
|
||||
viewportDirective.hide();
|
||||
main.detectChanges();
|
||||
expect(main.nativeElement).toHaveText('(, BC)');
|
||||
});
|
||||
|
||||
// GH-2095 - https://github.com/angular/angular/issues/2095
|
||||
// important as we are removing the ng-content element during compilation,
|
||||
@ -290,38 +302,36 @@ describe('projection', () => {
|
||||
expect(main.nativeElement).toHaveText('SIMPLE()START(A)END');
|
||||
});
|
||||
|
||||
fixmeIvy('FW-833: Directive / projected node matching against class name')
|
||||
.it('should support moving ng-content around', () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ConditionalContentComponent, ProjectDirective, ManualViewportDirective]
|
||||
});
|
||||
TestBed.overrideComponent(MainComp, {
|
||||
set: {
|
||||
template: '<conditional-content>' +
|
||||
'<div class="left">A</div>' +
|
||||
'<div>B</div>' +
|
||||
'</conditional-content>' +
|
||||
'START(<div project></div>)END'
|
||||
}
|
||||
});
|
||||
const main = TestBed.createComponent(MainComp);
|
||||
fixmeIvy('unknown').it('should support moving ng-content around', () => {
|
||||
TestBed.configureTestingModule(
|
||||
{declarations: [ConditionalContentComponent, ProjectDirective, ManualViewportDirective]});
|
||||
TestBed.overrideComponent(MainComp, {
|
||||
set: {
|
||||
template: '<conditional-content>' +
|
||||
'<div class="left">A</div>' +
|
||||
'<div>B</div>' +
|
||||
'</conditional-content>' +
|
||||
'START(<div project></div>)END'
|
||||
}
|
||||
});
|
||||
const main = TestBed.createComponent(MainComp);
|
||||
|
||||
const sourceDirective: ManualViewportDirective =
|
||||
main.debugElement.queryAllNodes(By.directive(ManualViewportDirective))[0].injector.get(
|
||||
ManualViewportDirective);
|
||||
const projectDirective: ProjectDirective =
|
||||
main.debugElement.queryAllNodes(By.directive(ProjectDirective))[0].injector.get(
|
||||
ProjectDirective);
|
||||
expect(main.nativeElement).toHaveText('(, B)START()END');
|
||||
const sourceDirective: ManualViewportDirective =
|
||||
main.debugElement.queryAllNodes(By.directive(ManualViewportDirective))[0].injector.get(
|
||||
ManualViewportDirective);
|
||||
const projectDirective: ProjectDirective =
|
||||
main.debugElement.queryAllNodes(By.directive(ProjectDirective))[0].injector.get(
|
||||
ProjectDirective);
|
||||
expect(main.nativeElement).toHaveText('(, B)START()END');
|
||||
|
||||
projectDirective.show(sourceDirective.templateRef);
|
||||
expect(main.nativeElement).toHaveText('(, B)START(A)END');
|
||||
projectDirective.show(sourceDirective.templateRef);
|
||||
expect(main.nativeElement).toHaveText('(, B)START(A)END');
|
||||
|
||||
// Stamping ng-content multiple times should not produce the content multiple
|
||||
// times...
|
||||
projectDirective.show(sourceDirective.templateRef);
|
||||
expect(main.nativeElement).toHaveText('(, B)START(A)END');
|
||||
});
|
||||
// Stamping ng-content multiple times should not produce the content multiple
|
||||
// times...
|
||||
projectDirective.show(sourceDirective.templateRef);
|
||||
expect(main.nativeElement).toHaveText('(, B)START(A)END');
|
||||
});
|
||||
|
||||
// Note: This does not use a ng-content element, but
|
||||
// is still important as we are merging proto views independent of
|
||||
@ -533,49 +543,48 @@ describe('projection', () => {
|
||||
expect(main.nativeElement).toHaveText('B(A)');
|
||||
});
|
||||
|
||||
fixmeIvy('FW-833: Directive / projected node matching against class name')
|
||||
.it('should project filled view containers into a view container', () => {
|
||||
TestBed.configureTestingModule(
|
||||
{declarations: [ConditionalContentComponent, ManualViewportDirective]});
|
||||
TestBed.overrideComponent(MainComp, {
|
||||
set: {
|
||||
template: '<conditional-content>' +
|
||||
'<div class="left">A</div>' +
|
||||
'<ng-template manual class="left">B</ng-template>' +
|
||||
'<div class="left">C</div>' +
|
||||
'<div>D</div>' +
|
||||
'</conditional-content>'
|
||||
}
|
||||
});
|
||||
const main = TestBed.createComponent(MainComp);
|
||||
fixmeIvy('unknown').it('should project filled view containers into a view container', () => {
|
||||
TestBed.configureTestingModule(
|
||||
{declarations: [ConditionalContentComponent, ManualViewportDirective]});
|
||||
TestBed.overrideComponent(MainComp, {
|
||||
set: {
|
||||
template: '<conditional-content>' +
|
||||
'<div class="left">A</div>' +
|
||||
'<ng-template manual class="left">B</ng-template>' +
|
||||
'<div class="left">C</div>' +
|
||||
'<div>D</div>' +
|
||||
'</conditional-content>'
|
||||
}
|
||||
});
|
||||
const main = TestBed.createComponent(MainComp);
|
||||
|
||||
const conditionalComp = main.debugElement.query(By.directive(ConditionalContentComponent));
|
||||
const conditionalComp = main.debugElement.query(By.directive(ConditionalContentComponent));
|
||||
|
||||
const viewViewportDir =
|
||||
conditionalComp.queryAllNodes(By.directive(ManualViewportDirective))[0].injector.get(
|
||||
ManualViewportDirective);
|
||||
const viewViewportDir =
|
||||
conditionalComp.queryAllNodes(By.directive(ManualViewportDirective))[0].injector.get(
|
||||
ManualViewportDirective);
|
||||
|
||||
expect(main.nativeElement).toHaveText('(, D)');
|
||||
expect(main.nativeElement).toHaveText('(, D)');
|
||||
expect(main.nativeElement).toHaveText('(, D)');
|
||||
expect(main.nativeElement).toHaveText('(, D)');
|
||||
|
||||
viewViewportDir.show();
|
||||
main.detectChanges();
|
||||
expect(main.nativeElement).toHaveText('(AC, D)');
|
||||
viewViewportDir.show();
|
||||
main.detectChanges();
|
||||
expect(main.nativeElement).toHaveText('(AC, D)');
|
||||
|
||||
const contentViewportDir =
|
||||
conditionalComp.queryAllNodes(By.directive(ManualViewportDirective))[1].injector.get(
|
||||
ManualViewportDirective);
|
||||
const contentViewportDir =
|
||||
conditionalComp.queryAllNodes(By.directive(ManualViewportDirective))[1].injector.get(
|
||||
ManualViewportDirective);
|
||||
|
||||
contentViewportDir.show();
|
||||
main.detectChanges();
|
||||
expect(main.nativeElement).toHaveText('(ABC, D)');
|
||||
contentViewportDir.show();
|
||||
main.detectChanges();
|
||||
expect(main.nativeElement).toHaveText('(ABC, D)');
|
||||
|
||||
// hide view viewport, and test that it also hides
|
||||
// the content viewport's views
|
||||
viewViewportDir.hide();
|
||||
main.detectChanges();
|
||||
expect(main.nativeElement).toHaveText('(, D)');
|
||||
});
|
||||
// hide view viewport, and test that it also hides
|
||||
// the content viewport's views
|
||||
viewViewportDir.hide();
|
||||
main.detectChanges();
|
||||
expect(main.nativeElement).toHaveText('(, D)');
|
||||
});
|
||||
});
|
||||
|
||||
@Component({selector: 'main', template: ''})
|
||||
@ -626,6 +635,13 @@ class Empty {
|
||||
class MultipleContentTagsComponent {
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'single-content-tag',
|
||||
template: '<ng-content SELECT=".target"></ng-content>',
|
||||
})
|
||||
class SingleContentTagComponent {
|
||||
}
|
||||
|
||||
@Directive({selector: '[manual]'})
|
||||
class ManualViewportDirective {
|
||||
constructor(public vc: ViewContainerRef, public templateRef: TemplateRef<Object>) {}
|
||||
|
Reference in New Issue
Block a user