2017-07-15 61 views
1

我有一个使用角度材质2 和角反应窗体生成的简单登录对话框。
对话框在程序中使用时应按原样运行,但其单元测试未反映此情况。应该禁用表单登录按钮,直到名称和密码字段都通过组件中设置的验证条件,此时登录按钮已启用并可供点击。
但是,当我运行测试并将名称和输入字段设置为有效内容时,登录按钮保持禁用状态,测试失败。
的代码测试的相关部分如下所示带有窗体测试失败的角度材质2对话框

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是链接到显示组件和测试套件的代码,代码如下。

组件

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'); 
    }); 
    })); 
}); 
+0

为什么使用'overlayContainerElement'作为HTML元素。组件实例必须用于获取任何HTMLElement。 – Aravind

+0

Aravind - 我在创建一个对话框时设置了这个测试有很多问题。为了解决这个问题,大部分的测试框架都是从GitHub上角度材料2源的对话框测试模块复制而来的。这似乎是他们这样做的方式,所以我认为最好遵循他们的方法。将尝试改变我的私人抢劫者,看看它是否有所作为。 – websterd

回答

1

好了,现在我觉得一个傻瓜因为我非常专注于让对话框在测试环境中运行,所以我忘了告诉表单更新!
我所需要的只是将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'); 
    }); 
    }));