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.
This commit is contained in:
vikerman 2017-03-01 11:19:22 -08:00 committed by Igor Minar
parent 47bdc2b0b7
commit 9402df92de
5 changed files with 74 additions and 46 deletions

View File

@ -13,9 +13,11 @@ const _SELECTOR_REGEXP = new RegExp(
'([-\\w]+)|' + // "tag" '([-\\w]+)|' + // "tag"
'(?:\\.([-\\w]+))|' + // ".class" '(?:\\.([-\\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
'(?:\\[([-.\\w*]+)(?:=([^\\]]*))?\\])|' + // "[name]", "[name=value]" '(?:\\[([-.\\w*]+)(?:=([\"\']?)([^\\]\"\']*)\\5)?\\])|' + // "[name]", "[name=value]",
'(\\))|' + // ")" // "[name="value"]",
'(\\s*,\\s*)', // "," // "[name='value']"
'(\\))|' + // ")"
'(\\s*,\\s*)', // ","
'g'); 'g');
/** /**
@ -59,13 +61,13 @@ export class CssSelector {
current.addClassName(match[3]); current.addClassName(match[3]);
} }
if (match[4]) { if (match[4]) {
current.addAttribute(match[4], match[5]); current.addAttribute(match[4], match[6]);
} }
if (match[6]) { if (match[7]) {
inNot = false; inNot = false;
current = cssSelector; current = cssSelector;
} }
if (match[7]) { if (match[8]) {
if (inNot) { if (inNot) {
throw new Error('Multiple selectors in :not are not supported'); throw new Error('Multiple selectors in :not are not supported');
} }

View File

@ -324,6 +324,18 @@ export function main() {
expect(cssSelector.toString()).toEqual('[attrname=attrvalue]'); 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', () => { it('should detect multiple parts', () => {
const cssSelector = CssSelector.parse('sometag[attrname=attrvalue].someclass')[0]; const cssSelector = CssSelector.parse('sometag[attrname=attrvalue].someclass')[0];
expect(cssSelector.element).toEqual('sometag'); expect(cssSelector.element).toEqual('sometag');

View File

@ -14,15 +14,15 @@ import {expect} from '@angular/platform-browser/testing/matchers';
export function main() { export function main() {
describe('Meta service', () => { describe('Meta service', () => {
const doc: HTMLDocument = getDOM().createHtmlDocument(); const doc = getDOM().createHtmlDocument();
const metaService: Meta = new Meta(doc); const metaService = new Meta(doc);
let defaultMeta: HTMLMetaElement; let defaultMeta: HTMLMetaElement;
beforeEach(() => { beforeEach(() => {
defaultMeta = getDOM().createElement('meta', doc) as HTMLMetaElement; defaultMeta = getDOM().createElement('meta', doc) as HTMLMetaElement;
defaultMeta.setAttribute('property', 'fb:app_id'); getDOM().setAttribute(defaultMeta, 'property', 'fb:app_id');
defaultMeta.setAttribute('content', '123456789'); getDOM().setAttribute(defaultMeta, 'content', '123456789');
getDOM().getElementsByTagName(doc, 'head')[0].appendChild(defaultMeta); getDOM().appendChild(getDOM().getElementsByTagName(doc, 'head')[0], defaultMeta);
}); });
afterEach(() => getDOM().remove(defaultMeta)); afterEach(() => getDOM().remove(defaultMeta));
@ -30,7 +30,7 @@ export function main() {
it('should return meta tag matching selector', () => { it('should return meta tag matching selector', () => {
const actual: HTMLMetaElement = metaService.getTag('property="fb:app_id"'); const actual: HTMLMetaElement = metaService.getTag('property="fb:app_id"');
expect(actual).not.toBeNull(); expect(actual).not.toBeNull();
expect(actual.content).toEqual('123456789'); expect(getDOM().getAttribute(actual, 'content')).toEqual('123456789');
}); });
it('should return all meta tags matching selector', () => { it('should return all meta tags matching selector', () => {
@ -39,8 +39,8 @@ export function main() {
const actual: HTMLMetaElement[] = metaService.getTags('name=author'); const actual: HTMLMetaElement[] = metaService.getTags('name=author');
expect(actual.length).toEqual(2); expect(actual.length).toEqual(2);
expect(actual[0].content).toEqual('page author'); expect(getDOM().getAttribute(actual[0], 'content')).toEqual('page author');
expect(actual[1].content).toEqual('another page author'); expect(getDOM().getAttribute(actual[1], 'content')).toEqual('another page author');
// clean up // clean up
metaService.removeTagElement(tag1); metaService.removeTagElement(tag1);
@ -53,83 +53,89 @@ export function main() {
}); });
it('should remove meta tag by the given selector', () => { 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'}); 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', () => { 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'}]); metaService.addTags([{name: 'keywords', content: 'meta test'}]);
const meta = metaService.getTag('name=keywords'); const meta = metaService.getTag(selector);
expect(meta).not.toBeNull(); expect(meta).not.toBeNull();
metaService.removeTagElement(meta); metaService.removeTagElement(meta);
expect(metaService.getTag('name=keywords')).toBeNull(); expect(metaService.getTag(selector)).toBeNull();
}); });
it('should update meta tag matching the given selector', () => { 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).not.toBeNull();
expect(actual.content).toEqual('4321'); expect(getDOM().getAttribute(actual, 'content')).toEqual('4321');
}); });
it('should extract selector from the tag definition', () => { it('should extract selector from the tag definition', () => {
const selector = 'property="fb:app_id"';
metaService.updateTag({property: 'fb:app_id', content: '666'}); 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).not.toBeNull();
expect(actual.content).toEqual('666'); expect(getDOM().getAttribute(actual, 'content')).toEqual('666');
}); });
it('should create meta tag if it does not exist', () => { it('should create meta tag if it does not exist', () => {
expect(metaService.getTag('name="twitter:title"')).toBeNull(); const selector = 'name="twitter:title"';
metaService.updateTag( metaService.updateTag({name: 'twitter:title', content: 'Content Title'}, selector);
{name: 'twitter:title', content: 'Content Title'}, 'name="twitter:title"');
const actual = metaService.getTag('name="twitter:title"'); const actual = metaService.getTag(selector);
expect(actual).not.toBeNull(); expect(actual).not.toBeNull();
expect(actual.content).toEqual('Content Title'); expect(getDOM().getAttribute(actual, 'content')).toEqual('Content Title');
// clean up // clean up
metaService.removeTagElement(actual); metaService.removeTagElement(actual);
}); });
it('should add new meta tag', () => { 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'}); 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).not.toBeNull();
expect(actual.content).toEqual('Content Title'); expect(getDOM().getAttribute(actual, 'content')).toEqual('Content Title');
// clean up // clean up
metaService.removeTagElement(actual); metaService.removeTagElement(actual);
}); });
it('should add multiple new meta tags', () => { it('should add multiple new meta tags', () => {
expect(metaService.getTag('name="twitter:title"')).toBeNull(); const nameSelector = 'name="twitter:title"';
expect(metaService.getTag('property="og:title"')).toBeNull(); const propertySelector = 'property="og:title"';
expect(metaService.getTag(nameSelector)).toBeNull();
expect(metaService.getTag(propertySelector)).toBeNull();
metaService.addTags([ metaService.addTags([
{name: 'twitter:title', content: 'Content Title'}, {name: 'twitter:title', content: 'Content Title'},
{property: 'og:title', content: 'Content Title'} {property: 'og:title', content: 'Content Title'}
]); ]);
const twitterMeta = metaService.getTag('name="twitter:title"'); const twitterMeta = metaService.getTag(nameSelector);
const fbMeta = metaService.getTag('property="og:title"'); const fbMeta = metaService.getTag(propertySelector);
expect(twitterMeta).not.toBeNull(); expect(twitterMeta).not.toBeNull();
expect(fbMeta).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', () => { 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'}); 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', 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'}); 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 // clean up
metaService.removeTagElement(meta); metaService.removeTagElement(meta);
}); });
it('should add meta tag if it is already present on the page and force true', () => { 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); 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 // clean up
metaService.removeTagElement(meta); metaService.removeTagElement(meta);

View File

@ -78,7 +78,9 @@ export class Parse5DomAdapter extends DomAdapter {
get attrToPropMap() { return _attrToPropMap; } 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[] { querySelectorAll(el: any, selector: string): any[] {
const res: any[] = []; const res: any[] = [];

View File

@ -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(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), <string[]>[]); .reduce((specFiles: string[], paths: string[]) => specFiles.concat(paths), <string[]>[]);
jasmine.DEFAULT_TIMEOUT_INTERVAL = 100; jasmine.DEFAULT_TIMEOUT_INTERVAL = 100;