puddlejumper26 / blogs

Personal Tech Blogs
4 stars 1 forks source link

Pipe testing in Angular #58

Open puddlejumper26 opened 4 years ago

puddlejumper26 commented 4 years ago

Background

There is a table in the template, and the status of project will be displayed.

name status date
name1 Passed 1990-04-01 11:00
name2 Stalled 1980-04-01 11:00

and the status value is connected and taken from the backend, through the Dto.

And IF we need to display the value always with first letter in capital, but actually in the backend, the data is stored inside JSON file, also like following

{
   'PASSED': 'Passed',
   'STALLED': 'Stalled',
   'PENDING': 'Pending',
}

Therefore, if we fetch the data directly from the backend, the 'PASSED' instead of 'Passed' will be displayed in the table.

Therefore, we need a PIPE for the translating.

So we have a EnumPipeModule here.

table.module.ts

import {EnumPipeModule} from './enumpipe.module.ts';  <=== this module imports 'enumPipe' pipe 
import { MatTableModule } from '@angular/material';
import { MomentModule } from 'angular2-moment';
import { MatSortModule } from '@angular/material/sort';

@NgModule{
    imports:[
          EnumPipeModule, 
         MatCardModule,
         MatSortModule,
         MatTableModule,
         MomentModule,
   ]
}

table.component.ts

export class TableComponent implements ngOnInit {
      public displayedColumns = [
            'name',
            'status',
            'date',
      ];
     public tableData = new MatTableDataSource<TableDto>();   <=== to the HTML

     @ViewChild(MatSort, { static: true }) private readonly sort!: MatSort;  <====sorting

     public ngOnInit(): void {      < ==sorting
        this.reportStatisticData.sort = this.sort;
    }
}

table.component.html


<table [dataSource]="tableData"
        matSort
        matSortDirection="desc"
        mat-table>

  <ng-container [matColumnDef]="displayedColumns[0]">  <====
            <th *matHeaderCellDef
                mat-header-cell
                mat-sort-header>name</th>
     <td *matCellDef="let table"
                mat-cell>{{ table.name }}</td>
  </ng-container>

  <ng-container [matColumnDef]="displayedColumns[1]">
            <th *matHeaderCellDef
                mat-header-cell
                mat-sort-header>status</th>
     <td *matCellDef="let table"
                mat-cell>{{ table.status | enumPipe | async }}</td>
  </ng-container>

  <ng-container [matColumnDef]="displayedColumns[2]">
            <th *matHeaderCellDef
                mat-header-cell
                mat-sort-header>date</th>
     <td *matCellDef="let table"
                mat-cell>{{ table.date | amDateFormat: 'YYYY-MM-DD HH:mm' }}</td> <=== from Moment
  </ng-container>

        <tr *matHeaderRowDef="displayedColumns"
            mat-header-row></tr>

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

table.component.ts

if the data like status is taken from tableDto.ts file

tableDto.ts

export interface TableDto{
    name?: string;
    date?: Date;
    status?: TableDto.StateEnum;
    id?: string;
}

export namespace TableDto{
   export type StateEnum = 'PASSED' | 'STALLED' | 'PENDING';
   export const StateEnum = {
         PENDING: 'PENDING' as StateEnum,
         PASSED: 'PASSED' as StateEnum,
         STALLED: 'STALLED' as StateEnum,
   }
}

table.component.spec.ts

Then in the test file, we need to mock the status data, with the correct type.


const ENUM_STATUS_MOCK = {
       [TableDto.StateEnum.PASSED]: 'Passed',
       [TableDto.StateEnum.PENDING]: 'Pending',
       [TableDto.StateEnum.STALLED]: 'Stalled', 
}

===> Notice here we use Required and Pick,  
const DATA_MOCK: Array<Required<Pick<TableDto, 'name' | 'status'|'date'>> & TableDto> =[
       {
            name: 'name1',
            date: new Date('1990-04-01 01:00 AM'),
            status: TableDto.StateEnum.PASSED,
            id: 'id1',
       },
       {
            name: 'name2',
            date: new Date('1980-04-01 01:00 AM'),
            status: TableDto.StateEnum.STALLED,
            id: 'id2',
       }
]

@Pipe({name: 'enumPipe'})              <=== here the string taken from the pipe
class EnumPipeMock implements PipeTransform {
      public transform(input: TableDto.StateEnum): Observable<string>{
                   return of (ENUM_STATUS_MOCK[input])
       }
}       

describe(... , ( ) => {
   it('should .....' ( ) = > {
      fixture.detectChanges();
      fixture.whenStable().then( ()=>{
          const tableRowElements = table.nativeElement.querySelectorAll('tbody tr') as HTMLTableRowElement[];
           tableRowElements.forEach((row, rowIndex) => {
                const {name, status} = DATA_MOCK(rowIndex);
                const tdElements = row.querySelectorAll('td');
                expect(tdElements[0].innerHTML).toBe(name);
                expect(tdElements[1].innerHTML).toBe(ENUM_STATUS_MOCK[status]);  <====
                expect(tdElements[2].innerHTML).toBe(moment(date).format('YYYY-MM-DD HH:mm'))
            }  
       })
     })
});

👉 Notice here we use Required and Pick, first we Pick 'name' | 'status'|'date' from the TableDto, and then use Required to make this required, then add & the rest of TableDto