Skip to content

Commit

Permalink
test: unit tests for fy-select-commute-details component - Part 1 (#2852
Browse files Browse the repository at this point in the history
)

* test: unit tests for fy-select-commute-details component

* test: unit tests for fy-select-commute-details component - Part 2 (#2853)

* changed branch percentage in workflow
  • Loading branch information
suyashpatil78 authored Apr 3, 2024
1 parent f859784 commit bdd7b67
Show file tree
Hide file tree
Showing 2 changed files with 241 additions and 5 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/unit-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ jobs:
if (( $(echo "$lines < 95.0" | bc -l) || \
$(echo "$statements < 95.0" | bc -l) || \
$(echo "$branches < 89.5" | bc -l) || \
$(echo "$branches < 91.10" | bc -l) || \
$(echo "$functions < 94.0" | bc -l) )); then
echo "Code Coverage Percentage is below 95%"
exit 1
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,260 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { IonicModule } from '@ionic/angular';
import { ComponentFixture, TestBed, fakeAsync, tick, waitForAsync } from '@angular/core/testing';
import { IonicModule, ModalController } from '@ionic/angular';

import { FySelectCommuteDetailsComponent } from './fy-select-commute-details.component';
import { FormBuilder, Validators } from '@angular/forms';
import { LocationService } from 'src/app/core/services/location.service';
import { EmployeesService } from 'src/app/core/services/platform/v1/spender/employees.service';
import { OrgSettingsService } from 'src/app/core/services/org-settings.service';
import { MatSnackBar } from '@angular/material/snack-bar';
import { SnackbarPropertiesService } from 'src/app/core/services/snackbar-properties.service';
import { TrackingService } from 'src/app/core/services/tracking.service';
import { commuteDetailsData } from 'src/app/core/mock-data/commute-details.data';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { ToastType } from 'src/app/core/enums/toast-type.enum';
import { ToastMessageComponent } from '../toast-message/toast-message.component';
import { snackbarPropertiesRes2 } from 'src/app/core/mock-data/snackbar-properties.data';
import { orgSettingsRes } from 'src/app/core/mock-data/org-settings.data';
import { of, throwError } from 'rxjs';
import { cloneDeep } from 'lodash';
import { locationData1, locationData2 } from 'src/app/core/mock-data/location.data';
import { commuteDetailsResponseData } from 'src/app/core/mock-data/commute-details-response.data';
import { Location } from 'src/app/core/models/location.model';
import { HttpErrorResponse } from '@angular/common/http';

xdescribe('FySelectCommuteDetailsComponent', () => {
describe('FySelectCommuteDetailsComponent', () => {
let component: FySelectCommuteDetailsComponent;
let fixture: ComponentFixture<FySelectCommuteDetailsComponent>;
let modalController: jasmine.SpyObj<ModalController>;
let formBuilder: jasmine.SpyObj<FormBuilder>;
let locationService: jasmine.SpyObj<LocationService>;
let employeesService: jasmine.SpyObj<EmployeesService>;
let orgSettingsService: jasmine.SpyObj<OrgSettingsService>;
let matSnackBar: jasmine.SpyObj<MatSnackBar>;
let snackbarProperties: jasmine.SpyObj<SnackbarPropertiesService>;
let trackingService: jasmine.SpyObj<TrackingService>;

beforeEach(waitForAsync(() => {
const modalControllerSpy = jasmine.createSpyObj('ModalController', ['dismiss']);
const formBuilderSpy = jasmine.createSpyObj('FormBuilder', ['group']);
const locationServiceSpy = jasmine.createSpyObj('LocationService', ['getDistance']);
const employeesServiceSpy = jasmine.createSpyObj('EmployeesService', ['postCommuteDetails']);
const orgSettingsServiceSpy = jasmine.createSpyObj('OrgSettingsService', ['get']);
const matSnackBarSpy = jasmine.createSpyObj('MatSnackBar', ['openFromComponent']);
const snackbarPropertiesSpy = jasmine.createSpyObj('SnackbarPropertiesService', ['setSnackbarProperties']);
const trackingServiceSpy = jasmine.createSpyObj('TrackingService', [
'showToastMessage',
'commuteDeductionDetailsError',
]);

TestBed.configureTestingModule({
declarations: [FySelectCommuteDetailsComponent],
imports: [IonicModule.forRoot()],
providers: [
FormBuilder,
{ provide: ModalController, useValue: modalControllerSpy },
{ provide: LocationService, useValue: locationServiceSpy },
{ provide: EmployeesService, useValue: employeesServiceSpy },
{ provide: OrgSettingsService, useValue: orgSettingsServiceSpy },
{ provide: MatSnackBar, useValue: matSnackBarSpy },
{ provide: SnackbarPropertiesService, useValue: snackbarPropertiesSpy },
{ provide: TrackingService, useValue: trackingServiceSpy },
],
schemas: [NO_ERRORS_SCHEMA],
}).compileComponents();

fixture = TestBed.createComponent(FySelectCommuteDetailsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
modalController = TestBed.inject(ModalController) as jasmine.SpyObj<ModalController>;
formBuilder = TestBed.inject(FormBuilder) as jasmine.SpyObj<FormBuilder>;
locationService = TestBed.inject(LocationService) as jasmine.SpyObj<LocationService>;
employeesService = TestBed.inject(EmployeesService) as jasmine.SpyObj<EmployeesService>;
orgSettingsService = TestBed.inject(OrgSettingsService) as jasmine.SpyObj<OrgSettingsService>;
matSnackBar = TestBed.inject(MatSnackBar) as jasmine.SpyObj<MatSnackBar>;
snackbarProperties = TestBed.inject(SnackbarPropertiesService) as jasmine.SpyObj<SnackbarPropertiesService>;
trackingService = TestBed.inject(TrackingService) as jasmine.SpyObj<TrackingService>;
}));

it('should create', () => {
expect(component).toBeTruthy();
});

describe('ngOnInit():', () => {
it('should patch values to form controls if existing commute details are present', () => {
component.existingCommuteDetails = commuteDetailsData;

component.ngOnInit();

const homeLocationWithDisplay = {
...commuteDetailsData.home_location,
display: commuteDetailsData.home_location.formatted_address,
};

const workLocationWithDisplay = {
...commuteDetailsData.work_location,
display: commuteDetailsData.work_location.formatted_address,
};

expect(component.commuteDetails.value).toEqual({
homeLocation: homeLocationWithDisplay,
workLocation: workLocationWithDisplay,
});
});

it('should set empty form if existing commute details are not present', () => {
component.ngOnInit();

expect(component.commuteDetails.value).toEqual({
homeLocation: null,
workLocation: null,
});
});
});

describe('getCalculatedDistance()', () => {
it('should return distance in KM if distance unit is KM', () => {
const distanceInMeters = 21000;
const distanceUnit = 'KM';

const distance = component.getCalculatedDistance(distanceInMeters, distanceUnit);

expect(distance).toBe(21);
});

it('should return distance in Miles if distance unit is MILES', () => {
const distanceInMeters = 21000;
const distanceUnit = 'MILES';

const distance = component.getCalculatedDistance(distanceInMeters, distanceUnit);

expect(distance).toBe(13.0473);
});
});

it('formatLocation(): should return location without display', () => {
const location = {
formatted_address: 'Home',
display: 'Home',
latitude: 12.9715987,
longitude: 77.5945667,
country: 'India',
state: 'Karnataka',
city: 'Bangalore',
};

const formattedLocation = component.formatLocation(location);

expect(formattedLocation).toEqual({
formatted_address: 'Home',
latitude: 12.9715987,
longitude: 77.5945667,
country: 'India',
state: 'Karnataka',
city: 'Bangalore',
});
});

it('showToastMessage(): should show toast message', () => {
const message = 'Commute Details updated successfully!';
snackbarProperties.setSnackbarProperties.and.returnValue(snackbarPropertiesRes2);
component.showToastMessage(message, ToastType.SUCCESS, 'msb-success');

expect(matSnackBar.openFromComponent).toHaveBeenCalledOnceWith(ToastMessageComponent, {
...snackbarPropertiesRes2,
panelClass: ['msb-success'],
});
expect(snackbarProperties.setSnackbarProperties).toHaveBeenCalledOnceWith('success', { message });
expect(trackingService.showToastMessage).toHaveBeenCalledOnceWith({ ToastContent: message });
});

describe('save():', () => {
let homeLocation: Location;
let workLocation: Location;
let homeLocationWithoutDisplay: Location;
let workLocationWithoutDisplay: Location;

beforeEach(() => {
homeLocation = cloneDeep(locationData1);
workLocation = cloneDeep(locationData2);
homeLocationWithoutDisplay = cloneDeep(locationData1);
workLocationWithoutDisplay = cloneDeep(locationData2);

delete homeLocationWithoutDisplay.display;
delete workLocationWithoutDisplay.display;

component.commuteDetails = formBuilder.group({
homeLocation: [homeLocation, Validators.required],
workLocation: [workLocation, Validators.required],
});
orgSettingsService.get.and.returnValue(of(orgSettingsRes));
locationService.getDistance.and.returnValue(of(21000));
spyOn(component, 'getCalculatedDistance').and.returnValue(21);
employeesService.postCommuteDetails.and.returnValue(of({ data: commuteDetailsResponseData.data[0] }));
});

it('should mark all fields as touched if form is invalid', () => {
component.commuteDetails = formBuilder.group({
homeLocation: [null, Validators.required],
workLocation: [null, Validators.required],
});

component.save();

expect(component.commuteDetails.controls.homeLocation.touched).toBeTrue();
expect(component.commuteDetails.controls.workLocation.touched).toBeTrue();
});

it('should save commute details if form is valid', fakeAsync(() => {
component.save();
tick(100);

expect(orgSettingsService.get).toHaveBeenCalledTimes(1);
expect(locationService.getDistance).toHaveBeenCalledOnceWith(homeLocation, workLocation);
expect(component.getCalculatedDistance).toHaveBeenCalledOnceWith(21000, 'MILES');
expect(employeesService.postCommuteDetails).toHaveBeenCalledOnceWith({
distance: 21,
distance_unit: 'MILES',
home_location: homeLocationWithoutDisplay,
work_location: workLocationWithoutDisplay,
});
expect(modalController.dismiss).toHaveBeenCalledOnceWith({
action: 'save',
commuteDetails: commuteDetailsResponseData.data[0],
});
}));

it('should show error toast message and track event if postCommuteDetails API throws error', fakeAsync(() => {
const errorResponse = new HttpErrorResponse({ error: 'API ERROR', status: 400 });
employeesService.postCommuteDetails.and.throwError(errorResponse);
spyOn(component, 'showToastMessage');

try {
component.save();
tick(100);
} catch (err) {
expect(orgSettingsService.get).toHaveBeenCalledTimes(1);
expect(locationService.getDistance).toHaveBeenCalledOnceWith(homeLocation, workLocation);
expect(component.getCalculatedDistance).toHaveBeenCalledOnceWith(21000, 'MILES');
expect(employeesService.postCommuteDetails).toHaveBeenCalledOnceWith({
distance: 21,
distance_unit: 'MILES',
home_location: homeLocationWithoutDisplay,
work_location: workLocationWithoutDisplay,
});
expect(modalController.dismiss).not.toHaveBeenCalled();
expect(trackingService.commuteDeductionDetailsError).toHaveBeenCalledOnceWith(errorResponse);
expect(component.showToastMessage).toHaveBeenCalledOnceWith(
'We were unable to save your commute details. Please enter correct home and work location.',
ToastType.FAILURE,
'msb-failure'
);
}
}));
});

it('close(): should dismiss modal with action as cancel', () => {
component.close();

expect(modalController.dismiss).toHaveBeenCalledOnceWith({ action: 'cancel' });
});
});

0 comments on commit bdd7b67

Please sign in to comment.