refactor(compiler): cleanup and preparation for integration

- Rename `DirectiveMetadata` into `CompileDirectiveMetadata`, merge
  with `NormalizedDirectiveMetadata` and remove `ChangeDetectionMetadata`
- Store change detector factories not as array but
  directly at the `CompiledTemplate` or the embedded template
  to make instantiation easier later on
- Already analyze variable values and map them
  to `Directive.exportAs`
- Keep the directive sort order as specified in the
  `@View()` annotation
- Allow to clear the runtime cache in `StyleCompiler`
  and `TemplateCompiler`
- Ignore `script` elements to match the semantics of the
  current compiler
- Make all components dynamically loadable and remove
  the previously introduced property `@Component#dynamicLoadable`
  for now until we find a better option to configure this
- Don’t allow to specify bindings in `@View#directives` and `@View#pipes` as this was never supported by the transformer (see below for the breaking change)

BREAKING CHANGE:
- don't support DI bindings in `@View#directives` and `@View@pipes` any more in preparation of integrating the new compiler. Use `@Directive#bindings` to reexport directives under a different token instead.

Part of #3605
Closes #4314
This commit is contained in:
Tobias Bosch
2015-09-18 10:33:23 -07:00
parent eb7839e0ec
commit cc0c30484f
37 changed files with 1480 additions and 1167 deletions

View File

@ -14,9 +14,8 @@ import {
} from 'angular2/test_lib';
import {MapWrapper} from 'angular2/src/core/facade/collection';
import {
NormalizedDirectiveMetadata,
TypeMetadata,
ChangeDetectionMetadata
CompileDirectiveMetadata,
CompileTypeMetadata
} from 'angular2/src/compiler/directive_metadata';
import {TemplateParser} from 'angular2/src/compiler/template_parser';
import {
@ -60,10 +59,10 @@ export function main() {
pipes = new TestPipes();
}));
function createChangeDetector(template: string, directives: NormalizedDirectiveMetadata[],
function createChangeDetector(template: string, directives: CompileDirectiveMetadata[],
protoViewIndex: number = 0): ChangeDetector {
var protoChangeDetectors =
createChangeDetectorDefinitions(new TypeMetadata({name: 'SomeComp'}),
createChangeDetectorDefinitions(new CompileTypeMetadata({name: 'SomeComp'}),
ChangeDetectionStrategy.Default,
new ChangeDetectorGenConfig(true, true, false, false),
parser.parse(template, directives, 'TestComp'))
@ -97,6 +96,14 @@ export function main() {
expect(context.eventLog).toEqual(['click']);
});
it('should handle events with targets', () => {
var changeDetector = createChangeDetector('<div (window:click)="onEvent($event)">', [], 0);
eventLocals.set('$event', 'click');
changeDetector.handleEvent('window:click', 0, eventLocals);
expect(context.eventLog).toEqual(['click']);
});
it('should watch variables', () => {
var changeDetector = createChangeDetector('<div #some-var [el-prop]="someVar">', [], 0);
@ -106,10 +113,10 @@ export function main() {
});
it('should write directive properties', () => {
var dirMeta = new NormalizedDirectiveMetadata({
type: new TypeMetadata({name: 'SomeDir'}),
selector: 'div',
changeDetection: new ChangeDetectionMetadata({properties: ['dirProp']})
var dirMeta = CompileDirectiveMetadata.create({
type: new CompileTypeMetadata({name: 'SomeDir'}),
selector: '[dir-prop]',
properties: ['dirProp']
});
var changeDetector = createChangeDetector('<div [dir-prop]="someProp">', [dirMeta], 0);
@ -119,11 +126,25 @@ export function main() {
expect(directive.dirProp).toEqual('someValue');
});
it('should write template directive properties', () => {
var dirMeta = CompileDirectiveMetadata.create({
type: new CompileTypeMetadata({name: 'SomeDir'}),
selector: '[dir-prop]',
properties: ['dirProp']
});
var changeDetector = createChangeDetector('<template [dir-prop]="someProp">', [dirMeta], 0);
context.someProp = 'someValue';
changeDetector.detectChanges();
expect(directive.dirProp).toEqual('someValue');
});
it('should watch directive host properties', () => {
var dirMeta = new NormalizedDirectiveMetadata({
type: new TypeMetadata({name: 'SomeDir'}),
var dirMeta = CompileDirectiveMetadata.create({
type: new CompileTypeMetadata({name: 'SomeDir'}),
selector: 'div',
changeDetection: new ChangeDetectionMetadata({hostProperties: {'elProp': 'dirProp'}})
host: {'[elProp]': 'dirProp'}
});
var changeDetector = createChangeDetector('<div>', [dirMeta], 0);
@ -134,11 +155,10 @@ export function main() {
});
it('should handle directive events', () => {
var dirMeta = new NormalizedDirectiveMetadata({
type: new TypeMetadata({name: 'SomeDir'}),
var dirMeta = CompileDirectiveMetadata.create({
type: new CompileTypeMetadata({name: 'SomeDir'}),
selector: 'div',
changeDetection:
new ChangeDetectionMetadata({hostListeners: {'click': 'onEvent($event)'}})
host: {'(click)': 'onEvent($event)'}
});
var changeDetector = createChangeDetector('<div>', [dirMeta], 0);

View File

@ -21,11 +21,15 @@ import {Promise} from 'angular2/src/core/facade/async';
import {ChangeDetectionCompiler} from 'angular2/src/compiler/change_detector_compiler';
import {
NormalizedDirectiveMetadata,
TypeMetadata,
ChangeDetectionMetadata
CompileDirectiveMetadata,
CompileTypeMetadata
} from 'angular2/src/compiler/directive_metadata';
import {SourceModule, SourceExpression, moduleRef} from 'angular2/src/compiler/source_module';
import {
SourceModule,
SourceExpression,
SourceExpressions,
moduleRef
} from 'angular2/src/compiler/source_module';
import {TemplateParser} from 'angular2/src/compiler/template_parser';
@ -63,8 +67,8 @@ export function main() {
describe('compileComponentRuntime', () => {
function detectChanges(compiler: ChangeDetectionCompiler, template: string,
directives: NormalizedDirectiveMetadata[] = CONST_EXPR([])): string[] {
var type = new TypeMetadata({name: 'SomeComp'});
directives: CompileDirectiveMetadata[] = CONST_EXPR([])): string[] {
var type = new CompileTypeMetadata({name: 'SomeComp'});
var parsedTemplate = parser.parse(template, directives, 'TestComp');
var factories =
compiler.compileComponentRuntime(type, ChangeDetectionStrategy.Default, parsedTemplate);
@ -99,13 +103,13 @@ export function main() {
describe('compileComponentCodeGen', () => {
function detectChanges(compiler: ChangeDetectionCompiler, template: string,
directives: NormalizedDirectiveMetadata[] = CONST_EXPR([])):
directives: CompileDirectiveMetadata[] = CONST_EXPR([])):
Promise<string[]> {
var type = new TypeMetadata({name: 'SomeComp'});
var type = new CompileTypeMetadata({name: 'SomeComp'});
var parsedTemplate = parser.parse(template, directives, 'TestComp');
var sourceExpression =
var sourceExpressions =
compiler.compileComponentCodeGen(type, ChangeDetectionStrategy.Default, parsedTemplate);
var testableModule = createTestableModule(sourceExpression, 0).getSourceWithImports();
var testableModule = createTestableModule(sourceExpressions, 0).getSourceWithImports();
return evalModule(testableModule.source, testableModule.imports, null);
}
@ -122,9 +126,10 @@ export function main() {
});
}
function createTestableModule(source: SourceExpression, changeDetectorIndex: number): SourceModule {
function createTestableModule(source: SourceExpressions, changeDetectorIndex: number):
SourceModule {
var resultExpression =
`${THIS_MODULE_REF}testChangeDetector((${source.expression})[${changeDetectorIndex}])`;
`${THIS_MODULE_REF}testChangeDetector(([${source.expressions.join(',')}])[${changeDetectorIndex}])`;
var testableSource = `${source.declarations.join('\n')}
${codeGenExportVariable('run')}${codeGenValueFn(['_'], resultExpression)};`;
return new SourceModule(null, testableSource);

View File

@ -29,9 +29,9 @@ import {
} from 'angular2/src/core/compiler/template_commands';
import {CommandCompiler} from 'angular2/src/compiler/command_compiler';
import {
NormalizedDirectiveMetadata,
TypeMetadata,
NormalizedTemplateMetadata
CompileDirectiveMetadata,
CompileTypeMetadata,
CompileTemplateMetadata
} from 'angular2/src/compiler/directive_metadata';
import {SourceModule, SourceExpression, moduleRef} from 'angular2/src/compiler/source_module';
import {ViewEncapsulation} from 'angular2/src/core/render/api';
@ -61,12 +61,12 @@ export class RootComp {}
export class SomeDir {}
export class AComp {}
var RootCompTypeMeta =
new TypeMetadata({id: 1, name: 'RootComp', runtime: RootComp, moduleId: THIS_MODULE_NAME});
var RootCompTypeMeta = new CompileTypeMetadata(
{id: 1, name: 'RootComp', runtime: RootComp, moduleId: THIS_MODULE_NAME});
var SomeDirTypeMeta =
new TypeMetadata({id: 2, name: 'SomeDir', runtime: SomeDir, moduleId: THIS_MODULE_NAME});
new CompileTypeMetadata({id: 2, name: 'SomeDir', runtime: SomeDir, moduleId: THIS_MODULE_NAME});
var ACompTypeMeta =
new TypeMetadata({id: 3, name: 'AComp', runtime: AComp, moduleId: THIS_MODULE_NAME});
new CompileTypeMetadata({id: 3, name: 'AComp', runtime: AComp, moduleId: THIS_MODULE_NAME});
var NESTED_COMPONENT = new CompiledTemplate(45, () => []);
@ -84,12 +84,12 @@ export function main() {
}));
function createComp({type, selector, template, encapsulation, ngContentSelectors}: {
type?: TypeMetadata,
type?: CompileTypeMetadata,
selector?: string,
template?: string,
encapsulation?: ViewEncapsulation,
ngContentSelectors?: string[]
}): NormalizedDirectiveMetadata {
}): CompileDirectiveMetadata {
if (isBlank(encapsulation)) {
encapsulation = ViewEncapsulation.None;
}
@ -102,11 +102,11 @@ export function main() {
if (isBlank(template)) {
template = '';
}
return new NormalizedDirectiveMetadata({
return CompileDirectiveMetadata.create({
selector: selector,
isComponent: true,
type: type,
template: new NormalizedTemplateMetadata({
template: new CompileTemplateMetadata({
template: template,
ngContentSelectors: ngContentSelectors,
encapsulation: encapsulation
@ -114,8 +114,10 @@ export function main() {
});
}
function createDirective(type: TypeMetadata, selector: string): NormalizedDirectiveMetadata {
return new NormalizedDirectiveMetadata({selector: selector, isComponent: false, type: type});
function createDirective(type: CompileTypeMetadata, selector: string, exportAs: string = null):
CompileDirectiveMetadata {
return CompileDirectiveMetadata.create(
{selector: selector, exportAs: exportAs, isComponent: false, type: type});
}
@ -159,18 +161,47 @@ export function main() {
it('should create bound element commands', inject([AsyncTestCompleter], (async) => {
var rootComp = createComp({
type: RootCompTypeMeta,
template: '<div a="b" #some-var="someValue" (click)="someHandler">'
template: '<div a="b" #some-var (click)="someHandler" (window:scroll)="scrollTo()">'
});
var dir = createDirective(SomeDirTypeMeta, '[a]');
run(rootComp, [dir])
run(rootComp, [])
.then((data) => {
expect(data).toEqual([
[
BEGIN_ELEMENT,
'div',
['a', 'b'],
['click'],
['someVar', 'someValue'],
[null, 'click', 'window', 'scroll'],
['someVar', '%implicit'],
[],
true,
null
],
[END_ELEMENT]
]);
async.done();
});
}));
it('should create element commands with directives',
inject([AsyncTestCompleter], (async) => {
var rootComp =
createComp({type: RootCompTypeMeta, template: '<div a #some-var="someExport">'});
var dir = CompileDirectiveMetadata.create({
selector: '[a]',
exportAs: 'someExport',
isComponent: false,
type: SomeDirTypeMeta,
host: {'(click)': 'doIt()', '(window:scroll)': 'doIt()', 'role': 'button'}
});
run(rootComp, [dir])
.then((data) => {
expect(data).toEqual([
[
BEGIN_ELEMENT,
'div',
['a', '', 'role', 'button'],
[null, 'click', 'window', 'scroll'],
['someVar', 0],
['SomeDirType'],
true,
null
@ -214,10 +245,8 @@ export function main() {
describe('components', () => {
it('should create component commands', inject([AsyncTestCompleter], (async) => {
var rootComp = createComp({
type: RootCompTypeMeta,
template: '<a a="b" #some-var="someValue" (click)="someHandler">'
});
var rootComp = createComp(
{type: RootCompTypeMeta, template: '<a a="b" #some-var (click)="someHandler">'});
var comp = createComp({type: ACompTypeMeta, selector: 'a'});
run(rootComp, [comp])
.then((data) => {
@ -226,8 +255,8 @@ export function main() {
BEGIN_COMPONENT,
'a',
['a', 'b'],
['click'],
['someVar', 'someValue'],
[null, 'click'],
['someVar', 0],
['ACompType'],
false,
null,
@ -305,7 +334,7 @@ export function main() {
template: '<template a="b" #some-var="someValue"></template>'
});
var dir = createDirective(SomeDirTypeMeta, '[a]');
run(rootComp, [dir])
run(rootComp, [dir], 1)
.then((data) => {
expect(data).toEqual([
[
@ -315,6 +344,7 @@ export function main() {
['SomeDirType'],
false,
null,
'cd1',
[]
]
]);
@ -325,10 +355,20 @@ export function main() {
it('should created nested nodes', inject([AsyncTestCompleter], (async) => {
var rootComp =
createComp({type: RootCompTypeMeta, template: '<template>t</template>'});
run(rootComp, [])
run(rootComp, [], 1)
.then((data) => {
expect(data).toEqual(
[[EMBEDDED_TEMPLATE, [], [], [], false, null, [[TEXT, 't', false, null]]]]);
expect(data).toEqual([
[
EMBEDDED_TEMPLATE,
[],
[],
[],
false,
null,
'cd1',
[[TEXT, 't', false, null]]
]
]);
async.done();
});
}));
@ -339,10 +379,10 @@ export function main() {
type: RootCompTypeMeta,
template: '<template><ng-content></ng-content></template>'
});
run(rootComp, [])
run(rootComp, [], 1)
.then((data) => {
expect(data).toEqual(
[[EMBEDDED_TEMPLATE, [], [], [], true, null, [[NG_CONTENT, null]]]]);
[[EMBEDDED_TEMPLATE, [], [], [], true, null, 'cd1', [[NG_CONTENT, null]]]]);
async.done();
});
}));
@ -364,17 +404,21 @@ export function main() {
describe('compileComponentRuntime', () => {
beforeEach(() => {
componentTemplateFactory = (directive: NormalizedDirectiveMetadata) => {
componentTemplateFactory = (directive: CompileDirectiveMetadata) => {
return new CompiledTemplate(directive.type.id, () => []);
};
});
function run(component: NormalizedDirectiveMetadata,
directives: NormalizedDirectiveMetadata[]): Promise<any[][]> {
function run(component: CompileDirectiveMetadata, directives: CompileDirectiveMetadata[],
embeddedTemplateCount: number = 0): Promise<any[][]> {
var changeDetectorFactories = [];
for (var i = 0; i < embeddedTemplateCount + 1; i++) {
(function(i) { changeDetectorFactories.push((_) => `cd${i}`); })(i);
}
var parsedTemplate =
parser.parse(component.template.template, directives, component.type.name);
var commands = commandCompiler.compileComponentRuntime(component, parsedTemplate,
componentTemplateFactory);
var commands = commandCompiler.compileComponentRuntime(
component, parsedTemplate, changeDetectorFactories, componentTemplateFactory);
return PromiseWrapper.resolve(humanize(commands));
}
@ -384,17 +428,21 @@ export function main() {
describe('compileComponentCodeGen', () => {
beforeEach(() => {
componentTemplateFactory = (directive: NormalizedDirectiveMetadata) => {
componentTemplateFactory = (directive: CompileDirectiveMetadata) => {
return `new ${TEMPLATE_COMMANDS_MODULE_REF}CompiledTemplate(${directive.type.id}, ${codeGenValueFn([], '{}')})`;
};
});
function run(component: NormalizedDirectiveMetadata,
directives: NormalizedDirectiveMetadata[]): Promise<any[][]> {
function run(component: CompileDirectiveMetadata, directives: CompileDirectiveMetadata[],
embeddedTemplateCount: number = 0): Promise<any[][]> {
var changeDetectorFactoryExpressions = [];
for (var i = 0; i < embeddedTemplateCount + 1; i++) {
changeDetectorFactoryExpressions.push(codeGenValueFn(['_'], `'cd${i}'`));
}
var parsedTemplate =
parser.parse(component.template.template, directives, component.type.name);
var sourceModule = commandCompiler.compileComponentCodeGen(component, parsedTemplate,
componentTemplateFactory);
var sourceModule = commandCompiler.compileComponentCodeGen(
component, parsedTemplate, changeDetectorFactoryExpressions, componentTemplateFactory);
var testableModule = createTestableModule(sourceModule).getSourceWithImports();
return evalModule(testableModule.source, testableModule.imports, null);
}
@ -432,7 +480,7 @@ class CommandHumanizer implements CommandVisitor {
BEGIN_ELEMENT,
cmd.name,
cmd.attrNameAndValues,
cmd.eventNames,
cmd.eventTargetAndNames,
cmd.variableNameAndValues,
cmd.directives.map(checkAndStringifyType),
cmd.isBound,
@ -449,12 +497,11 @@ class CommandHumanizer implements CommandVisitor {
BEGIN_COMPONENT,
cmd.name,
cmd.attrNameAndValues,
cmd.eventNames,
cmd.eventTargetAndNames,
cmd.variableNameAndValues,
cmd.directives.map(checkAndStringifyType),
cmd.nativeShadow,
cmd.ngContentIndex,
// TODO humanizeTemplate(cmd.template)
cmd.template.id
]);
return null;
@ -471,6 +518,7 @@ class CommandHumanizer implements CommandVisitor {
cmd.directives.map(checkAndStringifyType),
cmd.isMerged,
cmd.ngContentIndex,
cmd.changeDetectorFactory(null),
humanize(cmd.children)
]);
return null;

View File

@ -13,98 +13,76 @@ import {
} from 'angular2/test_lib';
import {
NormalizedDirectiveMetadata,
TypeMetadata,
NormalizedTemplateMetadata,
ChangeDetectionMetadata
CompileDirectiveMetadata,
CompileTypeMetadata,
CompileTemplateMetadata
} from 'angular2/src/compiler/directive_metadata';
import {ViewEncapsulation} from 'angular2/src/core/render/api';
import {ChangeDetectionStrategy} from 'angular2/src/core/change_detection';
import {LifecycleHooks} from 'angular2/src/core/compiler/interfaces';
export function main() {
describe('DirectiveMetadata', () => {
var fullTypeMeta: TypeMetadata;
var fullTemplateMeta: NormalizedTemplateMetadata;
var fullChangeDetectionMeta: ChangeDetectionMetadata;
var fullDirectiveMeta: NormalizedDirectiveMetadata;
var fullTypeMeta: CompileTypeMetadata;
var fullTemplateMeta: CompileTemplateMetadata;
var fullDirectiveMeta: CompileDirectiveMetadata;
beforeEach(() => {
fullTypeMeta = new TypeMetadata({id: 23, name: 'SomeType', moduleId: 'someUrl'});
fullTemplateMeta = new NormalizedTemplateMetadata({
fullTypeMeta = new CompileTypeMetadata({id: 23, name: 'SomeType', moduleId: 'someUrl'});
fullTemplateMeta = new CompileTemplateMetadata({
encapsulation: ViewEncapsulation.Emulated,
template: '<a></a>',
templateUrl: 'someTemplateUrl',
styles: ['someStyle'],
styleAbsUrls: ['someStyleUrl'],
hostAttributes: {'attr1': 'attrValue2'},
styleUrls: ['someStyleUrl'],
ngContentSelectors: ['*']
});
fullChangeDetectionMeta = new ChangeDetectionMetadata({
changeDetection: ChangeDetectionStrategy.Default,
properties: ['someProp'],
events: ['someEvent'],
hostListeners: {'event1': 'handler1'},
hostProperties: {'prop1': 'expr1'},
callAfterContentInit: true,
callAfterContentChecked: true,
callAfterViewInit: true,
callAfterViewChecked: true,
callOnChanges: true,
callDoCheck: true,
callOnInit: true
});
fullDirectiveMeta = new NormalizedDirectiveMetadata({
fullDirectiveMeta = CompileDirectiveMetadata.create({
selector: 'someSelector',
isComponent: true,
dynamicLoadable: true,
type: fullTypeMeta, template: fullTemplateMeta,
changeDetection: fullChangeDetectionMeta,
changeDetection: ChangeDetectionStrategy.Default,
properties: ['someProp'],
events: ['someEvent'],
host: {'(event1)': 'handler1', '[prop1]': 'expr1', 'attr1': 'attrValue2'},
lifecycleHooks: [LifecycleHooks.OnChanges]
});
});
describe('DirectiveMetadata', () => {
it('should serialize with full data', () => {
expect(NormalizedDirectiveMetadata.fromJson(fullDirectiveMeta.toJson()))
expect(CompileDirectiveMetadata.fromJson(fullDirectiveMeta.toJson()))
.toEqual(fullDirectiveMeta);
});
it('should serialize with no data', () => {
var empty = new NormalizedDirectiveMetadata();
expect(NormalizedDirectiveMetadata.fromJson(empty.toJson())).toEqual(empty);
var empty = CompileDirectiveMetadata.create();
expect(CompileDirectiveMetadata.fromJson(empty.toJson())).toEqual(empty);
});
});
describe('TypeMetadata', () => {
it('should serialize with full data',
() => { expect(TypeMetadata.fromJson(fullTypeMeta.toJson())).toEqual(fullTypeMeta); });
it('should serialize with full data', () => {
expect(CompileTypeMetadata.fromJson(fullTypeMeta.toJson())).toEqual(fullTypeMeta);
});
it('should serialize with no data', () => {
var empty = new TypeMetadata();
expect(TypeMetadata.fromJson(empty.toJson())).toEqual(empty);
var empty = new CompileTypeMetadata();
expect(CompileTypeMetadata.fromJson(empty.toJson())).toEqual(empty);
});
});
describe('TemplateMetadata', () => {
it('should serialize with full data', () => {
expect(NormalizedTemplateMetadata.fromJson(fullTemplateMeta.toJson()))
expect(CompileTemplateMetadata.fromJson(fullTemplateMeta.toJson()))
.toEqual(fullTemplateMeta);
});
it('should serialize with no data', () => {
var empty = new NormalizedTemplateMetadata();
expect(NormalizedTemplateMetadata.fromJson(empty.toJson())).toEqual(empty);
});
});
describe('ChangeDetectionMetadata', () => {
it('should serialize with full data', () => {
expect(ChangeDetectionMetadata.fromJson(fullChangeDetectionMeta.toJson()))
.toEqual(fullChangeDetectionMeta);
});
it('should serialize with no data', () => {
var empty = new ChangeDetectionMetadata();
expect(ChangeDetectionMetadata.fromJson(empty.toJson())).toEqual(empty);
var empty = new CompileTemplateMetadata();
expect(CompileTemplateMetadata.fromJson(empty.toJson())).toEqual(empty);
});
});
});

View File

@ -80,22 +80,6 @@ export function main() {
]);
});
});
describe('ng-non-bindable', () => {
it('should ignore text nodes and elements inside of elements with ng-non-bindable', () => {
expect(humanizeDom(
parser.parse('<div ng-non-bindable>hello<span></span></div>', 'TestComp')))
.toEqual([
[HtmlElementAst, 'div', 'TestComp > div:nth-child(0)'],
[
HtmlAttrAst,
'ng-non-bindable',
'',
'TestComp > div:nth-child(0)[ng-non-bindable=]'
]
]);
});
});
});
describe('unparse', () => {

View File

@ -15,6 +15,7 @@ import {
import {stringify} from 'angular2/src/core/facade/lang';
import {RuntimeMetadataResolver} from 'angular2/src/compiler/runtime_metadata';
import {LifecycleHooks, LIFECYCLE_HOOKS_VALUES} from 'angular2/src/core/compiler/interfaces';
import {
Component,
View,
@ -43,27 +44,20 @@ export function main() {
inject([RuntimeMetadataResolver], (resolver: RuntimeMetadataResolver) => {
var meta = resolver.getMetadata(ComponentWithEverything);
expect(meta.selector).toEqual('someSelector');
expect(meta.exportAs).toEqual('someExportAs');
expect(meta.isComponent).toBe(true);
expect(meta.dynamicLoadable).toBe(true);
expect(meta.type.runtime).toBe(ComponentWithEverything);
expect(meta.type.name).toEqual(stringify(ComponentWithEverything));
expect(meta.type.moduleId).toEqual('someModuleId');
expect(meta.changeDetection.callAfterContentChecked).toBe(true);
expect(meta.changeDetection.callAfterContentInit).toBe(true);
expect(meta.changeDetection.callAfterViewChecked).toBe(true);
expect(meta.changeDetection.callAfterViewInit).toBe(true);
expect(meta.changeDetection.callDoCheck).toBe(true);
expect(meta.changeDetection.callOnChanges).toBe(true);
expect(meta.changeDetection.callOnInit).toBe(true);
expect(meta.changeDetection.changeDetection).toBe(ChangeDetectionStrategy.CheckAlways);
expect(meta.changeDetection.properties).toEqual(['someProp']);
expect(meta.changeDetection.events).toEqual(['someEvent']);
expect(meta.changeDetection.hostListeners)
.toEqual({'someHostListener': 'someHostListenerExpr'});
expect(meta.changeDetection.hostProperties)
.toEqual({'someHostProp': 'someHostPropExpr'});
expect(meta.lifecycleHooks).toEqual(LIFECYCLE_HOOKS_VALUES);
expect(meta.changeDetection).toBe(ChangeDetectionStrategy.CheckAlways);
expect(meta.properties).toEqual({'someProp': 'someProp'});
expect(meta.events).toEqual({'someEvent': 'someEvent'});
expect(meta.hostListeners).toEqual({'someHostListener': 'someHostListenerExpr'});
expect(meta.hostProperties).toEqual({'someHostProp': 'someHostPropExpr'});
expect(meta.hostAttributes).toEqual({'someHostAttr': 'someHostAttrValue'});
expect(meta.template.encapsulation).toBe(ViewEncapsulation.Emulated);
expect(meta.template.hostAttributes).toEqual({'someHostAttr': 'someHostAttrValue'});
expect(meta.template.styles).toEqual(['someStyle']);
expect(meta.template.styleUrls).toEqual(['someStyleUrl']);
expect(meta.template.template).toEqual('someTemplate');
@ -105,7 +99,7 @@ class DirectiveWithoutModuleId {
'(someHostListener)': 'someHostListenerExpr',
'someHostAttr': 'someHostAttrValue'
},
dynamicLoadable: true,
exportAs: 'someExportAs',
moduleId: 'someModuleId',
changeDetection: ChangeDetectionStrategy.CheckAlways
})

View File

@ -17,14 +17,14 @@ import {SpyXHR} from '../core/spies';
import {XHR} from 'angular2/src/core/render/xhr';
import {BaseException, WrappedException} from 'angular2/src/core/facade/exceptions';
import {CONST_EXPR, isPresent, StringWrapper} from 'angular2/src/core/facade/lang';
import {CONST_EXPR, isPresent, isBlank, StringWrapper} 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 {
NormalizedDirectiveMetadata,
NormalizedTemplateMetadata,
TypeMetadata
CompileDirectiveMetadata,
CompileTemplateMetadata,
CompileTypeMetadata
} from 'angular2/src/compiler/directive_metadata';
import {SourceExpression, SourceModule} from 'angular2/src/compiler/source_module';
import {ViewEncapsulation} from 'angular2/src/core/render/api';
@ -52,152 +52,249 @@ export function main() {
beforeEach(inject([StyleCompiler], (_compiler) => { compiler = _compiler; }));
function comp(styles: string[], styleAbsUrls: string[], encapsulation: ViewEncapsulation):
NormalizedDirectiveMetadata {
return new NormalizedDirectiveMetadata({
type: new TypeMetadata({id: 23, moduleId: 'someUrl'}),
template: new NormalizedTemplateMetadata(
{styles: styles, styleAbsUrls: styleAbsUrls, encapsulation: encapsulation})
CompileDirectiveMetadata {
return CompileDirectiveMetadata.create({
type: new CompileTypeMetadata({id: 23, moduleId: 'someUrl'}),
template: new CompileTemplateMetadata(
{styles: styles, styleUrls: 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) => {
compareStyles(value, expectedStyles);
async.done();
});
var xhrUrlResults;
var xhrCount;
beforeEach(() => {
xhrCount = 0;
xhrUrlResults = {};
xhrUrlResults[IMPORT_ABS_MODULE_NAME] = 'span {color: blue}';
xhrUrlResults[IMPORT_ABS_MODULE_NAME_WITH_IMPORT] =
`a {color: green}@import ${IMPORT_REL_MODULE_NAME};`;
});
function compile(styles: string[], styleAbsUrls: string[], encapsulation: ViewEncapsulation):
Promise<string[]> {
// Note: Can't use MockXHR as the xhr is called recursively,
// so we can't trigger flush.
xhr.spy('get').andCallFake((url) => {
var response = xhrUrlResults[url];
xhrCount++;
if (isBlank(response)) {
throw new BaseException(`Unexpected url ${url}`);
}
return PromiseWrapper.resolve(response);
});
return compiler.compileComponentRuntime(comp(styles, styleAbsUrls, encapsulation));
}
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 compile plain css rules', inject([AsyncTestCompleter], (async) => {
compile(['div {color: red}', 'span {color: blue}'], [], encapsulation)
.then(styles => {
expect(styles).toEqual(['div {color: red}', 'span {color: blue}']);
async.done();
});
}));
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', inject([AsyncTestCompleter], (async) => {
compile(['div {color: red}'], [IMPORT_ABS_MODULE_NAME], encapsulation)
.then(styles => {
expect(styles).toEqual(['div {color: red}', 'span {color: blue}']);
async.done();
});
}));
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}']));
it('should allow to import rules transitively', inject([AsyncTestCompleter], (async) => {
compile(['div {color: red}'], [IMPORT_ABS_MODULE_NAME_WITH_IMPORT], encapsulation)
.then(styles => {
expect(styles)
.toEqual(['div {color: red}', 'a {color: green}', 'span {color: blue}']);
async.done();
});
}));
});
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 compile plain css rules', inject([AsyncTestCompleter], (async) => {
compile(['div {\ncolor: red;\n}', 'span {\ncolor: blue;\n}'], [], encapsulation)
.then(styles => {
expect(styles).toEqual([
'div[_ngcontent-23] {\ncolor: red;\n}',
'span[_ngcontent-23] {\ncolor: blue;\n}'
]);
async.done();
});
}));
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', inject([AsyncTestCompleter], (async) => {
compile(['div {\ncolor: red;\n}'], [IMPORT_ABS_MODULE_NAME], encapsulation)
.then(styles => {
expect(styles).toEqual([
'div[_ngcontent-23] {\ncolor: red;\n}',
'span[_ngcontent-23] {\ncolor: blue;\n}'
]);
async.done();
});
}));
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}'
]));
it('should allow to import rules transitively', inject([AsyncTestCompleter], (async) => {
compile(['div {\ncolor: red;\n}'], [IMPORT_ABS_MODULE_NAME_WITH_IMPORT], encapsulation)
.then(styles => {
expect(styles).toEqual([
'div[_ngcontent-23] {\ncolor: red;\n}',
'a[_ngcontent-23] {\ncolor: green;\n}',
'span[_ngcontent-23] {\ncolor: blue;\n}'
]);
async.done();
});
}));
});
it('should cache stylesheets for parallel requests', inject([AsyncTestCompleter], (async) => {
PromiseWrapper.all([
compile([], [IMPORT_ABS_MODULE_NAME], ViewEncapsulation.None),
compile([], [IMPORT_ABS_MODULE_NAME], ViewEncapsulation.None)
])
.then((styleArrays) => {
expect(styleArrays[0]).toEqual(['span {color: blue}']);
expect(styleArrays[1]).toEqual(['span {color: blue}']);
expect(xhrCount).toBe(1);
async.done();
});
}));
it('should cache stylesheets for serial requests', inject([AsyncTestCompleter], (async) => {
compile([], [IMPORT_ABS_MODULE_NAME], ViewEncapsulation.None)
.then((styles0) => {
xhrUrlResults[IMPORT_ABS_MODULE_NAME] = 'span {color: black}';
return compile([], [IMPORT_ABS_MODULE_NAME], ViewEncapsulation.None)
.then((styles1) => {
expect(styles0).toEqual(['span {color: blue}']);
expect(styles1).toEqual(['span {color: blue}']);
expect(xhrCount).toBe(1);
async.done();
});
});
}));
it('should allow to clear the cache', inject([AsyncTestCompleter], (async) => {
compile([], [IMPORT_ABS_MODULE_NAME], ViewEncapsulation.None)
.then((_) => {
compiler.clearCache();
xhrUrlResults[IMPORT_ABS_MODULE_NAME] = 'span {color: black}';
return compile([], [IMPORT_ABS_MODULE_NAME], ViewEncapsulation.None);
})
.then((styles) => {
expect(xhrCount).toBe(2);
expect(styles).toEqual(['span {color: black}']);
async.done();
});
}));
});
describe('compileComponentCodeGen', () => {
function runTest(styles: string[], styleAbsUrls: string[], encapsulation: ViewEncapsulation,
expectedStyles: string[]) {
return inject([AsyncTestCompleter], (async) => {
var sourceExpression =
compiler.compileComponentCodeGen(comp(styles, styleAbsUrls, encapsulation));
var sourceWithImports = testableExpression(sourceExpression).getSourceWithImports();
evalModule(sourceWithImports.source, sourceWithImports.imports, null)
.then((value) => {
compareStyles(value, expectedStyles);
async.done();
});
});
}
function compile(styles: string[], styleAbsUrls: string[], encapsulation: ViewEncapsulation):
Promise<string[]> {
var sourceExpression =
compiler.compileComponentCodeGen(comp(styles, styleAbsUrls, encapsulation));
var sourceWithImports = testableExpression(sourceExpression).getSourceWithImports();
return evalModule(sourceWithImports.source, sourceWithImports.imports, null);
};
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 plain css ruless', inject([AsyncTestCompleter], (async) => {
compile(['div {color: red}', 'span {color: blue}'], [], encapsulation)
.then(styles => {
expect(styles).toEqual(['div {color: red}', 'span {color: blue}']);
async.done();
});
}));
it('should compile css rules with newlines and quotes',
runTest(['div\n{"color": \'red\'}'], [], encapsulation, ['div\n{"color": \'red\'}']));
inject([AsyncTestCompleter], (async) => {
compile(['div\n{"color": \'red\'}'], [], encapsulation)
.then(styles => {
expect(styles).toEqual(['div\n{"color": \'red\'}']);
async.done();
});
}));
it('should allow to import rules',
runTest(['div {color: red}'], [IMPORT_ABS_MODULE_NAME], encapsulation,
['div {color: red}', 'span {color: blue}']),
1000);
it('should allow to import rules', inject([AsyncTestCompleter], (async) => {
compile(['div {color: red}'], [IMPORT_ABS_MODULE_NAME], encapsulation)
.then(styles => {
expect(styles).toEqual(['div {color: red}', 'span {color: blue}']);
async.done();
});
}));
});
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 compile plain css ruless', inject([AsyncTestCompleter], (async) => {
compile(['div {\ncolor: red;\n}', 'span {\ncolor: blue;\n}'], [], encapsulation)
.then(styles => {
expect(styles).toEqual([
'div[_ngcontent-23] {\ncolor: red;\n}',
'span[_ngcontent-23] {\ncolor: blue;\n}'
]);
async.done();
});
}));
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}']),
1000);
it('should allow to import rules', inject([AsyncTestCompleter], (async) => {
compile(['div {color: red}'], [IMPORT_ABS_MODULE_NAME], encapsulation)
.then(styles => {
expect(styles).toEqual([
'div[_ngcontent-23] {\ncolor: red;\n}',
'span[_ngcontent-23] {\ncolor: blue;\n}'
]);
async.done();
});
}));
});
});
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 => {
var sourceWithImports =
testableModule(sourceModule).getSourceWithImports();
return evalModule(sourceWithImports.source, sourceWithImports.imports,
null);
}))
.then((values) => {
compareStyles(values[0], expectedStyles);
compareStyles(values[1], expectedShimStyles);
async.done();
});
});
function compile(style: string): Promise<string[]> {
var sourceModules = compiler.compileStylesheetCodeGen(MODULE_NAME, style);
return PromiseWrapper.all(sourceModules.map(sourceModule => {
var sourceWithImports = testableModule(sourceModule).getSourceWithImports();
return evalModule(sourceWithImports.source, sourceWithImports.imports, null);
}));
}
it('should compile plain css rules', runTest('div {color: red;}', ['div {color: red;}'],
['div[_ngcontent-%COMP%] {\ncolor: red;\n}']));
it('should compile plain css rules', inject([AsyncTestCompleter], (async) => {
compile('div {color: red;}')
.then(stylesAndShimStyles => {
expect(stylesAndShimStyles)
.toEqual(
[['div {color: red;}'], ['div[_ngcontent-%COMP%] {\ncolor: red;\n}']]);
async.done();
});
}));
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}'
]));
inject([AsyncTestCompleter], (async) => {
compile(`div {color: red}@import ${IMPORT_REL_MODULE_NAME};`)
.then(stylesAndShimStyles => {
expect(stylesAndShimStyles)
.toEqual([
['div {color: red}', 'span {color: blue}'],
[
'div[_ngcontent-%COMP%] {\ncolor: red;\n}',
'span[_ngcontent-%COMP%] {\ncolor: blue;\n}'
]
]);
async.done();
});
}));
});
});
}

View File

@ -21,11 +21,7 @@ import {
TemplateCompiler,
NormalizedComponentWithViewDirectives
} from 'angular2/src/compiler/template_compiler';
import {
DirectiveMetadata,
NormalizedDirectiveMetadata,
INormalizedDirectiveMetadata
} from 'angular2/src/compiler/directive_metadata';
import {CompileDirectiveMetadata} from 'angular2/src/compiler/directive_metadata';
import {evalModule} from './eval_module';
import {SourceModule, moduleRef} from 'angular2/src/compiler/source_module';
import {XHR} from 'angular2/src/core/render/xhr';
@ -104,6 +100,18 @@ export function main() {
async.done();
});
}));
it('should pass the right change detector to embedded templates',
inject([AsyncTestCompleter], (async) => {
compile([CompWithEmbeddedTemplate])
.then((humanizedTemplate) => {
expect(humanizedTemplate['commands'][1]['commands'][0]).toEqual('<template>');
expect(humanizedTemplate['commands'][1]['commands'][1]['cd'])
.toEqual(['elementProperty(href)=someCtxValue']);
async.done();
});
}));
}
describe('compileHostComponentRuntime', () => {
@ -113,28 +121,53 @@ export function main() {
runTests(compile);
it('should cache components', inject([AsyncTestCompleter, XHR], (async, xhr: MockXHR) => {
// we expect only one request!
xhr.expect('angular2/test/compiler/compUrl.html', '');
PromiseWrapper.all([
compiler.compileHostComponentRuntime(CompWithTemplateUrl),
compiler.compileHostComponentRuntime(CompWithTemplateUrl)
])
it('should cache components for parallel requests',
inject([AsyncTestCompleter, XHR], (async, xhr: MockXHR) => {
xhr.expect('angular2/test/compiler/compUrl.html', 'a');
PromiseWrapper.all([compile([CompWithTemplateUrl]), compile([CompWithTemplateUrl])])
.then((humanizedTemplates) => {
expect(humanizedTemplates[0]).toEqual(humanizedTemplates[1]);
expect(humanizedTemplates[0]['commands'][1]['commands']).toEqual(['#text(a)']);
expect(humanizedTemplates[1]['commands'][1]['commands']).toEqual(['#text(a)']);
async.done();
});
xhr.flush();
}));
it('should only allow dynamic loadable components', () => {
expect(() => compiler.compileHostComponentRuntime(PlainDirective))
.toThrowError(
`Could not compile '${stringify(PlainDirective)}' because it is not dynamically loadable.`);
expect(() => compiler.compileHostComponentRuntime(CompWithoutHost))
.toThrowError(
`Could not compile '${stringify(CompWithoutHost)}' because it is not dynamically loadable.`);
});
it('should cache components for sequential requests',
inject([AsyncTestCompleter, XHR], (async, xhr: MockXHR) => {
xhr.expect('angular2/test/compiler/compUrl.html', 'a');
compile([CompWithTemplateUrl])
.then((humanizedTemplate0) => {
return compile([CompWithTemplateUrl])
.then((humanizedTemplate1) => {
expect(humanizedTemplate0['commands'][1]['commands'])
.toEqual(['#text(a)']);
expect(humanizedTemplate1['commands'][1]['commands'])
.toEqual(['#text(a)']);
async.done();
});
});
xhr.flush();
}));
it('should allow to clear the cache',
inject([AsyncTestCompleter, XHR], (async, xhr: MockXHR) => {
xhr.expect('angular2/test/compiler/compUrl.html', 'a');
compile([CompWithTemplateUrl])
.then((humanizedTemplate) => {
compiler.clearCache();
xhr.expect('angular2/test/compiler/compUrl.html', 'b');
var result = compile([CompWithTemplateUrl]);
xhr.flush();
return result;
})
.then((humanizedTemplate) => {
expect(humanizedTemplate['commands'][1]['commands']).toEqual(['#text(b)']);
async.done();
});
xhr.flush();
}));
});
@ -145,7 +178,7 @@ export function main() {
runtimeMetadataResolver.getViewDirectivesMetadata(component));
return PromiseWrapper.all(compAndViewDirMetas.map(
meta => compiler.normalizeDirectiveMetadata(meta)))
.then((normalizedCompAndViewDirMetas: NormalizedDirectiveMetadata[]) =>
.then((normalizedCompAndViewDirMetas: CompileDirectiveMetadata[]) =>
new NormalizedComponentWithViewDirectives(
normalizedCompAndViewDirMetas[0],
normalizedCompAndViewDirMetas.slice(1)));
@ -173,15 +206,15 @@ export function main() {
it('should serialize and deserialize', inject([AsyncTestCompleter], (async) => {
compiler.normalizeDirectiveMetadata(
runtimeMetadataResolver.getMetadata(CompWithBindingsAndStyles))
.then((meta: NormalizedDirectiveMetadata) => {
.then((meta: CompileDirectiveMetadata) => {
var json = compiler.serializeDirectiveMetadata(meta);
expect(isString(json)).toBe(true);
// Note: serializing will clear our the runtime type!
var clonedMeta =
<NormalizedDirectiveMetadata>compiler.deserializeDirectiveMetadata(json);
var clonedMeta = compiler.deserializeDirectiveMetadata(json);
expect(meta.changeDetection).toEqual(clonedMeta.changeDetection);
expect(meta.template).toEqual(clonedMeta.template);
expect(meta.selector).toEqual(clonedMeta.selector);
expect(meta.exportAs).toEqual(clonedMeta.exportAs);
expect(meta.type.name).toEqual(clonedMeta.type.name);
async.done();
});
@ -194,7 +227,7 @@ export function main() {
xhr.expect('angular2/test/compiler/compUrl.html', 'loadedTemplate');
compiler.normalizeDirectiveMetadata(
runtimeMetadataResolver.getMetadata(CompWithTemplateUrl))
.then((meta: NormalizedDirectiveMetadata) => {
.then((meta: CompileDirectiveMetadata) => {
expect(meta.template.template).toEqual('loadedTemplate');
async.done();
});
@ -203,15 +236,21 @@ export function main() {
it('should copy all the other fields', inject([AsyncTestCompleter], (async) => {
var meta = runtimeMetadataResolver.getMetadata(CompWithBindingsAndStyles);
compiler.normalizeDirectiveMetadata(meta)
.then((normMeta: NormalizedDirectiveMetadata) => {
expect(normMeta.selector).toEqual(meta.selector);
expect(normMeta.dynamicLoadable).toEqual(meta.dynamicLoadable);
expect(normMeta.isComponent).toEqual(meta.isComponent);
expect(normMeta.type).toEqual(meta.type);
expect(normMeta.changeDetection).toEqual(meta.changeDetection);
async.done();
});
compiler.normalizeDirectiveMetadata(meta).then((normMeta: CompileDirectiveMetadata) => {
expect(normMeta.type).toEqual(meta.type);
expect(normMeta.isComponent).toEqual(meta.isComponent);
expect(normMeta.dynamicLoadable).toEqual(meta.dynamicLoadable);
expect(normMeta.selector).toEqual(meta.selector);
expect(normMeta.exportAs).toEqual(meta.exportAs);
expect(normMeta.changeDetection).toEqual(meta.changeDetection);
expect(normMeta.properties).toEqual(meta.properties);
expect(normMeta.events).toEqual(meta.events);
expect(normMeta.hostListeners).toEqual(meta.hostListeners);
expect(normMeta.hostProperties).toEqual(meta.hostProperties);
expect(normMeta.hostAttributes).toEqual(meta.hostAttributes);
expect(normMeta.lifecycleHooks).toEqual(meta.lifecycleHooks);
async.done();
});
}));
});
@ -233,24 +272,30 @@ export function main() {
@Component({
selector: 'comp-a',
dynamicLoadable: true,
host: {'[title]': 'someProp'},
moduleId: THIS_MODULE
moduleId: THIS_MODULE,
exportAs: 'someExportAs'
})
@View({template: '<a [href]="someProp"></a>', styles: ['div {color: red}']})
class CompWithBindingsAndStyles {
}
@Component({selector: 'tree', dynamicLoadable: true, moduleId: THIS_MODULE})
@Component({selector: 'tree', moduleId: THIS_MODULE})
@View({template: '<tree></tree>', directives: [TreeComp]})
class TreeComp {
}
@Component({selector: 'comp-url', dynamicLoadable: true, moduleId: THIS_MODULE})
@Component({selector: 'comp-url', moduleId: THIS_MODULE})
@View({templateUrl: 'compUrl.html'})
class CompWithTemplateUrl {
}
@Component({selector: 'comp-tpl', moduleId: THIS_MODULE})
@View({template: '<template><a [href]="someProp"></a></template>'})
class CompWithEmbeddedTemplate {
}
@Directive({selector: 'plain', moduleId: THIS_MODULE})
class PlainDirective {
}
@ -260,9 +305,8 @@ class PlainDirective {
class CompWithoutHost {
}
function testableTemplateModule(sourceModule: SourceModule, comp: INormalizedDirectiveMetadata):
function testableTemplateModule(sourceModule: SourceModule, normComp: CompileDirectiveMetadata):
SourceModule {
var normComp = <NormalizedDirectiveMetadata>comp;
var resultExpression = `${THIS_MODULE_REF}humanizeTemplate(Host${normComp.type.name}Template)`;
var testableSource = `${sourceModule.sourceWithModuleRefs}
${codeGenExportVariable('run')}${codeGenValueFn(['_'], resultExpression)};`;
@ -290,7 +334,7 @@ export function humanizeTemplate(template: CompiledTemplate,
result = {
'styles': template.styles,
'commands': commands,
'cd': testChangeDetector(template.changeDetectorFactories[0])
'cd': testChangeDetector(template.changeDetectorFactory)
};
humanizedTemplates.set(template.id, result);
visitAllCommands(new CommandHumanizer(commands, humanizedTemplates), template.commands);
@ -316,7 +360,10 @@ function testChangeDetector(changeDetectorFactory: Function): string[] {
class CommandHumanizer implements CommandVisitor {
constructor(private result: any[],
private humanizedTemplates: Map<number, StringMap<string, any>>) {}
visitText(cmd: TextCmd, context: any): any { return null; }
visitText(cmd: TextCmd, context: any): any {
this.result.push(`#text(${cmd.value})`);
return null;
}
visitNgContent(cmd: NgContentCmd, context: any): any { return null; }
visitBeginElement(cmd: BeginElementCmd, context: any): any {
this.result.push(`<${cmd.name}>`);
@ -332,5 +379,10 @@ class CommandHumanizer implements CommandVisitor {
return null;
}
visitEndComponent(context: any): any { return this.visitEndElement(context); }
visitEmbeddedTemplate(cmd: EmbeddedTemplateCmd, context: any): any { return null; }
visitEmbeddedTemplate(cmd: EmbeddedTemplateCmd, context: any): any {
this.result.push(`<template>`);
this.result.push({'cd': testChangeDetector(cmd.changeDetectorFactory)});
this.result.push(`</template>`);
return null;
}
}

View File

@ -14,9 +14,8 @@ import {
} from 'angular2/test_lib';
import {
TypeMetadata,
NormalizedTemplateMetadata,
TemplateMetadata
CompileTypeMetadata,
CompileTemplateMetadata
} from 'angular2/src/compiler/directive_metadata';
import {ViewEncapsulation} from 'angular2/src/core/render/api';
@ -27,27 +26,29 @@ import {TEST_BINDINGS} from './test_bindings';
export function main() {
describe('TemplateNormalizer', () => {
var dirType: TypeMetadata;
var dirType: CompileTypeMetadata;
beforeEachBindings(() => TEST_BINDINGS);
beforeEach(
() => { dirType = new TypeMetadata({moduleId: 'some/module/id', name: 'SomeComp'}); });
beforeEach(() => {
dirType = new CompileTypeMetadata({moduleId: 'some/module/id', name: 'SomeComp'});
});
describe('loadTemplate', () => {
describe('inline template', () => {
it('should store the template',
inject([AsyncTestCompleter, TemplateNormalizer],
(async, normalizer: TemplateNormalizer) => {
normalizer.normalizeTemplate(dirType, new TemplateMetadata({
normalizer.normalizeTemplate(dirType, new CompileTemplateMetadata({
encapsulation: null,
template: 'a',
templateUrl: null,
styles: [],
styleUrls: ['test.css']
}))
.then((template: NormalizedTemplateMetadata) => {
.then((template: CompileTemplateMetadata) => {
expect(template.template).toEqual('a');
expect(template.templateUrl).toEqual('some/module/id');
async.done();
});
}));
@ -55,15 +56,15 @@ export function main() {
it('should resolve styles on the annotation against the moduleId',
inject([AsyncTestCompleter, TemplateNormalizer],
(async, normalizer: TemplateNormalizer) => {
normalizer.normalizeTemplate(dirType, new TemplateMetadata({
normalizer.normalizeTemplate(dirType, new CompileTemplateMetadata({
encapsulation: null,
template: '',
templateUrl: null,
styles: [],
styleUrls: ['test.css']
}))
.then((template: NormalizedTemplateMetadata) => {
expect(template.styleAbsUrls).toEqual(['some/module/test.css']);
.then((template: CompileTemplateMetadata) => {
expect(template.styleUrls).toEqual(['some/module/test.css']);
async.done();
});
}));
@ -71,15 +72,15 @@ export function main() {
it('should resolve styles in the template against the moduleId',
inject([AsyncTestCompleter, TemplateNormalizer],
(async, normalizer: TemplateNormalizer) => {
normalizer.normalizeTemplate(dirType, new TemplateMetadata({
normalizer.normalizeTemplate(dirType, new CompileTemplateMetadata({
encapsulation: null,
template: '<style>@import test.css</style>',
templateUrl: null,
styles: [],
styleUrls: []
}))
.then((template: NormalizedTemplateMetadata) => {
expect(template.styleAbsUrls).toEqual(['some/module/test.css']);
.then((template: CompileTemplateMetadata) => {
expect(template.styleUrls).toEqual(['some/module/test.css']);
async.done();
});
}));
@ -91,15 +92,16 @@ export function main() {
inject([AsyncTestCompleter, TemplateNormalizer, XHR],
(async, normalizer: TemplateNormalizer, xhr: MockXHR) => {
xhr.expect('some/module/sometplurl', 'a');
normalizer.normalizeTemplate(dirType, new TemplateMetadata({
normalizer.normalizeTemplate(dirType, new CompileTemplateMetadata({
encapsulation: null,
template: null,
templateUrl: 'sometplurl',
styles: [],
styleUrls: ['test.css']
}))
.then((template: NormalizedTemplateMetadata) => {
.then((template: CompileTemplateMetadata) => {
expect(template.template).toEqual('a');
expect(template.templateUrl).toEqual('some/module/sometplurl');
async.done();
});
xhr.flush();
@ -109,15 +111,15 @@ export function main() {
inject([AsyncTestCompleter, TemplateNormalizer, XHR],
(async, normalizer: TemplateNormalizer, xhr: MockXHR) => {
xhr.expect('some/module/tpl/sometplurl', '');
normalizer.normalizeTemplate(dirType, new TemplateMetadata({
normalizer.normalizeTemplate(dirType, new CompileTemplateMetadata({
encapsulation: null,
template: null,
templateUrl: 'tpl/sometplurl',
styles: [],
styleUrls: ['test.css']
}))
.then((template: NormalizedTemplateMetadata) => {
expect(template.styleAbsUrls).toEqual(['some/module/test.css']);
.then((template: CompileTemplateMetadata) => {
expect(template.styleUrls).toEqual(['some/module/test.css']);
async.done();
});
xhr.flush();
@ -127,15 +129,15 @@ export function main() {
inject([AsyncTestCompleter, TemplateNormalizer, XHR],
(async, normalizer: TemplateNormalizer, xhr: MockXHR) => {
xhr.expect('some/module/tpl/sometplurl', '<style>@import test.css</style>');
normalizer.normalizeTemplate(dirType, new TemplateMetadata({
normalizer.normalizeTemplate(dirType, new CompileTemplateMetadata({
encapsulation: null,
template: null,
templateUrl: 'tpl/sometplurl',
styles: [],
styleUrls: []
}))
.then((template: NormalizedTemplateMetadata) => {
expect(template.styleAbsUrls).toEqual(['some/module/tpl/test.css']);
.then((template: CompileTemplateMetadata) => {
expect(template.styleUrls).toEqual(['some/module/tpl/test.css']);
async.done();
});
xhr.flush();
@ -151,8 +153,8 @@ export function main() {
var viewEncapsulation = ViewEncapsulation.Native;
var template = normalizer.normalizeLoadedTemplate(
dirType,
new TemplateMetadata({encapsulation: viewEncapsulation, styles: [], styleUrls: []}),
dirType, new CompileTemplateMetadata(
{encapsulation: viewEncapsulation, styles: [], styleUrls: []}),
'', 'some/module/');
expect(template.encapsulation).toBe(viewEncapsulation);
}));
@ -160,88 +162,90 @@ export function main() {
it('should keep the template as html',
inject([TemplateNormalizer], (normalizer: TemplateNormalizer) => {
var template = normalizer.normalizeLoadedTemplate(
dirType, new TemplateMetadata({encapsulation: null, styles: [], styleUrls: []}), 'a',
dirType,
new CompileTemplateMetadata({encapsulation: null, styles: [], styleUrls: []}), 'a',
'some/module/');
expect(template.template).toEqual('a')
}));
it('should collect and keep ngContent',
it('should collect ngContent',
inject([TemplateNormalizer], (normalizer: TemplateNormalizer) => {
var template = normalizer.normalizeLoadedTemplate(
dirType, new TemplateMetadata({encapsulation: null, styles: [], styleUrls: []}),
dirType,
new CompileTemplateMetadata({encapsulation: null, styles: [], styleUrls: []}),
'<ng-content select="a"></ng-content>', 'some/module/');
expect(template.ngContentSelectors).toEqual(['a']);
expect(template.template).toEqual('<ng-content select="a"></ng-content>');
}));
it('should normalize ngContent wildcard selector',
inject([TemplateNormalizer], (normalizer: TemplateNormalizer) => {
var template = normalizer.normalizeLoadedTemplate(
dirType, new TemplateMetadata({encapsulation: null, styles: [], styleUrls: []}),
dirType,
new CompileTemplateMetadata({encapsulation: null, styles: [], styleUrls: []}),
'<ng-content></ng-content><ng-content select></ng-content><ng-content select="*"></ng-content>',
'some/module/');
expect(template.ngContentSelectors).toEqual(['*', '*', '*']);
}));
it('should collect and remove top level styles in the template',
it('should collect top level styles in the template',
inject([TemplateNormalizer], (normalizer: TemplateNormalizer) => {
var template = normalizer.normalizeLoadedTemplate(
dirType, new TemplateMetadata({encapsulation: null, styles: [], styleUrls: []}),
dirType,
new CompileTemplateMetadata({encapsulation: null, styles: [], styleUrls: []}),
'<style>a</style>', 'some/module/');
expect(template.styles).toEqual(['a']);
expect(template.template).toEqual('');
}));
it('should collect and remove styles inside in elements',
it('should collect styles inside in elements',
inject([TemplateNormalizer], (normalizer: TemplateNormalizer) => {
var template = normalizer.normalizeLoadedTemplate(
dirType, new TemplateMetadata({encapsulation: null, styles: [], styleUrls: []}),
dirType,
new CompileTemplateMetadata({encapsulation: null, styles: [], styleUrls: []}),
'<div><style>a</style></div>', 'some/module/');
expect(template.styles).toEqual(['a']);
expect(template.template).toEqual('<div></div>');
}));
it('should collect and remove styleUrls in the template',
it('should collect styleUrls in the template',
inject([TemplateNormalizer], (normalizer: TemplateNormalizer) => {
var template = normalizer.normalizeLoadedTemplate(
dirType, new TemplateMetadata({encapsulation: null, styles: [], styleUrls: []}),
dirType,
new CompileTemplateMetadata({encapsulation: null, styles: [], styleUrls: []}),
'<link rel="stylesheet" href="aUrl">', 'some/module/');
expect(template.styleAbsUrls).toEqual(['some/module/aUrl']);
expect(template.template).toEqual('');
expect(template.styleUrls).toEqual(['some/module/aUrl']);
}));
it('should collect and remove styleUrls in elements',
it('should collect styleUrls in elements',
inject([TemplateNormalizer], (normalizer: TemplateNormalizer) => {
var template = normalizer.normalizeLoadedTemplate(
dirType, new TemplateMetadata({encapsulation: null, styles: [], styleUrls: []}),
dirType,
new CompileTemplateMetadata({encapsulation: null, styles: [], styleUrls: []}),
'<div><link rel="stylesheet" href="aUrl"></div>', 'some/module/');
expect(template.styleAbsUrls).toEqual(['some/module/aUrl']);
expect(template.template).toEqual('<div></div>');
expect(template.styleUrls).toEqual(['some/module/aUrl']);
}));
it('should keep link elements with non stylesheet rel attribute',
it('should ignore link elements with non stylesheet rel attribute',
inject([TemplateNormalizer], (normalizer: TemplateNormalizer) => {
var template = normalizer.normalizeLoadedTemplate(
dirType, new TemplateMetadata({encapsulation: null, styles: [], styleUrls: []}),
dirType,
new CompileTemplateMetadata({encapsulation: null, styles: [], styleUrls: []}),
'<link href="b" rel="a"></link>', 'some/module/');
expect(template.styleAbsUrls).toEqual([]);
expect(template.template).toEqual('<link href="b" rel="a"></link>');
expect(template.styleUrls).toEqual([]);
}));
it('should extract @import style urls into styleAbsUrl',
inject([TemplateNormalizer], (normalizer: TemplateNormalizer) => {
var template = normalizer.normalizeLoadedTemplate(
dirType, new TemplateMetadata(
dirType, new CompileTemplateMetadata(
{encapsulation: null, styles: ['@import "test.css";'], styleUrls: []}),
'', 'some/module/id');
expect(template.styles).toEqual(['']);
expect(template.styleAbsUrls).toEqual(['some/module/test.css']);
expect(template.styleUrls).toEqual(['some/module/test.css']);
}));
it('should resolve relative urls in inline styles',
inject([TemplateNormalizer], (normalizer: TemplateNormalizer) => {
var template = normalizer.normalizeLoadedTemplate(
dirType, new TemplateMetadata({
dirType, new CompileTemplateMetadata({
encapsulation: null,
styles: ['.foo{background-image: url(\'double.jpg\');'],
styleUrls: []
@ -254,15 +258,32 @@ export function main() {
it('should resolve relative style urls in styleUrls',
inject([TemplateNormalizer], (normalizer: TemplateNormalizer) => {
var template = normalizer.normalizeLoadedTemplate(
dirType,
new TemplateMetadata({encapsulation: null, styles: [], styleUrls: ['test.css']}), '',
'some/module/id');
dirType, new CompileTemplateMetadata(
{encapsulation: null, styles: [], styleUrls: ['test.css']}),
'', 'some/module/id');
expect(template.styles).toEqual([]);
expect(template.styleAbsUrls).toEqual(['some/module/test.css']);
expect(template.styleUrls).toEqual(['some/module/test.css']);
}));
it('should normalize ViewEncapsulation.Emulated to ViewEncapsulation.None if there are no stlyes nor stylesheets',
inject([TemplateNormalizer], (normalizer: TemplateNormalizer) => {
var template = normalizer.normalizeLoadedTemplate(
dirType, new CompileTemplateMetadata(
{encapsulation: ViewEncapsulation.Emulated, styles: [], styleUrls: []}),
'', 'some/module/id');
expect(template.encapsulation).toEqual(ViewEncapsulation.None);
}));
it('should ignore elements with ng-non-bindable attribute and their children',
inject([TemplateNormalizer], (normalizer: TemplateNormalizer) => {
var template = normalizer.normalizeLoadedTemplate(
dirType,
new CompileTemplateMetadata({encapsulation: null, styles: [], styleUrls: []}),
'<div ng-non-bindable><ng-content select="a"></ng-content></div><ng-content ng-non-bindable select="b"></ng-content>',
'some/module/');
expect(template.ngContentSelectors).toEqual([]);
}));
});
});
}

View File

@ -16,10 +16,9 @@ import {TEST_BINDINGS} from './test_bindings';
import {isPresent} from 'angular2/src/core/facade/lang';
import {TemplateParser, splitClasses} from 'angular2/src/compiler/template_parser';
import {
NormalizedDirectiveMetadata,
TypeMetadata,
ChangeDetectionMetadata,
NormalizedTemplateMetadata
CompileDirectiveMetadata,
CompileTypeMetadata,
CompileTemplateMetadata
} from 'angular2/src/compiler/directive_metadata';
import {
templateVisitAll,
@ -59,14 +58,14 @@ export function main() {
beforeEach(inject([TemplateParser], (_parser) => {
parser = _parser;
ngIf = new NormalizedDirectiveMetadata({
ngIf = CompileDirectiveMetadata.create({
selector: '[ng-if]',
type: new TypeMetadata({name: 'NgIf'}),
changeDetection: new ChangeDetectionMetadata({properties: ['ngIf']})
type: new CompileTypeMetadata({name: 'NgIf'}),
properties: ['ngIf']
});
}));
function parse(template: string, directives: NormalizedDirectiveMetadata[]): TemplateAst[] {
function parse(template: string, directives: CompileDirectiveMetadata[]): TemplateAst[] {
return parser.parse(template, directives, 'TestComp');
}
@ -319,68 +318,39 @@ export function main() {
});
describe('variables', () => {
it('should parse variables via #... and not report them as attributes', () => {
expect(humanizeTemplateAsts(parse('<div #a="b">', [])))
.toEqual([
[ElementAst, 'div', 'TestComp > div:nth-child(0)'],
[VariableAst, 'a', 'b', 'TestComp > div:nth-child(0)[#a=b]']
]);
});
it('should parse variables via var-... and not report them as attributes', () => {
expect(humanizeTemplateAsts(parse('<div var-a="b">', [])))
.toEqual([
[ElementAst, 'div', 'TestComp > div:nth-child(0)'],
[VariableAst, 'a', 'b', 'TestComp > div:nth-child(0)[var-a=b]']
]);
});
it('should camel case variables', () => {
expect(humanizeTemplateAsts(parse('<div var-some-a="b">', [])))
.toEqual([
[ElementAst, 'div', 'TestComp > div:nth-child(0)'],
[VariableAst, 'someA', 'b', 'TestComp > div:nth-child(0)[var-some-a=b]']
]);
});
it('should use $implicit as variable name if none was specified', () => {
expect(humanizeTemplateAsts(parse('<div var-a>', [])))
.toEqual([
[ElementAst, 'div', 'TestComp > div:nth-child(0)'],
[VariableAst, 'a', '$implicit', 'TestComp > div:nth-child(0)[var-a=]']
]);
});
});
describe('directives', () => {
it('should locate directives ordered by name and components first', () => {
var dirA = new NormalizedDirectiveMetadata(
{selector: '[a=b]', type: new TypeMetadata({name: 'DirA'})});
var dirB = new NormalizedDirectiveMetadata(
{selector: '[a]', type: new TypeMetadata({name: 'DirB'})});
var comp = new NormalizedDirectiveMetadata({
selector: 'div',
isComponent: true,
type: new TypeMetadata({name: 'ZComp'}),
template: new NormalizedTemplateMetadata({ngContentSelectors: []})
});
expect(humanizeTemplateAsts(parse('<div a="b">', [dirB, dirA, comp])))
.toEqual([
[ElementAst, 'div', 'TestComp > div:nth-child(0)'],
[AttrAst, 'a', 'b', 'TestComp > div:nth-child(0)[a=b]'],
[DirectiveAst, comp, 'TestComp > div:nth-child(0)'],
[DirectiveAst, dirA, 'TestComp > div:nth-child(0)'],
[DirectiveAst, dirB, 'TestComp > div:nth-child(0)']
]);
});
it('should locate directives components first and ordered by the directives array in the View',
() => {
var dirA = CompileDirectiveMetadata.create(
{selector: '[a]', type: new CompileTypeMetadata({name: 'DirA', id: 3})});
var dirB = CompileDirectiveMetadata.create(
{selector: '[b]', type: new CompileTypeMetadata({name: 'DirB', id: 2})});
var dirC = CompileDirectiveMetadata.create(
{selector: '[c]', type: new CompileTypeMetadata({name: 'DirC', id: 1})});
var comp = CompileDirectiveMetadata.create({
selector: 'div',
isComponent: true,
type: new CompileTypeMetadata({name: 'ZComp'}),
template: new CompileTemplateMetadata({ngContentSelectors: []})
});
expect(humanizeTemplateAsts(parse('<div a c b>', [dirA, dirB, dirC, comp])))
.toEqual([
[ElementAst, 'div', 'TestComp > div:nth-child(0)'],
[AttrAst, 'a', '', 'TestComp > div:nth-child(0)[a=]'],
[AttrAst, 'b', '', 'TestComp > div:nth-child(0)[b=]'],
[AttrAst, 'c', '', 'TestComp > div:nth-child(0)[c=]'],
[DirectiveAst, comp, 'TestComp > div:nth-child(0)'],
[DirectiveAst, dirA, 'TestComp > div:nth-child(0)'],
[DirectiveAst, dirB, 'TestComp > div:nth-child(0)'],
[DirectiveAst, dirC, 'TestComp > div:nth-child(0)']
]);
});
it('should locate directives in property bindings', () => {
var dirA = new NormalizedDirectiveMetadata(
{selector: '[a=b]', type: new TypeMetadata({name: 'DirA'})});
var dirB = new NormalizedDirectiveMetadata(
{selector: '[b]', type: new TypeMetadata({name: 'DirB'})});
var dirA = CompileDirectiveMetadata.create(
{selector: '[a=b]', type: new CompileTypeMetadata({name: 'DirA'})});
var dirB = CompileDirectiveMetadata.create(
{selector: '[b]', type: new CompileTypeMetadata({name: 'DirB'})});
expect(humanizeTemplateAsts(parse('<div [a]="b">', [dirA, dirB])))
.toEqual([
[ElementAst, 'div', 'TestComp > div:nth-child(0)'],
@ -397,23 +367,23 @@ export function main() {
});
it('should locate directives in variable bindings', () => {
var dirA = new NormalizedDirectiveMetadata(
{selector: '[a=b]', type: new TypeMetadata({name: 'DirA'})});
var dirB = new NormalizedDirectiveMetadata(
{selector: '[b]', type: new TypeMetadata({name: 'DirB'})});
var dirA = CompileDirectiveMetadata.create(
{selector: '[a=b]', exportAs: 'b', type: new CompileTypeMetadata({name: 'DirA'})});
var dirB = CompileDirectiveMetadata.create(
{selector: '[b]', type: new CompileTypeMetadata({name: 'DirB'})});
expect(humanizeTemplateAsts(parse('<div #a="b">', [dirA, dirB])))
.toEqual([
[ElementAst, 'div', 'TestComp > div:nth-child(0)'],
[VariableAst, 'a', 'b', 'TestComp > div:nth-child(0)[#a=b]'],
[DirectiveAst, dirA, 'TestComp > div:nth-child(0)']
[DirectiveAst, dirA, 'TestComp > div:nth-child(0)'],
[VariableAst, 'a', 'b', 'TestComp > div:nth-child(0)[#a=b]']
]);
});
it('should parse directive host properties', () => {
var dirA = new NormalizedDirectiveMetadata({
var dirA = CompileDirectiveMetadata.create({
selector: 'div',
type: new TypeMetadata({name: 'DirA'}),
changeDetection: new ChangeDetectionMetadata({hostProperties: {'a': 'expr'}})
type: new CompileTypeMetadata({name: 'DirA'}),
host: {'[a]': 'expr'}
});
expect(humanizeTemplateAsts(parse('<div></div>', [dirA])))
.toEqual([
@ -431,10 +401,10 @@ export function main() {
});
it('should parse directive host listeners', () => {
var dirA = new NormalizedDirectiveMetadata({
var dirA = CompileDirectiveMetadata.create({
selector: 'div',
type: new TypeMetadata({name: 'DirA'}),
changeDetection: new ChangeDetectionMetadata({hostListeners: {'a': 'expr'}})
type: new CompileTypeMetadata({name: 'DirA'}),
host: {'(a)': 'expr'}
});
expect(humanizeTemplateAsts(parse('<div></div>', [dirA])))
.toEqual([
@ -445,10 +415,10 @@ export function main() {
});
it('should parse directive properties', () => {
var dirA = new NormalizedDirectiveMetadata({
var dirA = CompileDirectiveMetadata.create({
selector: 'div',
type: new TypeMetadata({name: 'DirA'}),
changeDetection: new ChangeDetectionMetadata({properties: ['aProp']})
type: new CompileTypeMetadata({name: 'DirA'}),
properties: ['aProp']
});
expect(humanizeTemplateAsts(parse('<div [a-prop]="expr"></div>', [dirA])))
.toEqual([
@ -464,10 +434,10 @@ export function main() {
});
it('should parse renamed directive properties', () => {
var dirA = new NormalizedDirectiveMetadata({
var dirA = CompileDirectiveMetadata.create({
selector: 'div',
type: new TypeMetadata({name: 'DirA'}),
changeDetection: new ChangeDetectionMetadata({properties: ['b:a']})
type: new CompileTypeMetadata({name: 'DirA'}),
properties: ['b:a']
});
expect(humanizeTemplateAsts(parse('<div [a]="expr"></div>', [dirA])))
.toEqual([
@ -478,11 +448,8 @@ export function main() {
});
it('should parse literal directive properties', () => {
var dirA = new NormalizedDirectiveMetadata({
selector: 'div',
type: new TypeMetadata({name: 'DirA'}),
changeDetection: new ChangeDetectionMetadata({properties: ['a']})
});
var dirA = CompileDirectiveMetadata.create(
{selector: 'div', type: new CompileTypeMetadata({name: 'DirA'}), properties: ['a']});
expect(humanizeTemplateAsts(parse('<div a="literal"></div>', [dirA])))
.toEqual([
[ElementAst, 'div', 'TestComp > div:nth-child(0)'],
@ -498,11 +465,8 @@ export function main() {
});
it('should support optional directive properties', () => {
var dirA = new NormalizedDirectiveMetadata({
selector: 'div',
type: new TypeMetadata({name: 'DirA'}),
changeDetection: new ChangeDetectionMetadata({properties: ['a']})
});
var dirA = CompileDirectiveMetadata.create(
{selector: 'div', type: new CompileTypeMetadata({name: 'DirA'}), properties: ['a']});
expect(humanizeTemplateAsts(parse('<div></div>', [dirA])))
.toEqual([
[ElementAst, 'div', 'TestComp > div:nth-child(0)'],
@ -512,6 +476,83 @@ export function main() {
});
describe('variables', () => {
it('should parse variables via #... and not report them as attributes', () => {
expect(humanizeTemplateAsts(parse('<div #a>', [])))
.toEqual([
[ElementAst, 'div', 'TestComp > div:nth-child(0)'],
[VariableAst, 'a', '', 'TestComp > div:nth-child(0)[#a=]']
]);
});
it('should parse variables via var-... and not report them as attributes', () => {
expect(humanizeTemplateAsts(parse('<div var-a>', [])))
.toEqual([
[ElementAst, 'div', 'TestComp > div:nth-child(0)'],
[VariableAst, 'a', '', 'TestComp > div:nth-child(0)[var-a=]']
]);
});
it('should camel case variables', () => {
expect(humanizeTemplateAsts(parse('<div var-some-a>', [])))
.toEqual([
[ElementAst, 'div', 'TestComp > div:nth-child(0)'],
[VariableAst, 'someA', '', 'TestComp > div:nth-child(0)[var-some-a=]']
]);
});
it('should assign variables with empty value to element', () => {
expect(humanizeTemplateAsts(parse('<div #a></div>', [])))
.toEqual([
[ElementAst, 'div', 'TestComp > div:nth-child(0)'],
[VariableAst, 'a', '', 'TestComp > div:nth-child(0)[#a=]']
]);
});
it('should assign variables to directives via exportAs', () => {
var dirA = CompileDirectiveMetadata.create(
{selector: '[a]', type: new CompileTypeMetadata({name: 'DirA'}), exportAs: 'dirA'});
expect(humanizeTemplateAsts(parse('<div #a="dirA"></div>', [dirA])))
.toEqual([
[ElementAst, 'div', 'TestComp > div:nth-child(0)'],
[DirectiveAst, dirA, 'TestComp > div:nth-child(0)'],
[VariableAst, 'a', 'dirA', 'TestComp > div:nth-child(0)[#a=dirA]']
]);
});
it('should report variables with values that dont match a directive as errors', () => {
expect(() => parse('<div #a="dirA"></div>', [])).toThrowError(`Template parse errors:
There is no directive with "exportAs" set to "dirA" at TestComp > div:nth-child(0)[#a=dirA]`);
});
it('should allow variables with values that dont match a directive on embedded template elements',
() => {
expect(humanizeTemplateAsts(parse('<template #a="b"></template>', [])))
.toEqual([
[EmbeddedTemplateAst, 'TestComp > template:nth-child(0)'],
[VariableAst, 'a', 'b', 'TestComp > template:nth-child(0)[#a=b]']
]);
});
it('should assign variables with empty value to components', () => {
var dirA = CompileDirectiveMetadata.create({
selector: '[a]',
isComponent: true,
type: new CompileTypeMetadata({name: 'DirA'}),
exportAs: 'dirA', template: new CompileTemplateMetadata({ngContentSelectors: []})
});
expect(humanizeTemplateAsts(parse('<div #a></div>', [dirA])))
.toEqual([
[ElementAst, 'div', 'TestComp > div:nth-child(0)'],
[VariableAst, 'a', '', 'TestComp > div:nth-child(0)[#a=]'],
[DirectiveAst, dirA, 'TestComp > div:nth-child(0)'],
[VariableAst, 'a', '', 'TestComp > div:nth-child(0)[#a=]']
]);
});
});
describe('explicit templates', () => {
it('should create embedded templates for <template> elements', () => {
expect(humanizeTemplateAsts(parse('<template></template>', [])))
@ -563,13 +604,13 @@ export function main() {
describe('directives', () => {
it('should locate directives in property bindings', () => {
var dirA = new NormalizedDirectiveMetadata({
var dirA = CompileDirectiveMetadata.create({
selector: '[a=b]',
type: new TypeMetadata({name: 'DirA'}),
changeDetection: new ChangeDetectionMetadata({properties: ['a']})
type: new CompileTypeMetadata({name: 'DirA'}),
properties: ['a']
});
var dirB = new NormalizedDirectiveMetadata(
{selector: '[b]', type: new TypeMetadata({name: 'DirB'})});
var dirB = CompileDirectiveMetadata.create(
{selector: '[b]', type: new CompileTypeMetadata({name: 'DirB'})});
expect(humanizeTemplateAsts(parse('<div template="a b" b>', [dirA, dirB])))
.toEqual([
[EmbeddedTemplateAst, 'TestComp > div:nth-child(0)'],
@ -587,10 +628,10 @@ export function main() {
});
it('should locate directives in variable bindings', () => {
var dirA = new NormalizedDirectiveMetadata(
{selector: '[a=b]', type: new TypeMetadata({name: 'DirA'})});
var dirB = new NormalizedDirectiveMetadata(
{selector: '[b]', type: new TypeMetadata({name: 'DirB'})});
var dirA = CompileDirectiveMetadata.create(
{selector: '[a=b]', type: new CompileTypeMetadata({name: 'DirA'})});
var dirB = CompileDirectiveMetadata.create(
{selector: '[b]', type: new CompileTypeMetadata({name: 'DirB'})});
expect(humanizeTemplateAsts(parse('<div template="#a=b" b>', [dirA, dirB])))
.toEqual([
[EmbeddedTemplateAst, 'TestComp > div:nth-child(0)'],
@ -624,12 +665,12 @@ export function main() {
describe('content projection', () => {
function createComp(selector: string, ngContentSelectors: string[]):
NormalizedDirectiveMetadata {
return new NormalizedDirectiveMetadata({
CompileDirectiveMetadata {
return CompileDirectiveMetadata.create({
selector: selector,
isComponent: true,
type: new TypeMetadata({name: 'SomeComp'}),
template: new NormalizedTemplateMetadata({ngContentSelectors: ngContentSelectors})
type: new CompileTypeMetadata({name: 'SomeComp'}),
template: new CompileTemplateMetadata({ngContentSelectors: ngContentSelectors})
})
}
@ -725,38 +766,38 @@ Parser Error: Unexpected token 'b' at column 3 in [a b] in TestComp > div:nth-ch
it('should not throw on invalid property names if the property is used by a directive',
() => {
var dirA = new NormalizedDirectiveMetadata({
var dirA = CompileDirectiveMetadata.create({
selector: 'div',
type: new TypeMetadata({name: 'DirA'}),
changeDetection: new ChangeDetectionMetadata({properties: ['invalidProp']})
type: new CompileTypeMetadata({name: 'DirA'}),
properties: ['invalidProp']
});
expect(() => parse('<div [invalid-prop]></div>', [dirA])).not.toThrow();
});
it('should not allow more than 1 component per element', () => {
var dirA = new NormalizedDirectiveMetadata({
var dirA = CompileDirectiveMetadata.create({
selector: 'div',
isComponent: true,
type: new TypeMetadata({name: 'DirA'}),
template: new NormalizedTemplateMetadata({ngContentSelectors: []})
type: new CompileTypeMetadata({name: 'DirA'}),
template: new CompileTemplateMetadata({ngContentSelectors: []})
});
var dirB = new NormalizedDirectiveMetadata({
var dirB = CompileDirectiveMetadata.create({
selector: 'div',
isComponent: true,
type: new TypeMetadata({name: 'DirB'}),
template: new NormalizedTemplateMetadata({ngContentSelectors: []})
type: new CompileTypeMetadata({name: 'DirB'}),
template: new CompileTemplateMetadata({ngContentSelectors: []})
});
expect(() => parse('<div>', [dirB, dirA])).toThrowError(`Template parse errors:
More than one component: DirA,DirB in TestComp > div:nth-child(0)`);
More than one component: DirB,DirA in TestComp > div:nth-child(0)`);
});
it('should not allow components or element nor event bindings on explicit embedded templates',
() => {
var dirA = new NormalizedDirectiveMetadata({
var dirA = CompileDirectiveMetadata.create({
selector: '[a]',
isComponent: true,
type: new TypeMetadata({name: 'DirA'}),
template: new NormalizedTemplateMetadata({ngContentSelectors: []})
type: new CompileTypeMetadata({name: 'DirA'}),
template: new CompileTemplateMetadata({ngContentSelectors: []})
});
expect(() => parse('<template [a]="b" (e)="f"></template>', [dirA]))
.toThrowError(`Template parse errors:
@ -766,17 +807,45 @@ Event binding e on an embedded template in TestComp > template:nth-child(0)[(e)=
});
it('should not allow components or element bindings on inline embedded templates', () => {
var dirA = new NormalizedDirectiveMetadata({
var dirA = CompileDirectiveMetadata.create({
selector: '[a]',
isComponent: true,
type: new TypeMetadata({name: 'DirA'}),
template: new NormalizedTemplateMetadata({ngContentSelectors: []})
type: new CompileTypeMetadata({name: 'DirA'}),
template: new CompileTemplateMetadata({ngContentSelectors: []})
});
expect(() => parse('<div *a="b">', [dirA])).toThrowError(`Template parse errors:
Components on an embedded template: DirA in TestComp > div:nth-child(0)
Property binding a not used by any directive on an embedded template in TestComp > div:nth-child(0)[*a=b]`);
});
});
describe('ignore elements', () => {
it('should ignore <script> elements but include them for source info', () => {
expect(humanizeTemplateAsts(parse('<script></script>a', [])))
.toEqual([[TextAst, 'a', 'TestComp > #text(a):nth-child(1)']]);
});
it('should ignore <style> elements but include them for source info', () => {
expect(humanizeTemplateAsts(parse('<style></style>a', [])))
.toEqual([[TextAst, 'a', 'TestComp > #text(a):nth-child(1)']]);
});
it('should ignore <link rel="stylesheet"> elements but include them for source info', () => {
expect(humanizeTemplateAsts(parse('<link rel="stylesheet"></link>a', [])))
.toEqual([[TextAst, 'a', 'TestComp > #text(a):nth-child(1)']]);
});
it('should ignore elements with ng-non-bindable, including their children, but include them for source info',
() => {
expect(humanizeTemplateAsts(parse('<div ng-non-bindable>b</div>a', [])))
.toEqual([[TextAst, 'a', 'TestComp > #text(a):nth-child(1)']]);
});
});
});
}
@ -805,7 +874,7 @@ class TemplateHumanizer implements TemplateAstVisitor {
templateVisitAll(this, ast.attrs);
templateVisitAll(this, ast.properties);
templateVisitAll(this, ast.events);
templateVisitAll(this, ast.vars);
templateVisitAll(this, ast.exportAsVars);
templateVisitAll(this, ast.directives);
templateVisitAll(this, ast.children);
return null;
@ -852,6 +921,7 @@ class TemplateHumanizer implements TemplateAstVisitor {
templateVisitAll(this, ast.properties);
templateVisitAll(this, ast.hostProperties);
templateVisitAll(this, ast.hostEvents);
templateVisitAll(this, ast.exportAsVars);
return null;
}
visitDirectiveProperty(ast: BoundDirectivePropertyAst, context: any): any {