fix(ivy): throw on bindings to unknown properties (#28537)

This commit adds a devMode-only check which will throw if a user
attempts to bind a property that does not match a directive
input or a known HTML property.

Example:
```
<div [unknownProp]="someValue"></div>
```

The above will throw because "unknownProp" is not a known
property of HTMLDivElement.

This check is similar to the check executed in View Engine during
template parsing, but occurs at runtime instead of compile-time.

Note: This change uncovered an existing bug with host binding
inheritance, so some Material tests had to be turned off. They
will be fixed in an upcoming PR.

PR Close #28537
This commit is contained in:
Kara Erickson
2019-02-04 21:42:55 -08:00
committed by Miško Hevery
parent 7660d0d74a
commit 1950e2d9ba
13 changed files with 262 additions and 160 deletions

View File

@ -6,8 +6,8 @@
* found in the LICENSE file at https://angular.io/license
*/
import {DomElementSchemaRegistry, ElementSchemaRegistry, ResourceLoader, UrlResolver} from '@angular/compiler';
import {MockResourceLoader, MockSchemaRegistry} from '@angular/compiler/testing';
import {ResourceLoader, UrlResolver} from '@angular/compiler';
import {MockResourceLoader} from '@angular/compiler/testing';
import {AfterContentChecked, AfterContentInit, AfterViewChecked, AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChild, DebugElement, Directive, DoCheck, EventEmitter, HostBinding, Inject, Injectable, Input, OnChanges, OnDestroy, OnInit, Output, Pipe, PipeTransform, Provider, RenderComponentType, Renderer, RendererFactory2, RootRenderer, SimpleChange, SimpleChanges, TemplateRef, Type, ViewChild, ViewContainerRef, WrappedValue} from '@angular/core';
import {ComponentFixture, TestBed, fakeAsync} from '@angular/core/testing';
import {By} from '@angular/platform-browser/src/dom/debug/by';
@ -20,14 +20,12 @@ export function createUrlResolverWithoutPackagePrefix(): UrlResolver {
}
const TEST_COMPILER_PROVIDERS: Provider[] = [
{provide: ElementSchemaRegistry, useValue: new MockSchemaRegistry({}, {}, {}, [], [])},
{provide: ResourceLoader, useClass: MockResourceLoader, deps: []},
{provide: UrlResolver, useFactory: createUrlResolverWithoutPackagePrefix, deps: []}
];
(function() {
let elSchema: MockSchemaRegistry;
let renderLog: RenderLog;
let directiveLog: DirectiveLog;
@ -43,10 +41,8 @@ const TEST_COMPILER_PROVIDERS: Provider[] = [
}
function initHelpers(): void {
elSchema = TestBed.get(ElementSchemaRegistry);
renderLog = TestBed.get(RenderLog);
directiveLog = TestBed.get(DirectiveLog);
elSchema.existingProperties['someProp'] = true;
patchLoggingRenderer2(TestBed.get(RendererFactory2), renderLog);
}
@ -67,7 +63,7 @@ const TEST_COMPILER_PROVIDERS: Provider[] = [
function _bindSimpleValue<T>(expression: any, compType: Type<T>): ComponentFixture<T>;
function _bindSimpleValue<T>(
expression: any, compType: Type<T> = <any>TestComponent): ComponentFixture<T> {
return _bindSimpleProp(`[someProp]='${expression}'`, compType);
return _bindSimpleProp(`[id]='${expression}'`, compType);
}
function _bindAndCheckSimpleValue(
@ -117,145 +113,125 @@ const TEST_COMPILER_PROVIDERS: Provider[] = [
describe('expressions', () => {
it('should support literals',
fakeAsync(() => { expect(_bindAndCheckSimpleValue(10)).toEqual(['someProp=10']); }));
fakeAsync(() => { expect(_bindAndCheckSimpleValue(10)).toEqual(['id=10']); }));
it('should strip quotes from literals',
fakeAsync(() => { expect(_bindAndCheckSimpleValue('"str"')).toEqual(['someProp=str']); }));
fakeAsync(() => { expect(_bindAndCheckSimpleValue('"str"')).toEqual(['id=str']); }));
it('should support newlines in literals', fakeAsync(() => {
expect(_bindAndCheckSimpleValue('"a\n\nb"')).toEqual(['someProp=a\n\nb']);
}));
it('should support newlines in literals',
fakeAsync(() => { expect(_bindAndCheckSimpleValue('"a\n\nb"')).toEqual(['id=a\n\nb']); }));
it('should support + operations',
fakeAsync(() => { expect(_bindAndCheckSimpleValue('10 + 2')).toEqual(['someProp=12']); }));
fakeAsync(() => { expect(_bindAndCheckSimpleValue('10 + 2')).toEqual(['id=12']); }));
it('should support - operations',
fakeAsync(() => { expect(_bindAndCheckSimpleValue('10 - 2')).toEqual(['someProp=8']); }));
fakeAsync(() => { expect(_bindAndCheckSimpleValue('10 - 2')).toEqual(['id=8']); }));
it('should support * operations',
fakeAsync(() => { expect(_bindAndCheckSimpleValue('10 * 2')).toEqual(['someProp=20']); }));
fakeAsync(() => { expect(_bindAndCheckSimpleValue('10 * 2')).toEqual(['id=20']); }));
it('should support / operations', fakeAsync(() => {
expect(_bindAndCheckSimpleValue('10 / 2')).toEqual([`someProp=${5.0}`]);
expect(_bindAndCheckSimpleValue('10 / 2')).toEqual([`id=${5.0}`]);
})); // dart exp=5.0, js exp=5
it('should support % operations',
fakeAsync(() => { expect(_bindAndCheckSimpleValue('11 % 2')).toEqual(['someProp=1']); }));
fakeAsync(() => { expect(_bindAndCheckSimpleValue('11 % 2')).toEqual(['id=1']); }));
it('should support == operations on identical', fakeAsync(() => {
expect(_bindAndCheckSimpleValue('1 == 1')).toEqual(['someProp=true']);
}));
it('should support == operations on identical',
fakeAsync(() => { expect(_bindAndCheckSimpleValue('1 == 1')).toEqual(['id=true']); }));
it('should support != operations', fakeAsync(() => {
expect(_bindAndCheckSimpleValue('1 != 1')).toEqual(['someProp=false']);
}));
it('should support != operations',
fakeAsync(() => { expect(_bindAndCheckSimpleValue('1 != 1')).toEqual(['id=false']); }));
it('should support == operations on coerceible', fakeAsync(() => {
expect(_bindAndCheckSimpleValue('1 == true')).toEqual([`someProp=true`]);
}));
it('should support == operations on coerceible',
fakeAsync(() => { expect(_bindAndCheckSimpleValue('1 == true')).toEqual([`id=true`]); }));
it('should support === operations on identical', fakeAsync(() => {
expect(_bindAndCheckSimpleValue('1 === 1')).toEqual(['someProp=true']);
}));
it('should support === operations on identical',
fakeAsync(() => { expect(_bindAndCheckSimpleValue('1 === 1')).toEqual(['id=true']); }));
it('should support !== operations', fakeAsync(() => {
expect(_bindAndCheckSimpleValue('1 !== 1')).toEqual(['someProp=false']);
}));
it('should support !== operations',
fakeAsync(() => { expect(_bindAndCheckSimpleValue('1 !== 1')).toEqual(['id=false']); }));
it('should support === operations on coerceible', fakeAsync(() => {
expect(_bindAndCheckSimpleValue('1 === true')).toEqual(['someProp=false']);
expect(_bindAndCheckSimpleValue('1 === true')).toEqual(['id=false']);
}));
it('should support true < operations', fakeAsync(() => {
expect(_bindAndCheckSimpleValue('1 < 2')).toEqual(['someProp=true']);
}));
it('should support true < operations',
fakeAsync(() => { expect(_bindAndCheckSimpleValue('1 < 2')).toEqual(['id=true']); }));
it('should support false < operations', fakeAsync(() => {
expect(_bindAndCheckSimpleValue('2 < 1')).toEqual(['someProp=false']);
}));
it('should support false < operations',
fakeAsync(() => { expect(_bindAndCheckSimpleValue('2 < 1')).toEqual(['id=false']); }));
it('should support false > operations', fakeAsync(() => {
expect(_bindAndCheckSimpleValue('1 > 2')).toEqual(['someProp=false']);
}));
it('should support false > operations',
fakeAsync(() => { expect(_bindAndCheckSimpleValue('1 > 2')).toEqual(['id=false']); }));
it('should support true > operations', fakeAsync(() => {
expect(_bindAndCheckSimpleValue('2 > 1')).toEqual(['someProp=true']);
}));
it('should support true > operations',
fakeAsync(() => { expect(_bindAndCheckSimpleValue('2 > 1')).toEqual(['id=true']); }));
it('should support true <= operations', fakeAsync(() => {
expect(_bindAndCheckSimpleValue('1 <= 2')).toEqual(['someProp=true']);
}));
it('should support true <= operations',
fakeAsync(() => { expect(_bindAndCheckSimpleValue('1 <= 2')).toEqual(['id=true']); }));
it('should support equal <= operations', fakeAsync(() => {
expect(_bindAndCheckSimpleValue('2 <= 2')).toEqual(['someProp=true']);
}));
it('should support equal <= operations',
fakeAsync(() => { expect(_bindAndCheckSimpleValue('2 <= 2')).toEqual(['id=true']); }));
it('should support false <= operations', fakeAsync(() => {
expect(_bindAndCheckSimpleValue('2 <= 1')).toEqual(['someProp=false']);
}));
it('should support false <= operations',
fakeAsync(() => { expect(_bindAndCheckSimpleValue('2 <= 1')).toEqual(['id=false']); }));
it('should support true >= operations', fakeAsync(() => {
expect(_bindAndCheckSimpleValue('2 >= 1')).toEqual(['someProp=true']);
}));
it('should support true >= operations',
fakeAsync(() => { expect(_bindAndCheckSimpleValue('2 >= 1')).toEqual(['id=true']); }));
it('should support equal >= operations', fakeAsync(() => {
expect(_bindAndCheckSimpleValue('2 >= 2')).toEqual(['someProp=true']);
}));
it('should support equal >= operations',
fakeAsync(() => { expect(_bindAndCheckSimpleValue('2 >= 2')).toEqual(['id=true']); }));
it('should support false >= operations', fakeAsync(() => {
expect(_bindAndCheckSimpleValue('1 >= 2')).toEqual(['someProp=false']);
}));
it('should support false >= operations',
fakeAsync(() => { expect(_bindAndCheckSimpleValue('1 >= 2')).toEqual(['id=false']); }));
it('should support true && operations', fakeAsync(() => {
expect(_bindAndCheckSimpleValue('true && true')).toEqual(['someProp=true']);
expect(_bindAndCheckSimpleValue('true && true')).toEqual(['id=true']);
}));
it('should support false && operations', fakeAsync(() => {
expect(_bindAndCheckSimpleValue('true && false')).toEqual(['someProp=false']);
expect(_bindAndCheckSimpleValue('true && false')).toEqual(['id=false']);
}));
it('should support true || operations', fakeAsync(() => {
expect(_bindAndCheckSimpleValue('true || false')).toEqual(['someProp=true']);
expect(_bindAndCheckSimpleValue('true || false')).toEqual(['id=true']);
}));
it('should support false || operations', fakeAsync(() => {
expect(_bindAndCheckSimpleValue('false || false')).toEqual(['someProp=false']);
expect(_bindAndCheckSimpleValue('false || false')).toEqual(['id=false']);
}));
it('should support negate', fakeAsync(() => {
expect(_bindAndCheckSimpleValue('!true')).toEqual(['someProp=false']);
}));
it('should support negate',
fakeAsync(() => { expect(_bindAndCheckSimpleValue('!true')).toEqual(['id=false']); }));
it('should support double negate', fakeAsync(() => {
expect(_bindAndCheckSimpleValue('!!true')).toEqual(['someProp=true']);
}));
it('should support double negate',
fakeAsync(() => { expect(_bindAndCheckSimpleValue('!!true')).toEqual(['id=true']); }));
it('should support true conditionals', fakeAsync(() => {
expect(_bindAndCheckSimpleValue('1 < 2 ? 1 : 2')).toEqual(['someProp=1']);
}));
it('should support true conditionals',
fakeAsync(() => { expect(_bindAndCheckSimpleValue('1 < 2 ? 1 : 2')).toEqual(['id=1']); }));
it('should support false conditionals', fakeAsync(() => {
expect(_bindAndCheckSimpleValue('1 > 2 ? 1 : 2')).toEqual(['someProp=2']);
}));
it('should support false conditionals',
fakeAsync(() => { expect(_bindAndCheckSimpleValue('1 > 2 ? 1 : 2')).toEqual(['id=2']); }));
it('should support keyed access to a list item', fakeAsync(() => {
expect(_bindAndCheckSimpleValue('["foo", "bar"][0]')).toEqual(['someProp=foo']);
expect(_bindAndCheckSimpleValue('["foo", "bar"][0]')).toEqual(['id=foo']);
}));
it('should support keyed access to a map item', fakeAsync(() => {
expect(_bindAndCheckSimpleValue('{"foo": "bar"}["foo"]')).toEqual(['someProp=bar']);
expect(_bindAndCheckSimpleValue('{"foo": "bar"}["foo"]')).toEqual(['id=bar']);
}));
it('should report all changes on the first run including uninitialized values',
fakeAsync(() => {
expect(_bindAndCheckSimpleValue('value', Uninitialized)).toEqual(['someProp=null']);
expect(_bindAndCheckSimpleValue('value', Uninitialized)).toEqual(['id=null']);
}));
it('should report all changes on the first run including null values', fakeAsync(() => {
const ctx = _bindSimpleValue('a', TestData);
ctx.componentInstance.a = null;
ctx.detectChanges(false);
expect(renderLog.log).toEqual(['someProp=null']);
expect(renderLog.log).toEqual(['id=null']);
}));
it('should support simple chained property access', fakeAsync(() => {
@ -263,7 +239,7 @@ const TEST_COMPILER_PROVIDERS: Provider[] = [
ctx.componentInstance.name = 'Victor';
ctx.componentInstance.address = new Address('Grenoble');
ctx.detectChanges(false);
expect(renderLog.log).toEqual(['someProp=Grenoble']);
expect(renderLog.log).toEqual(['id=Grenoble']);
}));
describe('safe navigation operator', () => {
@ -271,54 +247,54 @@ const TEST_COMPILER_PROVIDERS: Provider[] = [
const ctx = _bindSimpleValue('address?.city', Person);
ctx.componentInstance.address = null !;
ctx.detectChanges(false);
expect(renderLog.log).toEqual(['someProp=null']);
expect(renderLog.log).toEqual(['id=null']);
}));
it('should support calling methods on nulls', fakeAsync(() => {
const ctx = _bindSimpleValue('address?.toString()', Person);
ctx.componentInstance.address = null !;
ctx.detectChanges(false);
expect(renderLog.log).toEqual(['someProp=null']);
expect(renderLog.log).toEqual(['id=null']);
}));
it('should support reading properties on non nulls', fakeAsync(() => {
const ctx = _bindSimpleValue('address?.city', Person);
ctx.componentInstance.address = new Address('MTV');
ctx.detectChanges(false);
expect(renderLog.log).toEqual(['someProp=MTV']);
expect(renderLog.log).toEqual(['id=MTV']);
}));
it('should support calling methods on non nulls', fakeAsync(() => {
const ctx = _bindSimpleValue('address?.toString()', Person);
ctx.componentInstance.address = new Address('MTV');
ctx.detectChanges(false);
expect(renderLog.log).toEqual(['someProp=MTV']);
expect(renderLog.log).toEqual(['id=MTV']);
}));
it('should support short-circuting safe navigation', fakeAsync(() => {
const ctx = _bindSimpleValue('value?.address.city', PersonHolder);
ctx.componentInstance.value = null !;
ctx.detectChanges(false);
expect(renderLog.log).toEqual(['someProp=null']);
expect(renderLog.log).toEqual(['id=null']);
}));
it('should support nested short-circuting safe navigation', fakeAsync(() => {
const ctx = _bindSimpleValue('value.value?.address.city', PersonHolderHolder);
ctx.componentInstance.value = new PersonHolder();
ctx.detectChanges(false);
expect(renderLog.log).toEqual(['someProp=null']);
expect(renderLog.log).toEqual(['id=null']);
}));
it('should support chained short-circuting safe navigation', fakeAsync(() => {
const ctx = _bindSimpleValue('value?.value?.address.city', PersonHolderHolder);
ctx.detectChanges(false);
expect(renderLog.log).toEqual(['someProp=null']);
expect(renderLog.log).toEqual(['id=null']);
}));
it('should support short-circuting array index operations', fakeAsync(() => {
const ctx = _bindSimpleValue('value?.phones[0]', PersonHolder);
ctx.detectChanges(false);
expect(renderLog.log).toEqual(['someProp=null']);
expect(renderLog.log).toEqual(['id=null']);
}));
it('should still throw if right-side would throw', fakeAsync(() => {
@ -335,21 +311,21 @@ const TEST_COMPILER_PROVIDERS: Provider[] = [
it('should support method calls', fakeAsync(() => {
const ctx = _bindSimpleValue('sayHi("Jim")', Person);
ctx.detectChanges(false);
expect(renderLog.log).toEqual(['someProp=Hi, Jim']);
expect(renderLog.log).toEqual(['id=Hi, Jim']);
}));
it('should support function calls', fakeAsync(() => {
const ctx = _bindSimpleValue('a()(99)', TestData);
ctx.componentInstance.a = () => (a: any) => a;
ctx.detectChanges(false);
expect(renderLog.log).toEqual(['someProp=99']);
expect(renderLog.log).toEqual(['id=99']);
}));
it('should support chained method calls', fakeAsync(() => {
const ctx = _bindSimpleValue('address.toString()', Person);
ctx.componentInstance.address = new Address('MTV');
ctx.detectChanges(false);
expect(renderLog.log).toEqual(['someProp=MTV']);
expect(renderLog.log).toEqual(['id=MTV']);
}));
it('should support NaN', fakeAsync(() => {
@ -357,7 +333,7 @@ const TEST_COMPILER_PROVIDERS: Provider[] = [
ctx.componentInstance.age = NaN;
ctx.detectChanges(false);
expect(renderLog.log).toEqual(['someProp=NaN']);
expect(renderLog.log).toEqual(['id=NaN']);
renderLog.clear();
ctx.detectChanges(false);
@ -369,7 +345,7 @@ const TEST_COMPILER_PROVIDERS: Provider[] = [
ctx.componentInstance.name = 'misko';
ctx.detectChanges(false);
expect(renderLog.log).toEqual(['someProp=misko']);
expect(renderLog.log).toEqual(['id=misko']);
renderLog.clear();
ctx.detectChanges(false);
@ -378,7 +354,7 @@ const TEST_COMPILER_PROVIDERS: Provider[] = [
ctx.componentInstance.name = 'Misko';
ctx.detectChanges(false);
expect(renderLog.log).toEqual(['someProp=Misko']);
expect(renderLog.log).toEqual(['id=Misko']);
}));
it('should support literal array made of literals', fakeAsync(() => {
@ -445,7 +421,7 @@ const TEST_COMPILER_PROVIDERS: Provider[] = [
it('should ignore empty bindings', fakeAsync(() => {
const ctx = _bindSimpleProp('[someProp]', TestData);
const ctx = _bindSimpleProp('[id]', TestData);
ctx.componentInstance.a = 'value';
ctx.detectChanges(false);
@ -453,23 +429,23 @@ const TEST_COMPILER_PROVIDERS: Provider[] = [
}));
it('should support interpolation', fakeAsync(() => {
const ctx = _bindSimpleProp('someProp="B{{a}}A"', TestData);
const ctx = _bindSimpleProp('id="B{{a}}A"', TestData);
ctx.componentInstance.a = 'value';
ctx.detectChanges(false);
expect(renderLog.log).toEqual(['someProp=BvalueA']);
expect(renderLog.log).toEqual(['id=BvalueA']);
}));
it('should output empty strings for null values in interpolation', fakeAsync(() => {
const ctx = _bindSimpleProp('someProp="B{{a}}A"', TestData);
const ctx = _bindSimpleProp('id="B{{a}}A"', TestData);
ctx.componentInstance.a = null;
ctx.detectChanges(false);
expect(renderLog.log).toEqual(['someProp=BA']);
expect(renderLog.log).toEqual(['id=BA']);
}));
it('should escape values in literals that indicate interpolation',
fakeAsync(() => { expect(_bindAndCheckSimpleValue('"$"')).toEqual(['someProp=$']); }));
fakeAsync(() => { expect(_bindAndCheckSimpleValue('"$"')).toEqual(['id=$']); }));
it('should read locals', fakeAsync(() => {
const ctx = createCompFixture(
@ -515,7 +491,7 @@ const TEST_COMPILER_PROVIDERS: Provider[] = [
ctx.detectChanges(false);
expect(renderLog.log).toEqual(['someProp=Megatron']);
expect(renderLog.log).toEqual(['id=Megatron']);
renderLog.clear();
ctx.detectChanges(false);
@ -528,12 +504,12 @@ const TEST_COMPILER_PROVIDERS: Provider[] = [
ctx.detectChanges(false);
expect(renderLog.log).toEqual(['someProp=Megatron']);
expect(renderLog.log).toEqual(['id=Megatron']);
renderLog.clear();
ctx.detectChanges(false);
expect(renderLog.log).toEqual(['someProp=Megatron']);
expect(renderLog.log).toEqual(['id=Megatron']);
}));
it('should record unwrapped values via ngOnChanges', fakeAsync(() => {
@ -591,8 +567,8 @@ const TEST_COMPILER_PROVIDERS: Provider[] = [
.it('should call pure pipes that are used multiple times only when the arguments change and share state between pipe instances',
fakeAsync(() => {
const ctx = createCompFixture(
`<div [someProp]="name | countingPipe"></div><div [someProp]="age | countingPipe"></div>` +
'<div *ngFor="let x of [1,2]" [someProp]="address.city | countingPipe"></div>',
`<div [id]="name | countingPipe"></div><div [id]="age | countingPipe"></div>` +
'<div *ngFor="let x of [1,2]" [id]="address.city | countingPipe"></div>',
Person);
ctx.componentInstance.name = 'a';
ctx.componentInstance.age = 10;
@ -618,8 +594,8 @@ const TEST_COMPILER_PROVIDERS: Provider[] = [
it('should call pure pipes that are used multiple times only when the arguments change',
fakeAsync(() => {
const ctx = createCompFixture(
`<div [someProp]="name | countingPipe"></div><div [someProp]="age | countingPipe"></div>` +
'<div *ngFor="let x of [1,2]" [someProp]="address.city | countingPipe"></div>',
`<div [id]="name | countingPipe"></div><div [id]="age | countingPipe"></div>` +
'<div *ngFor="let x of [1,2]" [id]="address.city | countingPipe"></div>',
Person);
ctx.componentInstance.name = 'a';
ctx.componentInstance.age = 10;
@ -731,7 +707,7 @@ const TEST_COMPILER_PROVIDERS: Provider[] = [
describe('reading directives', () => {
it('should read directive properties', fakeAsync(() => {
const ctx = createCompFixture(
'<div testDirective [a]="42" ref-dir="testDirective" [someProp]="dir.a"></div>');
'<div testDirective [a]="42" ref-dir="testDirective" [id]="dir.a"></div>');
ctx.detectChanges(false);
expect(renderLog.loggedValues).toEqual([42]);
}));
@ -1191,7 +1167,7 @@ const TEST_COMPILER_PROVIDERS: Provider[] = [
TestBed.configureTestingModule({declarations: [ChangingDirective]});
const ctx = createCompFixture('<div [someProp]="a" [changed]="b"></div>', TestData);
const ctx = createCompFixture('<div [id]="a" [changed]="b"></div>', TestData);
ctx.componentInstance.b = 1;
const errMsgRegExp = ivyEnabled ?
@ -1210,7 +1186,7 @@ const TEST_COMPILER_PROVIDERS: Provider[] = [
TestBed.configureTestingModule({declarations: [ChangingDirective]});
const ctx = createCompFixture('<div [someProp]="a" [changed]="b"></div>', TestData);
const ctx = createCompFixture('<div [id]="a" [changed]="b"></div>', TestData);
ctx.componentInstance.b = 1;
ctx.detectChanges();
@ -1532,13 +1508,7 @@ const TEST_COMPILER_PROVIDERS: Provider[] = [
}
const ctx =
TestBed
.configureCompiler({
providers:
[{provide: ElementSchemaRegistry, useExisting: DomElementSchemaRegistry}]
})
.configureTestingModule({declarations: [Comp, SomeDir]})
.createComponent(Comp);
TestBed.configureTestingModule({declarations: [Comp, SomeDir]}).createComponent(Comp);
ctx.detectChanges();

View File

@ -22,7 +22,7 @@ import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
import {DOCUMENT} from '@angular/platform-browser/src/dom/dom_tokens';
import {dispatchEvent, el} from '@angular/platform-browser/testing/src/browser_util';
import {expect} from '@angular/platform-browser/testing/src/matchers';
import {fixmeIvy, modifiedInIvy, obsoleteInIvy} from '@angular/private/testing';
import {modifiedInIvy, obsoleteInIvy, onlyInIvy} from '@angular/private/testing';
import {stringify} from '../../src/util/stringify';
@ -1592,7 +1592,7 @@ function declareTests(config?: {useJit: boolean}) {
});
describe('Property bindings', () => {
fixmeIvy('FW-721: Bindings to unknown properties are not reported as errors')
modifiedInIvy('Unknown property error thrown during update mode, not creation mode')
.it('should throw on bindings to unknown properties', () => {
TestBed.configureTestingModule({declarations: [MyComp]});
const template = '<div unknown="{{ctxProp}}"></div>';
@ -1606,6 +1606,21 @@ function declareTests(config?: {useJit: boolean}) {
}
});
onlyInIvy('Unknown property error thrown during update mode, not creation mode')
.it('should throw on bindings to unknown properties', () => {
TestBed.configureTestingModule({declarations: [MyComp]});
const template = '<div unknown="{{ctxProp}}"></div>';
TestBed.overrideComponent(MyComp, {set: {template}});
try {
const fixture = TestBed.createComponent(MyComp);
fixture.detectChanges();
throw 'Should throw';
} catch (e) {
expect(e.message).toMatch(
/Template error: Can't bind to 'unknown' since it isn't a known property of 'div'./);
}
});
it('should not throw for property binding to a non-existing property when there is a matching directive property',
() => {
TestBed.configureTestingModule({declarations: [MyComp, MyDir]});

View File

@ -239,7 +239,7 @@ function declareTests(config?: {useJit: boolean}) {
});
describe('schemas', () => {
fixmeIvy('FW-819: ngtsc compiler should support schemas')
modifiedInIvy('Unknown property error thrown during update mode, not creation mode')
.it('should error on unknown bound properties on custom elements by default', () => {
@Component({template: '<some-element [someUnknownProp]="true"></some-element>'})
class ComponentUsingInvalidProperty {
@ -252,19 +252,36 @@ function declareTests(config?: {useJit: boolean}) {
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: '<some-element [someUnknownProp]="true"></some-element>'})
class ComponentUsingInvalidProperty {
}
onlyInIvy('Unknown property error thrown during update mode, not creation mode')
.it('should error on unknown bound properties on custom elements by default', () => {
@Component({template: '<some-element [someUnknownProp]="true"></some-element>'})
class ComponentUsingInvalidProperty {
}
@NgModule(
{schemas: [CUSTOM_ELEMENTS_SCHEMA], declarations: [ComponentUsingInvalidProperty]})
class SomeModule {
}
@NgModule({declarations: [ComponentUsingInvalidProperty]})
class SomeModule {
}
expect(() => createModule(SomeModule)).not.toThrow();
});
const fixture = createComp(ComponentUsingInvalidProperty, SomeModule);
expect(() => fixture.detectChanges()).toThrowError(/Can't bind to 'someUnknownProp'/);
});
fixmeIvy('FW-819: ngtsc compiler should support schemas')
.it('should not error on unknown bound properties on custom elements when using the CUSTOM_ELEMENTS_SCHEMA',
() => {
@Component({template: '<some-element [someUnknownProp]="true"></some-element>'})
class ComponentUsingInvalidProperty {
}
@NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
declarations: [ComponentUsingInvalidProperty]
})
class SomeModule {
}
expect(() => createModule(SomeModule)).not.toThrow();
});
});
describe('id', () => {

View File

@ -255,7 +255,7 @@ function declareTests(config?: {useJit: boolean}) {
expect(getDOM().getStyle(e, 'background')).not.toContain('javascript');
});
fixmeIvy('FW-850: Should throw on unsafe SVG attributes')
modifiedInIvy('Unknown property error thrown during update mode, not creation mode')
.it('should escape unsafe SVG attributes', () => {
const template = `<svg:circle [xlink:href]="ctxProp">Text</svg:circle>`;
TestBed.overrideComponent(SecuredComponent, {set: {template}});
@ -264,6 +264,15 @@ function declareTests(config?: {useJit: boolean}) {
.toThrowError(/Can't bind to 'xlink:href'/);
});
onlyInIvy('Unknown property error thrown during update mode, not creation mode')
.it('should escape unsafe SVG attributes', () => {
const template = `<svg:circle [xlink:href]="ctxProp">Text</svg:circle>`;
TestBed.overrideComponent(SecuredComponent, {set: {template}});
const fixture = TestBed.createComponent(SecuredComponent);
expect(() => fixture.detectChanges()).toThrowError(/Can't bind to 'xlink:href'/);
});
it('should escape unsafe HTML values', () => {
const template = `<div [innerHTML]="ctxProp">Text</div>`;
TestBed.overrideComponent(SecuredComponent, {set: {template}});