fix(ivy): support for #id bootstrap selectors (#33784)
Fixes: #33485 PR Close #33784
This commit is contained in:
parent
c5a75fd807
commit
ab0bcee144
|
@ -675,6 +675,35 @@ describe('compiler compliance', () => {
|
|||
expectEmit(source, OtherDirectiveFactory, 'Incorrect OtherDirective.ɵfac');
|
||||
});
|
||||
|
||||
it('should convert #my-app selector to ["", "id", "my-app"]', () => {
|
||||
const files = {
|
||||
app: {
|
||||
'spec.ts': `
|
||||
import {Component, NgModule} from '@angular/core';
|
||||
|
||||
@Component({selector: '#my-app', template: ''})
|
||||
export class SomeComponent {}
|
||||
|
||||
@NgModule({declarations: [SomeComponent]})
|
||||
export class MyModule{}
|
||||
`
|
||||
}
|
||||
};
|
||||
|
||||
// SomeDirective definition should be:
|
||||
const SomeDirectiveDefinition = `
|
||||
SomeComponent.ɵcmp = $r3$.ɵɵdefineComponent({
|
||||
type: SomeComponent,
|
||||
selectors: [["", "id", "my-app"]],
|
||||
…
|
||||
});
|
||||
`;
|
||||
|
||||
const result = compile(files, angularFiles);
|
||||
const source = result.source;
|
||||
|
||||
expectEmit(source, SomeDirectiveDefinition, 'Incorrect SomeComponent.ɵcomp');
|
||||
});
|
||||
it('should support components without selector', () => {
|
||||
const files = {
|
||||
app: {
|
||||
|
|
|
@ -9,17 +9,31 @@
|
|||
import {getHtmlTagDefinition} from './ml_parser/html_tags';
|
||||
|
||||
const _SELECTOR_REGEXP = new RegExp(
|
||||
'(\\:not\\()|' + //":not("
|
||||
'([-\\w]+)|' + // "tag"
|
||||
'(?:\\.([-\\w]+))|' + // ".class"
|
||||
'(\\:not\\()|' + // 1: ":not("
|
||||
'(([\\.\\#]?)[-\\w]+)|' + // 2: "tag"; 3: "."/"#";
|
||||
// "-" should appear first in the regexp below as FF31 parses "[.-\w]" as a range
|
||||
// 4: attribute; 5: attribute_string; 6: attribute_value
|
||||
'(?:\\[([-.\\w*]+)(?:=([\"\']?)([^\\]\"\']*)\\5)?\\])|' + // "[name]", "[name=value]",
|
||||
// "[name="value"]",
|
||||
// "[name='value']"
|
||||
'(\\))|' + // ")"
|
||||
'(\\s*,\\s*)', // ","
|
||||
'(\\))|' + // 7: ")"
|
||||
'(\\s*,\\s*)', // 8: ","
|
||||
'g');
|
||||
|
||||
/**
|
||||
* These offsets should match the match-groups in `_SELECTOR_REGEXP` offsets.
|
||||
*/
|
||||
const enum SelectorRegexp {
|
||||
ALL = 0, // The whole match
|
||||
NOT = 1,
|
||||
TAG = 2,
|
||||
PREFIX = 3,
|
||||
ATTRIBUTE = 4,
|
||||
ATTRIBUTE_STRING = 5,
|
||||
ATTRIBUTE_VALUE = 6,
|
||||
NOT_END = 7,
|
||||
SEPARATOR = 8,
|
||||
}
|
||||
/**
|
||||
* A css selector contains an element name,
|
||||
* css classes and attribute/value pairs with the purpose
|
||||
|
@ -57,28 +71,37 @@ export class CssSelector {
|
|||
let inNot = false;
|
||||
_SELECTOR_REGEXP.lastIndex = 0;
|
||||
while (match = _SELECTOR_REGEXP.exec(selector)) {
|
||||
if (match[1]) {
|
||||
if (match[SelectorRegexp.NOT]) {
|
||||
if (inNot) {
|
||||
throw new Error('Nesting :not is not allowed in a selector');
|
||||
throw new Error('Nesting :not in a selector is not allowed');
|
||||
}
|
||||
inNot = true;
|
||||
current = new CssSelector();
|
||||
cssSelector.notSelectors.push(current);
|
||||
}
|
||||
if (match[2]) {
|
||||
current.setElement(match[2]);
|
||||
const tag = match[SelectorRegexp.TAG];
|
||||
if (tag) {
|
||||
const prefix = match[SelectorRegexp.PREFIX];
|
||||
if (prefix === '#') {
|
||||
// #hash
|
||||
current.addAttribute('id', tag.substr(1));
|
||||
} else if (prefix === '.') {
|
||||
// Class
|
||||
current.addClassName(tag.substr(1));
|
||||
} else {
|
||||
// Element
|
||||
current.setElement(tag);
|
||||
}
|
||||
if (match[3]) {
|
||||
current.addClassName(match[3]);
|
||||
}
|
||||
if (match[4]) {
|
||||
current.addAttribute(match[4], match[6]);
|
||||
const attribute = match[SelectorRegexp.ATTRIBUTE];
|
||||
if (attribute) {
|
||||
current.addAttribute(attribute, match[SelectorRegexp.ATTRIBUTE_VALUE]);
|
||||
}
|
||||
if (match[7]) {
|
||||
if (match[SelectorRegexp.NOT_END]) {
|
||||
inNot = false;
|
||||
current = cssSelector;
|
||||
}
|
||||
if (match[8]) {
|
||||
if (match[SelectorRegexp.SEPARATOR]) {
|
||||
if (inNot) {
|
||||
throw new Error('Multiple selectors in :not are not supported');
|
||||
}
|
||||
|
|
|
@ -330,6 +330,12 @@ import {el} from '@angular/platform-browser/testing/src/browser_util';
|
|||
expect(cssSelector.toString()).toEqual('[attrname=attrvalue]');
|
||||
});
|
||||
|
||||
it('should detect #some-value syntax and treat as attribute', () => {
|
||||
const cssSelector = CssSelector.parse('#some-value')[0];
|
||||
expect(cssSelector.attrs).toEqual(['id', 'some-value']);
|
||||
expect(cssSelector.toString()).toEqual('[id=some-value]');
|
||||
});
|
||||
|
||||
it('should detect attr values with single quotes', () => {
|
||||
const cssSelector = CssSelector.parse('[attrname=\'attrvalue\']')[0];
|
||||
expect(cssSelector.attrs).toEqual(['attrname', 'attrvalue']);
|
||||
|
@ -381,7 +387,7 @@ import {el} from '@angular/platform-browser/testing/src/browser_util';
|
|||
it('should throw when nested :not', () => {
|
||||
expect(() => {
|
||||
CssSelector.parse('sometag:not(:not([attrname=attrvalue].someclass))')[0];
|
||||
}).toThrowError('Nesting :not is not allowed in a selector');
|
||||
}).toThrowError('Nesting :not in a selector is not allowed');
|
||||
});
|
||||
|
||||
it('should throw when multiple selectors in :not', () => {
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
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';
|
||||
|
||||
describe('bootstrap', () => {
|
||||
it('should bootstrap using #id selector', withBody('<div #my-app>', async() => {
|
||||
try {
|
||||
const ngModuleRef = await platformBrowserDynamic().bootstrapModule(MyAppModule);
|
||||
expect(document.body.textContent).toEqual('works!');
|
||||
ngModuleRef.destroy();
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}));
|
||||
});
|
||||
|
||||
@Component({
|
||||
selector: '#my-app',
|
||||
template: 'works!',
|
||||
})
|
||||
export class MyAppComponent {
|
||||
}
|
||||
|
||||
@NgModule({imports: [BrowserModule], declarations: [MyAppComponent], bootstrap: [MyAppComponent]})
|
||||
export class MyAppModule {
|
||||
}
|
Loading…
Reference in New Issue