diff --git a/packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts b/packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts index c0d2e29892..d16705e225 100644 --- a/packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts +++ b/packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts @@ -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: { diff --git a/packages/compiler/src/selector.ts b/packages/compiler/src/selector.ts index aacd1d9d29..b5e131de5d 100644 --- a/packages/compiler/src/selector.ts +++ b/packages/compiler/src/selector.ts @@ -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]); + const attribute = match[SelectorRegexp.ATTRIBUTE]; + if (attribute) { + current.addAttribute(attribute, match[SelectorRegexp.ATTRIBUTE_VALUE]); } - if (match[4]) { - current.addAttribute(match[4], match[6]); - } - 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'); } diff --git a/packages/compiler/test/selector/selector_spec.ts b/packages/compiler/test/selector/selector_spec.ts index 01fb5a8913..b61bc7ad44 100644 --- a/packages/compiler/test/selector/selector_spec.ts +++ b/packages/compiler/test/selector/selector_spec.ts @@ -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', () => { diff --git a/packages/core/test/acceptance/bootstrap_spec.ts b/packages/core/test/acceptance/bootstrap_spec.ts new file mode 100644 index 0000000000..b11889bfb9 --- /dev/null +++ b/packages/core/test/acceptance/bootstrap_spec.ts @@ -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('