[英]Angular Material 2 dialog with form test failing
我有一個使用 Angular Material 2 和 Angular 響應式表單生成的簡單登錄對話框。
該對話框在程序中使用時按應有的方式工作,但對其進行的單元測試並未反映這一點。 表單登錄按鈕應該被禁用,直到名稱和密碼字段都通過組件中設置的驗證標准,此時登錄按鈕被啟用並可供單擊。
但是,當我運行測試並將名稱和輸入字段設置為有效內容時,登錄按鈕保持禁用狀態並且測試失敗。
測試相關部分代碼如下
it('should enable the login button when a valid username and password are entered', fakeAsync(() => {
(overlayContainerElement.querySelector('input[formcontrolname="name"]') as HTMLInputElement).value = 'ABC';
(overlayContainerElement.querySelector('input[formcontrolname="password"]') as HTMLInputElement).value = '12345678';
viewContainerFixture.detectChanges();
tick();
viewContainerFixture.detectChanges();
const loginBtn = overlayContainerElement.querySelector('button[md-raised-button]');
const nameInput = overlayContainerElement.querySelector('input[formcontrolname="name"]');
const passwordInput = overlayContainerElement.querySelector('input[formcontrolname="password"]');
console.log('Login Button is:', loginBtn.textContent);
console.log('Login Button is:', loginBtn.getAttribute('ng-reflect-disabled'));
expect((nameInput as HTMLInputElement).value).toEqual('ABC');
expect((passwordInput as HTMLInputElement).value).toEqual('12345678');
expect((overlayContainerElement.querySelector('button[md-raised-button]')).getAttribute('ng-reflect-disabled')).toBe('false');
}));
我顯然沒有刷新登錄按鈕的狀態,但不明白為什么會這樣。
任何幫助將不勝感激。
https://plnkr.co/edit/U1lpoa?p=info是指向 plunker 的鏈接,它顯示了組件和測試套件,代碼在下面轉載。
零件
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { MdDialog, MdDialogRef } from '@angular/material';
@Component({
selector: 'lpa-login-dialog',
templateUrl: './login-dialog.component.html',
})
export class LoginDialogComponent implements OnInit {
loginForm: FormGroup;
constructor(
private fb: FormBuilder,
private dlgRef: MdDialogRef<LoginDialogComponent>
) {
this.createForm()
}
ngOnInit() {
}
private createForm() {
this.loginForm = this.fb.group({
name: ['', [Validators.required, Validators.minLength(3)]],
password: ['', [Validators.required, Validators.minLength(8)]]
})
}
public login() {
this.dlgRef.close(this.loginForm.value.name);
}
}
HTML
<h1 class="mdl-dialog-title" style="text-align: center">App Login</h1>
<form [formGroup]="loginForm" (ngSubmit)="login()" ngnovalidate>
<div class="mdl-dialog-content">
<div class="form-group">
<md-input-container style="width: 100%">
<input mdInput class="form-control" formControlName="name" placeholder="Name">
</md-input-container>
</div>
<div class="form-group">
<md-input-container style="width: 100%">
<input mdInput type="password" class="form-control" formControlName="password" placeholder="Password">
</md-input-container>
</div>
</div>
<div class="mdl-dialog-actions" style="text-align: center">
<button md-raised-button color="primary" type="submit" [disabled]="!loginForm.valid" >Login</button>
<button md-button md-dialog-close=false color="warn">Cancel</button>
</div>
</form>
單元測試(.spec)
import { inject, async, fakeAsync, flushMicrotasks, ComponentFixture, TestBed, tick, } from '@angular/core/testing';
import { NgModule, Component, Directive, ViewChild, ViewContainerRef, Injector, Inject, DebugElement } from '@angular/core';
import { By } from '@angular/platform-browser';
import { ReactiveFormsModule, FormBuilder, FormGroup, Validators } from '@angular/forms';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { MaterialModule, MdDialogModule, MdDialog, MdDialogRef, MdButton, OverlayContainer } from '@angular/material';
import { Observable } from 'rxjs/Observable';
import { Subscriber } from 'rxjs/Subscriber';
import { LoginDialogComponent } from './login-dialog.component';
// helper classes
// tslint:disable-next-line:directive-selector
@Directive({ selector: 'dir-with-view-container' })
class DlgTestViewContainerDirective {
constructor(public viewContainerRef: ViewContainerRef) { }
}
@Component({
selector: 'lpa-arbitrary-component',
template: `<dir-with-view-container></dir-with-view-container>`,
})
class DlgTestChildViewContainerComponent {
@ViewChild(DlgTestViewContainerDirective) childWithViewContainer: DlgTestViewContainerDirective;
get childViewContainer() {
return this.childWithViewContainer.viewContainerRef;
}
}
// Create a real (non-test) NgModule as a workaround for
// https://github.com/angular/angular/issues/10760
const TEST_DIRECTIVES = [
DlgTestViewContainerDirective,
DlgTestChildViewContainerComponent,
LoginDialogComponent
];
@NgModule({
imports: [
MdDialogModule,
ReactiveFormsModule,
MaterialModule,
NoopAnimationsModule
],
exports: TEST_DIRECTIVES,
declarations: TEST_DIRECTIVES,
entryComponents: [
LoginDialogComponent
]
})
class DialogTestModule { }
describe('Login Dialog Component', () => {
let dialog: MdDialog;
let dialogRef: MdDialogRef<LoginDialogComponent>;
let overlayContainerElement: HTMLElement;
let viewContainerFixture: ComponentFixture<DlgTestChildViewContainerComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
DialogTestModule,
],
declarations: [
],
providers: [
{
provide: OverlayContainer, useFactory: () => {
overlayContainerElement = document.createElement('div');
return { getContainerElement: () => overlayContainerElement };
}
}
]
})
.compileComponents();
}));
beforeEach(inject([MdDialog], (d: MdDialog) => {
dialog = d;
}));
beforeEach(() => {
viewContainerFixture = TestBed.createComponent(DlgTestChildViewContainerComponent);
viewContainerFixture.detectChanges();
dialogRef = dialog.open(LoginDialogComponent);
viewContainerFixture.detectChanges();
});
it('should be created', fakeAsync(() => {
expect(dialogRef.componentInstance instanceof LoginDialogComponent).toBe(true, 'Failed to open');
expect(overlayContainerElement.querySelector('h1').innerText).toEqual('App Login');
dialogRef.close();
tick(500);
viewContainerFixture.detectChanges();
}));
it('should close and return false when cancel button pressed', async(() => {
const afterCloseCallback = jasmine.createSpy('afterClose callback');
dialogRef.afterClosed().subscribe(afterCloseCallback);
(overlayContainerElement.querySelector('button[md-dialog-close="false"]') as HTMLElement).click();
viewContainerFixture.detectChanges();
viewContainerFixture.whenStable().then(() => {
expect(overlayContainerElement.querySelector('md-dialog-container')).toBeNull('Dialog box still open');
expect(afterCloseCallback).toHaveBeenCalledWith('false');
});
}));
describe('should disable login button', () => {
it('without a user and password entry', fakeAsync(() => {
const btn = overlayContainerElement.querySelector('button[md-raised-button]');
expect(btn.getAttribute('ng-reflect-disabled')).toBe('true');
dialogRef.close()
tick(500);
viewContainerFixture.detectChanges();
}));
it('with a user entry but without a password entry', async(() => {
(overlayContainerElement.querySelector('input[formcontrolname="name"]') as HTMLInputElement).value = 'DD';
viewContainerFixture.detectChanges();
viewContainerFixture.whenStable().then(() => {
viewContainerFixture.detectChanges();
const nameInput = overlayContainerElement.querySelector('input[formcontrolname="name"]');
const passwordInput = overlayContainerElement.querySelector('input[formcontrolname="password"]');
expect((nameInput as HTMLInputElement).value).toEqual('DD');
expect((passwordInput as HTMLInputElement).value).toEqual('');
expect((overlayContainerElement.querySelector('button[md-raised-button]')).getAttribute('ng-reflect-disabled')).toBe('true');
});
}));
it('with a password but without a user entry', async(() => {
(overlayContainerElement.querySelector('input[formcontrolname="password"]') as HTMLInputElement).value = 'Password';
viewContainerFixture.detectChanges();
viewContainerFixture.whenStable().then(() => {
viewContainerFixture.detectChanges();
const nameInput = overlayContainerElement.querySelector('input[formcontrolname="name"]');
const passwordInput = overlayContainerElement.querySelector('input[formcontrolname="password"]');
expect((nameInput as HTMLInputElement).value).toEqual('');
expect((passwordInput as HTMLInputElement).value).toEqual('Password');
expect((overlayContainerElement.querySelector('button[md-raised-button]')).getAttribute('ng-reflect-disabled')).toBe('true');
});
}));
it('with a valid user name but invalid password', async(() => {
(overlayContainerElement.querySelector('input[formcontrolname="name"]') as HTMLInputElement).value = 'ABC';
(overlayContainerElement.querySelector('input[formcontrolname="password"]') as HTMLInputElement).value = '1234567';
viewContainerFixture.detectChanges();
viewContainerFixture.whenStable().then(() => {
viewContainerFixture.detectChanges();
const nameInput = overlayContainerElement.querySelector('input[formcontrolname="name"]');
const passwordInput = overlayContainerElement.querySelector('input[formcontrolname="password"]');
expect((nameInput as HTMLInputElement).value).toEqual('ABC');
expect((passwordInput as HTMLInputElement).value).toEqual('1234567');
expect((overlayContainerElement.querySelector('button[md-raised-button]')).getAttribute('ng-reflect-disabled')).toBe('true');
});
}));
it('with an invalid user name but with a valid password', async(() => {
(overlayContainerElement.querySelector('input[formcontrolname="name"]') as HTMLInputElement).value = 'AB';
(overlayContainerElement.querySelector('input[formcontrolname="password"]') as HTMLInputElement).value = '12345678';
viewContainerFixture.detectChanges();
viewContainerFixture.whenStable().then(() => {
viewContainerFixture.detectChanges();
const nameInput = overlayContainerElement.querySelector('input[formcontrolname="name"]');
const passwordInput = overlayContainerElement.querySelector('input[formcontrolname="password"]');
expect((nameInput as HTMLInputElement).value).toEqual('AB');
expect((passwordInput as HTMLInputElement).value).toEqual('12345678');
expect((overlayContainerElement.querySelector('button[md-raised-button]')).getAttribute('ng-reflect-disabled')).toBe('true');
});
}));
});
it('should enable the login button when a valid username and password are entered', fakeAsync(() => {
(overlayContainerElement.querySelector('input[formcontrolname="name"]') as HTMLInputElement).value = 'ABC';
(overlayContainerElement.querySelector('input[formcontrolname="password"]') as HTMLInputElement).value = '12345678';
viewContainerFixture.detectChanges();
tick();
viewContainerFixture.detectChanges();
const loginBtn = overlayContainerElement.querySelector('button[md-raised-button]');
const nameInput = overlayContainerElement.querySelector('input[formcontrolname="name"]');
const passwordInput = overlayContainerElement.querySelector('input[formcontrolname="password"]');
console.log('Login Button is:', loginBtn.textContent);
console.log('Login Button is:', loginBtn.getAttribute('ng-reflect-disabled'));
expect((nameInput as HTMLInputElement).value).toEqual('ABC');
expect((passwordInput as HTMLInputElement).value).toEqual('12345678');
expect((overlayContainerElement.querySelector('button[md-raised-button]')).getAttribute('ng-reflect-disabled')).toBe('false');
}));
it('should enable the login button when a valid username and password are entered', async(() => {
(overlayContainerElement.querySelector('input[formcontrolname="name"]') as HTMLInputElement).value = 'ABC';
(overlayContainerElement.querySelector('input[formcontrolname="password"]') as HTMLInputElement).value = '12345678';
viewContainerFixture.detectChanges();
viewContainerFixture.whenStable().then(() => {
viewContainerFixture.detectChanges();
const loginBtn = overlayContainerElement.querySelector('button[md-raised-button]');
const nameInput = overlayContainerElement.querySelector('input[formcontrolname="name"]');
const passwordInput = overlayContainerElement.querySelector('input[formcontrolname="password"]');
console.log('Login Button is:', loginBtn.textContent);
console.log('Login Button is:', loginBtn.getAttribute('ng-reflect-disabled'));
expect((nameInput as HTMLInputElement).value).toEqual('ABC');
expect((passwordInput as HTMLInputElement).value).toEqual('12345678');
expect((overlayContainerElement.querySelector('button[md-raised-button]')).getAttribute('ng-reflect-disabled')).toBe('false');
});
}));
});
好吧,現在我覺得自己很傻,因為我非常專注於讓對話框在測試環境中運行,我忘了告訴表單更新!
我所需要的只是將 dispatchEvent 調用添加到輸入框。 我的新代碼是(稍微整理一下):
it('should enable the login button when a valid username and password are entered', async(() => {
const loginBtn = overlayContainerElement.querySelector('button[md-raised-button]') as HTMLButtonElement;
const nameInput = overlayContainerElement.querySelector('input[formcontrolname="name"]') as HTMLInputElement;
const passwordInput = overlayContainerElement.querySelector('input[formcontrolname="password"]') as HTMLInputElement;
nameInput.value = 'ABC';
nameInput.dispatchEvent(new Event('input'));
passwordInput.value = '12345678';
passwordInput.dispatchEvent(new Event('input'));
viewContainerFixture.detectChanges();
viewContainerFixture.whenStable().then(() => {
viewContainerFixture.detectChanges();
expect(nameInput.value).toEqual('ABC');
expect(passwordInput.value).toEqual('12345678');
expect(loginBtn.getAttribute('ng-reflect-disabled')).toBe('false', 'Login button disabled should now be false');
});
}));
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.