Angular Testing - expect not seen if encompassed within whenStable
Asked Answered
P

1

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>
Pyrography answered 9/7, 2020 at 1:31 Comment(6)
Can you please reproduce this issue in stackblitz?Henry
I don't believe that this is an error. I believe that a test writer is supposed to do something else in this case, I just don't know what.Pyrography
Is the console.log('Lowered expectations') executed?Dyer
Yes. It is executed in the proper order as well. I have an console log in the function that I am spying on that is executed as well.Pyrography
You could use await on the blur triggerChemarin
I do not believe that the await would solve the problemPyrography
C
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.

Convulsion answered 14/7, 2020 at 7:2 Comment(5)
I still got the error that "Cannot read property 'triggerEventHandler' of null". I did try the tick command with various times.Pyrography
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.Convulsion
@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?Convulsion
I tried changing the names, but no go. There has to be some way to make the test wait until all actions have completed .Pyrography
@JoshuaFoxworth - did you try with the modified solution? If yes, did it work for you?Convulsion

© 2022 - 2024 — McMap. All rights reserved.