fix(ivy): properly bootstrap components with attribute selectors (#34450)
Fixes #34349 PR Close #34450
This commit is contained in:
@ -7,16 +7,28 @@
|
||||
*/
|
||||
|
||||
import {Component, NgModule} from '@angular/core';
|
||||
import {getComponentDef} from '@angular/core/src/render3/definition';
|
||||
import {BrowserModule} from '@angular/platform-browser';
|
||||
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
|
||||
import {onlyInIvy, withBody} from '@angular/private/testing';
|
||||
import {withBody} from '@angular/private/testing';
|
||||
|
||||
describe('bootstrap', () => {
|
||||
it('should bootstrap using #id selector', withBody('<div #my-app>', async() => {
|
||||
it('should bootstrap using #id selector',
|
||||
withBody('<div>before|</div><button id="my-app"></button>', async() => {
|
||||
try {
|
||||
const ngModuleRef = await platformBrowserDynamic().bootstrapModule(MyAppModule);
|
||||
expect(document.body.textContent).toEqual('works!');
|
||||
const ngModuleRef = await platformBrowserDynamic().bootstrapModule(IdSelectorAppModule);
|
||||
expect(document.body.textContent).toEqual('before|works!');
|
||||
ngModuleRef.destroy();
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}));
|
||||
|
||||
it('should bootstrap using one of selectors from the list',
|
||||
withBody('<div>before|</div><div class="bar"></div>', async() => {
|
||||
try {
|
||||
const ngModuleRef =
|
||||
await platformBrowserDynamic().bootstrapModule(MultipleSelectorsAppModule);
|
||||
expect(document.body.textContent).toEqual('before|works!');
|
||||
ngModuleRef.destroy();
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
@ -28,9 +40,28 @@ describe('bootstrap', () => {
|
||||
selector: '#my-app',
|
||||
template: 'works!',
|
||||
})
|
||||
export class MyAppComponent {
|
||||
export class IdSelectorAppComponent {
|
||||
}
|
||||
|
||||
@NgModule({imports: [BrowserModule], declarations: [MyAppComponent], bootstrap: [MyAppComponent]})
|
||||
export class MyAppModule {
|
||||
@NgModule({
|
||||
imports: [BrowserModule],
|
||||
declarations: [IdSelectorAppComponent],
|
||||
bootstrap: [IdSelectorAppComponent],
|
||||
})
|
||||
export class IdSelectorAppModule {
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: '[foo],span,.bar',
|
||||
template: 'works!',
|
||||
})
|
||||
export class MultipleSelectorsAppComponent {
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
imports: [BrowserModule],
|
||||
declarations: [MultipleSelectorsAppComponent],
|
||||
bootstrap: [MultipleSelectorsAppComponent],
|
||||
})
|
||||
export class MultipleSelectorsAppModule {
|
||||
}
|
@ -340,6 +340,44 @@ describe('component', () => {
|
||||
expect(log).toEqual(['CompB:ngDoCheck']);
|
||||
});
|
||||
|
||||
it('should preserve simple component selector in a component factory', () => {
|
||||
@Component({selector: '[foo]', template: ''})
|
||||
class AttSelectorCmp {
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [AttSelectorCmp],
|
||||
entryComponents: [AttSelectorCmp],
|
||||
})
|
||||
class AppModule {
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({imports: [AppModule]});
|
||||
const cmpFactoryResolver = TestBed.inject(ComponentFactoryResolver);
|
||||
const cmpFactory = cmpFactoryResolver.resolveComponentFactory(AttSelectorCmp);
|
||||
|
||||
expect(cmpFactory.selector).toBe('[foo]');
|
||||
});
|
||||
|
||||
it('should preserve complex component selector in a component factory', () => {
|
||||
@Component({selector: '[foo],div:not(.bar)', template: ''})
|
||||
class ComplexSelectorCmp {
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [ComplexSelectorCmp],
|
||||
entryComponents: [ComplexSelectorCmp],
|
||||
})
|
||||
class AppModule {
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({imports: [AppModule]});
|
||||
const cmpFactoryResolver = TestBed.inject(ComponentFactoryResolver);
|
||||
const cmpFactory = cmpFactoryResolver.resolveComponentFactory(ComplexSelectorCmp);
|
||||
|
||||
expect(cmpFactory.selector).toBe('[foo],div:not(.bar)');
|
||||
});
|
||||
|
||||
describe('should clear host element if provided in ComponentFactory.create', () => {
|
||||
function runTestWithRenderer(rendererProviders: any[]) {
|
||||
@Component({
|
||||
|
@ -23,7 +23,7 @@ describe('ComponentFactory', () => {
|
||||
static ɵfac = () => new TestComponent();
|
||||
static ɵcmp = ɵɵdefineComponent({
|
||||
type: TestComponent,
|
||||
selectors: [['test', 'foo'], ['bar']],
|
||||
selectors: [['test', 'foo', ''], ['bar']],
|
||||
decls: 0,
|
||||
vars: 0,
|
||||
template: () => undefined,
|
||||
@ -32,7 +32,7 @@ describe('ComponentFactory', () => {
|
||||
|
||||
const cf = cfr.resolveComponentFactory(TestComponent);
|
||||
|
||||
expect(cf.selector).toBe('test');
|
||||
expect(cf.selector).toBe('test[foo],bar');
|
||||
expect(cf.componentType).toBe(TestComponent);
|
||||
expect(cf.ngContentSelectors).toEqual([]);
|
||||
expect(cf.inputs).toEqual([]);
|
||||
@ -45,7 +45,7 @@ describe('ComponentFactory', () => {
|
||||
static ɵcmp = ɵɵdefineComponent({
|
||||
type: TestComponent,
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
selectors: [['test', 'foo'], ['bar']],
|
||||
selectors: [['test', 'foo', ''], ['bar']],
|
||||
decls: 0,
|
||||
vars: 0,
|
||||
template: () => undefined,
|
||||
@ -65,7 +65,7 @@ describe('ComponentFactory', () => {
|
||||
|
||||
expect(cf.componentType).toBe(TestComponent);
|
||||
expect(cf.ngContentSelectors).toEqual(['*', 'a', 'b']);
|
||||
expect(cf.selector).toBe('test');
|
||||
expect(cf.selector).toBe('test[foo],bar');
|
||||
|
||||
expect(cf.inputs).toEqual([
|
||||
{propName: 'in1', templateName: 'in1'},
|
||||
|
@ -10,7 +10,7 @@ import {createTNode} from '@angular/core/src/render3/instructions/shared';
|
||||
|
||||
import {AttributeMarker, TAttributes, TNode, TNodeType} from '../../src/render3/interfaces/node';
|
||||
import {CssSelector, CssSelectorList, SelectorFlags} from '../../src/render3/interfaces/projection';
|
||||
import {getProjectAsAttrValue, isNodeMatchingSelector, isNodeMatchingSelectorList} from '../../src/render3/node_selector_matcher';
|
||||
import {getProjectAsAttrValue, isNodeMatchingSelector, isNodeMatchingSelectorList, stringifyCSSSelectorList} from '../../src/render3/node_selector_matcher';
|
||||
|
||||
function testLStaticData(tagName: string, attrs: TAttributes | null): TNode {
|
||||
return createTNode(null !, null, TNodeType.Element, 0, tagName, attrs);
|
||||
@ -508,3 +508,84 @@ describe('css selector matching', () => {
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('stringifyCSSSelectorList', () => {
|
||||
|
||||
it('should stringify selector with a tag name only',
|
||||
() => { expect(stringifyCSSSelectorList([['button']])).toBe('button'); });
|
||||
|
||||
it('should stringify selector with attributes', () => {
|
||||
expect(stringifyCSSSelectorList([['', 'id', '']])).toBe('[id]');
|
||||
expect(stringifyCSSSelectorList([['button', 'id', '']])).toBe('button[id]');
|
||||
expect(stringifyCSSSelectorList([['button', 'id', 'value']])).toBe('button[id="value"]');
|
||||
expect(stringifyCSSSelectorList([['button', 'id', 'value', 'title', 'other']]))
|
||||
.toBe('button[id="value"][title="other"]');
|
||||
});
|
||||
|
||||
it('should stringify selector with class names', () => {
|
||||
expect(stringifyCSSSelectorList([['', SelectorFlags.CLASS, 'foo']])).toBe('.foo');
|
||||
expect(stringifyCSSSelectorList([['button', SelectorFlags.CLASS, 'foo']])).toBe('button.foo');
|
||||
|
||||
expect(stringifyCSSSelectorList([['button', SelectorFlags.CLASS, 'foo', 'bar']]))
|
||||
.toBe('button.foo.bar');
|
||||
|
||||
expect(stringifyCSSSelectorList([
|
||||
['button', 'id', 'value', 'title', 'other', SelectorFlags.CLASS, 'foo', 'bar']
|
||||
])).toBe('button[id="value"][title="other"].foo.bar');
|
||||
});
|
||||
|
||||
it('should stringify selector with `:not()` rules', () => {
|
||||
expect(stringifyCSSSelectorList([['', SelectorFlags.CLASS | SelectorFlags.NOT, 'foo', 'bar']]))
|
||||
.toBe(':not(.foo.bar)');
|
||||
|
||||
expect(stringifyCSSSelectorList([
|
||||
['button', SelectorFlags.ATTRIBUTE | SelectorFlags.NOT, 'foo', 'bar']
|
||||
])).toBe('button:not([foo="bar"])');
|
||||
|
||||
expect(stringifyCSSSelectorList([['', SelectorFlags.ELEMENT | SelectorFlags.NOT, 'foo']]))
|
||||
.toBe(':not(foo)');
|
||||
|
||||
expect(stringifyCSSSelectorList([
|
||||
['span', SelectorFlags.CLASS, 'foo', SelectorFlags.CLASS | SelectorFlags.NOT, 'bar', 'baz']
|
||||
])).toBe('span.foo:not(.bar.baz)');
|
||||
|
||||
expect(stringifyCSSSelectorList([
|
||||
['span', 'id', 'value', SelectorFlags.ATTRIBUTE | SelectorFlags.NOT, 'title', 'other']
|
||||
])).toBe('span[id="value"]:not([title="other"])');
|
||||
|
||||
expect(stringifyCSSSelectorList([[
|
||||
'', SelectorFlags.CLASS, 'bar', SelectorFlags.ATTRIBUTE | SelectorFlags.NOT, 'foo', '',
|
||||
SelectorFlags.ELEMENT | SelectorFlags.NOT, 'div'
|
||||
]])).toBe('.bar:not([foo]):not(div)');
|
||||
|
||||
expect(stringifyCSSSelectorList([[
|
||||
'div', SelectorFlags.ATTRIBUTE | SelectorFlags.NOT, 'foo', '', SelectorFlags.CLASS, 'bar',
|
||||
SelectorFlags.CLASS | SelectorFlags.NOT, 'baz'
|
||||
]])).toBe('div:not([foo].bar):not(.baz)');
|
||||
|
||||
expect(stringifyCSSSelectorList([[
|
||||
'div', SelectorFlags.ELEMENT | SelectorFlags.NOT, 'p', SelectorFlags.CLASS, 'bar',
|
||||
SelectorFlags.CLASS | SelectorFlags.NOT, 'baz'
|
||||
]])).toBe('div:not(p.bar):not(.baz)');
|
||||
});
|
||||
|
||||
it('should stringify multiple comma-separated selectors', () => {
|
||||
expect(stringifyCSSSelectorList([
|
||||
['', 'id', ''], ['button', 'id', 'value']
|
||||
])).toBe('[id],button[id="value"]');
|
||||
|
||||
expect(stringifyCSSSelectorList([
|
||||
['', 'id', ''], ['button', 'id', 'value'],
|
||||
['div', SelectorFlags.ATTRIBUTE | SelectorFlags.NOT, 'foo', '']
|
||||
])).toBe('[id],button[id="value"],div:not([foo])');
|
||||
|
||||
expect(stringifyCSSSelectorList([
|
||||
['', 'id', ''], ['button', 'id', 'value'],
|
||||
['div', SelectorFlags.ATTRIBUTE | SelectorFlags.NOT, 'foo', ''],
|
||||
[
|
||||
'div', SelectorFlags.ELEMENT | SelectorFlags.NOT, 'p', SelectorFlags.CLASS, 'bar',
|
||||
SelectorFlags.CLASS | SelectorFlags.NOT, 'baz'
|
||||
]
|
||||
])).toBe('[id],button[id="value"],div:not([foo]),div:not(p.bar):not(.baz)');
|
||||
});
|
||||
});
|
||||
|
Reference in New Issue
Block a user