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:
@ -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({
|
||||
|
@ -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',
|
||||
() => {
|
||||
|
@ -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',
|
||||
|
@ -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', () => {
|
||||
|
Reference in New Issue
Block a user