|
8 | 8 |
|
9 | 9 | import {CommonModule, DOCUMENT} from '@angular/common'; |
10 | 10 | import {computeMsgId} from '@angular/compiler'; |
11 | | -import {Compiler, Component, ComponentFactoryResolver, Directive, DoCheck, ElementRef, EmbeddedViewRef, ErrorHandler, NO_ERRORS_SCHEMA, NgModule, OnInit, Pipe, PipeTransform, QueryList, Renderer2, RendererFactory2, RendererType2, Sanitizer, TemplateRef, ViewChild, ViewChildren, ViewContainerRef, ɵsetDocument} from '@angular/core'; |
| 11 | +import {Compiler, Component, ComponentFactoryResolver, Directive, DoCheck, ElementRef, EmbeddedViewRef, ErrorHandler, Injector, NO_ERRORS_SCHEMA, NgModule, OnInit, Pipe, PipeTransform, QueryList, RendererFactory2, RendererType2, Sanitizer, TemplateRef, ViewChild, ViewChildren, ViewContainerRef, ɵsetDocument} from '@angular/core'; |
12 | 12 | import {Input} from '@angular/core/src/metadata'; |
13 | 13 | import {ngDevModeResetPerfCounters} from '@angular/core/src/util/ng_dev_mode'; |
14 | 14 | import {TestBed, TestComponentRenderer} from '@angular/core/testing'; |
@@ -251,6 +251,98 @@ describe('ViewContainerRef', () => { |
251 | 251 | // Also test with selector that has element name in uppercase |
252 | 252 | runTestWithSelectors('SVG[some-attr]', 'MATH[some-attr]'); |
253 | 253 | }); |
| 254 | + |
| 255 | + it('should apply attributes and classes to host element based on selector', () => { |
| 256 | + @Component({ |
| 257 | + selector: '[attr-a=a].class-a:not(.class-b):not([attr-b=b]).class-c[attr-c]', |
| 258 | + template: 'Hello' |
| 259 | + }) |
| 260 | + class HelloComp { |
| 261 | + } |
| 262 | + |
| 263 | + @NgModule({entryComponents: [HelloComp], declarations: [HelloComp]}) |
| 264 | + class HelloCompModule { |
| 265 | + } |
| 266 | + |
| 267 | + @Component({ |
| 268 | + template: ` |
| 269 | + <div id="factory" attr-a="a-original" class="class-original"></div> |
| 270 | + <div id="vcr"> |
| 271 | + <ng-container #container></ng-container> |
| 272 | + </div> |
| 273 | + ` |
| 274 | + }) |
| 275 | + class TestComp { |
| 276 | + @ViewChild('container', {read: ViewContainerRef}) vcRef !: ViewContainerRef; |
| 277 | + |
| 278 | + private helloCompFactory = this.cfr.resolveComponentFactory(HelloComp); |
| 279 | + |
| 280 | + constructor(public cfr: ComponentFactoryResolver, public injector: Injector) {} |
| 281 | + |
| 282 | + createComponentViaVCRef() { |
| 283 | + this.vcRef.createComponent(this.helloCompFactory); // |
| 284 | + } |
| 285 | + |
| 286 | + createComponentViaFactory() { |
| 287 | + this.helloCompFactory.create(this.injector, undefined, '#factory'); |
| 288 | + } |
| 289 | + } |
| 290 | + |
| 291 | + TestBed.configureTestingModule({declarations: [TestComp], imports: [HelloCompModule]}); |
| 292 | + const fixture = TestBed.createComponent(TestComp); |
| 293 | + fixture.detectChanges(); |
| 294 | + fixture.componentInstance.createComponentViaVCRef(); |
| 295 | + fixture.componentInstance.createComponentViaFactory(); |
| 296 | + fixture.detectChanges(); |
| 297 | + |
| 298 | + // Verify host element for a component created via `vcRef.createComponent` method |
| 299 | + const vcrHostElement = fixture.nativeElement.querySelector('#vcr > div'); |
| 300 | + |
| 301 | + expect(vcrHostElement.classList.contains('class-a')).toBe(true); |
| 302 | + // `class-b` should not be present, since it's wrapped in `:not()` selector |
| 303 | + expect(vcrHostElement.classList.contains('class-b')).toBe(false); |
| 304 | + expect(vcrHostElement.classList.contains('class-c')).toBe(true); |
| 305 | + |
| 306 | + expect(vcrHostElement.getAttribute('attr-a')).toBe('a'); |
| 307 | + // `attr-b` should not be present, since it's wrapped in `:not()` selector |
| 308 | + expect(vcrHostElement.getAttribute('attr-b')).toBe(null); |
| 309 | + expect(vcrHostElement.getAttribute('attr-c')).toBe(''); |
| 310 | + |
| 311 | + // Verify host element for a component created using `factory.createComponent` method when |
| 312 | + // also passing element selector as an argument |
| 313 | + const factoryHostElement = fixture.nativeElement.querySelector('#factory'); |
| 314 | + |
| 315 | + if (ivyEnabled) { |
| 316 | + // In Ivy, if selector is passed when component is created, matched host node (found using |
| 317 | + // this selector) retains all attrs/classes and selector-based attrs/classes should *not* be |
| 318 | + // added |
| 319 | + |
| 320 | + // Verify original attrs and classes are still present |
| 321 | + expect(factoryHostElement.classList.contains('class-original')).toBe(true); |
| 322 | + expect(factoryHostElement.getAttribute('attr-a')).toBe('a-original'); |
| 323 | + |
| 324 | + // Make sure selector-based attrs and classes were not added to the host element |
| 325 | + expect(factoryHostElement.classList.contains('class-a')).toBe(false); |
| 326 | + expect(factoryHostElement.getAttribute('attr-c')).toBe(null); |
| 327 | + |
| 328 | + } else { |
| 329 | + // In View Engine, selector-based attrs/classes are *always* added to the host element |
| 330 | + |
| 331 | + expect(factoryHostElement.classList.contains('class-a')).toBe(true); |
| 332 | + // `class-b` should not be present, since it's wrapped in `:not()` selector |
| 333 | + expect(factoryHostElement.classList.contains('class-b')).toBe(false); |
| 334 | + expect(factoryHostElement.classList.contains('class-c')).toBe(true); |
| 335 | + // Make sure classes are overridden with ones used in component selector |
| 336 | + expect(factoryHostElement.classList.contains('class-original')).toBe(false); |
| 337 | + |
| 338 | + // Note: `attr-a` attr is also present on host element, but we update the value with the |
| 339 | + // value from component selector (i.e. using `[attr-a=a]`) |
| 340 | + expect(factoryHostElement.getAttribute('attr-a')).toBe('a'); |
| 341 | + // `attr-b` should not be present, since it's wrapped in `:not()` selector |
| 342 | + expect(factoryHostElement.getAttribute('attr-b')).toBe(null); |
| 343 | + expect(factoryHostElement.getAttribute('attr-c')).toBe(''); |
| 344 | + } |
| 345 | + }); |
254 | 346 | }); |
255 | 347 |
|
256 | 348 | describe('insert', () => { |
|
0 commit comments