Skip to content
Permalink
Browse files

fix(ivy): support for #id bootstrap selectors (#33784)

Fixes: #33485

PR Close #33784
  • Loading branch information
mhevery authored and alxhub committed Nov 13, 2019
1 parent 9882a82 commit 9761ebe7bd92fc771cd6be6101272196f130e020
@@ -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]);
}
if (match[3]) {
current.addClassName(match[3]);
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[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 {
}

0 comments on commit 9761ebe

Please sign in to comment.
You can’t perform that action at this time.