parent
2a126f72f3
commit
2384082b5c
|
@ -3,11 +3,13 @@ import {HtmlAst} from './html_ast';
|
||||||
import {ChangeDetectionStrategy} from 'angular2/src/core/change_detection/change_detection';
|
import {ChangeDetectionStrategy} from 'angular2/src/core/change_detection/change_detection';
|
||||||
|
|
||||||
export class TypeMetadata {
|
export class TypeMetadata {
|
||||||
|
id: number;
|
||||||
type: any;
|
type: any;
|
||||||
typeName: string;
|
typeName: string;
|
||||||
typeUrl: string;
|
typeUrl: string;
|
||||||
constructor({type, typeName, typeUrl}:
|
constructor({id, type, typeName, typeUrl}:
|
||||||
{type?: string, typeName?: string, typeUrl?: string} = {}) {
|
{id?: number, type?: string, typeName?: string, typeUrl?: string} = {}) {
|
||||||
|
this.id = id;
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.typeName = typeName;
|
this.typeName = typeName;
|
||||||
this.typeUrl = typeUrl;
|
this.typeUrl = typeUrl;
|
||||||
|
@ -65,11 +67,11 @@ export class TemplateMetadata {
|
||||||
styleAbsUrls: string[];
|
styleAbsUrls: string[];
|
||||||
ngContentSelectors: string[];
|
ngContentSelectors: string[];
|
||||||
constructor({encapsulation, nodes, styles, styleAbsUrls, ngContentSelectors}: {
|
constructor({encapsulation, nodes, styles, styleAbsUrls, ngContentSelectors}: {
|
||||||
encapsulation: ViewEncapsulation,
|
encapsulation?: ViewEncapsulation,
|
||||||
nodes: HtmlAst[],
|
nodes?: HtmlAst[],
|
||||||
styles: string[],
|
styles?: string[],
|
||||||
styleAbsUrls: string[],
|
styleAbsUrls?: string[],
|
||||||
ngContentSelectors: string[]
|
ngContentSelectors?: string[]
|
||||||
}) {
|
}) {
|
||||||
this.encapsulation = encapsulation;
|
this.encapsulation = encapsulation;
|
||||||
this.nodes = nodes;
|
this.nodes = nodes;
|
||||||
|
@ -121,3 +123,7 @@ export class DirectiveMetadata {
|
||||||
this.template = template;
|
this.template = template;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class SourceModule {
|
||||||
|
constructor(public moduleName: string, public source: string, public imports: string[][]) {}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,138 @@
|
||||||
|
import {DirectiveMetadata, SourceModule, ViewEncapsulation} from './api';
|
||||||
|
import {XHR} from 'angular2/src/core/render/xhr';
|
||||||
|
import {StringWrapper, isJsObject, isBlank} from 'angular2/src/core/facade/lang';
|
||||||
|
import {PromiseWrapper, Promise} from 'angular2/src/core/facade/async';
|
||||||
|
import {ShadowCss} from 'angular2/src/core/render/dom/compiler/shadow_css';
|
||||||
|
import {UrlResolver} from 'angular2/src/core/services/url_resolver';
|
||||||
|
import {resolveStyleUrls} from './style_url_resolver';
|
||||||
|
|
||||||
|
const COMPONENT_VARIABLE = '%COMP%';
|
||||||
|
var COMPONENT_REGEX = /%COMP%/g;
|
||||||
|
const HOST_ATTR = `_nghost-${COMPONENT_VARIABLE}`;
|
||||||
|
const CONTENT_ATTR = `_ngcontent-${COMPONENT_VARIABLE}`;
|
||||||
|
var ESCAPE_STRING_RE = /'|\\|\n/g;
|
||||||
|
var IS_DART = !isJsObject({});
|
||||||
|
|
||||||
|
export class StyleCompiler {
|
||||||
|
private _styleCache: Map<string, Promise<string[]>> = new Map<string, Promise<string[]>>();
|
||||||
|
private _shadowCss: ShadowCss = new ShadowCss();
|
||||||
|
|
||||||
|
constructor(private _xhr: XHR, private _urlResolver: UrlResolver) {}
|
||||||
|
|
||||||
|
compileComponentRuntime(component: DirectiveMetadata): Promise<string[]> {
|
||||||
|
var styles = component.template.styles;
|
||||||
|
var styleAbsUrls = component.template.styleAbsUrls;
|
||||||
|
return this._loadStyles(styles, styleAbsUrls,
|
||||||
|
component.template.encapsulation === ViewEncapsulation.Emulated)
|
||||||
|
.then(styles => styles.map(style => StringWrapper.replaceAll(style, COMPONENT_REGEX,
|
||||||
|
`${component.type.id}`)));
|
||||||
|
}
|
||||||
|
|
||||||
|
compileComponentCodeGen(component: DirectiveMetadata): SourceModule {
|
||||||
|
var shim = component.template.encapsulation === ViewEncapsulation.Emulated;
|
||||||
|
var suffix;
|
||||||
|
if (shim) {
|
||||||
|
var componentId = `${ component.type.id}`;
|
||||||
|
suffix =
|
||||||
|
codeGenMapArray(['style'], `style${codeGenReplaceAll(COMPONENT_VARIABLE, componentId)}`);
|
||||||
|
} else {
|
||||||
|
suffix = '';
|
||||||
|
}
|
||||||
|
return this._styleCodeGen(`$component.type.typeUrl}.styles`, component.template.styles,
|
||||||
|
component.template.styleAbsUrls, shim, suffix);
|
||||||
|
}
|
||||||
|
|
||||||
|
compileStylesheetCodeGen(moduleName: string, cssText: string): SourceModule[] {
|
||||||
|
var styleWithImports = resolveStyleUrls(this._urlResolver, moduleName, cssText);
|
||||||
|
return [
|
||||||
|
this._styleCodeGen(moduleName, [styleWithImports.style], styleWithImports.styleUrls, false,
|
||||||
|
''),
|
||||||
|
this._styleCodeGen(moduleName, [styleWithImports.style], styleWithImports.styleUrls, true, '')
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private _loadStyles(plainStyles: string[], absUrls: string[],
|
||||||
|
encapsulate: boolean): Promise<string[]> {
|
||||||
|
var promises = absUrls.map((absUrl) => {
|
||||||
|
var cacheKey = `${absUrl}${encapsulate ? '.shim' : ''}`;
|
||||||
|
var result = this._styleCache.get(cacheKey);
|
||||||
|
if (isBlank(result)) {
|
||||||
|
result = this._xhr.get(absUrl).then((style) => {
|
||||||
|
var styleWithImports = resolveStyleUrls(this._urlResolver, absUrl, style);
|
||||||
|
return this._loadStyles([styleWithImports.style], styleWithImports.styleUrls,
|
||||||
|
encapsulate);
|
||||||
|
});
|
||||||
|
this._styleCache.set(cacheKey, result);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
return PromiseWrapper.all(promises).then((nestedStyles: string[][]) => {
|
||||||
|
var result = plainStyles.map(plainStyle => this._shimIfNeeded(plainStyle, encapsulate));
|
||||||
|
nestedStyles.forEach(styles => styles.forEach(style => result.push(style)));
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _styleCodeGen(moduleName: string, plainStyles: string[], absUrls: string[], shim: boolean,
|
||||||
|
suffix: string): SourceModule {
|
||||||
|
var imports: string[][] = [];
|
||||||
|
var moduleSource = `${codeGenExportVar('STYLES')} (`;
|
||||||
|
moduleSource +=
|
||||||
|
`[${plainStyles.map( plainStyle => escapeString(this._shimIfNeeded(plainStyle, shim)) ).join(',')}]`;
|
||||||
|
for (var i = 0; i < absUrls.length; i++) {
|
||||||
|
var url = absUrls[i];
|
||||||
|
var moduleAlias = `import${i}`;
|
||||||
|
imports.push([this._shimModuleName(url, shim), moduleAlias]);
|
||||||
|
moduleSource += `${codeGenConcatArray(moduleAlias+'.STYLES')}`;
|
||||||
|
}
|
||||||
|
moduleSource += `)${suffix};`;
|
||||||
|
return new SourceModule(this._shimModuleName(moduleName, shim), moduleSource, imports);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _shimIfNeeded(style: string, shim: boolean): string {
|
||||||
|
return shim ? this._shadowCss.shimCssText(style, CONTENT_ATTR, HOST_ATTR) : style;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _shimModuleName(originalUrl: string, shim: boolean): string {
|
||||||
|
return shim ? `${originalUrl}.shim` : originalUrl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function escapeString(input: string): string {
|
||||||
|
var escapedInput = StringWrapper.replaceAllMapped(input, ESCAPE_STRING_RE, (match) => {
|
||||||
|
if (match[0] == "'" || match[0] == '\\') {
|
||||||
|
return `\\${match[0]}`;
|
||||||
|
} else {
|
||||||
|
return '\\n';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return `'${escapedInput}'`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function codeGenExportVar(name: string): string {
|
||||||
|
if (IS_DART) {
|
||||||
|
return `var ${name} =`;
|
||||||
|
} else {
|
||||||
|
return `var ${name} = exports.${name} =`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function codeGenConcatArray(expression: string): string {
|
||||||
|
return `${IS_DART ? '..addAll' : '.concat'}(${expression})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function codeGenMapArray(argNames: string[], callback: string): string {
|
||||||
|
if (IS_DART) {
|
||||||
|
return `.map( (${argNames.join(',')}) => ${callback} ).toList()`;
|
||||||
|
} else {
|
||||||
|
return `.map(function(${argNames.join(',')}) { return ${callback}; })`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function codeGenReplaceAll(pattern: string, value: string): string {
|
||||||
|
if (IS_DART) {
|
||||||
|
return `.replaceAll('${pattern}', '${value}')`;
|
||||||
|
} else {
|
||||||
|
return `.replace(/${pattern}/g, '${value}')`;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,31 +1,36 @@
|
||||||
// Some of the code comes from WebComponents.JS
|
// Some of the code comes from WebComponents.JS
|
||||||
// https://github.com/webcomponents/webcomponentsjs/blob/master/src/HTMLImports/path.js
|
// https://github.com/webcomponents/webcomponentsjs/blob/master/src/HTMLImports/path.js
|
||||||
|
|
||||||
import {Injectable} from 'angular2/di';
|
import {RegExp, RegExpWrapper, StringWrapper, isPresent} from 'angular2/src/core/facade/lang';
|
||||||
import {RegExp, RegExpWrapper, StringWrapper} from 'angular2/src/core/facade/lang';
|
|
||||||
import {UrlResolver} from 'angular2/src/core/services/url_resolver';
|
import {UrlResolver} from 'angular2/src/core/services/url_resolver';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rewrites URLs by resolving '@import' and 'url()' URLs from the given base URL,
|
* Rewrites URLs by resolving '@import' and 'url()' URLs from the given base URL,
|
||||||
* removes and returns the @import urls
|
* removes and returns the @import urls
|
||||||
*/
|
*/
|
||||||
@Injectable()
|
export function resolveStyleUrls(resolver: UrlResolver, baseUrl: string, cssText: string):
|
||||||
export class StyleUrlResolver {
|
StyleWithImports {
|
||||||
constructor(public _resolver: UrlResolver) {}
|
|
||||||
|
|
||||||
resolveUrls(cssText: string, baseUrl: string): string {
|
|
||||||
cssText = this._replaceUrls(cssText, _cssUrlRe, baseUrl);
|
|
||||||
return cssText;
|
|
||||||
}
|
|
||||||
|
|
||||||
extractImports(cssText: string): StyleWithImports {
|
|
||||||
var foundUrls = [];
|
var foundUrls = [];
|
||||||
cssText = this._extractUrls(cssText, _cssImportRe, foundUrls);
|
cssText = extractUrls(resolver, baseUrl, cssText, foundUrls);
|
||||||
|
cssText = replaceUrls(resolver, baseUrl, cssText);
|
||||||
return new StyleWithImports(cssText, foundUrls);
|
return new StyleWithImports(cssText, foundUrls);
|
||||||
}
|
}
|
||||||
|
|
||||||
_replaceUrls(cssText: string, re: RegExp, baseUrl: string) {
|
export class StyleWithImports {
|
||||||
return StringWrapper.replaceAllMapped(cssText, re, (m) => {
|
constructor(public style: string, public styleUrls: string[]) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractUrls(resolver: UrlResolver, baseUrl: string, cssText: string, foundUrls: string[]):
|
||||||
|
string {
|
||||||
|
return StringWrapper.replaceAllMapped(cssText, _cssImportRe, (m) => {
|
||||||
|
var url = isPresent(m[1]) ? m[1] : m[2];
|
||||||
|
foundUrls.push(resolver.resolve(baseUrl, url));
|
||||||
|
return '';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function replaceUrls(resolver: UrlResolver, baseUrl: string, cssText: string): string {
|
||||||
|
return StringWrapper.replaceAllMapped(cssText, _cssUrlRe, (m) => {
|
||||||
var pre = m[1];
|
var pre = m[1];
|
||||||
var originalUrl = m[2];
|
var originalUrl = m[2];
|
||||||
if (RegExpWrapper.test(_dataUrlRe, originalUrl)) {
|
if (RegExpWrapper.test(_dataUrlRe, originalUrl)) {
|
||||||
|
@ -35,31 +40,13 @@ export class StyleUrlResolver {
|
||||||
var url = StringWrapper.replaceAll(originalUrl, _quoteRe, '');
|
var url = StringWrapper.replaceAll(originalUrl, _quoteRe, '');
|
||||||
var post = m[3];
|
var post = m[3];
|
||||||
|
|
||||||
var resolvedUrl = this._resolver.resolve(baseUrl, url);
|
var resolvedUrl = resolver.resolve(baseUrl, url);
|
||||||
|
|
||||||
return pre + "'" + resolvedUrl + "'" + post;
|
return pre + "'" + resolvedUrl + "'" + post;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_extractUrls(cssText: string, re: RegExp, foundUrls: string[]) {
|
|
||||||
return StringWrapper.replaceAllMapped(cssText, re, (m) => {
|
|
||||||
var originalUrl = m[2];
|
|
||||||
if (RegExpWrapper.test(_dataUrlRe, originalUrl)) {
|
|
||||||
// Do not attempt to resolve data: URLs
|
|
||||||
return m[0];
|
|
||||||
}
|
|
||||||
var url = StringWrapper.replaceAll(originalUrl, _quoteRe, '');
|
|
||||||
foundUrls.push(url);
|
|
||||||
return '';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class StyleWithImports {
|
|
||||||
constructor(public style: string, public styleUrls: string[]) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
var _cssUrlRe = /(url\()([^)]*)(\))/g;
|
var _cssUrlRe = /(url\()([^)]*)(\))/g;
|
||||||
var _cssImportRe = /(@import[\s]+(?:url\()?)['"]?([^'"\)]*)['"]?(.*;)/g;
|
var _cssImportRe = /@import\s+(?:url\()?\s*(?:(?:['"]([^'"]*))|([^;\)\s]*))[^;]*;?/g;
|
||||||
var _quoteRe = /['"]/g;
|
var _quoteRe = /['"]/g;
|
||||||
var _dataUrlRe = /^['"]?data:/g;
|
var _dataUrlRe = /^['"]?data:/g;
|
||||||
|
|
|
@ -4,7 +4,7 @@ import {Promise, PromiseWrapper} from 'angular2/src/core/facade/async';
|
||||||
|
|
||||||
import {XHR} from 'angular2/src/core/render/xhr';
|
import {XHR} from 'angular2/src/core/render/xhr';
|
||||||
import {UrlResolver} from 'angular2/src/core/services/url_resolver';
|
import {UrlResolver} from 'angular2/src/core/services/url_resolver';
|
||||||
import {StyleUrlResolver} from './style_url_resolver';
|
import {resolveStyleUrls} from './style_url_resolver';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
HtmlAstVisitor,
|
HtmlAstVisitor,
|
||||||
|
@ -26,7 +26,7 @@ const STYLE_ELEMENT = 'style';
|
||||||
|
|
||||||
export class TemplateLoader {
|
export class TemplateLoader {
|
||||||
constructor(private _xhr: XHR, private _urlResolver: UrlResolver,
|
constructor(private _xhr: XHR, private _urlResolver: UrlResolver,
|
||||||
private _styleUrlResolver: StyleUrlResolver, private _domParser: HtmlParser) {}
|
private _domParser: HtmlParser) {}
|
||||||
|
|
||||||
loadTemplate(directiveType: TypeMetadata, encapsulation: ViewEncapsulation, template: string,
|
loadTemplate(directiveType: TypeMetadata, encapsulation: ViewEncapsulation, template: string,
|
||||||
templateUrl: string, styles: string[],
|
templateUrl: string, styles: string[],
|
||||||
|
@ -51,14 +51,12 @@ export class TemplateLoader {
|
||||||
var remainingNodes = htmlVisitAll(visitor, domNodes);
|
var remainingNodes = htmlVisitAll(visitor, domNodes);
|
||||||
var allStyles = styles.concat(visitor.styles);
|
var allStyles = styles.concat(visitor.styles);
|
||||||
var allStyleUrls = styleUrls.concat(visitor.styleUrls);
|
var allStyleUrls = styleUrls.concat(visitor.styleUrls);
|
||||||
allStyles = allStyles.map(style => {
|
var allResolvedStyles = allStyles.map(style => {
|
||||||
var styleWithImports = this._styleUrlResolver.extractImports(style);
|
var styleWithImports = resolveStyleUrls(this._urlResolver, templateSourceUrl, style);
|
||||||
styleWithImports.styleUrls.forEach(styleUrl => allStyleUrls.push(styleUrl));
|
styleWithImports.styleUrls.forEach(styleUrl => allStyleUrls.push(styleUrl));
|
||||||
return styleWithImports.style;
|
return styleWithImports.style;
|
||||||
});
|
});
|
||||||
|
|
||||||
var allResolvedStyles =
|
|
||||||
allStyles.map(style => this._styleUrlResolver.resolveUrls(style, templateSourceUrl));
|
|
||||||
var allStyleAbsUrls =
|
var allStyleAbsUrls =
|
||||||
allStyleUrls.map(styleUrl => this._urlResolver.resolve(templateSourceUrl, styleUrl));
|
allStyleUrls.map(styleUrl => this._urlResolver.resolve(templateSourceUrl, styleUrl));
|
||||||
return new TemplateMetadata({
|
return new TemplateMetadata({
|
||||||
|
|
|
@ -11,11 +11,9 @@ import {
|
||||||
AsyncTestCompleter,
|
AsyncTestCompleter,
|
||||||
inject
|
inject
|
||||||
} from 'angular2/test_lib';
|
} from 'angular2/test_lib';
|
||||||
import {PromiseWrapper} from 'angular2/src/core/facade/async';
|
|
||||||
import {IS_DART} from '../platform';
|
import {IS_DART} from '../platform';
|
||||||
|
|
||||||
import {evalModule} from './eval_module';
|
import {evalModule} from './eval_module';
|
||||||
import {SourceModule} from 'angular2/src/compiler/api';
|
|
||||||
|
|
||||||
// This export is used by this test code
|
// This export is used by this test code
|
||||||
// when evaling the test module!
|
// when evaling the test module!
|
||||||
|
@ -23,11 +21,13 @@ export var TEST_VALUE = 23;
|
||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
describe('evalModule', () => {
|
describe('evalModule', () => {
|
||||||
it('should call the "run" function and allow to use imports', inject([AsyncTestCompleter], (async) => {
|
it('should call the "run" function and allow to use imports',
|
||||||
|
inject([AsyncTestCompleter], (async) => {
|
||||||
var moduleSource = IS_DART ? testDartModule : testJsModule;
|
var moduleSource = IS_DART ? testDartModule : testJsModule;
|
||||||
var imports = [['angular2/test/compiler/eval_module_spec', 'testMod']];
|
var imports = [['angular2/test/compiler/eval_module_spec', 'testMod']];
|
||||||
|
|
||||||
evalModule(moduleSource, imports, [1]).then( (value) => {
|
evalModule(moduleSource, imports, [1])
|
||||||
|
.then((value) => {
|
||||||
expect(value).toEqual([1, 23]);
|
expect(value).toEqual([1, 23]);
|
||||||
async.done();
|
async.done();
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
// used by style_compiler_spec.ts
|
||||||
|
export var STYLES = ['span[_ngcontent-%COMP%] {\ncolor: blue;\n}'];
|
|
@ -0,0 +1,2 @@
|
||||||
|
// used by style_compiler_spec.ts
|
||||||
|
export var STYLES = ['span {color: blue}'];
|
|
@ -0,0 +1,201 @@
|
||||||
|
import {
|
||||||
|
ddescribe,
|
||||||
|
describe,
|
||||||
|
xdescribe,
|
||||||
|
it,
|
||||||
|
iit,
|
||||||
|
xit,
|
||||||
|
expect,
|
||||||
|
beforeEach,
|
||||||
|
afterEach,
|
||||||
|
AsyncTestCompleter,
|
||||||
|
inject
|
||||||
|
} from 'angular2/test_lib';
|
||||||
|
import {IS_DART} from '../platform';
|
||||||
|
import {SpyXHR} from '../core/spies';
|
||||||
|
|
||||||
|
import {CONST_EXPR, isPresent, BaseException} from 'angular2/src/core/facade/lang';
|
||||||
|
import {PromiseWrapper, Promise} from 'angular2/src/core/facade/async';
|
||||||
|
import {evalModule} from './eval_module';
|
||||||
|
import {StyleCompiler} from 'angular2/src/compiler/style_compiler';
|
||||||
|
import {UrlResolver} from 'angular2/src/core/services/url_resolver';
|
||||||
|
import {
|
||||||
|
DirectiveMetadata,
|
||||||
|
TemplateMetadata,
|
||||||
|
TypeMetadata,
|
||||||
|
ViewEncapsulation
|
||||||
|
} from 'angular2/src/compiler/api';
|
||||||
|
|
||||||
|
// Attention: These module names have to correspond to real modules!
|
||||||
|
const MODULE_NAME = 'angular2/test/compiler/style_compiler_spec';
|
||||||
|
const IMPORT_ABS_MODULE_NAME = 'angular2/test/compiler/style_compiler_import';
|
||||||
|
const IMPORT_REL_MODULE_NAME = './style_compiler_import';
|
||||||
|
// Note: Not a real module, only used via mocks.
|
||||||
|
const IMPORT_ABS_MODULE_NAME_WITH_IMPORT =
|
||||||
|
'angular2/test/compiler/style_compiler_transitive_import';
|
||||||
|
|
||||||
|
export function main() {
|
||||||
|
describe('StyleCompiler', () => {
|
||||||
|
var compiler: StyleCompiler;
|
||||||
|
var xhr;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
xhr = <any>new SpyXHR();
|
||||||
|
compiler = new StyleCompiler(xhr, new UrlResolver());
|
||||||
|
});
|
||||||
|
|
||||||
|
function comp(styles: string[], styleAbsUrls: string[], encapsulation: ViewEncapsulation):
|
||||||
|
DirectiveMetadata {
|
||||||
|
return new DirectiveMetadata({
|
||||||
|
type: new TypeMetadata({id: 23, typeUrl: 'someUrl'}),
|
||||||
|
template: new TemplateMetadata(
|
||||||
|
{styles: styles, styleAbsUrls: styleAbsUrls, encapsulation: encapsulation})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('compileComponentRuntime', () => {
|
||||||
|
function runTest(styles: string[], styleAbsUrls: string[], encapsulation: ViewEncapsulation,
|
||||||
|
expectedStyles: string[]) {
|
||||||
|
return inject([AsyncTestCompleter], (async) => {
|
||||||
|
// Note: Can't use MockXHR as the xhr is called recursively,
|
||||||
|
// so we can't trigger flush.
|
||||||
|
xhr.spy('get').andCallFake((url) => {
|
||||||
|
var response;
|
||||||
|
if (url == IMPORT_ABS_MODULE_NAME) {
|
||||||
|
response = 'span {color: blue}';
|
||||||
|
} else if (url == IMPORT_ABS_MODULE_NAME_WITH_IMPORT) {
|
||||||
|
response = `a {color: green}@import ${IMPORT_REL_MODULE_NAME};`;
|
||||||
|
} else {
|
||||||
|
throw new BaseException(`Unexpected url ${url}`);
|
||||||
|
}
|
||||||
|
return PromiseWrapper.resolve(response);
|
||||||
|
});
|
||||||
|
compiler.compileComponentRuntime(comp(styles, styleAbsUrls, encapsulation))
|
||||||
|
.then((value) => {
|
||||||
|
expect(value).toEqual(expectedStyles);
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('no shim', () => {
|
||||||
|
var encapsulation = ViewEncapsulation.None;
|
||||||
|
|
||||||
|
it('should compile plain css rules',
|
||||||
|
runTest(['div {color: red}', 'span {color: blue}'], [], encapsulation,
|
||||||
|
['div {color: red}', 'span {color: blue}']));
|
||||||
|
|
||||||
|
it('should allow to import rules',
|
||||||
|
runTest(['div {color: red}'], [IMPORT_ABS_MODULE_NAME], encapsulation,
|
||||||
|
['div {color: red}', 'span {color: blue}']));
|
||||||
|
|
||||||
|
it('should allow to import rules transitively',
|
||||||
|
runTest(['div {color: red}'], [IMPORT_ABS_MODULE_NAME_WITH_IMPORT], encapsulation,
|
||||||
|
['div {color: red}', 'a {color: green}', 'span {color: blue}']));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('with shim', () => {
|
||||||
|
var encapsulation = ViewEncapsulation.Emulated;
|
||||||
|
|
||||||
|
it('should compile plain css rules',
|
||||||
|
runTest(
|
||||||
|
['div {\ncolor: red;\n}', 'span {\ncolor: blue;\n}'], [], encapsulation,
|
||||||
|
['div[_ngcontent-23] {\ncolor: red;\n}', 'span[_ngcontent-23] {\ncolor: blue;\n}']));
|
||||||
|
|
||||||
|
it('should allow to import rules',
|
||||||
|
runTest(
|
||||||
|
['div {\ncolor: red;\n}'], [IMPORT_ABS_MODULE_NAME], encapsulation,
|
||||||
|
['div[_ngcontent-23] {\ncolor: red;\n}', 'span[_ngcontent-23] {\ncolor: blue;\n}']));
|
||||||
|
|
||||||
|
it('should allow to import rules transitively',
|
||||||
|
runTest(['div {\ncolor: red;\n}'], [IMPORT_ABS_MODULE_NAME_WITH_IMPORT], encapsulation, [
|
||||||
|
'div[_ngcontent-23] {\ncolor: red;\n}',
|
||||||
|
'a[_ngcontent-23] {\ncolor: green;\n}',
|
||||||
|
'span[_ngcontent-23] {\ncolor: blue;\n}'
|
||||||
|
]));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('compileComponentCodeGen', () => {
|
||||||
|
function runTest(styles: string[], styleAbsUrls: string[], encapsulation: ViewEncapsulation,
|
||||||
|
expectedStyles: string[]) {
|
||||||
|
return inject([AsyncTestCompleter], (async) => {
|
||||||
|
var sourceModule =
|
||||||
|
compiler.compileComponentCodeGen(comp(styles, styleAbsUrls, encapsulation));
|
||||||
|
evalModule(testableModule(sourceModule.source), sourceModule.imports, null)
|
||||||
|
.then((value) => {
|
||||||
|
expect(value).toEqual(expectedStyles);
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('no shim', () => {
|
||||||
|
var encapsulation = ViewEncapsulation.None;
|
||||||
|
|
||||||
|
it('should compile plain css ruless',
|
||||||
|
runTest(['div {color: red}', 'span {color: blue}'], [], encapsulation,
|
||||||
|
['div {color: red}', 'span {color: blue}']));
|
||||||
|
|
||||||
|
it('should compile css rules with newlines and quotes',
|
||||||
|
runTest(['div\n{"color": \'red\'}'], [], encapsulation, ['div\n{"color": \'red\'}']));
|
||||||
|
|
||||||
|
it('should allow to import rules',
|
||||||
|
runTest(['div {color: red}'], [IMPORT_ABS_MODULE_NAME], encapsulation,
|
||||||
|
['div {color: red}', 'span {color: blue}']));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('with shim', () => {
|
||||||
|
var encapsulation = ViewEncapsulation.Emulated;
|
||||||
|
|
||||||
|
it('should compile plain css ruless',
|
||||||
|
runTest(
|
||||||
|
['div {\ncolor: red;\n}', 'span {\ncolor: blue;\n}'], [], encapsulation,
|
||||||
|
['div[_ngcontent-23] {\ncolor: red;\n}', 'span[_ngcontent-23] {\ncolor: blue;\n}']));
|
||||||
|
|
||||||
|
it('should allow to import rules',
|
||||||
|
runTest(
|
||||||
|
['div {color: red}'], [IMPORT_ABS_MODULE_NAME], encapsulation,
|
||||||
|
['div[_ngcontent-23] {\ncolor: red;\n}', 'span[_ngcontent-23] {\ncolor: blue;\n}']));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('compileStylesheetCodeGen', () => {
|
||||||
|
function runTest(style: string, expectedStyles: string[], expectedShimStyles: string[]) {
|
||||||
|
return inject([AsyncTestCompleter], (async) => {
|
||||||
|
var sourceModules = compiler.compileStylesheetCodeGen(MODULE_NAME, style);
|
||||||
|
PromiseWrapper.all(sourceModules.map(sourceModule =>
|
||||||
|
evalModule(testableModule(sourceModule.source),
|
||||||
|
sourceModule.imports, null)))
|
||||||
|
.then((values) => {
|
||||||
|
expect(values[0]).toEqual(expectedStyles);
|
||||||
|
expect(values[1]).toEqual(expectedShimStyles);
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
it('should compile plain css rules', runTest('div {color: red;}', ['div {color: red;}'],
|
||||||
|
['div[_ngcontent-%COMP%] {\ncolor: red;\n}']));
|
||||||
|
|
||||||
|
it('should allow to import rules with relative paths',
|
||||||
|
runTest(`div {color: red}@import ${IMPORT_REL_MODULE_NAME};`,
|
||||||
|
['div {color: red}', 'span {color: blue}'], [
|
||||||
|
'div[_ngcontent-%COMP%] {\ncolor: red;\n}',
|
||||||
|
'span[_ngcontent-%COMP%] {\ncolor: blue;\n}'
|
||||||
|
]));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function testableModule(sourceModule: string) {
|
||||||
|
if (IS_DART) {
|
||||||
|
return `${sourceModule}
|
||||||
|
run(_) { return STYLES; }
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
|
return `${sourceModule}
|
||||||
|
exports.run = function(_) { return STYLES; };
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,15 +1,14 @@
|
||||||
import {describe, it, expect, beforeEach, ddescribe, iit, xit, el} from 'angular2/test_lib';
|
import {describe, it, expect, beforeEach, ddescribe, iit, xit, el} from 'angular2/test_lib';
|
||||||
import {StyleUrlResolver} from 'angular2/src/compiler/style_url_resolver';
|
import {resolveStyleUrls} from 'angular2/src/compiler/style_url_resolver';
|
||||||
|
|
||||||
import {UrlResolver} from 'angular2/src/core/services/url_resolver';
|
import {UrlResolver} from 'angular2/src/core/services/url_resolver';
|
||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
describe('StyleUrlResolver', () => {
|
describe('StyleUrlResolver', () => {
|
||||||
let styleUrlResolver: StyleUrlResolver;
|
var urlResolver;
|
||||||
|
|
||||||
beforeEach(() => { styleUrlResolver = new StyleUrlResolver(new UrlResolver()); });
|
beforeEach(() => { urlResolver = new UrlResolver(); });
|
||||||
|
|
||||||
describe('resolveUrls', () => {
|
|
||||||
it('should resolve "url()" urls', () => {
|
it('should resolve "url()" urls', () => {
|
||||||
var css = `
|
var css = `
|
||||||
.foo {
|
.foo {
|
||||||
|
@ -24,7 +23,7 @@ export function main() {
|
||||||
background-image: url('http://ng.io/noquote.jpg');
|
background-image: url('http://ng.io/noquote.jpg');
|
||||||
}`;
|
}`;
|
||||||
|
|
||||||
var resolvedCss = styleUrlResolver.resolveUrls(css, 'http://ng.io');
|
var resolvedCss = resolveStyleUrls(urlResolver, 'http://ng.io', css).style;
|
||||||
expect(resolvedCss).toEqual(expectedCss);
|
expect(resolvedCss).toEqual(expectedCss);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -45,20 +44,18 @@ export function main() {
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
var resolvedCss = styleUrlResolver.resolveUrls(css, 'http://ng.io');
|
var resolvedCss = resolveStyleUrls(urlResolver, 'http://ng.io', css).style;
|
||||||
expect(resolvedCss).toEqual(expectedCss);
|
expect(resolvedCss).toEqual(expectedCss);
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
describe('extractUrls', () => {
|
|
||||||
it('should extract "@import" urls', () => {
|
it('should extract "@import" urls', () => {
|
||||||
var css = `
|
var css = `
|
||||||
@import '1.css';
|
@import '1.css';
|
||||||
@import "2.css";
|
@import "2.css";
|
||||||
`;
|
`;
|
||||||
var styleWithImports = styleUrlResolver.extractImports(css);
|
var styleWithImports = resolveStyleUrls(urlResolver, 'http://ng.io', css);
|
||||||
expect(styleWithImports.style.trim()).toEqual('');
|
expect(styleWithImports.style.trim()).toEqual('');
|
||||||
expect(styleWithImports.styleUrls).toEqual(['1.css', '2.css']);
|
expect(styleWithImports.styleUrls).toEqual(['http://ng.io/1.css', 'http://ng.io/2.css']);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should extract "@import url()" urls', () => {
|
it('should extract "@import url()" urls', () => {
|
||||||
|
@ -67,21 +64,28 @@ export function main() {
|
||||||
@import url("4.css");
|
@import url("4.css");
|
||||||
@import url(5.css);
|
@import url(5.css);
|
||||||
`;
|
`;
|
||||||
var styleWithImports = styleUrlResolver.extractImports(css);
|
var styleWithImports = resolveStyleUrls(urlResolver, 'http://ng.io', css);
|
||||||
expect(styleWithImports.style.trim()).toEqual('');
|
expect(styleWithImports.style.trim()).toEqual('');
|
||||||
expect(styleWithImports.styleUrls).toEqual(['3.css', '4.css', '5.css']);
|
expect(styleWithImports.styleUrls)
|
||||||
|
.toEqual(['http://ng.io/3.css', 'http://ng.io/4.css', 'http://ng.io/5.css']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should extract "@import urls and keep rules in the same line', () => {
|
||||||
|
var css = `@import url('some.css');div {color: red};`;
|
||||||
|
var styleWithImports = resolveStyleUrls(urlResolver, 'http://ng.io', css);
|
||||||
|
expect(styleWithImports.style.trim()).toEqual('div {color: red};');
|
||||||
|
expect(styleWithImports.styleUrls).toEqual(['http://ng.io/some.css']);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should extract media query in "@import"', () => {
|
it('should extract media query in "@import"', () => {
|
||||||
var css = `
|
var css = `
|
||||||
@import 'print.css' print;
|
@import 'print1.css' print;
|
||||||
@import url(print2.css) print;
|
@import url(print2.css) print;
|
||||||
`;
|
`;
|
||||||
var styleWithImports = styleUrlResolver.extractImports(css);
|
var styleWithImports = resolveStyleUrls(urlResolver, 'http://ng.io', css);
|
||||||
expect(styleWithImports.style.trim()).toEqual('');
|
expect(styleWithImports.style.trim()).toEqual('');
|
||||||
expect(styleWithImports.styleUrls).toEqual(['print.css', 'print2.css']);
|
expect(styleWithImports.styleUrls)
|
||||||
});
|
.toEqual(['http://ng.io/print1.css', 'http://ng.io/print2.css']);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -19,7 +19,6 @@ import {TypeMetadata, ViewEncapsulation, TemplateMetadata} from 'angular2/src/co
|
||||||
|
|
||||||
import {TemplateLoader} from 'angular2/src/compiler/template_loader';
|
import {TemplateLoader} from 'angular2/src/compiler/template_loader';
|
||||||
import {UrlResolver} from 'angular2/src/core/services/url_resolver';
|
import {UrlResolver} from 'angular2/src/core/services/url_resolver';
|
||||||
import {StyleUrlResolver} from 'angular2/src/compiler/style_url_resolver';
|
|
||||||
import {humanizeDom} from './html_parser_spec';
|
import {humanizeDom} from './html_parser_spec';
|
||||||
import {HtmlTextAst, HtmlElementAst, HtmlAttrAst} from 'angular2/src/compiler/html_ast';
|
import {HtmlTextAst, HtmlElementAst, HtmlAttrAst} from 'angular2/src/compiler/html_ast';
|
||||||
import {XHR} from 'angular2/src/core/render/xhr';
|
import {XHR} from 'angular2/src/core/render/xhr';
|
||||||
|
@ -33,8 +32,7 @@ export function main() {
|
||||||
beforeEach(inject([XHR], (mockXhr) => {
|
beforeEach(inject([XHR], (mockXhr) => {
|
||||||
xhr = mockXhr;
|
xhr = mockXhr;
|
||||||
var urlResolver = new UrlResolver();
|
var urlResolver = new UrlResolver();
|
||||||
loader =
|
loader = new TemplateLoader(xhr, urlResolver, new HtmlParser());
|
||||||
new TemplateLoader(xhr, urlResolver, new StyleUrlResolver(urlResolver), new HtmlParser());
|
|
||||||
dirType = new TypeMetadata({typeUrl: 'http://sometypeurl', typeName: 'SomeComp'});
|
dirType = new TypeMetadata({typeUrl: 'http://sometypeurl', typeName: 'SomeComp'});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ import 'package:angular2/src/core/compiler/view_listener.dart';
|
||||||
import 'package:angular2/src/core/compiler/element_injector.dart';
|
import 'package:angular2/src/core/compiler/element_injector.dart';
|
||||||
import 'package:angular2/src/core/dom/dom_adapter.dart';
|
import 'package:angular2/src/core/dom/dom_adapter.dart';
|
||||||
import 'package:angular2/test_lib.dart';
|
import 'package:angular2/test_lib.dart';
|
||||||
|
import 'package:angular2/src/core/render/xhr.dart';
|
||||||
|
|
||||||
@proxy
|
@proxy
|
||||||
class SpyDependencyProvider extends SpyObject implements DependencyProvider {
|
class SpyDependencyProvider extends SpyObject implements DependencyProvider {
|
||||||
|
@ -103,3 +104,8 @@ class SpyPreBuiltObjects extends SpyObject implements PreBuiltObjects {
|
||||||
class SpyDomAdapter extends SpyObject implements DomAdapter {
|
class SpyDomAdapter extends SpyObject implements DomAdapter {
|
||||||
noSuchMethod(m) => super.noSuchMethod(m);
|
noSuchMethod(m) => super.noSuchMethod(m);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@proxy
|
||||||
|
class SpyXHR extends SpyObject implements XHR {
|
||||||
|
noSuchMethod(m) => super.noSuchMethod(m);
|
||||||
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ import {AppViewPool} from 'angular2/src/core/compiler/view_pool';
|
||||||
import {AppViewListener} from 'angular2/src/core/compiler/view_listener';
|
import {AppViewListener} from 'angular2/src/core/compiler/view_listener';
|
||||||
import {DomAdapter} from 'angular2/src/core/dom/dom_adapter';
|
import {DomAdapter} from 'angular2/src/core/dom/dom_adapter';
|
||||||
import {ClientMessageBroker} from 'angular2/src/web_workers/shared/client_message_broker';
|
import {ClientMessageBroker} from 'angular2/src/web_workers/shared/client_message_broker';
|
||||||
|
import {XHR} from 'angular2/src/core/render/xhr';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ElementInjector,
|
ElementInjector,
|
||||||
|
@ -88,3 +89,7 @@ export class SpyPreBuiltObjects extends SpyObject {
|
||||||
export class SpyDomAdapter extends SpyObject {
|
export class SpyDomAdapter extends SpyObject {
|
||||||
constructor() { super(DomAdapter); }
|
constructor() { super(DomAdapter); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class SpyXHR extends SpyObject {
|
||||||
|
constructor() { super(XHR); }
|
||||||
|
}
|
|
@ -6,7 +6,7 @@ import 'package:angular2/src/test_lib/test_lib.dart' show testSetup;
|
||||||
main() {
|
main() {
|
||||||
unit.filterStacks = true;
|
unit.filterStacks = true;
|
||||||
unit.formatStacks = false;
|
unit.formatStacks = false;
|
||||||
unit.unittestConfiguration.timeout = new Duration(milliseconds: 1000);
|
unit.unittestConfiguration.timeout = new Duration(milliseconds: 2000);
|
||||||
|
|
||||||
_printWarnings();
|
_printWarnings();
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue