7

I have a component with tabs. I need to navigate to a tab and then test the viewable inputs, etc. I can see from the console log that the actions that I am taking are calling the function that I am spying on using the process below. I can also see that this is happening in the correct order.

Unfortunately, this produces a successful test with no errors, there is no expectation found within the test according to ng test. (SPEC HAS NO EXPECTATIONS)

Placing the expectation outside of the whenStable command makes the expectation run before the blur command.

it('should call to save changes when project name is blurred', 
    fakeAsync(() => {
        component.ngOnInit();
        tick();
        const tabLabels = fixture.debugElement.queryAll(By.css('.mat-tab-label'));
        console.log(tabLabels);
        tabLabels[1].triggerEventHandler('click', null);
        fixture.detectChanges();
        fixture.whenStable().then(() => {
            const projectName = fixture.debugElement.query(By.css('#projectName'));
            console.log(projectName);
            let mySpy = spyOn(component, 'saveProject');
            projectName.triggerEventHandler('blur', null);
            console.log('Lowered expectations');
            expect(mySpy).toHaveBeenCalled();
        });
    })
);

Here is the HTML associated with the test.

    <!-- HEADER -->
    <div class="header accent" fxLayout="row"></div>
    <!-- / HEADER -->

    <!-- CONTENT -->
    <div class="content">


        <!-- CENTER -->
        <div class="center p-24" fusePerfectScrollbar>

            <!-- CONTENT -->
            <div class="content p-24" style="box-shadow:0px 0px 0px rgba(0,0,0,0) !important;">

                <mat-tab-group fxFlex="80%"
                               style="margin:0px 10%">
                    
                    <mat-tab id="projectSelectionTab" label="Project Selection">


                        <div fxFlex="80%" 
                             fxLayout="column"
                             style="margin:0px 10%"
                             *ngIf="versionList && projectList">

                            <h1 class="mt-32 mb-20">Select a Project</h1>

                            <mat-divider></mat-divider>


                            <div *ngIf="projectList.length==0">
                                You currently have no designs. Go to the marketplace and select a design to add to your list of projects.
                            </div>

                            <table class="displayTable">


                                <tr (click)="setCurrentProject( project ) "
                                   target="_blank"
                                   class="projectRow"
                                   [ngClass]="{'currentItem' : project==currentProject}"
                                   *ngFor="let project of projectList">

                                    <td class="mat-subheading-2" 
                                        style="width:10%">
                                        <mat-icon style="cursor:pointer" 
                                                  matTooltip="Open in design studio"
                                                  [routerLink]="['/designStudio/project/'+project.uid]">
                                            color_lens
                                        </mat-icon>

                                        <mat-icon style="cursor:pointer" 
                                                  matTooltip="View Quotes for this project"
                                                  [routerLink]="['/invoice/'+versionList[versionList.length-1]['uid']]">
                                            attach_money
                                        </mat-icon>
                                    </td>

                                    <td class="mat-subheading-2" 
                                        style="width:25%">
                                            {{ project.name }} 
                                    </td>


                                    <td class="mat-subheading-2" 
                                        style="width:20%">
                                            {{ project.dateCreated | date:'short' }} 
                                    </td>


                                    <td class="mat-body-1" > 
                                        
                                        <span style="font-weight:bold; margin-right:10px;">Type : {{project.designType}}</span> 
                                        <span style="font-weight:bold; margin-right:10px;">Versions : {{project.versions.length}}</span>
                                        {{ project.description }}
                                    </td>

                                </tr>


                            </table>

                        </div>

                    </mat-tab>


                    <mat-tab id="projectDataTab" label="Project Data" flex="100%">

                        <div flex="100%" 
                             layout="column" 
                             style="width:100%"
                             *ngIf="currentProject.uid">

                            <h2 class="mt-32 mb-20">
                                Project Data -                   
                                    <mat-icon style="cursor:pointer" 
                                              matTooltip="Open in design studio"
                                              [routerLink]="['/designStudio/project/'+currentProject.uid]">
                                        color_lens
                                    </mat-icon>

                            </h2>
                      

                            <!-- TOP ROW OF PROJECT DATA -->
                            <div fxLayout="row" fxLayoutAlign="center" flex="100%">

                                <div fxFlex="50%"fxLayout="column" class="text-center">
                                    <mat-form-field appearance="outline" floatLabel="always" class="w-100-p">
                                        <mat-label>Project Name</mat-label>
                                        <input matInput
                                               id="projectName"
                                               class="form-control" 
                                               placeholder="Product Name"
                                               name="projectName"
                                               #projectName="ngModel"
                                               [(ngModel)]="currentProject['name']"
                                               (blur)="saveProject()"
                                               minlength="5"
                                               maxlength="150"
                                               required>
                                    </mat-form-field>

                                    <div [hidden]="projectName.valid || projectName.pristine || !projectName.errors?.minlength"
                                         class="alert alert-danger">
                                        Project name is required with at least 5 characters
                                    </div>
                                            

                                    <mat-form-field style="min-height:190px" appearance="outline" floatLabel="always" class="w-100-p">
                                        <mat-label>Project Description</mat-label>
                                        <textarea style="min-height:190px"
                                                  matInput 
                                                  id="projectDescription"
                                                  placeholder="Product Description"
                                                  name="description"
                                                  #projectDescription="ngModel"
                                                  [(ngModel)]="currentProject['description']"
                                                  (blur)="saveProject()"
                                                  rows="5"
                                                  required
                                                  minlength="25">
                                        </textarea>
                                    </mat-form-field>

                                    <div [hidden]="projectDescription.valid || projectDescription.pristine || !projectDescription.errors?.minlength"
                                         class="alert alert-danger">
                                        Project description is required with at least 25 characters
                                    </div>


                                </div>


                                <div fxFlex="50" fxLayout="column" fxLayoutAlign="center center">

                                    <div fxFlex="80" style="margin:0px 10%">
                                        <img [src]="(designImageUrl | async)">
                                    </div>


                                    <div fxLayout="row" 
                                         fxLayoutAlign="center" 
                                         class="w-80-p">
                                        <button mat-raised-button 
                                                color="primary"
                                                *ngIf="changesExist"
                                                (click)="saveProject(); changesExist=false;"
                                                style="margin:10px 25%; width:50%">
                                            Save Project Changes
                                        </button>
                                    </div>
                                
                                </div>

                            </div>

                        </div>
                        <!-- / TOP ROW OF PROJECT DATA -->
                        
                    </mat-tab>









                    <mat-tab id="versionDataTab" label="Version Data">


                        <div layout="column" 
                             flex="100%" 
                             style="width:100%"
                             *ngIf="currentProject !== undefined">

                            <h2 class="mt-32 mb-20">Version Data</h2>



                            <!-- Row showing button to create default version -->
                            <div fxLayout="row" 
                                 fxLayoutAlign="center center" 
                                 flex="100%">

                                <div fxFlex="20" style="margin:20px 5%">
                                    <button mat-stroked-button 
                                            color="accent"
                                            (click)="createNewVersion('default')">
                                        New version (default)
                                    </button>
                                </div>

                            </div>


                            <!-- Row showing the version list -->
                            <div fxLayout="row" fxLayoutAlign="center" flex="100%">

                                <table class="displayTable"
                                       fxFlex="60" style="margin:0px 5%">
                                    <thead>
                                        <tr>
                                            <th>VERSION NUMBER AND NAME</th>
                                            <th></th>
                                        </tr>
                                    </thead>
                                    <tbody>
                                        <tr *ngFor="let version of versionList; index as j"
                                            class="projectRow"
                                            [ngClass]="{'currentItem' : version==currentVersion}"
                                            (click)="onVersionSelected( j )">
                                            <td>{{j+1}} - {{version.name}}</td>
                                            <td>
                                                <mat-icon style="cursor:pointer" 
                                                          matTooltip="View Quotes for this version"
                                                          [routerLink]="['/invoice/'+version['uid']]">
                                                    attach_money
                                                </mat-icon>
                                                <mat-icon style="cursor:pointer" 
                                                          matTooltip="Make copy as latest version"
                                                          (click)="createNewVersion( j )">
                                                    add_circle
                                                </mat-icon>
                                            </td>
                                        </tr>
                                    </tbody>
                                </table>

                                <!--

                                <div fxFlex="40" style="margin:0px 5%">
                                    <mat-form-field class="w-100-p ml-10-p mt-20 form-group">
                                      <mat-label>Select a version to see the details</mat-label>
                                      <mat-select  (selectionChange)="onVersionSelected(versionIndex)"
                                                   name="versionIndex"
                                                   #name="ngModel"
                                                   [(ngModel)]="versionIndex">
                                        <mat-option *ngFor="let version of versionList; index as i" [value]="i">
                                          {{i+1}} - {{version.name}}
                                        </mat-option>
                                      </mat-select>
                                    </mat-form-field>
                                </div>



                                <div fxLayoutAlign="center center" fxFlex="40" style="margin:0px 5%">
                                    <button mat-button class="red-900 mt-8" *ngIf="currentVersion.latest">Submit for purchase</button>
                                    <button mat-button class="primary mt-8" *ngIf="!currentVersion.latest">Recreate as latest version</button>  
                                </div>

                            -->


                            </div>


                            <div fxLayout="row" fxLayoutAlign="center" flex="100%">

                                <!-- VERSIONS DATA -->
                                <div fxLayout="column" 
                                     class="mt-32"
                                     flex="45%"
                                     style="width:40%; margin:0px 5%">

                                    <h3>Data</h3>

                                    <div fxLayout="row"
                                         class="mt-20 mb-32">
                                        <div fxFlex="50">
                                            Estimated price - {{currentVersion.price}}
                                        </div>
                                        <div fxFlex="50">
                                            Date Created - {{currentVersion.dateCreated | date:'short'}}
                                        </div>
                                    </div>


                                    <mat-form-field appearance="outline" 
                                                    floatLabel="always" 
                                                    class="w-100-p">
                                        <mat-label>Version Name</mat-label>
                                        <input matInput 
                                               class="form-control"
                                               placeholder="Version Name"
                                               name="vName"
                                               #versionName="ngModel"
                                               (blur)="versionChangesExist=true"
                                               [(ngModel)]="currentVersion.name"
                                               required
                                               minlength="5"
                                               maxlength="150">
                                    </mat-form-field>

                                    <div [hidden]="versionName.valid || versionName.pristine || !versionName.errors?.minlength"
                                         class="alert alert-danger">
                                        Version name is required with at least 5 characters
                                    </div>


                                    <mat-form-field appearance="outline" 
                                                    floatLabel="always" 
                                                    class="w-100-p">
                                        <mat-label>Version Description</mat-label>
                                        <textarea matInput 
                                                  placeholder="Version Description"
                                                  class="form-control"
                                                  name="versionDescription"
                                                  #versionDescription="ngModel"
                                                  (blur)="versionChangesExist=true"
                                                  [(ngModel)]="currentVersion.description"
                                                  rows="5"
                                                  required>
                                        </textarea>
                                    </mat-form-field>

                                    <div [hidden]="versionDescription.valid || versionDescription.pristine || !versionDescription.errors?.minlength"
                                         class="alert alert-danger">
                                        Version description is required with at least 25 characters
                                    </div>

                                </div>
                                <!--/  VERSION DATA -->



                                <!-- MEASUREMENT LIST -->
                                <div fxLayout="column" 
                                     class="mt-32"
                                     flex="45%"
                                     style="width:40%; margin:0px 5%">

                                    <div fxLayout="row" fxLayoutAlign="center" flex="100%">
                                        <button mat-raised-button 
                                                color="primary"
                                                *ngIf="versionChangesExist"
                                                (click)="saveVersion(); versionChangesExist=false;"
                                                style="margin:10px 15%; width:70%">
                                            Save Version
                                        </button>
                                    </div>

                                     <h3>Measurements</h3>

                                    <table mat-table [dataSource]="currentVersion.measurements" 
                                           class="mat-elevation-z8"
                                           style="box-shadow:none;">

                                      <ng-container matColumnDef="name">
                                        <th mat-header-cell *matHeaderCellDef> Name </th>
                                        <td mat-cell *matCellDef="let meas"> {{meas.name}} </td>
                                      </ng-container>

                                      <ng-container matColumnDef="value">
                                        <th mat-header-cell *matHeaderCellDef> Value </th>
                                        <td mat-cell *matCellDef="let meas"> {{meas.value}} </td>
                                      </ng-container>

                                      <tr mat-header-row *matHeaderRowDef="columnsToDisplayMeas"></tr>
                                      <tr mat-row *matRowDef="let row; columns: columnsToDisplayMeas;"></tr>
                                    </table>

                                </div>
                                <!--/  MEASUREMENT LIST -->

                            </div>

                        </div>
                    
                    </mat-tab>







                    <mat-tab id="designAndPurchaseTab" label="Design and Purchase Status">  


                        <div flex="100%" 
                             layout="column"
                             style="width:100%"
                             *ngIf="currentProject !== undefined">


                            <h2 class="mt-32 mb-20">Design and Purchase Status</h2>


                            <div fxLayout="row"
                                 fxLayoutAlign="center center">
                                <div *ngFor="let stage of projectStages; index as j;"
                                     fxLayoutAlign="center center"
                                     [class]="projectStatus[j] ? 'arrow_box_green' : 'arrow_box_red' "
                                     [ngStyle]="{'z-index':1000-j}" 
                                     (click)="setSelected( j )">
                                    <span>{{stage}}</span>
                                </div>
                            </div>

                            <div fxLayout="row"
                                 fxLayoutAlign="center center">
                                <div *ngFor="let stage of projectStages; index as j;"
                                     fxLayoutAlign="center center">

                                    <div [class]="projectStatus[j] ? 'green_right' : '' "
                                         *ngIf="selectedStatus[j]"></div>

                                    <div [class]="!projectStatus[j] ? 'red_right' : '' "
                                         *ngIf="selectedStatus[j]"></div>

                                    <div *ngIf="!selectedStatus[j]"
                                         class="filler"></div>

                                    <div class="filler"></div>

                                </div>

                            </div>



                            <div fxLayout="row"
                                 fxLayoutAlign="center center">
                                
                                <div *ngFor="let text of stageTexts; index as j;"
                                     fxLayoutAlign="center center">

                                    <div fxFlex="50"
                                         *ngIf="selectedStatus[j] && projectStatus[j]">{{text.done}}</div>

                                    <div fxFlex="50"
                                         *ngIf="selectedStatus[j] && !projectStatus[j]">{{text.notdone}}</div>

                                </div>

                            </div>


                        </div>

                    </mat-tab>
                      





                </mat-tab-group>


            </div>
            <!-- / CONTENT -->

        </div>
        <!-- / CENTER -->

    </div>
    <!-- / CONTENT -->

</div>
Joshua Foxworth
  • 1,236
  • 1
  • 22
  • 50

1 Answers1

1

That's because whenStable() doesn't play well with fakeAsync() function as it is an async function's stuff.

For using fakeAsync() efficiently, one must rely on tick() or flush().

Maybe changing your test case like this, should work.

it('should call to save changes when project name is blurred', 
    fakeAsync(() => {
        component.ngOnInit();
        tick();
        const tabLabels = fixture.debugElement.queryAll(By.css('.mat-tab-label'));
        console.log(tabLabels);
        tabLabels[1].triggerEventHandler('click', null);
        fixture.detectChanges();

        flushMicrotasks(); // or alternatively flush() or tick(250);

        fixture.detectChanges();

        const projectName = fixture.debugElement.query(By.css('#projectName'));
        console.log(projectName);
        let mySpy = spyOn(component, 'saveProject');
        projectName.triggerEventHandler('blur', null);
        console.log('Lowered expectations');
        expect(mySpy).toHaveBeenCalled();
    })
);

Edit:

Replaced flush() with flushMicrotasks() followed by fixture.detectChanges(), it will allow the DOM to be updated with the response after resolving the Promise. On second thought, you can use this approach with flush() or tick() as well.

GoWin
  • 338
  • 3
  • 15
  • I still got the error that "Cannot read property 'triggerEventHandler' of null". I did try the tick command with various times. – Joshua Foxworth Jul 14 '20 at 13:56
  • It should've worked, it may be the issue with the query selector. If you could help me with your HTML template that you are writing test for, maybe then I can provide some pointer. – GoWin Jul 15 '20 at 09:39
  • @JoshuaFoxworth, looking at your HTML I see that you've used the `id`, `name`, and the `template variable` identifiers as `projectName`. I wonder if it has something to do with that. Maybe that's why it's not able to find that element through the query selector. Can you try after making the `id` unique for the project name input? – GoWin Jul 20 '20 at 07:52
  • I tried changing the names, but no go. There has to be some way to make the test wait until all actions have completed . – Joshua Foxworth Jul 22 '20 at 21:05
  • @JoshuaFoxworth - did you try with the modified solution? If yes, did it work for you? – GoWin Aug 05 '20 at 10:40