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');
|
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', () => {
|
it('should support components without selector', () => {
|
||||||
const files = {
|
const files = {
|
||||||
app: {
|
app: {
|
||||||
|
@ -9,17 +9,31 @@
|
|||||||
import {getHtmlTagDefinition} from './ml_parser/html_tags';
|
import {getHtmlTagDefinition} from './ml_parser/html_tags';
|
||||||
|
|
||||||
const _SELECTOR_REGEXP = new RegExp(
|
const _SELECTOR_REGEXP = new RegExp(
|
||||||
'(\\:not\\()|' + //":not("
|
'(\\:not\\()|' + // 1: ":not("
|
||||||
'([-\\w]+)|' + // "tag"
|
'(([\\.\\#]?)[-\\w]+)|' + // 2: "tag"; 3: "."/"#";
|
||||||
'(?:\\.([-\\w]+))|' + // ".class"
|
|
||||||
// "-" should appear first in the regexp below as FF31 parses "[.-\w]" as a range
|
// "-" 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]",
|
'(?:\\[([-.\\w*]+)(?:=([\"\']?)([^\\]\"\']*)\\5)?\\])|' + // "[name]", "[name=value]",
|
||||||
// "[name="value"]",
|
// "[name="value"]",
|
||||||
// "[name='value']"
|
// "[name='value']"
|
||||||
'(\\))|' + // ")"
|
'(\\))|' + // 7: ")"
|
||||||
'(\\s*,\\s*)', // ","
|
'(\\s*,\\s*)', // 8: ","
|
||||||
'g');
|
'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,
|
* A css selector contains an element name,
|
||||||
* css classes and attribute/value pairs with the purpose
|
* css classes and attribute/value pairs with the purpose
|
||||||
@ -57,28 +71,37 @@ export class CssSelector {
|
|||||||
let inNot = false;
|
let inNot = false;
|
||||||
_SELECTOR_REGEXP.lastIndex = 0;
|
_SELECTOR_REGEXP.lastIndex = 0;
|
||||||
while (match = _SELECTOR_REGEXP.exec(selector)) {
|
while (match = _SELECTOR_REGEXP.exec(selector)) {
|
||||||
if (match[1]) {
|
if (match[SelectorRegexp.NOT]) {
|
||||||
if (inNot) {
|
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;
|
inNot = true;
|
||||||
current = new CssSelector();
|
current = new CssSelector();
|
||||||
cssSelector.notSelectors.push(current);
|
cssSelector.notSelectors.push(current);
|
||||||
}
|
}
|
||||||
if (match[2]) {
|
const tag = match[SelectorRegexp.TAG];
|
||||||
current.setElement(match[2]);
|
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]) {
|
const attribute = match[SelectorRegexp.ATTRIBUTE];
|
||||||
current.addClassName(match[3]);
|
if (attribute) {
|
||||||
|
current.addAttribute(attribute, match[SelectorRegexp.ATTRIBUTE_VALUE]);
|
||||||
}
|
}
|
||||||
if (match[4]) {
|
if (match[SelectorRegexp.NOT_END]) {
|
||||||
current.addAttribute(match[4], match[6]);
|
|
||||||
}
|
|
||||||
if (match[7]) {
|
|
||||||
inNot = false;
|
inNot = false;
|
||||||
current = cssSelector;
|
current = cssSelector;
|
||||||
}
|
}
|
||||||
if (match[8]) {
|
if (match[SelectorRegexp.SEPARATOR]) {
|
||||||
if (inNot) {
|
if (inNot) {
|
||||||
throw new Error('Multiple selectors in :not are not supported');
|
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]');
|
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', () => {
|
it('should detect attr values with single quotes', () => {
|
||||||
const cssSelector = CssSelector.parse('[attrname=\'attrvalue\']')[0];
|
const cssSelector = CssSelector.parse('[attrname=\'attrvalue\']')[0];
|
||||||
expect(cssSelector.attrs).toEqual(['attrname', 'attrvalue']);
|
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', () => {
|
it('should throw when nested :not', () => {
|
||||||
expect(() => {
|
expect(() => {
|
||||||
CssSelector.parse('sometag:not(:not([attrname=attrvalue].someclass))')[0];
|
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', () => {
|
it('should throw when multiple selectors in :not', () => {
|
||||||
|
36
packages/core/test/acceptance/bootstrap_spec.ts
Normal file
36
packages/core/test/acceptance/bootstrap_spec.ts
Normal file
@ -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…
x
Reference in New Issue
Block a user