From 9402df92deb3f57011377428217e93b3a172bbb5 Mon Sep 17 00:00:00 2001 From: vikerman Date: Wed, 1 Mar 2017 11:19:22 -0800 Subject: [PATCH] test(platform-server): enable meta tag and title service specs for server (#14574) Fix CSS selector syntax to allow single and double quotes. Needed for meta tag service selector to work properly on parse5. Fixes #14565. --- modules/@angular/compiler/src/selector.ts | 14 +-- .../@angular/compiler/test/selector_spec.ts | 12 +++ .../test/browser/meta_spec.ts | 85 ++++++++++--------- .../platform-server/src/parse5_adapter.ts | 4 +- tools/cjs-jasmine/index.ts | 5 +- 5 files changed, 74 insertions(+), 46 deletions(-) diff --git a/modules/@angular/compiler/src/selector.ts b/modules/@angular/compiler/src/selector.ts index 62a9c028a5..609bf3b255 100644 --- a/modules/@angular/compiler/src/selector.ts +++ b/modules/@angular/compiler/src/selector.ts @@ -13,9 +13,11 @@ const _SELECTOR_REGEXP = new RegExp( '([-\\w]+)|' + // "tag" '(?:\\.([-\\w]+))|' + // ".class" // "-" should appear first in the regexp below as FF31 parses "[.-\w]" as a range - '(?:\\[([-.\\w*]+)(?:=([^\\]]*))?\\])|' + // "[name]", "[name=value]" - '(\\))|' + // ")" - '(\\s*,\\s*)', // "," + '(?:\\[([-.\\w*]+)(?:=([\"\']?)([^\\]\"\']*)\\5)?\\])|' + // "[name]", "[name=value]", + // "[name="value"]", + // "[name='value']" + '(\\))|' + // ")" + '(\\s*,\\s*)', // "," 'g'); /** @@ -59,13 +61,13 @@ export class CssSelector { current.addClassName(match[3]); } if (match[4]) { - current.addAttribute(match[4], match[5]); + current.addAttribute(match[4], match[6]); } - if (match[6]) { + if (match[7]) { inNot = false; current = cssSelector; } - if (match[7]) { + if (match[8]) { if (inNot) { throw new Error('Multiple selectors in :not are not supported'); } diff --git a/modules/@angular/compiler/test/selector_spec.ts b/modules/@angular/compiler/test/selector_spec.ts index 36825c414a..f21ead93d5 100644 --- a/modules/@angular/compiler/test/selector_spec.ts +++ b/modules/@angular/compiler/test/selector_spec.ts @@ -324,6 +324,18 @@ export function main() { expect(cssSelector.toString()).toEqual('[attrname=attrvalue]'); }); + it('should detect attr values with double quotes', () => { + const cssSelector = CssSelector.parse('[attrname="attrvalue"]')[0]; + expect(cssSelector.attrs).toEqual(['attrname', 'attrvalue']); + expect(cssSelector.toString()).toEqual('[attrname=attrvalue]'); + }); + + it('should detect attr values with single quotes', () => { + const cssSelector = CssSelector.parse('[attrname=\'attrvalue\']')[0]; + expect(cssSelector.attrs).toEqual(['attrname', 'attrvalue']); + expect(cssSelector.toString()).toEqual('[attrname=attrvalue]'); + }); + it('should detect multiple parts', () => { const cssSelector = CssSelector.parse('sometag[attrname=attrvalue].someclass')[0]; expect(cssSelector.element).toEqual('sometag'); diff --git a/modules/@angular/platform-browser/test/browser/meta_spec.ts b/modules/@angular/platform-browser/test/browser/meta_spec.ts index ce2dcdfba2..271c3884f0 100644 --- a/modules/@angular/platform-browser/test/browser/meta_spec.ts +++ b/modules/@angular/platform-browser/test/browser/meta_spec.ts @@ -14,15 +14,15 @@ import {expect} from '@angular/platform-browser/testing/matchers'; export function main() { describe('Meta service', () => { - const doc: HTMLDocument = getDOM().createHtmlDocument(); - const metaService: Meta = new Meta(doc); + const doc = getDOM().createHtmlDocument(); + const metaService = new Meta(doc); let defaultMeta: HTMLMetaElement; beforeEach(() => { defaultMeta = getDOM().createElement('meta', doc) as HTMLMetaElement; - defaultMeta.setAttribute('property', 'fb:app_id'); - defaultMeta.setAttribute('content', '123456789'); - getDOM().getElementsByTagName(doc, 'head')[0].appendChild(defaultMeta); + getDOM().setAttribute(defaultMeta, 'property', 'fb:app_id'); + getDOM().setAttribute(defaultMeta, 'content', '123456789'); + getDOM().appendChild(getDOM().getElementsByTagName(doc, 'head')[0], defaultMeta); }); afterEach(() => getDOM().remove(defaultMeta)); @@ -30,7 +30,7 @@ export function main() { it('should return meta tag matching selector', () => { const actual: HTMLMetaElement = metaService.getTag('property="fb:app_id"'); expect(actual).not.toBeNull(); - expect(actual.content).toEqual('123456789'); + expect(getDOM().getAttribute(actual, 'content')).toEqual('123456789'); }); it('should return all meta tags matching selector', () => { @@ -39,8 +39,8 @@ export function main() { const actual: HTMLMetaElement[] = metaService.getTags('name=author'); expect(actual.length).toEqual(2); - expect(actual[0].content).toEqual('page author'); - expect(actual[1].content).toEqual('another page author'); + expect(getDOM().getAttribute(actual[0], 'content')).toEqual('page author'); + expect(getDOM().getAttribute(actual[1], 'content')).toEqual('another page author'); // clean up metaService.removeTagElement(tag1); @@ -53,83 +53,89 @@ export function main() { }); it('should remove meta tag by the given selector', () => { - expect(metaService.getTag('name=author')).toBeNull(); + const selector = 'name=author'; + expect(metaService.getTag(selector)).toBeNull(); metaService.addTag({name: 'author', content: 'page author'}); - expect(metaService.getTag('name=author')).not.toBeNull(); + expect(metaService.getTag(selector)).not.toBeNull(); - metaService.removeTag('name=author'); + metaService.removeTag(selector); - expect(metaService.getTag('name=author')).toBeNull(); + expect(metaService.getTag(selector)).toBeNull(); }); it('should remove meta tag by the given element', () => { - expect(metaService.getTag('name=keywords')).toBeNull(); + const selector = 'name=keywords'; + expect(metaService.getTag(selector)).toBeNull(); metaService.addTags([{name: 'keywords', content: 'meta test'}]); - const meta = metaService.getTag('name=keywords'); + const meta = metaService.getTag(selector); expect(meta).not.toBeNull(); metaService.removeTagElement(meta); - expect(metaService.getTag('name=keywords')).toBeNull(); + expect(metaService.getTag(selector)).toBeNull(); }); it('should update meta tag matching the given selector', () => { - metaService.updateTag({content: '4321'}, 'property="fb:app_id"'); + const selector = 'property="fb:app_id"'; + metaService.updateTag({content: '4321'}, selector); - const actual = metaService.getTag('property="fb:app_id"'); + const actual = metaService.getTag(selector); expect(actual).not.toBeNull(); - expect(actual.content).toEqual('4321'); + expect(getDOM().getAttribute(actual, 'content')).toEqual('4321'); }); it('should extract selector from the tag definition', () => { + const selector = 'property="fb:app_id"'; metaService.updateTag({property: 'fb:app_id', content: '666'}); - const actual = metaService.getTag('property="fb:app_id"'); + const actual = metaService.getTag(selector); expect(actual).not.toBeNull(); - expect(actual.content).toEqual('666'); + expect(getDOM().getAttribute(actual, 'content')).toEqual('666'); }); it('should create meta tag if it does not exist', () => { - expect(metaService.getTag('name="twitter:title"')).toBeNull(); + const selector = 'name="twitter:title"'; - metaService.updateTag( - {name: 'twitter:title', content: 'Content Title'}, 'name="twitter:title"'); + metaService.updateTag({name: 'twitter:title', content: 'Content Title'}, selector); - const actual = metaService.getTag('name="twitter:title"'); + const actual = metaService.getTag(selector); expect(actual).not.toBeNull(); - expect(actual.content).toEqual('Content Title'); + expect(getDOM().getAttribute(actual, 'content')).toEqual('Content Title'); // clean up metaService.removeTagElement(actual); }); it('should add new meta tag', () => { - expect(metaService.getTag('name="og:title"')).toBeNull(); + const selector = 'name="og:title"'; + expect(metaService.getTag(selector)).toBeNull(); metaService.addTag({name: 'og:title', content: 'Content Title'}); - const actual = metaService.getTag('name="og:title"'); + const actual = metaService.getTag(selector); expect(actual).not.toBeNull(); - expect(actual.content).toEqual('Content Title'); + expect(getDOM().getAttribute(actual, 'content')).toEqual('Content Title'); // clean up metaService.removeTagElement(actual); }); it('should add multiple new meta tags', () => { - expect(metaService.getTag('name="twitter:title"')).toBeNull(); - expect(metaService.getTag('property="og:title"')).toBeNull(); + const nameSelector = 'name="twitter:title"'; + const propertySelector = 'property="og:title"'; + expect(metaService.getTag(nameSelector)).toBeNull(); + expect(metaService.getTag(propertySelector)).toBeNull(); metaService.addTags([ {name: 'twitter:title', content: 'Content Title'}, {property: 'og:title', content: 'Content Title'} ]); - const twitterMeta = metaService.getTag('name="twitter:title"'); - const fbMeta = metaService.getTag('property="og:title"'); + const twitterMeta = metaService.getTag(nameSelector); + const fbMeta = metaService.getTag(propertySelector); expect(twitterMeta).not.toBeNull(); expect(fbMeta).not.toBeNull(); @@ -139,31 +145,34 @@ export function main() { }); it('should not add meta tag if it is already present on the page and has the same attr', () => { - expect(metaService.getTags('property="fb:app_id"').length).toEqual(1); + const selector = 'property="fb:app_id"'; + expect(metaService.getTags(selector).length).toEqual(1); metaService.addTag({property: 'fb:app_id', content: '123456789'}); - expect(metaService.getTags('property="fb:app_id"').length).toEqual(1); + expect(metaService.getTags(selector).length).toEqual(1); }); it('should add meta tag if it is already present on the page and but has different attr', () => { - expect(metaService.getTags('property="fb:app_id"').length).toEqual(1); + const selector = 'property="fb:app_id"'; + expect(metaService.getTags(selector).length).toEqual(1); const meta = metaService.addTag({property: 'fb:app_id', content: '666'}); - expect(metaService.getTags('property="fb:app_id"').length).toEqual(2); + expect(metaService.getTags(selector).length).toEqual(2); // clean up metaService.removeTagElement(meta); }); it('should add meta tag if it is already present on the page and force true', () => { - expect(metaService.getTags('property="fb:app_id"').length).toEqual(1); + const selector = 'property="fb:app_id"'; + expect(metaService.getTags(selector).length).toEqual(1); const meta = metaService.addTag({property: 'fb:app_id', content: '123456789'}, true); - expect(metaService.getTags('property="fb:app_id"').length).toEqual(2); + expect(metaService.getTags(selector).length).toEqual(2); // clean up metaService.removeTagElement(meta); diff --git a/modules/@angular/platform-server/src/parse5_adapter.ts b/modules/@angular/platform-server/src/parse5_adapter.ts index 383e1cc532..f36d18b03f 100644 --- a/modules/@angular/platform-server/src/parse5_adapter.ts +++ b/modules/@angular/platform-server/src/parse5_adapter.ts @@ -78,7 +78,9 @@ export class Parse5DomAdapter extends DomAdapter { get attrToPropMap() { return _attrToPropMap; } - querySelector(el: any, selector: string): any { return this.querySelectorAll(el, selector)[0]; } + querySelector(el: any, selector: string): any { + return this.querySelectorAll(el, selector)[0] || null; + } querySelectorAll(el: any, selector: string): any[] { const res: any[] = []; diff --git a/tools/cjs-jasmine/index.ts b/tools/cjs-jasmine/index.ts index 0e7f0fde25..2e0ad2d3d7 100644 --- a/tools/cjs-jasmine/index.ts +++ b/tools/cjs-jasmine/index.ts @@ -68,8 +68,11 @@ var specFiles: any = ] }); }) - // The security spec however works (and must work!) on the server side. + // Run relevant subset of browser tests for features reused on the server side. + // Make sure the security spec works on the server side! .concat(glob.sync('@angular/platform-browser/test/security/**/*_spec.js', {cwd: distAll})) + .concat(['/@angular/platform-browser/test/browser/meta_spec.js']) + .concat(['/@angular/platform-browser/test/browser/title_spec.js']) .reduce((specFiles: string[], paths: string[]) => specFiles.concat(paths), []); jasmine.DEFAULT_TIMEOUT_INTERVAL = 100;