fix(ivy): warn instead of throwing for unknown properties (#32463)

Logs a warning instead of throwing when running into a binding to an unknown property in JIT mode. Since we aren't using a schema for the runtime validation anymore, this allows us to support browsers where properties are unsupported.

PR Close #32463
This commit is contained in:
crisbeto
2019-09-04 21:48:04 +02:00
committed by Matias Niemelä
parent 4c3674f660
commit bc061b78be
6 changed files with 122 additions and 100 deletions

View File

@ -9,6 +9,7 @@
import {CommonModule} from '@angular/common';
import {Component, NO_ERRORS_SCHEMA, NgModule} from '@angular/core';
import {TestBed} from '@angular/core/testing';
import {modifiedInIvy, onlyInIvy} from '@angular/private/testing';
describe('NgModule', () => {
@Component({template: 'hello'})
@ -76,33 +77,64 @@ describe('NgModule', () => {
});
describe('schemas', () => {
it('should throw on unknown props if NO_ERRORS_SCHEMA is absent', () => {
@Component({
selector: 'my-comp',
template: `
<ng-container *ngIf="condition">
<div [unknown-prop]="true"></div>
</ng-container>
`,
})
class MyComp {
condition = true;
}
onlyInIvy('Unknown property warning logged instead of throwing an error')
.it('should throw on unknown props if NO_ERRORS_SCHEMA is absent', () => {
@Component({
selector: 'my-comp',
template: `
<ng-container *ngIf="condition">
<div [unknown-prop]="true"></div>
</ng-container>
`,
})
class MyComp {
condition = true;
}
@NgModule({
imports: [CommonModule],
declarations: [MyComp],
})
class MyModule {
}
@NgModule({
imports: [CommonModule],
declarations: [MyComp],
})
class MyModule {
}
TestBed.configureTestingModule({imports: [MyModule]});
TestBed.configureTestingModule({imports: [MyModule]});
expect(() => {
const fixture = TestBed.createComponent(MyComp);
fixture.detectChanges();
}).toThrowError(/Can't bind to 'unknown-prop' since it isn't a known property of 'div'/);
});
const spy = spyOn(console, 'warn');
const fixture = TestBed.createComponent(MyComp);
fixture.detectChanges();
expect(spy.calls.mostRecent().args[0])
.toMatch(/Can't bind to 'unknown-prop' since it isn't a known property of 'div'/);
});
modifiedInIvy('Unknown properties throw an error instead of logging a warning')
.it('should throw on unknown props if NO_ERRORS_SCHEMA is absent', () => {
@Component({
selector: 'my-comp',
template: `
<ng-container *ngIf="condition">
<div [unknown-prop]="true"></div>
</ng-container>
`,
})
class MyComp {
condition = true;
}
@NgModule({
imports: [CommonModule],
declarations: [MyComp],
})
class MyModule {
}
TestBed.configureTestingModule({imports: [MyModule]});
expect(() => {
const fixture = TestBed.createComponent(MyComp);
fixture.detectChanges();
}).toThrowError(/Can't bind to 'unknown-prop' since it isn't a known property of 'div'/);
});
it('should not throw on unknown props if NO_ERRORS_SCHEMA is present', () => {
@Component({

View File

@ -1630,7 +1630,7 @@ function declareTests(config?: {useJit: boolean}) {
});
describe('Property bindings', () => {
modifiedInIvy('Unknown property error thrown during update mode, not creation mode')
modifiedInIvy('Unknown property error throws an error instead of logging a warning')
.it('should throw on bindings to unknown properties', () => {
TestBed.configureTestingModule({declarations: [MyComp]});
const template = '<div unknown="{{ctxProp}}"></div>';
@ -1644,35 +1644,46 @@ function declareTests(config?: {useJit: boolean}) {
}
});
onlyInIvy('Unknown property error thrown during update mode, not creation mode')
onlyInIvy('Unknown property warning logged instead of throwing an error')
.it('should throw on bindings to unknown properties', () => {
TestBed.configureTestingModule({declarations: [MyComp]});
const template = '<div unknown="{{ctxProp}}"></div>';
TestBed.overrideComponent(MyComp, {set: {template}});
const spy = spyOn(console, 'warn');
const fixture = TestBed.createComponent(MyComp);
fixture.detectChanges();
expect(spy.calls.mostRecent().args[0])
.toMatch(/Can't bind to 'unknown' since it isn't a known property of 'div'./);
});
modifiedInIvy('Unknown property error thrown instead of a warning')
.it('should throw on bindings to unknown properties', () => {
TestBed.configureTestingModule({imports: [CommonModule], declarations: [MyComp]});
const template = '<div *ngFor="let item in ctxArrProp">{{item}}</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'./);
/Can't bind to 'ngForIn' since it isn't a known property of 'div'./);
}
});
it('should throw on bindings to unknown properties of containers', () => {
TestBed.configureTestingModule({imports: [CommonModule], declarations: [MyComp]});
const template = '<div *ngFor="let item in ctxArrProp">{{item}}</div>';
TestBed.overrideComponent(MyComp, {set: {template}});
try {
const fixture = TestBed.createComponent(MyComp);
fixture.detectChanges();
throw 'Should throw';
} catch (e) {
expect(e.message).toMatch(
/Can't bind to 'ngForIn' since it isn't a known property of 'div'./);
}
});
onlyInIvy('Unknown property logs a warning instead of throwing an error')
.it('should throw on bindings to unknown properties', () => {
TestBed.configureTestingModule({imports: [CommonModule], declarations: [MyComp]});
const template = '<div *ngFor="let item in ctxArrProp">{{item}}</div>';
TestBed.overrideComponent(MyComp, {set: {template}});
const spy = spyOn(console, 'warn');
const fixture = TestBed.createComponent(MyComp);
fixture.detectChanges();
expect(spy.calls.mostRecent().args[0])
.toMatch(/Can't bind to 'ngForIn' 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',
() => {

View File

@ -92,13 +92,6 @@ class SomePipe {
class CompUsingModuleDirectiveAndPipe {
}
class DummyConsole implements Console {
public warnings: string[] = [];
log(message: string) {}
warn(message: string) { this.warnings.push(message); }
}
{
if (ivyEnabled) {
describe('ivy', () => { declareTests(); });
@ -112,12 +105,8 @@ function declareTests(config?: {useJit: boolean}) {
describe('NgModule', () => {
let compiler: Compiler;
let injector: Injector;
let console: DummyConsole;
beforeEach(() => {
console = new DummyConsole();
TestBed.configureCompiler({...config, providers: [{provide: Console, useValue: console}]});
});
beforeEach(() => { TestBed.configureCompiler(config || {}); });
beforeEach(inject([Compiler, Injector], (_compiler: Compiler, _injector: Injector) => {
compiler = _compiler;
@ -261,7 +250,7 @@ function declareTests(config?: {useJit: boolean}) {
expect(() => createModule(SomeModule)).toThrowError(/Can't bind to 'someUnknownProp'/);
});
onlyInIvy('Unknown property error thrown during update mode, not creation mode')
onlyInIvy('Unknown property warning logged, instead of throwing an error')
.it('should error on unknown bound properties on custom elements by default', () => {
@Component({template: '<some-element [someUnknownProp]="true"></some-element>'})
class ComponentUsingInvalidProperty {
@ -271,8 +260,10 @@ function declareTests(config?: {useJit: boolean}) {
class SomeModule {
}
const spy = spyOn(console, 'warn');
const fixture = createComp(ComponentUsingInvalidProperty, SomeModule);
expect(() => fixture.detectChanges()).toThrowError(/Can't bind to 'someUnknownProp'/);
fixture.detectChanges();
expect(spy.calls.mostRecent().args[0]).toMatch(/Can't bind to 'someUnknownProp'/);
});
it('should not error on unknown bound properties on custom elements when using the CUSTOM_ELEMENTS_SCHEMA',

View File

@ -263,13 +263,15 @@ function declareTests(config?: {useJit: boolean}) {
.toThrowError(/Can't bind to 'xlink:href'/);
});
onlyInIvy('Unknown property error thrown during update mode, not creation mode')
onlyInIvy('Unknown property warning logged instead of throwing an error')
.it('should escape unsafe SVG attributes', () => {
const template = `<svg:circle [xlink:href]="ctxProp">Text</svg:circle>`;
TestBed.overrideComponent(SecuredComponent, {set: {template}});
const spy = spyOn(console, 'warn');
const fixture = TestBed.createComponent(SecuredComponent);
expect(() => fixture.detectChanges()).toThrowError(/Can't bind to 'xlink:href'/);
fixture.detectChanges();
expect(spy.calls.mostRecent().args[0]).toMatch(/Can't bind to 'xlink:href'/);
});
it('should escape unsafe HTML values', () => {