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:

committed by
Miško Hevery

parent
7660d0d74a
commit
1950e2d9ba
@ -182,11 +182,12 @@ describe('instructions', () => {
|
||||
});
|
||||
|
||||
it('should not stringify non string values', () => {
|
||||
const t = new TemplateFixture(createDiv, () => {}, 1);
|
||||
const t = new TemplateFixture(() => { element(0, 'input'); }, () => {}, 1);
|
||||
|
||||
t.update(() => elementProperty(0, 'hidden', false));
|
||||
// The hidden property would be true if `false` was stringified into `"false"`.
|
||||
expect((t.hostElement as HTMLElement).querySelector('div') !.hidden).toEqual(false);
|
||||
// Note: don't use 'hidden' here because IE10 does not support the hidden property
|
||||
t.update(() => elementProperty(0, 'required', false));
|
||||
// The required property would be true if `false` was stringified into `"false"`.
|
||||
expect((t.hostElement as HTMLElement).querySelector('input') !.required).toEqual(false);
|
||||
expect(ngDevMode).toHaveProperties({
|
||||
firstTemplatePass: 1,
|
||||
tNode: 2, // 1 for div, 1 for host element
|
||||
|
@ -443,7 +443,8 @@ describe('lifecycles', () => {
|
||||
factory: () => new Component(), template,
|
||||
consts: consts,
|
||||
vars: vars,
|
||||
directives: directives
|
||||
directives: directives,
|
||||
inputs: {val: 'val'}
|
||||
});
|
||||
};
|
||||
}
|
||||
@ -2665,10 +2666,10 @@ describe('lifecycles', () => {
|
||||
element(0, 'div');
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
elementProperty(0, 'data-a', bind(ctx.a));
|
||||
elementProperty(0, 'id', bind(ctx.a));
|
||||
}
|
||||
},
|
||||
selectors: [['mycomp']],
|
||||
selectors: [['my-comp']],
|
||||
inputs: {
|
||||
value: 'value',
|
||||
},
|
||||
@ -2683,7 +2684,7 @@ describe('lifecycles', () => {
|
||||
|
||||
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
element(0, 'mycomp');
|
||||
element(0, 'my-comp');
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
elementProperty(0, 'value', bind(1));
|
||||
|
@ -170,12 +170,12 @@ describe('pipe', () => {
|
||||
elementEnd();
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
elementProperty(0, 'someProp', bind(pipeBind1(1, 1, 'Megatron')));
|
||||
elementProperty(0, 'id', bind(pipeBind1(1, 1, 'Megatron')));
|
||||
}
|
||||
}
|
||||
|
||||
renderToHtml(Template, person, 2, 3, null, [IdentityPipe], rendererFactory2);
|
||||
expect(renderLog.log).toEqual(['someProp=Megatron']);
|
||||
expect(renderLog.log).toEqual(['id=Megatron']);
|
||||
|
||||
renderLog.clear();
|
||||
renderToHtml(Template, person, 2, 3, null, pipes, rendererFactory2);
|
||||
@ -255,8 +255,8 @@ describe('pipe', () => {
|
||||
container(4);
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
elementProperty(0, 'someProp', bind(pipeBind1(1, 2, true)));
|
||||
elementProperty(2, 'someProp', bind(pipeBind1(3, 4, true)));
|
||||
elementProperty(0, 'id', bind(pipeBind1(1, 2, true)));
|
||||
elementProperty(2, 'id', bind(pipeBind1(3, 4, true)));
|
||||
pipeInstances.push(load<CountingImpurePipe>(1), load(3));
|
||||
containerRefreshStart(4);
|
||||
{
|
||||
@ -269,7 +269,7 @@ describe('pipe', () => {
|
||||
elementEnd();
|
||||
}
|
||||
if (rf1 & RenderFlags.Update) {
|
||||
elementProperty(0, 'someProp', bind(pipeBind1(1, 1, true)));
|
||||
elementProperty(0, 'id', bind(pipeBind1(1, 1, true)));
|
||||
pipeInstances.push(load<CountingImpurePipe>(1));
|
||||
}
|
||||
}
|
||||
|
@ -42,12 +42,14 @@ describe('ViewContainerRef', () => {
|
||||
factory: () => directiveInstance = new DirectiveWithVCRef(
|
||||
|
||||
directiveInject(ViewContainerRef as any), injectComponentFactoryResolver()),
|
||||
inputs: {tplRef: 'tplRef'}
|
||||
inputs: {tplRef: 'tplRef', name: 'name'}
|
||||
});
|
||||
|
||||
// TODO(issue/24571): remove '!'.
|
||||
tplRef !: TemplateRef<{}>;
|
||||
|
||||
name: string = '';
|
||||
|
||||
// injecting a ViewContainerRef to create a dynamic container in which embedded views will be
|
||||
// created
|
||||
constructor(public vcref: ViewContainerRef, public cfr: ComponentFactoryResolver) {}
|
||||
|
Reference in New Issue
Block a user