diff --git a/modules/@angular/compiler-cli/integrationtest/src/features.ts b/modules/@angular/compiler-cli/integrationtest/src/features.ts index 79809a5714..6320393458 100644 --- a/modules/@angular/compiler-cli/integrationtest/src/features.ts +++ b/modules/@angular/compiler-cli/integrationtest/src/features.ts @@ -7,7 +7,7 @@ */ import * as common from '@angular/common'; -import {Component, Inject, OpaqueToken} from '@angular/core'; +import {CUSTOM_ELEMENTS_SCHEMA, Component, Inject, NgModule, OpaqueToken} from '@angular/core'; import {wrapInArray} from './funcs'; @@ -37,3 +37,16 @@ export class CompWithProviders { }) export class CompWithReferences { } + +@Component({ + selector: 'cmp-custom-els', + template: ` + + `, +}) +export class CompUsingCustomElements { +} + +@NgModule({schemas: [CUSTOM_ELEMENTS_SCHEMA], declarations: [CompUsingCustomElements]}) +export class ModuleUsingCustomElements { +} diff --git a/modules/@angular/compiler-cli/integrationtest/src/module.ts b/modules/@angular/compiler-cli/integrationtest/src/module.ts index 0a0962b83b..ad9029a6c5 100644 --- a/modules/@angular/compiler-cli/integrationtest/src/module.ts +++ b/modules/@angular/compiler-cli/integrationtest/src/module.ts @@ -13,7 +13,7 @@ import {BrowserModule} from '@angular/platform-browser'; import {AnimateCmp} from './animate'; import {BasicComp} from './basic'; import {CompWithAnalyzeEntryComponentsProvider, CompWithEntryComponents} from './entry_components'; -import {CompWithProviders, CompWithReferences} from './features'; +import {CompWithProviders, CompWithReferences, ModuleUsingCustomElements} from './features'; import {CompUsingRootModuleDirectiveAndPipe, SomeDirectiveInRootModule, someLibModuleWithProviders, SomePipeInRootModule, SomeService} from './module_fixtures'; import {ProjectingComp} from './projection'; import {CompWithChildQuery, CompWithDirectiveChild} from './queries'; @@ -25,7 +25,7 @@ import {CompWithChildQuery, CompWithDirectiveChild} from './queries'; CompWithDirectiveChild, CompUsingRootModuleDirectiveAndPipe, CompWithProviders, CompWithReferences ], - imports: [BrowserModule, FormsModule, someLibModuleWithProviders()], + imports: [BrowserModule, FormsModule, someLibModuleWithProviders(), ModuleUsingCustomElements], providers: [SomeService], entryComponents: [ AnimateCmp, BasicComp, CompWithEntryComponents, CompWithAnalyzeEntryComponentsProvider, diff --git a/modules/@angular/compiler/src/compile_metadata.ts b/modules/@angular/compiler/src/compile_metadata.ts index 05a3639f1d..e1dd002b44 100644 --- a/modules/@angular/compiler/src/compile_metadata.ts +++ b/modules/@angular/compiler/src/compile_metadata.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {ChangeDetectionStrategy, ViewEncapsulation} from '@angular/core'; +import {ChangeDetectionStrategy, SchemaMetadata, ViewEncapsulation} from '@angular/core'; import {CHANGE_DETECTION_STRATEGY_VALUES, LIFECYCLE_HOOKS_VALUES, LifecycleHooks, VIEW_ENCAPSULATION_VALUES, reflector} from '../core_private'; import {ListWrapper, StringMapWrapper} from '../src/facade/collection'; @@ -18,6 +18,7 @@ import {getUrlScheme} from './url_resolver'; import {sanitizeIdentifier, splitAtColon} from './util'; + // group 0: "[prop] or (event) or @trigger" // group 1: "prop" from "[prop]" // group 2: "event" from "(event)" @@ -625,12 +626,13 @@ export class CompileNgModuleMetadata implements CompileMetadataWithIdentifier { importedModules: CompileNgModuleMetadata[]; exportedModules: CompileNgModuleMetadata[]; + schemas: SchemaMetadata[]; transitiveModule: TransitiveCompileNgModuleMetadata; constructor( {type, providers, declaredDirectives, exportedDirectives, declaredPipes, exportedPipes, - entryComponents, importedModules, exportedModules, transitiveModule}: { + entryComponents, importedModules, exportedModules, schemas, transitiveModule}: { type?: CompileTypeMetadata, providers?: Array, @@ -641,7 +643,8 @@ export class CompileNgModuleMetadata implements CompileMetadataWithIdentifier { entryComponents?: CompileTypeMetadata[], importedModules?: CompileNgModuleMetadata[], exportedModules?: CompileNgModuleMetadata[], - transitiveModule?: TransitiveCompileNgModuleMetadata + transitiveModule?: TransitiveCompileNgModuleMetadata, + schemas?: SchemaMetadata[] } = {}) { this.type = type; this.declaredDirectives = _normalizeArray(declaredDirectives); @@ -652,6 +655,7 @@ export class CompileNgModuleMetadata implements CompileMetadataWithIdentifier { this.entryComponents = _normalizeArray(entryComponents); this.importedModules = _normalizeArray(importedModules); this.exportedModules = _normalizeArray(exportedModules); + this.schemas = _normalizeArray(schemas); this.transitiveModule = transitiveModule; } diff --git a/modules/@angular/compiler/src/metadata_resolver.ts b/modules/@angular/compiler/src/metadata_resolver.ts index 4b5f4e41e6..276e3a0c78 100644 --- a/modules/@angular/compiler/src/metadata_resolver.ts +++ b/modules/@angular/compiler/src/metadata_resolver.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {AnimationAnimateMetadata, AnimationEntryMetadata, AnimationGroupMetadata, AnimationKeyframesSequenceMetadata, AnimationMetadata, AnimationStateDeclarationMetadata, AnimationStateMetadata, AnimationStateTransitionMetadata, AnimationStyleMetadata, AnimationWithStepsMetadata, AttributeMetadata, ChangeDetectionStrategy, ComponentMetadata, HostMetadata, Inject, InjectMetadata, Injectable, ModuleWithProviders, NgModule, NgModuleMetadata, Optional, OptionalMetadata, Provider, QueryMetadata, SelfMetadata, SkipSelfMetadata, ViewMetadata, ViewQueryMetadata, resolveForwardRef} from '@angular/core'; +import {AnimationAnimateMetadata, AnimationEntryMetadata, AnimationGroupMetadata, AnimationKeyframesSequenceMetadata, AnimationMetadata, AnimationStateDeclarationMetadata, AnimationStateMetadata, AnimationStateTransitionMetadata, AnimationStyleMetadata, AnimationWithStepsMetadata, AttributeMetadata, ChangeDetectionStrategy, ComponentMetadata, HostMetadata, Inject, InjectMetadata, Injectable, ModuleWithProviders, NgModule, NgModuleMetadata, Optional, OptionalMetadata, Provider, QueryMetadata, SchemaMetadata, SelfMetadata, SkipSelfMetadata, ViewMetadata, ViewQueryMetadata, resolveForwardRef} from '@angular/core'; import {Console, LIFECYCLE_HOOKS_VALUES, ReflectorReader, createProvider, isProviderLiteral, reflector} from '../core_private'; import {MapWrapper, StringMapWrapper} from '../src/facade/collection'; @@ -208,6 +208,19 @@ export class CompileMetadataResolver { const exportedModules: cpl.CompileNgModuleMetadata[] = []; const providers: any[] = []; const entryComponents: cpl.CompileTypeMetadata[] = []; + const schemas: SchemaMetadata[] = []; + + if (meta.providers) { + providers.push(...this.getProvidersMetadata(meta.providers, entryComponents)); + } + if (meta.entryComponents) { + entryComponents.push( + ...flattenArray(meta.entryComponents) + .map(type => this.getTypeMetadata(type, staticTypeModuleUrl(type)))); + } + if (meta.schemas) { + schemas.push(...flattenArray(meta.schemas)); + } if (meta.imports) { flattenArray(meta.imports).forEach((importedType) => { @@ -282,15 +295,6 @@ export class CompileMetadataResolver { }); } - if (meta.providers) { - providers.push(...this.getProvidersMetadata(meta.providers, entryComponents)); - } - if (meta.entryComponents) { - entryComponents.push( - ...flattenArray(meta.entryComponents) - .map(type => this.getTypeMetadata(type, staticTypeModuleUrl(type)))); - } - transitiveModule.entryComponents.push(...entryComponents); transitiveModule.providers.push(...providers); @@ -298,6 +302,7 @@ export class CompileMetadataResolver { type: this.getTypeMetadata(moduleType, staticTypeModuleUrl(moduleType)), providers: providers, entryComponents: entryComponents, + schemas: schemas, declaredDirectives: declaredDirectives, exportedDirectives: exportedDirectives, declaredPipes: declaredPipes, diff --git a/modules/@angular/compiler/src/offline_compiler.ts b/modules/@angular/compiler/src/offline_compiler.ts index 66d8d7875d..03d387bd07 100644 --- a/modules/@angular/compiler/src/offline_compiler.ts +++ b/modules/@angular/compiler/src/offline_compiler.ts @@ -6,6 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ +import {SchemaMetadata} from '@angular/core'; import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompilePipeMetadata, StaticSymbol, createHostComponentMeta} from './compile_metadata'; import {DirectiveNormalizer} from './directive_normalizer'; import {ListWrapper} from './facade/collection'; @@ -91,7 +92,7 @@ export class OfflineCompiler { // compile components exportedVars.push(this._compileComponentFactory(compMeta, fileSuffix, statements)); exportedVars.push(this._compileComponent( - compMeta, dirMetas, ngModule.transitiveModule.pipes, + compMeta, dirMetas, ngModule.transitiveModule.pipes, ngModule.schemas, stylesCompileResults.componentStylesheet, fileSuffix, statements)); }); })) @@ -120,7 +121,7 @@ export class OfflineCompiler { targetStatements: o.Statement[]): string { var hostMeta = createHostComponentMeta(compMeta); var hostViewFactoryVar = - this._compileComponent(hostMeta, [compMeta], [], null, fileSuffix, targetStatements); + this._compileComponent(hostMeta, [compMeta], [], [], null, fileSuffix, targetStatements); var compFactoryVar = _componentFactoryName(compMeta.type); targetStatements.push( o.variable(compFactoryVar) @@ -139,10 +140,10 @@ export class OfflineCompiler { private _compileComponent( compMeta: CompileDirectiveMetadata, directives: CompileDirectiveMetadata[], - pipes: CompilePipeMetadata[], componentStyles: CompiledStylesheet, fileSuffix: string, - targetStatements: o.Statement[]): string { + pipes: CompilePipeMetadata[], schemas: SchemaMetadata[], componentStyles: CompiledStylesheet, + fileSuffix: string, targetStatements: o.Statement[]): string { var parsedTemplate = this._templateParser.parse( - compMeta, compMeta.template.template, directives, pipes, compMeta.type.name); + compMeta, compMeta.template.template, directives, pipes, schemas, compMeta.type.name); var stylesExpr = componentStyles ? o.variable(componentStyles.stylesVar) : o.literalArr([]); var viewResult = this._viewCompiler.compileComponent(compMeta, parsedTemplate, stylesExpr, pipes); diff --git a/modules/@angular/compiler/src/runtime_compiler.ts b/modules/@angular/compiler/src/runtime_compiler.ts index c9a1113f0f..524d249e1f 100644 --- a/modules/@angular/compiler/src/runtime_compiler.ts +++ b/modules/@angular/compiler/src/runtime_compiler.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {Compiler, ComponentFactory, ComponentResolver, ComponentStillLoadingError, Injectable, Injector, NgModule, NgModuleFactory, NgModuleMetadata, OptionalMetadata, Provider, SkipSelfMetadata} from '@angular/core'; +import {Compiler, ComponentFactory, ComponentResolver, ComponentStillLoadingError, Injectable, Injector, NgModule, NgModuleFactory, NgModuleMetadata, OptionalMetadata, Provider, SchemaMetadata, SkipSelfMetadata} from '@angular/core'; import {Console} from '../core_private'; import {BaseException} from '../src/facade/exceptions'; @@ -143,9 +143,7 @@ export class RuntimeCompiler implements Compiler { ngModule.transitiveModule.modules.forEach((localModuleMeta) => { localModuleMeta.declaredDirectives.forEach((dirMeta) => { if (dirMeta.isComponent) { - templates.add(this._createCompiledTemplate( - dirMeta, localModuleMeta.transitiveModule.directives, - localModuleMeta.transitiveModule.pipes)); + templates.add(this._createCompiledTemplate(dirMeta, localModuleMeta)); dirMeta.entryComponents.forEach((entryComponentType) => { templates.add(this._createCompiledHostTemplate(entryComponentType.runtime)); }); @@ -200,7 +198,7 @@ export class RuntimeCompiler implements Compiler { assertComponent(compMeta); var hostMeta = createHostComponentMeta(compMeta); compiledTemplate = new CompiledTemplate( - true, compMeta.selector, compMeta.type, [compMeta], [], + true, compMeta.selector, compMeta.type, [compMeta], [], [], this._templateNormalizer.normalizeDirective(hostMeta)); this._compiledHostTemplateCache.set(compType, compiledTemplate); } @@ -208,13 +206,13 @@ export class RuntimeCompiler implements Compiler { } private _createCompiledTemplate( - compMeta: CompileDirectiveMetadata, directives: CompileDirectiveMetadata[], - pipes: CompilePipeMetadata[]): CompiledTemplate { + compMeta: CompileDirectiveMetadata, ngModule: CompileNgModuleMetadata): CompiledTemplate { var compiledTemplate = this._compiledTemplateCache.get(compMeta.type.runtime); if (isBlank(compiledTemplate)) { assertComponent(compMeta); compiledTemplate = new CompiledTemplate( - false, compMeta.selector, compMeta.type, directives, pipes, + false, compMeta.selector, compMeta.type, ngModule.transitiveModule.directives, + ngModule.transitiveModule.pipes, ngModule.schemas, this._templateNormalizer.normalizeDirective(compMeta)); this._compiledTemplateCache.set(compMeta.type.runtime, compiledTemplate); } @@ -255,7 +253,7 @@ export class RuntimeCompiler implements Compiler { (compType) => this._assertComponentLoaded(compType, false).normalizedCompMeta); const parsedTemplate = this._templateParser.parse( compMeta, compMeta.template.template, template.viewDirectives.concat(viewCompMetas), - template.viewPipes, compMeta.type.name); + template.viewPipes, template.schemas, compMeta.type.name); const compileResult = this._viewCompiler.compileComponent( compMeta, parsedTemplate, ir.variable(stylesCompileResult.componentStylesheet.stylesVar), template.viewPipes); @@ -322,7 +320,7 @@ class CompiledTemplate { constructor( public isHost: boolean, selector: string, public compType: CompileIdentifierMetadata, viewDirectivesAndComponents: CompileDirectiveMetadata[], - public viewPipes: CompilePipeMetadata[], + public viewPipes: CompilePipeMetadata[], public schemas: SchemaMetadata[], _normalizeResult: SyncAsyncResult) { viewDirectivesAndComponents.forEach((dirMeta) => { if (dirMeta.isComponent) { diff --git a/modules/@angular/compiler/src/schema/dom_element_schema_registry.ts b/modules/@angular/compiler/src/schema/dom_element_schema_registry.ts index 81a81a4263..5347fa90d3 100644 --- a/modules/@angular/compiler/src/schema/dom_element_schema_registry.ts +++ b/modules/@angular/compiler/src/schema/dom_element_schema_registry.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {Injectable, SecurityContext} from '@angular/core'; +import {CUSTOM_ELEMENTS_SCHEMA, Injectable, SchemaMetadata, SecurityContext} from '@angular/core'; import {StringMapWrapper} from '../facade/collection'; import {isPresent} from '../facade/lang'; @@ -270,7 +270,9 @@ export class DomElementSchemaRegistry extends ElementSchemaRegistry { }); } - hasProperty(tagName: string, propName: string): boolean { + hasProperty(tagName: string, propName: string, schemaMetas: SchemaMetadata[]): boolean { + const hasCustomElementSchema = + schemaMetas.some((schema) => schema.name === CUSTOM_ELEMENTS_SCHEMA.name); if (tagName.indexOf('-') !== -1) { if (tagName === 'ng-container' || tagName === 'ng-content') { return false; @@ -278,7 +280,7 @@ export class DomElementSchemaRegistry extends ElementSchemaRegistry { // Can't tell now as we don't know which properties a custom element will get // once it is instantiated - return true; + return hasCustomElementSchema; } else { var elementProperties = this.schema[tagName.toLowerCase()]; if (!isPresent(elementProperties)) { diff --git a/modules/@angular/compiler/src/schema/element_schema_registry.ts b/modules/@angular/compiler/src/schema/element_schema_registry.ts index d9503616bc..32a0f3a304 100644 --- a/modules/@angular/compiler/src/schema/element_schema_registry.ts +++ b/modules/@angular/compiler/src/schema/element_schema_registry.ts @@ -6,8 +6,10 @@ * found in the LICENSE file at https://angular.io/license */ +import {SchemaMetadata} from '@angular/core'; + export abstract class ElementSchemaRegistry { - abstract hasProperty(tagName: string, propName: string): boolean; + abstract hasProperty(tagName: string, propName: string, schemaMetas: SchemaMetadata[]): boolean; abstract securityContext(tagName: string, propName: string): any; abstract getMappedPropName(propName: string): string; } diff --git a/modules/@angular/compiler/src/template_parser.ts b/modules/@angular/compiler/src/template_parser.ts index c22e91ee98..59ddfef19c 100644 --- a/modules/@angular/compiler/src/template_parser.ts +++ b/modules/@angular/compiler/src/template_parser.ts @@ -6,8 +6,10 @@ * found in the LICENSE file at https://angular.io/license */ -import {Inject, Injectable, OpaqueToken, Optional, SecurityContext} from '@angular/core'; +import {Inject, Injectable, OpaqueToken, Optional, SchemaMetadata, SecurityContext} from '@angular/core'; + import {Console, MAX_INTERPOLATION_VALUES} from '../core_private'; + import {ListWrapper, StringMapWrapper, SetWrapper,} from '../src/facade/collection'; import {RegExpWrapper, isPresent, StringWrapper, isBlank} from '../src/facade/lang'; import {BaseException} from '../src/facade/exceptions'; @@ -83,8 +85,8 @@ export class TemplateParser { parse( component: CompileDirectiveMetadata, template: string, directives: CompileDirectiveMetadata[], - pipes: CompilePipeMetadata[], templateUrl: string): TemplateAst[] { - const result = this.tryParse(component, template, directives, pipes, templateUrl); + pipes: CompilePipeMetadata[], schemas: SchemaMetadata[], templateUrl: string): TemplateAst[] { + const result = this.tryParse(component, template, directives, pipes, schemas, templateUrl); const warnings = result.errors.filter(error => error.level === ParseErrorLevel.WARNING); const errors = result.errors.filter(error => error.level === ParseErrorLevel.FATAL); if (warnings.length > 0) { @@ -100,7 +102,8 @@ export class TemplateParser { tryParse( component: CompileDirectiveMetadata, template: string, directives: CompileDirectiveMetadata[], - pipes: CompilePipeMetadata[], templateUrl: string): TemplateParseResult { + pipes: CompilePipeMetadata[], schemas: SchemaMetadata[], + templateUrl: string): TemplateParseResult { let interpolationConfig: any; if (component.template) { interpolationConfig = InterpolationConfig.fromArray(component.template.interpolation); @@ -123,7 +126,8 @@ export class TemplateParser { const providerViewContext = new ProviderViewContext(component, htmlAstWithErrors.rootNodes[0].sourceSpan); const parseVisitor = new TemplateParseVisitor( - providerViewContext, uniqDirectives, uniqPipes, this._exprParser, this._schemaRegistry); + providerViewContext, uniqDirectives, uniqPipes, schemas, this._exprParser, + this._schemaRegistry); result = htmlVisitAll(parseVisitor, htmlAstWithErrors.rootNodes, EMPTY_ELEMENT_CONTEXT); errors.push(...parseVisitor.errors, ...providerViewContext.errors); @@ -175,7 +179,7 @@ class TemplateParseVisitor implements HtmlAstVisitor { constructor( public providerViewContext: ProviderViewContext, directives: CompileDirectiveMetadata[], - pipes: CompilePipeMetadata[], private _exprParser: Parser, + pipes: CompilePipeMetadata[], private _schemas: SchemaMetadata[], private _exprParser: Parser, private _schemaRegistry: ElementSchemaRegistry) { this.selectorMatcher = new SelectorMatcher(); @@ -801,10 +805,14 @@ class TemplateParseVisitor implements HtmlAstVisitor { boundPropertyName = this._schemaRegistry.getMappedPropName(partValue); securityContext = this._schemaRegistry.securityContext(elementName, boundPropertyName); bindingType = PropertyBindingType.Property; - if (!this._schemaRegistry.hasProperty(elementName, boundPropertyName)) { - this._reportError( - `Can't bind to '${boundPropertyName}' since it isn't a known native property`, - sourceSpan); + if (!this._schemaRegistry.hasProperty(elementName, boundPropertyName, this._schemas)) { + let errorMsg = + `Can't bind to '${boundPropertyName}' since it isn't a known native property`; + if (elementName.indexOf('-') !== -1) { + errorMsg += + `. To ignore this error on custom elements, add the "CUSTOM_ELEMENTS_SCHEMA" to the NgModule of this component`; + } + this._reportError(errorMsg, sourceSpan); } } } else { diff --git a/modules/@angular/compiler/test/schema/dom_element_schema_registry_spec.ts b/modules/@angular/compiler/test/schema/dom_element_schema_registry_spec.ts index d49393b8eb..293df6daf6 100644 --- a/modules/@angular/compiler/test/schema/dom_element_schema_registry_spec.ts +++ b/modules/@angular/compiler/test/schema/dom_element_schema_registry_spec.ts @@ -9,7 +9,7 @@ import {HtmlElementAst} from '@angular/compiler/src/html_ast'; import {HtmlParser} from '@angular/compiler/src/html_parser'; import {DomElementSchemaRegistry} from '@angular/compiler/src/schema/dom_element_schema_registry'; -import {SecurityContext} from '@angular/core'; +import {CUSTOM_ELEMENTS_SCHEMA, SecurityContext} from '@angular/core'; import {beforeEach, ddescribe, describe, expect, iit, inject, it, xdescribe, xit} from '@angular/core/testing/testing_internal'; import {browserDetection} from '@angular/platform-browser/testing/browser_util'; @@ -21,34 +21,38 @@ export function main() { beforeEach(() => { registry = new DomElementSchemaRegistry(); }); it('should detect properties on regular elements', () => { - expect(registry.hasProperty('div', 'id')).toBeTruthy(); - expect(registry.hasProperty('div', 'title')).toBeTruthy(); - expect(registry.hasProperty('h1', 'align')).toBeTruthy(); - expect(registry.hasProperty('h2', 'align')).toBeTruthy(); - expect(registry.hasProperty('h3', 'align')).toBeTruthy(); - expect(registry.hasProperty('h4', 'align')).toBeTruthy(); - expect(registry.hasProperty('h5', 'align')).toBeTruthy(); - expect(registry.hasProperty('h6', 'align')).toBeTruthy(); - expect(registry.hasProperty('h7', 'align')).toBeFalsy(); - expect(registry.hasProperty('textarea', 'disabled')).toBeTruthy(); - expect(registry.hasProperty('input', 'disabled')).toBeTruthy(); - expect(registry.hasProperty('div', 'unknown')).toBeFalsy(); + expect(registry.hasProperty('div', 'id', [])).toBeTruthy(); + expect(registry.hasProperty('div', 'title', [])).toBeTruthy(); + expect(registry.hasProperty('h1', 'align', [])).toBeTruthy(); + expect(registry.hasProperty('h2', 'align', [])).toBeTruthy(); + expect(registry.hasProperty('h3', 'align', [])).toBeTruthy(); + expect(registry.hasProperty('h4', 'align', [])).toBeTruthy(); + expect(registry.hasProperty('h5', 'align', [])).toBeTruthy(); + expect(registry.hasProperty('h6', 'align', [])).toBeTruthy(); + expect(registry.hasProperty('h7', 'align', [])).toBeFalsy(); + expect(registry.hasProperty('textarea', 'disabled', [])).toBeTruthy(); + expect(registry.hasProperty('input', 'disabled', [])).toBeTruthy(); + expect(registry.hasProperty('div', 'unknown', [])).toBeFalsy(); }); it('should detect different kinds of types', () => { // inheritance: video => media => * - expect(registry.hasProperty('video', 'className')).toBeTruthy(); // from * - expect(registry.hasProperty('video', 'id')).toBeTruthy(); // string - expect(registry.hasProperty('video', 'scrollLeft')).toBeTruthy(); // number - expect(registry.hasProperty('video', 'height')).toBeTruthy(); // number - expect(registry.hasProperty('video', 'autoplay')).toBeTruthy(); // boolean - expect(registry.hasProperty('video', 'classList')).toBeTruthy(); // object + expect(registry.hasProperty('video', 'className', [])).toBeTruthy(); // from * + expect(registry.hasProperty('video', 'id', [])).toBeTruthy(); // string + expect(registry.hasProperty('video', 'scrollLeft', [])).toBeTruthy(); // number + expect(registry.hasProperty('video', 'height', [])).toBeTruthy(); // number + expect(registry.hasProperty('video', 'autoplay', [])).toBeTruthy(); // boolean + expect(registry.hasProperty('video', 'classList', [])).toBeTruthy(); // object // from *; but events are not properties - expect(registry.hasProperty('video', 'click')).toBeFalsy(); + expect(registry.hasProperty('video', 'click', [])).toBeFalsy(); }); - it('should return true for custom-like elements', - () => { expect(registry.hasProperty('custom-like', 'unknown')).toBeTruthy(); }); + it('should return false for custom-like elements by default', + () => { expect(registry.hasProperty('custom-like', 'unknown', [])).toBe(false); }); + + it('should return true for custom-like elements if the CUSTOM_ELEMENTS_SCHEMA was used', () => { + expect(registry.hasProperty('custom-like', 'unknown', [CUSTOM_ELEMENTS_SCHEMA])).toBeTruthy(); + }); it('should re-map property names that are specified in DOM facade', () => { expect(registry.getMappedPropName('readonly')).toEqual('readOnly'); }); @@ -70,7 +74,7 @@ export function main() { it('should detect properties on namespaced elements', () => { const htmlAst = new HtmlParser().parse('', 'TestComp'); const nodeName = (htmlAst.rootNodes[0]).name; - expect(registry.hasProperty(nodeName, 'type')).toBeTruthy(); + expect(registry.hasProperty(nodeName, 'type', [])).toBeTruthy(); }); it('should check security contexts case insensitive', () => { @@ -81,11 +85,11 @@ export function main() { describe('Angular custom elements', () => { it('should support ', - () => { expect(registry.hasProperty('ng-container', 'id')).toBeFalsy(); }); + () => { expect(registry.hasProperty('ng-container', 'id', [])).toBeFalsy(); }); it('should support ', () => { - expect(registry.hasProperty('ng-content', 'id')).toBeFalsy(); - expect(registry.hasProperty('ng-content', 'select')).toBeFalsy(); + expect(registry.hasProperty('ng-content', 'id', [])).toBeFalsy(); + expect(registry.hasProperty('ng-content', 'select', [])).toBeFalsy(); }); }); diff --git a/modules/@angular/compiler/test/template_parser_spec.ts b/modules/@angular/compiler/test/template_parser_spec.ts index 1c5332cf5c..1c752aa543 100644 --- a/modules/@angular/compiler/test/template_parser_spec.ts +++ b/modules/@angular/compiler/test/template_parser_spec.ts @@ -12,7 +12,7 @@ import {ElementSchemaRegistry} from '@angular/compiler/src/schema/element_schema import {AttrAst, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventAst, BoundTextAst, DirectiveAst, ElementAst, EmbeddedTemplateAst, NgContentAst, PropertyBindingType, ProviderAstType, ReferenceAst, TemplateAst, TemplateAstVisitor, TextAst, VariableAst, templateVisitAll} from '@angular/compiler/src/template_ast'; import {TEMPLATE_TRANSFORMS, TemplateParser, splitClasses} from '@angular/compiler/src/template_parser'; import {MockSchemaRegistry} from '@angular/compiler/testing'; -import {SecurityContext} from '@angular/core'; +import {SchemaMetadata, SecurityContext} from '@angular/core'; import {Console} from '@angular/core/src/console'; import {configureCompiler} from '@angular/core/testing'; import {afterEach, beforeEach, beforeEachProviders, ddescribe, describe, expect, iit, inject, it, xit} from '@angular/core/testing/testing_internal'; @@ -56,11 +56,11 @@ export function main() { parse = (template: string, directives: CompileDirectiveMetadata[], - pipes: CompilePipeMetadata[] = null): TemplateAst[] => { + pipes: CompilePipeMetadata[] = null, schemas: SchemaMetadata[] = []): TemplateAst[] => { if (pipes === null) { pipes = []; } - return parser.parse(component, template, directives, pipes, 'TestComp'); + return parser.parse(component, template, directives, pipes, schemas, 'TestComp'); }; })); } @@ -181,7 +181,7 @@ export function main() { isComponent: true, template: new CompileTemplateMetadata({interpolation: ['{%', '%}']}) }); - expect(humanizeTplAst(parser.parse(component, '{%a%}', [], [], 'TestComp'), { + expect(humanizeTplAst(parser.parse(component, '{%a%}', [], [], [], 'TestComp'), { start: '{%', end: '%}' })).toEqual([[BoundTextAst, '{% a %}']]); diff --git a/modules/@angular/compiler/testing/schema_registry_mock.ts b/modules/@angular/compiler/testing/schema_registry_mock.ts index 57c30fc6c5..42f3e441fe 100644 --- a/modules/@angular/compiler/testing/schema_registry_mock.ts +++ b/modules/@angular/compiler/testing/schema_registry_mock.ts @@ -6,7 +6,8 @@ * found in the LICENSE file at https://angular.io/license */ -import {SecurityContext} from '@angular/core'; +import {SchemaMetadata, SecurityContext} from '@angular/core'; + import {ElementSchemaRegistry} from '../index'; import {isPresent} from '../src/facade/lang'; @@ -15,7 +16,7 @@ export class MockSchemaRegistry implements ElementSchemaRegistry { public existingProperties: {[key: string]: boolean}, public attrPropMapping: {[key: string]: string}) {} - hasProperty(tagName: string, property: string): boolean { + hasProperty(tagName: string, property: string, schemas: SchemaMetadata[]): boolean { var result = this.existingProperties[property]; return isPresent(result) ? result : true; } diff --git a/modules/@angular/core/src/metadata.ts b/modules/@angular/core/src/metadata.ts index 8bffa69842..77050c2c99 100644 --- a/modules/@angular/core/src/metadata.ts +++ b/modules/@angular/core/src/metadata.ts @@ -16,13 +16,13 @@ import {ChangeDetectionStrategy} from '../src/change_detection/change_detection' import {AnimationEntryMetadata} from './animation/metadata'; import {AttributeMetadata, ContentChildMetadata, ContentChildrenMetadata, QueryMetadata, ViewChildMetadata, ViewChildrenMetadata, ViewQueryMetadata} from './metadata/di'; import {ComponentMetadata, DirectiveMetadata, HostBindingMetadata, HostListenerMetadata, InputMetadata, OutputMetadata, PipeMetadata} from './metadata/directives'; -import {ModuleWithProviders, NgModuleMetadata} from './metadata/ng_module'; +import {ModuleWithProviders, NgModuleMetadata, SchemaMetadata} from './metadata/ng_module'; import {ViewEncapsulation, ViewMetadata} from './metadata/view'; export {ANALYZE_FOR_ENTRY_COMPONENTS, AttributeMetadata, ContentChildMetadata, ContentChildrenMetadata, QueryMetadata, ViewChildMetadata, ViewChildrenMetadata, ViewQueryMetadata} from './metadata/di'; export {ComponentMetadata, DirectiveMetadata, HostBindingMetadata, HostListenerMetadata, InputMetadata, OutputMetadata, PipeMetadata} from './metadata/directives'; export {AfterContentChecked, AfterContentInit, AfterViewChecked, AfterViewInit, DoCheck, OnChanges, OnDestroy, OnInit} from './metadata/lifecycle_hooks'; -export {ModuleWithProviders, NgModuleMetadata} from './metadata/ng_module'; +export {CUSTOM_ELEMENTS_SCHEMA, ModuleWithProviders, NgModuleMetadata, SchemaMetadata} from './metadata/ng_module'; export {ViewEncapsulation, ViewMetadata} from './metadata/view'; import {makeDecorator, makeParamDecorator, makePropDecorator, TypeDecorator,} from './util/decorators'; @@ -500,14 +500,16 @@ export interface NgModuleMetadataFactory { declarations?: Array, imports?: Array, exports?: Array, - entryComponents?: Array + entryComponents?: Array, + schemas?: Array }): NgModuleDecorator; new (obj?: { providers?: any[], declarations?: Array, imports?: Array, exports?: Array, - entryComponents?: Array + entryComponents?: Array, + schemas?: Array }): NgModuleMetadata; } diff --git a/modules/@angular/core/src/metadata/ng_module.ts b/modules/@angular/core/src/metadata/ng_module.ts index 50b75bbbb3..8c131761f5 100644 --- a/modules/@angular/core/src/metadata/ng_module.ts +++ b/modules/@angular/core/src/metadata/ng_module.ts @@ -19,6 +19,23 @@ export interface ModuleWithProviders { providers?: any[]; } +/** + * Interface for schema definitions in @NgModules. + * + * @experimental + */ +export interface SchemaMetadata { name: string; } + +/** + * Defines a schema that will allow any property on elements with a `-` in their name, + * which is the common rule for custom elements. + * + * @experimental + */ +export const CUSTOM_ELEMENTS_SCHEMA: SchemaMetadata = { + name: 'custom-elements' +}; + /** * Declares an Angular Module. * @experimental @@ -114,12 +131,15 @@ export class NgModuleMetadata extends InjectableMetadata { */ entryComponents: Array; - constructor({providers, declarations, imports, exports, entryComponents}: { + schemas: Array; + + constructor({providers, declarations, imports, exports, entryComponents, schemas}: { providers?: any[], declarations?: Array, imports?: Array, exports?: Array, - entryComponents?: Array + entryComponents?: Array, + schemas?: Array } = {}) { super(); this._providers = providers; @@ -127,5 +147,6 @@ export class NgModuleMetadata extends InjectableMetadata { this.imports = imports; this.exports = exports; this.entryComponents = entryComponents; + this.schemas = schemas; } } diff --git a/modules/@angular/core/test/linker/integration_spec.ts b/modules/@angular/core/test/linker/integration_spec.ts index 8f5a177dca..923ca32065 100644 --- a/modules/@angular/core/test/linker/integration_spec.ts +++ b/modules/@angular/core/test/linker/integration_spec.ts @@ -1651,7 +1651,7 @@ function declareTests({useJit}: {useJit: boolean}) { (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { tcb.overrideView(MyComp, new ViewMetadata({ - template: '', + template: '', directives: [ChildComp] })) .createAsync(MyComp) diff --git a/modules/@angular/core/test/linker/ng_module_integration_spec.ts b/modules/@angular/core/test/linker/ng_module_integration_spec.ts index 9b44a039bd..428fba6933 100644 --- a/modules/@angular/core/test/linker/ng_module_integration_spec.ts +++ b/modules/@angular/core/test/linker/ng_module_integration_spec.ts @@ -9,7 +9,7 @@ import {LowerCasePipe, NgIf} from '@angular/common'; import {CompilerConfig, NgModuleResolver, ViewResolver} from '@angular/compiler'; import {MockNgModuleResolver, MockViewResolver} from '@angular/compiler/testing'; -import {ANALYZE_FOR_ENTRY_COMPONENTS, Compiler, Component, ComponentFactoryResolver, ComponentRef, ComponentResolver, DebugElement, Directive, Host, HostBinding, Inject, Injectable, Injector, Input, ModuleWithProviders, NgModule, NgModuleMetadata, NgModuleRef, OpaqueToken, Optional, Pipe, Provider, ReflectiveInjector, SelfMetadata, SkipSelf, SkipSelfMetadata, ViewMetadata, forwardRef, getDebugNode, provide} from '@angular/core'; +import {ANALYZE_FOR_ENTRY_COMPONENTS, CUSTOM_ELEMENTS_SCHEMA, Compiler, Component, ComponentFactoryResolver, ComponentRef, ComponentResolver, DebugElement, Directive, Host, HostBinding, Inject, Injectable, Injector, Input, ModuleWithProviders, NgModule, NgModuleMetadata, NgModuleRef, OpaqueToken, Optional, Pipe, Provider, ReflectiveInjector, SelfMetadata, SkipSelf, SkipSelfMetadata, ViewMetadata, forwardRef, getDebugNode, provide} from '@angular/core'; import {Console} from '@angular/core/src/console'; import {ComponentFixture, configureCompiler} from '@angular/core/testing'; import {AsyncTestCompleter, beforeEach, beforeEachProviders, ddescribe, describe, iit, inject, it, xdescribe, xit} from '@angular/core/testing/testing_internal'; @@ -238,7 +238,35 @@ function declareTests({useJit}: {useJit: boolean}) { }); - describe('entryComponents', function() { + describe('schemas', () => { + it('should error on unknown bound properties on custom elements by default', () => { + @Component({template: ''}) + class ComponentUsingInvalidProperty { + } + + @NgModule({declarations: [ComponentUsingInvalidProperty]}) + class SomeModule { + } + + expect(() => createModule(SomeModule)).toThrowError(/Can't bind to 'someUnknownProp'/); + }); + + it('should not error on unknown bound properties on custom elements when using the CUSTOM_ELEMENTS_SCHEMA', + () => { + @Component({template: ''}) + class ComponentUsingInvalidProperty { + } + + @NgModule( + {schemas: [CUSTOM_ELEMENTS_SCHEMA], declarations: [ComponentUsingInvalidProperty]}) + class SomeModule { + } + + expect(() => createModule(SomeModule)).not.toThrow(); + }); + }); + + describe('entryComponents', () => { it('should entryComponents ComponentFactories in root modules', () => { @NgModule({declarations: [SomeComp], entryComponents: [SomeComp]}) class SomeModule { diff --git a/modules/@angular/core/testing/test_bed.ts b/modules/@angular/core/testing/test_bed.ts index d3c69553d9..824a900c35 100644 --- a/modules/@angular/core/testing/test_bed.ts +++ b/modules/@angular/core/testing/test_bed.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {Compiler, CompilerFactory, CompilerOptions, ComponentStillLoadingError, Injector, NgModule, NgModuleFactory, NgModuleMetadata, NgModuleRef, PlatformRef, Provider, ReflectiveInjector, Type, assertPlatform, createPlatform, getPlatform} from '../index'; +import {Compiler, SchemaMetadata, CompilerFactory, CompilerOptions, ComponentStillLoadingError, Injector, NgModule, NgModuleFactory, NgModuleMetadata, NgModuleRef, PlatformRef, Provider, ReflectiveInjector, Type, assertPlatform, createPlatform, getPlatform} from '../index'; import {ListWrapper} from '../src/facade/collection'; import {BaseException} from '../src/facade/exceptions'; import {ConcreteType, FunctionWrapper, isPresent, stringify} from '../src/facade/lang'; @@ -31,6 +31,7 @@ export class TestBed implements Injector { private _declarations: Array = []; private _imports: Array = []; private _entryComponents: Array = []; + private _schemas: Array = []; reset() { this._compiler = null; @@ -41,6 +42,7 @@ export class TestBed implements Injector { this._declarations = []; this._imports = []; this._entryComponents = []; + this._schemas = []; this._instantiated = false; } @@ -55,9 +57,13 @@ export class TestBed implements Injector { this._compilerOptions.push(config); } - configureModule( - moduleDef: - {providers?: any[], declarations?: any[], imports?: any[], entryComponents?: any[]}) { + configureModule(moduleDef: { + providers?: any[], + declarations?: any[], + imports?: any[], + entryComponents?: any[], + schemas?: Array + }) { if (this._instantiated) { throw new BaseException('Cannot add configuration after test injector is instantiated'); } @@ -73,6 +79,9 @@ export class TestBed implements Injector { if (moduleDef.entryComponents) { this._entryComponents = ListWrapper.concat(this._entryComponents, moduleDef.entryComponents); } + if (moduleDef.schemas) { + this._schemas = ListWrapper.concat(this._schemas, moduleDef.schemas); + } } createModuleFactory(): Promise> { @@ -124,12 +133,14 @@ export class TestBed implements Injector { const declarations = this._declarations; const imports = [this.ngModule, this._imports]; const entryComponents = this._entryComponents; + const schemas = this._schemas; @NgModule({ providers: providers, declarations: declarations, imports: imports, - entryComponents: entryComponents + entryComponents: entryComponents, + schemas: schemas }) class DynamicTestModule { } @@ -364,7 +375,8 @@ export function withModule(moduleDef: () => { providers?: any[], declarations?: any[], imports?: any[], - entryComponents?: any[] + entryComponents?: any[], + schemas?: Array }) { return new InjectSetupWrapper(moduleDef); } diff --git a/modules/@angular/core/testing/testing.ts b/modules/@angular/core/testing/testing.ts index ce1194eef5..d5f55ca573 100644 --- a/modules/@angular/core/testing/testing.ts +++ b/modules/@angular/core/testing/testing.ts @@ -12,6 +12,7 @@ * allows tests to be asynchronous by either returning a promise or using a 'done' parameter. */ +import {SchemaMetadata} from '../index'; import {TestBed, getTestBed} from './test_bed'; declare var global: any; @@ -49,9 +50,13 @@ export function addProviders(providers: Array): void { * * @stable */ -export function configureModule( - moduleDef: {providers?: any[], declarations?: any[], imports?: any[], entryComponents?: any[]}): - void { +export function configureModule(moduleDef: { + providers?: any[], + declarations?: any[], + imports?: any[], + entryComponents?: any[], + schemas?: Array +}): void { if (!moduleDef) return; try { testBed.configureModule(moduleDef); diff --git a/modules/@angular/platform-browser-dynamic/index.ts b/modules/@angular/platform-browser-dynamic/index.ts index 44b8f68f95..b35fecff04 100644 --- a/modules/@angular/platform-browser-dynamic/index.ts +++ b/modules/@angular/platform-browser-dynamic/index.ts @@ -7,7 +7,7 @@ */ import {XHR, analyzeAppProvidersForDeprecatedConfiguration, coreDynamicPlatform} from '@angular/compiler'; -import {ApplicationRef, Compiler, CompilerFactory, CompilerOptions, ComponentRef, ComponentResolver, ExceptionHandler, NgModule, NgModuleRef, OpaqueToken, PLATFORM_DIRECTIVES, PLATFORM_INITIALIZER, PLATFORM_PIPES, PlatformRef, ReflectiveInjector, Type, assertPlatform, bootstrapModule, bootstrapModuleFactory, createPlatform, createPlatformFactory, getPlatform, isDevMode} from '@angular/core'; +import {SchemaMetadata, ApplicationRef, Compiler, CompilerFactory, CompilerOptions, ComponentRef, ComponentResolver, ExceptionHandler, NgModule, NgModuleRef, OpaqueToken, PLATFORM_DIRECTIVES, PLATFORM_INITIALIZER, PLATFORM_PIPES, PlatformRef, ReflectiveInjector, Type, assertPlatform, bootstrapModule, bootstrapModuleFactory, createPlatform, createPlatformFactory, getPlatform, isDevMode} from '@angular/core'; import {BROWSER_PLATFORM_PROVIDERS, BrowserModule, WORKER_APP_PLATFORM_PROVIDERS, WORKER_SCRIPT, WorkerAppModule, browserPlatform, workerAppPlatform, workerUiPlatform} from '@angular/platform-browser'; import {Console} from './core_private'; @@ -121,11 +121,12 @@ export function bootstrap( customProviders?: Array): Promise>; export function bootstrap( appComponentType: ConcreteType, - {providers, imports, declarations, entryComponents, compilerOptions}?: { + {providers, imports, declarations, entryComponents, schemas, compilerOptions}?: { providers?: Array, declarations?: any[], imports?: any[], entryComponents?: any[], + schemas?: Array, compilerOptions?: CompilerOptions }): Promise>; export function bootstrap( @@ -134,7 +135,7 @@ export function bootstrap( providers: Array, declarations?: any[], imports: any[], - entryComponents: any[], + entryComponents: any[], schemas?: Array, compilerOptions: CompilerOptions }): Promise> { let compilerOptions: CompilerOptions; @@ -143,6 +144,7 @@ export function bootstrap( let imports: any[] = []; let entryComponents: any[] = []; let deprecationMessages: string[] = []; + let schemas: any[] = []; if (customProvidersOrDynamicModule instanceof Array) { providers = customProvidersOrDynamicModule; const deprecatedConfiguration = analyzeAppProvidersForDeprecatedConfiguration(providers); @@ -154,6 +156,7 @@ export function bootstrap( declarations = normalizeArray(customProvidersOrDynamicModule.declarations); imports = normalizeArray(customProvidersOrDynamicModule.imports); entryComponents = normalizeArray(customProvidersOrDynamicModule.entryComponents); + schemas = normalizeArray(customProvidersOrDynamicModule.schemas); compilerOptions = customProvidersOrDynamicModule.compilerOptions; } @@ -161,7 +164,8 @@ export function bootstrap( providers: providers, declarations: declarations.concat([appComponentType]), imports: [BrowserModule, imports], - entryComponents: entryComponents.concat([appComponentType]) + entryComponents: entryComponents.concat([appComponentType]), + schemas: schemas }) class DynamicModule { } diff --git a/modules/@angular/platform-browser/test/browser/bootstrap_spec.ts b/modules/@angular/platform-browser/test/browser/bootstrap_spec.ts index 6e0f958c44..56f721f887 100644 --- a/modules/@angular/platform-browser/test/browser/bootstrap_spec.ts +++ b/modules/@angular/platform-browser/test/browser/bootstrap_spec.ts @@ -8,7 +8,7 @@ import {LowerCasePipe, NgIf} from '@angular/common'; import {XHR} from '@angular/compiler'; -import {APP_INITIALIZER, Component, Directive, ExceptionHandler, Inject, Input, NgModule, OnDestroy, PLATFORM_DIRECTIVES, PLATFORM_INITIALIZER, PLATFORM_PIPES, Pipe, ReflectiveInjector, bootstrapModule, createPlatformFactory, provide} from '@angular/core'; +import {APP_INITIALIZER, createPlatformFactory, CUSTOM_ELEMENTS_SCHEMA, Component, Directive, ExceptionHandler, Inject, Input, NgModule, OnDestroy, PLATFORM_DIRECTIVES, PLATFORM_INITIALIZER, PLATFORM_PIPES, Pipe, ReflectiveInjector, bootstrapModule, createPlatform, provide} from '@angular/core'; import {ApplicationRef, disposePlatform} from '@angular/core/src/application_ref'; import {Console} from '@angular/core/src/console'; import {ComponentRef} from '@angular/core/src/linker/component_factory'; @@ -94,6 +94,10 @@ class HelloCmpUsingPlatformDirectiveAndPipe { show: boolean = false; } +@Component({selector: 'hello-app', template: 'hello world!'}) +class HelloCmpUsingCustomElement { +} + class _ArrayLogger { res: any[] = []; log(s: any): void { this.res.push(s); } @@ -331,5 +335,15 @@ export function main() { }); })); + it('should allow to pass schemas', inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { + bootstrap(HelloCmpUsingCustomElement, { + providers: testProviders, + schemas: [CUSTOM_ELEMENTS_SCHEMA] + }).then((compRef) => { + expect(el).toHaveText('hello world!'); + async.done(); + }); + })); + }); } diff --git a/modules/@angular/platform-browser/test/testing_public_spec.ts b/modules/@angular/platform-browser/test/testing_public_spec.ts index 67e2037681..7f2b41b9ec 100644 --- a/modules/@angular/platform-browser/test/testing_public_spec.ts +++ b/modules/@angular/platform-browser/test/testing_public_spec.ts @@ -8,7 +8,7 @@ import {NgIf} from '@angular/common'; import {CompilerConfig, XHR} from '@angular/compiler'; -import {Component, ComponentFactoryResolver, Directive, Injectable, Input, NgModule, Pipe, ViewMetadata, provide} from '@angular/core'; +import {CUSTOM_ELEMENTS_SCHEMA, Component, ComponentFactoryResolver, Directive, Injectable, Input, NgModule, Pipe, ViewMetadata, provide} from '@angular/core'; import {TestComponentBuilder, addProviders, async, configureCompiler, configureModule, doAsyncEntryPointCompilation, fakeAsync, inject, tick, withModule, withProviders} from '@angular/core/testing'; import {expect} from '@angular/platform-browser/testing/matchers'; @@ -265,6 +265,30 @@ export function main() { expect(resolver.resolveComponentFactory(CompUsingModuleDirectiveAndPipe).componentType) .toBe(CompUsingModuleDirectiveAndPipe); })); + + it('should error on unknown bound properties on custom elements by default', + inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { + @Component({template: ''}) + class ComponentUsingInvalidProperty { + } + + expect(() => tcb.createSync(ComponentUsingInvalidProperty)) + .toThrowError(/Can't bind to 'someUnknownProp'/); + })); + + describe('provided schemas', () => { + beforeEach(() => { configureModule({schemas: [CUSTOM_ELEMENTS_SCHEMA]}); }); + + it('should not error on unknown bound properties on custom elements when using the CUSTOM_ELEMENTS_SCHEMA', + inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { + @Component({template: ''}) + class ComponentUsingInvalidProperty { + } + + tcb.createSync(ComponentUsingInvalidProperty) + expect(() => tcb.createSync(ComponentUsingInvalidProperty)).not.toThrow(); + })); + }); }); describe('per test modules', () => {