fix(ivy): compiler should generate bindings to host attrs properly (#26973)
PR Close #26973
This commit is contained in:
parent
a4398aa17f
commit
9e26216c40
|
@ -559,87 +559,6 @@ describe('compiler compliance', () => {
|
||||||
expectEmit(source, OtherDirectiveDefinition, 'Incorrect OtherDirective.ngDirectiveDef');
|
expectEmit(source, OtherDirectiveDefinition, 'Incorrect OtherDirective.ngDirectiveDef');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should support host bindings', () => {
|
|
||||||
const files = {
|
|
||||||
app: {
|
|
||||||
'spec.ts': `
|
|
||||||
import {Directive, HostBinding, NgModule} from '@angular/core';
|
|
||||||
|
|
||||||
@Directive({selector: '[hostBindingDir]'})
|
|
||||||
export class HostBindingDir {
|
|
||||||
@HostBinding('id') dirId = 'some id';
|
|
||||||
}
|
|
||||||
|
|
||||||
@NgModule({declarations: [HostBindingDir]})
|
|
||||||
export class MyModule {}
|
|
||||||
`
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const HostBindingDirDeclaration = `
|
|
||||||
HostBindingDir.ngDirectiveDef = $r3$.ɵdefineDirective({
|
|
||||||
type: HostBindingDir,
|
|
||||||
selectors: [["", "hostBindingDir", ""]],
|
|
||||||
factory: function HostBindingDir_Factory(t) { return new (t || HostBindingDir)(); },
|
|
||||||
hostBindings: function HostBindingDir_HostBindings(dirIndex, elIndex) {
|
|
||||||
$r3$.ɵelementProperty(elIndex, "id", $r3$.ɵbind($r3$.ɵload(dirIndex).dirId));
|
|
||||||
},
|
|
||||||
hostVars: 1
|
|
||||||
});
|
|
||||||
`;
|
|
||||||
|
|
||||||
const result = compile(files, angularFiles);
|
|
||||||
const source = result.source;
|
|
||||||
|
|
||||||
expectEmit(source, HostBindingDirDeclaration, 'Invalid host binding code');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should support host bindings with pure functions', () => {
|
|
||||||
const files = {
|
|
||||||
app: {
|
|
||||||
'spec.ts': `
|
|
||||||
import {Component, NgModule} from '@angular/core';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'host-binding-comp',
|
|
||||||
host: {
|
|
||||||
'[id]': '["red", id]'
|
|
||||||
},
|
|
||||||
template: ''
|
|
||||||
})
|
|
||||||
export class HostBindingComp {
|
|
||||||
id = 'some id';
|
|
||||||
}
|
|
||||||
|
|
||||||
@NgModule({declarations: [HostBindingComp]})
|
|
||||||
export class MyModule {}
|
|
||||||
`
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const HostBindingCompDeclaration = `
|
|
||||||
const $ff$ = function ($v$) { return ["red", $v$]; };
|
|
||||||
…
|
|
||||||
HostBindingComp.ngComponentDef = $r3$.ɵdefineComponent({
|
|
||||||
type: HostBindingComp,
|
|
||||||
selectors: [["host-binding-comp"]],
|
|
||||||
factory: function HostBindingComp_Factory(t) { return new (t || HostBindingComp)(); },
|
|
||||||
hostBindings: function HostBindingComp_HostBindings(dirIndex, elIndex) {
|
|
||||||
$r3$.ɵelementProperty(elIndex, "id", $r3$.ɵbind($r3$.ɵpureFunction1(1, $ff$, $r3$.ɵload(dirIndex).id)));
|
|
||||||
},
|
|
||||||
hostVars: 3,
|
|
||||||
consts: 0,
|
|
||||||
vars: 0,
|
|
||||||
template: function HostBindingComp_Template(rf, ctx) {}
|
|
||||||
});
|
|
||||||
`;
|
|
||||||
|
|
||||||
const result = compile(files, angularFiles);
|
|
||||||
const source = result.source;
|
|
||||||
|
|
||||||
expectEmit(source, HostBindingCompDeclaration, 'Invalid host binding code');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not treat ElementRef, ViewContainerRef, or ChangeDetectorRef specially when injecting',
|
it('should not treat ElementRef, ViewContainerRef, or ChangeDetectorRef specially when injecting',
|
||||||
() => {
|
() => {
|
||||||
const files = {
|
const files = {
|
||||||
|
|
|
@ -122,6 +122,166 @@ describe('compiler compliance: bindings', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('host bindings', () => {
|
||||||
|
it('should support host bindings', () => {
|
||||||
|
const files = {
|
||||||
|
app: {
|
||||||
|
'spec.ts': `
|
||||||
|
import {Directive, HostBinding, NgModule} from '@angular/core';
|
||||||
|
|
||||||
|
@Directive({selector: '[hostBindingDir]'})
|
||||||
|
export class HostBindingDir {
|
||||||
|
@HostBinding('id') dirId = 'some id';
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({declarations: [HostBindingDir]})
|
||||||
|
export class MyModule {}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const HostBindingDirDeclaration = `
|
||||||
|
HostBindingDir.ngDirectiveDef = $r3$.ɵdefineDirective({
|
||||||
|
type: HostBindingDir,
|
||||||
|
selectors: [["", "hostBindingDir", ""]],
|
||||||
|
factory: function HostBindingDir_Factory(t) { return new (t || HostBindingDir)(); },
|
||||||
|
hostBindings: function HostBindingDir_HostBindings(dirIndex, elIndex) {
|
||||||
|
$r3$.ɵelementProperty(elIndex, "id", $r3$.ɵbind($r3$.ɵload(dirIndex).dirId));
|
||||||
|
},
|
||||||
|
hostVars: 1
|
||||||
|
});
|
||||||
|
`;
|
||||||
|
|
||||||
|
const result = compile(files, angularFiles);
|
||||||
|
const source = result.source;
|
||||||
|
|
||||||
|
expectEmit(source, HostBindingDirDeclaration, 'Invalid host binding code');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support host bindings with pure functions', () => {
|
||||||
|
const files = {
|
||||||
|
app: {
|
||||||
|
'spec.ts': `
|
||||||
|
import {Component, NgModule} from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'host-binding-comp',
|
||||||
|
host: {
|
||||||
|
'[id]': '["red", id]'
|
||||||
|
},
|
||||||
|
template: ''
|
||||||
|
})
|
||||||
|
export class HostBindingComp {
|
||||||
|
id = 'some id';
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({declarations: [HostBindingComp]})
|
||||||
|
export class MyModule {}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const HostBindingCompDeclaration = `
|
||||||
|
const $ff$ = function ($v$) { return ["red", $v$]; };
|
||||||
|
…
|
||||||
|
HostBindingComp.ngComponentDef = $r3$.ɵdefineComponent({
|
||||||
|
type: HostBindingComp,
|
||||||
|
selectors: [["host-binding-comp"]],
|
||||||
|
factory: function HostBindingComp_Factory(t) { return new (t || HostBindingComp)(); },
|
||||||
|
hostBindings: function HostBindingComp_HostBindings(dirIndex, elIndex) {
|
||||||
|
$r3$.ɵelementProperty(elIndex, "id", $r3$.ɵbind($r3$.ɵpureFunction1(1, $ff$, $r3$.ɵload(dirIndex).id)));
|
||||||
|
},
|
||||||
|
hostVars: 3,
|
||||||
|
consts: 0,
|
||||||
|
vars: 0,
|
||||||
|
template: function HostBindingComp_Template(rf, ctx) {}
|
||||||
|
});
|
||||||
|
`;
|
||||||
|
|
||||||
|
const result = compile(files, angularFiles);
|
||||||
|
const source = result.source;
|
||||||
|
|
||||||
|
expectEmit(source, HostBindingCompDeclaration, 'Invalid host binding code');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support host attribute bindings', () => {
|
||||||
|
const files = {
|
||||||
|
app: {
|
||||||
|
'spec.ts': `
|
||||||
|
import {Directive, NgModule} from '@angular/core';
|
||||||
|
|
||||||
|
@Directive({
|
||||||
|
selector: '[hostAttributeDir]',
|
||||||
|
host: {
|
||||||
|
'[attr.required]': 'required'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
export class HostAttributeDir {
|
||||||
|
required = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({declarations: [HostAttributeDir]})
|
||||||
|
export class MyModule {}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const HostAttributeDirDeclaration = `
|
||||||
|
HostAttributeDir.ngDirectiveDef = $r3$.ɵdefineDirective({
|
||||||
|
type: HostAttributeDir,
|
||||||
|
selectors: [["", "hostAttributeDir", ""]],
|
||||||
|
factory: function HostAttributeDir_Factory(t) { return new (t || HostAttributeDir)(); },
|
||||||
|
hostBindings: function HostAttributeDir_HostBindings(dirIndex, elIndex) {
|
||||||
|
$r3$.ɵelementAttribute(elIndex, "required", $r3$.ɵbind($r3$.ɵload(dirIndex).required));
|
||||||
|
},
|
||||||
|
hostVars: 1
|
||||||
|
});
|
||||||
|
`;
|
||||||
|
|
||||||
|
const result = compile(files, angularFiles);
|
||||||
|
const source = result.source;
|
||||||
|
|
||||||
|
expectEmit(source, HostAttributeDirDeclaration, 'Invalid host attribute code');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support host attributes', () => {
|
||||||
|
const files = {
|
||||||
|
app: {
|
||||||
|
'spec.ts': `
|
||||||
|
import {Directive, NgModule} from '@angular/core';
|
||||||
|
|
||||||
|
@Directive({
|
||||||
|
selector: '[hostAttributeDir]',
|
||||||
|
host: {
|
||||||
|
'aria-label': 'label'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
export class HostAttributeDir {
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({declarations: [HostAttributeDir]})
|
||||||
|
export class MyModule {}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const HostAttributeDirDeclaration = `
|
||||||
|
HostAttributeDir.ngDirectiveDef = $r3$.ɵdefineDirective({
|
||||||
|
type: HostAttributeDir,
|
||||||
|
selectors: [["", "hostAttributeDir", ""]],
|
||||||
|
factory: function HostAttributeDir_Factory(t) { return new (t || HostAttributeDir)(); },
|
||||||
|
attributes: ["aria-label", "label"]
|
||||||
|
});
|
||||||
|
`;
|
||||||
|
|
||||||
|
const result = compile(files, angularFiles);
|
||||||
|
const source = result.source;
|
||||||
|
|
||||||
|
expectEmit(source, HostAttributeDirDeclaration, 'Invalid host attribute code');
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
describe('non bindable behavior', () => {
|
describe('non bindable behavior', () => {
|
||||||
const getAppFiles = (template: string = ''): MockDirectory => ({
|
const getAppFiles = (template: string = ''): MockDirectory => ({
|
||||||
app: {
|
app: {
|
||||||
|
|
|
@ -477,7 +477,7 @@ describe('ngtsc behavioral tests', () => {
|
||||||
env.driveMain();
|
env.driveMain();
|
||||||
const jsContents = env.getContents('test.js');
|
const jsContents = env.getContents('test.js');
|
||||||
expect(jsContents)
|
expect(jsContents)
|
||||||
.toContain(`i0.ɵelementProperty(elIndex, "attr.hello", i0.ɵbind(i0.ɵload(dirIndex).foo));`);
|
.toContain(`i0.ɵelementAttribute(elIndex, "hello", i0.ɵbind(i0.ɵload(dirIndex).foo));`);
|
||||||
expect(jsContents)
|
expect(jsContents)
|
||||||
.toContain(`i0.ɵelementProperty(elIndex, "prop", i0.ɵbind(i0.ɵload(dirIndex).bar));`);
|
.toContain(`i0.ɵelementProperty(elIndex, "prop", i0.ɵbind(i0.ɵload(dirIndex).bar));`);
|
||||||
expect(jsContents)
|
expect(jsContents)
|
||||||
|
|
|
@ -32,6 +32,10 @@ import {CONTEXT_NAME, DefinitionMap, RENDER_FLAGS, TEMPORARY_NAME, asLiteral, co
|
||||||
|
|
||||||
const EMPTY_ARRAY: any[] = [];
|
const EMPTY_ARRAY: any[] = [];
|
||||||
|
|
||||||
|
// This regex matches any binding names that contain the "attr." prefix, e.g. "attr.required"
|
||||||
|
// If there is a match, the first matching group will contain the attribute name to bind.
|
||||||
|
const ATTR_REGEX = /attr\.([^\]]+)/;
|
||||||
|
|
||||||
function baseDirectiveFields(
|
function baseDirectiveFields(
|
||||||
meta: R3DirectiveMetadata, constantPool: ConstantPool,
|
meta: R3DirectiveMetadata, constantPool: ConstantPool,
|
||||||
bindingParser: BindingParser): {definitionMap: DefinitionMap, statements: o.Statement[]} {
|
bindingParser: BindingParser): {definitionMap: DefinitionMap, statements: o.Statement[]} {
|
||||||
|
@ -625,11 +629,14 @@ function createHostBindingsFunction(
|
||||||
const bindingExpr = convertPropertyBinding(
|
const bindingExpr = convertPropertyBinding(
|
||||||
null, bindingContext, value, 'b', BindingForm.TrySimple,
|
null, bindingContext, value, 'b', BindingForm.TrySimple,
|
||||||
() => error('Unexpected interpolation'));
|
() => error('Unexpected interpolation'));
|
||||||
|
|
||||||
|
const {bindingName, instruction} = getBindingNameAndInstruction(binding.name);
|
||||||
|
|
||||||
statements.push(...bindingExpr.stmts);
|
statements.push(...bindingExpr.stmts);
|
||||||
statements.push(o.importExpr(R3.elementProperty)
|
statements.push(o.importExpr(instruction)
|
||||||
.callFn([
|
.callFn([
|
||||||
o.variable('elIndex'),
|
o.variable('elIndex'),
|
||||||
o.literal(binding.name),
|
o.literal(bindingName),
|
||||||
o.importExpr(R3.bind).callFn([bindingExpr.currValExpr]),
|
o.importExpr(R3.bind).callFn([bindingExpr.currValExpr]),
|
||||||
])
|
])
|
||||||
.toStmt());
|
.toStmt());
|
||||||
|
@ -649,6 +656,22 @@ function createHostBindingsFunction(
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getBindingNameAndInstruction(bindingName: string):
|
||||||
|
{bindingName: string, instruction: o.ExternalReference} {
|
||||||
|
let instruction !: o.ExternalReference;
|
||||||
|
|
||||||
|
// Check to see if this is an attr binding or a property binding
|
||||||
|
const attrMatches = bindingName.match(ATTR_REGEX);
|
||||||
|
if (attrMatches) {
|
||||||
|
bindingName = attrMatches[1];
|
||||||
|
instruction = R3.elementAttribute;
|
||||||
|
} else {
|
||||||
|
instruction = R3.elementProperty;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {bindingName, instruction};
|
||||||
|
}
|
||||||
|
|
||||||
function createFactoryExtraStatementsFn(meta: R3DirectiveMetadata, bindingParser: BindingParser):
|
function createFactoryExtraStatementsFn(meta: R3DirectiveMetadata, bindingParser: BindingParser):
|
||||||
((instance: o.Expression) => o.Statement[])|null {
|
((instance: o.Expression) => o.Statement[])|null {
|
||||||
const eventBindings =
|
const eventBindings =
|
||||||
|
@ -698,8 +721,8 @@ const HOST_REG_EXP = /^(?:(?:\[([^\]]+)\])|(?:\(([^\)]+)\)))|(\@[-\w]+)$/;
|
||||||
|
|
||||||
// Represents the groups in the above regex.
|
// Represents the groups in the above regex.
|
||||||
const enum HostBindingGroup {
|
const enum HostBindingGroup {
|
||||||
// group 1: "prop" from "[prop]"
|
// group 1: "prop" from "[prop]", or "attr.role" from "[attr.role]"
|
||||||
Property = 1,
|
Binding = 1,
|
||||||
|
|
||||||
// group 2: "event" from "(event)"
|
// group 2: "event" from "(event)"
|
||||||
Event = 2,
|
Event = 2,
|
||||||
|
@ -724,8 +747,8 @@ export function parseHostBindings(host: {[key: string]: string}): {
|
||||||
const matches = key.match(HOST_REG_EXP);
|
const matches = key.match(HOST_REG_EXP);
|
||||||
if (matches === null) {
|
if (matches === null) {
|
||||||
attributes[key] = value;
|
attributes[key] = value;
|
||||||
} else if (matches[HostBindingGroup.Property] != null) {
|
} else if (matches[HostBindingGroup.Binding] != null) {
|
||||||
properties[matches[HostBindingGroup.Property]] = value;
|
properties[matches[HostBindingGroup.Binding]] = value;
|
||||||
} else if (matches[HostBindingGroup.Event] != null) {
|
} else if (matches[HostBindingGroup.Event] != null) {
|
||||||
listeners[matches[HostBindingGroup.Event]] = value;
|
listeners[matches[HostBindingGroup.Event]] = value;
|
||||||
} else if (matches[HostBindingGroup.Animation] != null) {
|
} else if (matches[HostBindingGroup.Animation] != null) {
|
||||||
|
|
|
@ -622,4 +622,28 @@ describe('host bindings', () => {
|
||||||
expect(hostElement.title).toBe('other title');
|
expect(hostElement.title).toBe('other title');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should support host attributes', () => {
|
||||||
|
// host: {
|
||||||
|
// 'role': 'listbox'
|
||||||
|
// }
|
||||||
|
class HostAttributeDir {
|
||||||
|
static ngDirectiveDef = defineDirective({
|
||||||
|
selectors: [['', 'hostAttributeDir', '']],
|
||||||
|
type: HostAttributeDir,
|
||||||
|
factory: () => new HostAttributeDir(),
|
||||||
|
attributes: ['role', 'listbox']
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// <div hostAttributeDir></div>
|
||||||
|
const App = createComponent('app', (rf: RenderFlags, ctx: any) => {
|
||||||
|
if (rf & RenderFlags.Create) {
|
||||||
|
element(0, 'div', ['hostAttributeDir', '']);
|
||||||
|
}
|
||||||
|
}, 1, 0, [HostAttributeDir]);
|
||||||
|
|
||||||
|
const fixture = new ComponentFixture(App);
|
||||||
|
expect(fixture.html).toEqual(`<div hostattributedir="" role="listbox"></div>`);
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -1918,81 +1918,80 @@ import {MyInput, MyInputForm} from './value_accessor_integration_spec';
|
||||||
expect(form.valid).toEqual(true);
|
expect(form.valid).toEqual(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
fixmeIvy('host attribute instructions are not generated properly') &&
|
it('changes on bound properties should change the validation state of the form', () => {
|
||||||
it('changes on bound properties should change the validation state of the form', () => {
|
const fixture = initTest(ValidationBindingsForm);
|
||||||
const fixture = initTest(ValidationBindingsForm);
|
const form = new FormGroup({
|
||||||
const form = new FormGroup({
|
'login': new FormControl(''),
|
||||||
'login': new FormControl(''),
|
'min': new FormControl(''),
|
||||||
'min': new FormControl(''),
|
'max': new FormControl(''),
|
||||||
'max': new FormControl(''),
|
'pattern': new FormControl('')
|
||||||
'pattern': new FormControl('')
|
});
|
||||||
});
|
fixture.componentInstance.form = form;
|
||||||
fixture.componentInstance.form = form;
|
fixture.detectChanges();
|
||||||
fixture.detectChanges();
|
|
||||||
|
|
||||||
const required = fixture.debugElement.query(By.css('[name=required]'));
|
const required = fixture.debugElement.query(By.css('[name=required]'));
|
||||||
const minLength = fixture.debugElement.query(By.css('[name=minlength]'));
|
const minLength = fixture.debugElement.query(By.css('[name=minlength]'));
|
||||||
const maxLength = fixture.debugElement.query(By.css('[name=maxlength]'));
|
const maxLength = fixture.debugElement.query(By.css('[name=maxlength]'));
|
||||||
const pattern = fixture.debugElement.query(By.css('[name=pattern]'));
|
const pattern = fixture.debugElement.query(By.css('[name=pattern]'));
|
||||||
|
|
||||||
required.nativeElement.value = '';
|
required.nativeElement.value = '';
|
||||||
minLength.nativeElement.value = '1';
|
minLength.nativeElement.value = '1';
|
||||||
maxLength.nativeElement.value = '1234';
|
maxLength.nativeElement.value = '1234';
|
||||||
pattern.nativeElement.value = '12';
|
pattern.nativeElement.value = '12';
|
||||||
|
|
||||||
dispatchEvent(required.nativeElement, 'input');
|
dispatchEvent(required.nativeElement, 'input');
|
||||||
dispatchEvent(minLength.nativeElement, 'input');
|
dispatchEvent(minLength.nativeElement, 'input');
|
||||||
dispatchEvent(maxLength.nativeElement, 'input');
|
dispatchEvent(maxLength.nativeElement, 'input');
|
||||||
dispatchEvent(pattern.nativeElement, 'input');
|
dispatchEvent(pattern.nativeElement, 'input');
|
||||||
|
|
||||||
expect(form.hasError('required', ['login'])).toEqual(false);
|
expect(form.hasError('required', ['login'])).toEqual(false);
|
||||||
expect(form.hasError('minlength', ['min'])).toEqual(false);
|
expect(form.hasError('minlength', ['min'])).toEqual(false);
|
||||||
expect(form.hasError('maxlength', ['max'])).toEqual(false);
|
expect(form.hasError('maxlength', ['max'])).toEqual(false);
|
||||||
expect(form.hasError('pattern', ['pattern'])).toEqual(false);
|
expect(form.hasError('pattern', ['pattern'])).toEqual(false);
|
||||||
expect(form.valid).toEqual(true);
|
expect(form.valid).toEqual(true);
|
||||||
|
|
||||||
fixture.componentInstance.required = true;
|
fixture.componentInstance.required = true;
|
||||||
fixture.componentInstance.minLen = 3;
|
fixture.componentInstance.minLen = 3;
|
||||||
fixture.componentInstance.maxLen = 3;
|
fixture.componentInstance.maxLen = 3;
|
||||||
fixture.componentInstance.pattern = '.{3,}';
|
fixture.componentInstance.pattern = '.{3,}';
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
dispatchEvent(required.nativeElement, 'input');
|
dispatchEvent(required.nativeElement, 'input');
|
||||||
dispatchEvent(minLength.nativeElement, 'input');
|
dispatchEvent(minLength.nativeElement, 'input');
|
||||||
dispatchEvent(maxLength.nativeElement, 'input');
|
dispatchEvent(maxLength.nativeElement, 'input');
|
||||||
dispatchEvent(pattern.nativeElement, 'input');
|
dispatchEvent(pattern.nativeElement, 'input');
|
||||||
|
|
||||||
expect(form.hasError('required', ['login'])).toEqual(true);
|
expect(form.hasError('required', ['login'])).toEqual(true);
|
||||||
expect(form.hasError('minlength', ['min'])).toEqual(true);
|
expect(form.hasError('minlength', ['min'])).toEqual(true);
|
||||||
expect(form.hasError('maxlength', ['max'])).toEqual(true);
|
expect(form.hasError('maxlength', ['max'])).toEqual(true);
|
||||||
expect(form.hasError('pattern', ['pattern'])).toEqual(true);
|
expect(form.hasError('pattern', ['pattern'])).toEqual(true);
|
||||||
expect(form.valid).toEqual(false);
|
expect(form.valid).toEqual(false);
|
||||||
|
|
||||||
expect(required.nativeElement.getAttribute('required')).toEqual('');
|
expect(required.nativeElement.getAttribute('required')).toEqual('');
|
||||||
expect(fixture.componentInstance.minLen.toString())
|
expect(fixture.componentInstance.minLen.toString())
|
||||||
.toEqual(minLength.nativeElement.getAttribute('minlength'));
|
.toEqual(minLength.nativeElement.getAttribute('minlength'));
|
||||||
expect(fixture.componentInstance.maxLen.toString())
|
expect(fixture.componentInstance.maxLen.toString())
|
||||||
.toEqual(maxLength.nativeElement.getAttribute('maxlength'));
|
.toEqual(maxLength.nativeElement.getAttribute('maxlength'));
|
||||||
expect(fixture.componentInstance.pattern.toString())
|
expect(fixture.componentInstance.pattern.toString())
|
||||||
.toEqual(pattern.nativeElement.getAttribute('pattern'));
|
.toEqual(pattern.nativeElement.getAttribute('pattern'));
|
||||||
|
|
||||||
fixture.componentInstance.required = false;
|
fixture.componentInstance.required = false;
|
||||||
fixture.componentInstance.minLen = null !;
|
fixture.componentInstance.minLen = null !;
|
||||||
fixture.componentInstance.maxLen = null !;
|
fixture.componentInstance.maxLen = null !;
|
||||||
fixture.componentInstance.pattern = null !;
|
fixture.componentInstance.pattern = null !;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
expect(form.hasError('required', ['login'])).toEqual(false);
|
expect(form.hasError('required', ['login'])).toEqual(false);
|
||||||
expect(form.hasError('minlength', ['min'])).toEqual(false);
|
expect(form.hasError('minlength', ['min'])).toEqual(false);
|
||||||
expect(form.hasError('maxlength', ['max'])).toEqual(false);
|
expect(form.hasError('maxlength', ['max'])).toEqual(false);
|
||||||
expect(form.hasError('pattern', ['pattern'])).toEqual(false);
|
expect(form.hasError('pattern', ['pattern'])).toEqual(false);
|
||||||
expect(form.valid).toEqual(true);
|
expect(form.valid).toEqual(true);
|
||||||
|
|
||||||
expect(required.nativeElement.getAttribute('required')).toEqual(null);
|
expect(required.nativeElement.getAttribute('required')).toEqual(null);
|
||||||
expect(required.nativeElement.getAttribute('minlength')).toEqual(null);
|
expect(required.nativeElement.getAttribute('minlength')).toEqual(null);
|
||||||
expect(required.nativeElement.getAttribute('maxlength')).toEqual(null);
|
expect(required.nativeElement.getAttribute('maxlength')).toEqual(null);
|
||||||
expect(required.nativeElement.getAttribute('pattern')).toEqual(null);
|
expect(required.nativeElement.getAttribute('pattern')).toEqual(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should support rebound controls with rebound validators', () => {
|
it('should support rebound controls with rebound validators', () => {
|
||||||
const fixture = initTest(ValidationBindingsForm);
|
const fixture = initTest(ValidationBindingsForm);
|
||||||
|
|
|
@ -1420,77 +1420,76 @@ import {NgModelCustomComp, NgModelCustomWrapper} from './value_accessor_integrat
|
||||||
expect(form.control.hasError('minlength', ['tovalidate'])).toBeTruthy();
|
expect(form.control.hasError('minlength', ['tovalidate'])).toBeTruthy();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
fixmeIvy('host attribute instructions are not generated properly') &&
|
it('changes on bound properties should change the validation state of the form',
|
||||||
it('changes on bound properties should change the validation state of the form',
|
fakeAsync(() => {
|
||||||
fakeAsync(() => {
|
const fixture = initTest(NgModelValidationBindings);
|
||||||
const fixture = initTest(NgModelValidationBindings);
|
fixture.detectChanges();
|
||||||
fixture.detectChanges();
|
tick();
|
||||||
tick();
|
|
||||||
|
|
||||||
const required = fixture.debugElement.query(By.css('[name=required]'));
|
const required = fixture.debugElement.query(By.css('[name=required]'));
|
||||||
const minLength = fixture.debugElement.query(By.css('[name=minlength]'));
|
const minLength = fixture.debugElement.query(By.css('[name=minlength]'));
|
||||||
const maxLength = fixture.debugElement.query(By.css('[name=maxlength]'));
|
const maxLength = fixture.debugElement.query(By.css('[name=maxlength]'));
|
||||||
const pattern = fixture.debugElement.query(By.css('[name=pattern]'));
|
const pattern = fixture.debugElement.query(By.css('[name=pattern]'));
|
||||||
|
|
||||||
required.nativeElement.value = '';
|
required.nativeElement.value = '';
|
||||||
minLength.nativeElement.value = '1';
|
minLength.nativeElement.value = '1';
|
||||||
maxLength.nativeElement.value = '1234';
|
maxLength.nativeElement.value = '1234';
|
||||||
pattern.nativeElement.value = '12';
|
pattern.nativeElement.value = '12';
|
||||||
|
|
||||||
dispatchEvent(required.nativeElement, 'input');
|
dispatchEvent(required.nativeElement, 'input');
|
||||||
dispatchEvent(minLength.nativeElement, 'input');
|
dispatchEvent(minLength.nativeElement, 'input');
|
||||||
dispatchEvent(maxLength.nativeElement, 'input');
|
dispatchEvent(maxLength.nativeElement, 'input');
|
||||||
dispatchEvent(pattern.nativeElement, 'input');
|
dispatchEvent(pattern.nativeElement, 'input');
|
||||||
|
|
||||||
const form = fixture.debugElement.children[0].injector.get(NgForm);
|
const form = fixture.debugElement.children[0].injector.get(NgForm);
|
||||||
expect(form.control.hasError('required', ['required'])).toEqual(false);
|
expect(form.control.hasError('required', ['required'])).toEqual(false);
|
||||||
expect(form.control.hasError('minlength', ['minlength'])).toEqual(false);
|
expect(form.control.hasError('minlength', ['minlength'])).toEqual(false);
|
||||||
expect(form.control.hasError('maxlength', ['maxlength'])).toEqual(false);
|
expect(form.control.hasError('maxlength', ['maxlength'])).toEqual(false);
|
||||||
expect(form.control.hasError('pattern', ['pattern'])).toEqual(false);
|
expect(form.control.hasError('pattern', ['pattern'])).toEqual(false);
|
||||||
expect(form.valid).toEqual(true);
|
expect(form.valid).toEqual(true);
|
||||||
|
|
||||||
fixture.componentInstance.required = true;
|
fixture.componentInstance.required = true;
|
||||||
fixture.componentInstance.minLen = 3;
|
fixture.componentInstance.minLen = 3;
|
||||||
fixture.componentInstance.maxLen = 3;
|
fixture.componentInstance.maxLen = 3;
|
||||||
fixture.componentInstance.pattern = '.{3,}';
|
fixture.componentInstance.pattern = '.{3,}';
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
dispatchEvent(required.nativeElement, 'input');
|
dispatchEvent(required.nativeElement, 'input');
|
||||||
dispatchEvent(minLength.nativeElement, 'input');
|
dispatchEvent(minLength.nativeElement, 'input');
|
||||||
dispatchEvent(maxLength.nativeElement, 'input');
|
dispatchEvent(maxLength.nativeElement, 'input');
|
||||||
dispatchEvent(pattern.nativeElement, 'input');
|
dispatchEvent(pattern.nativeElement, 'input');
|
||||||
|
|
||||||
expect(form.control.hasError('required', ['required'])).toEqual(true);
|
expect(form.control.hasError('required', ['required'])).toEqual(true);
|
||||||
expect(form.control.hasError('minlength', ['minlength'])).toEqual(true);
|
expect(form.control.hasError('minlength', ['minlength'])).toEqual(true);
|
||||||
expect(form.control.hasError('maxlength', ['maxlength'])).toEqual(true);
|
expect(form.control.hasError('maxlength', ['maxlength'])).toEqual(true);
|
||||||
expect(form.control.hasError('pattern', ['pattern'])).toEqual(true);
|
expect(form.control.hasError('pattern', ['pattern'])).toEqual(true);
|
||||||
expect(form.valid).toEqual(false);
|
expect(form.valid).toEqual(false);
|
||||||
|
|
||||||
expect(required.nativeElement.getAttribute('required')).toEqual('');
|
expect(required.nativeElement.getAttribute('required')).toEqual('');
|
||||||
expect(fixture.componentInstance.minLen.toString())
|
expect(fixture.componentInstance.minLen.toString())
|
||||||
.toEqual(minLength.nativeElement.getAttribute('minlength'));
|
.toEqual(minLength.nativeElement.getAttribute('minlength'));
|
||||||
expect(fixture.componentInstance.maxLen.toString())
|
expect(fixture.componentInstance.maxLen.toString())
|
||||||
.toEqual(maxLength.nativeElement.getAttribute('maxlength'));
|
.toEqual(maxLength.nativeElement.getAttribute('maxlength'));
|
||||||
expect(fixture.componentInstance.pattern.toString())
|
expect(fixture.componentInstance.pattern.toString())
|
||||||
.toEqual(pattern.nativeElement.getAttribute('pattern'));
|
.toEqual(pattern.nativeElement.getAttribute('pattern'));
|
||||||
|
|
||||||
fixture.componentInstance.required = false;
|
fixture.componentInstance.required = false;
|
||||||
fixture.componentInstance.minLen = null !;
|
fixture.componentInstance.minLen = null !;
|
||||||
fixture.componentInstance.maxLen = null !;
|
fixture.componentInstance.maxLen = null !;
|
||||||
fixture.componentInstance.pattern = null !;
|
fixture.componentInstance.pattern = null !;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
expect(form.control.hasError('required', ['required'])).toEqual(false);
|
expect(form.control.hasError('required', ['required'])).toEqual(false);
|
||||||
expect(form.control.hasError('minlength', ['minlength'])).toEqual(false);
|
expect(form.control.hasError('minlength', ['minlength'])).toEqual(false);
|
||||||
expect(form.control.hasError('maxlength', ['maxlength'])).toEqual(false);
|
expect(form.control.hasError('maxlength', ['maxlength'])).toEqual(false);
|
||||||
expect(form.control.hasError('pattern', ['pattern'])).toEqual(false);
|
expect(form.control.hasError('pattern', ['pattern'])).toEqual(false);
|
||||||
expect(form.valid).toEqual(true);
|
expect(form.valid).toEqual(true);
|
||||||
|
|
||||||
expect(required.nativeElement.getAttribute('required')).toEqual(null);
|
expect(required.nativeElement.getAttribute('required')).toEqual(null);
|
||||||
expect(required.nativeElement.getAttribute('minlength')).toEqual(null);
|
expect(required.nativeElement.getAttribute('minlength')).toEqual(null);
|
||||||
expect(required.nativeElement.getAttribute('maxlength')).toEqual(null);
|
expect(required.nativeElement.getAttribute('maxlength')).toEqual(null);
|
||||||
expect(required.nativeElement.getAttribute('pattern')).toEqual(null);
|
expect(required.nativeElement.getAttribute('pattern')).toEqual(null);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
fixmeIvy('ngModelChange callback never called') &&
|
fixmeIvy('ngModelChange callback never called') &&
|
||||||
it('should update control status', fakeAsync(() => {
|
it('should update control status', fakeAsync(() => {
|
||||||
|
|
Loading…
Reference in New Issue