Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: Add tests for Connnect Card - 1 #3410

Open
wants to merge 1 commit into
base: FYLE-86cx2t82k-tests-for-connect-card
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
import { ComponentFixture, fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing';
import { CorporateCreditCardExpenseService } from 'src/app/core/services/corporate-credit-card-expense.service';
import { SpenderOnboardingConnectCardStepComponent } from './spender-onboarding-connect-card-step.component';
import { RealTimeFeedService } from 'src/app/core/services/real-time-feed.service';
import { FormBuilder, FormControl, ReactiveFormsModule, Validators } from '@angular/forms';
import { IonicModule, PopoverController } from '@ionic/angular';
import { By } from '@angular/platform-browser';
import { CardNetworkType } from 'src/app/core/enums/card-network-type';
import { NgxMaskModule } from 'ngx-mask';
import { HttpErrorResponse } from '@angular/common/http';
import { of, throwError } from 'rxjs';
import { statementUploadedCard } from 'src/app/core/mock-data/platform-corporate-card.data';
import { SimpleChange, SimpleChanges } from '@angular/core';
import { orgSettingsData } from 'src/app/core/test-data/org-settings.service.spec.data';

describe('SpenderOnboardingConnectCardStepComponent', () => {
let component: SpenderOnboardingConnectCardStepComponent;
let fixture: ComponentFixture<SpenderOnboardingConnectCardStepComponent>;
let corporateCreditCardExpenseService: jasmine.SpyObj<CorporateCreditCardExpenseService>;
let realTimeFeedService: jasmine.SpyObj<RealTimeFeedService>;
let popoverController: jasmine.SpyObj<PopoverController>;
let fb: FormBuilder;

beforeEach(waitForAsync(() => {
const corporateCreditCardExpenseServiceSpy = jasmine.createSpyObj('CorporateCreditCardExpenseService', [
'getCorporateCards',
]);
const popoverControllerSpy = jasmine.createSpyObj('PopoverController', ['dismiss']);
const realTimeFeedServiceSpy = jasmine.createSpyObj('RealTimeFeedService', [
'enroll',
'getCardTypeFromNumber',
'isCardNumberValid',
]);

TestBed.configureTestingModule({
declarations: [SpenderOnboardingConnectCardStepComponent],
imports: [IonicModule.forRoot(), NgxMaskModule.forRoot(), ReactiveFormsModule],
providers: [
FormBuilder,
{ provide: RealTimeFeedService, useValue: realTimeFeedServiceSpy },
{ provide: CorporateCreditCardExpenseService, useValue: corporateCreditCardExpenseServiceSpy },
{ provide: PopoverController, useValue: popoverControllerSpy },
],
}).compileComponents();

fixture = TestBed.createComponent(SpenderOnboardingConnectCardStepComponent);
component = fixture.componentInstance;

realTimeFeedService = TestBed.inject(RealTimeFeedService) as jasmine.SpyObj<RealTimeFeedService>;
corporateCreditCardExpenseService = TestBed.inject(
CorporateCreditCardExpenseService
) as jasmine.SpyObj<CorporateCreditCardExpenseService>;
fb = TestBed.inject(FormBuilder);
component.fg = fb.group({});
popoverController = TestBed.inject(PopoverController) as jasmine.SpyObj<PopoverController>;
corporateCreditCardExpenseService.getCorporateCards.and.returnValue(null);
realTimeFeedService.isCardNumberValid.and.returnValue(true);
realTimeFeedService.getCardTypeFromNumber.and.returnValue(CardNetworkType.VISA);
}));

it('ngOnInit(): ', () => {
corporateCreditCardExpenseService.getCorporateCards.and.returnValue(of([]));
component.ngOnInit();
});

it('ngOnChanges(): should update isVisaRTFEnabled and isMastercardRTFEnabled when orgSettings changes', () => {
component.orgSettings = orgSettingsData;
const changes: SimpleChanges = {
orgSettings: {
firstChange: false,
isFirstChange: () => false,
previousValue: '',
currentValue: orgSettingsData,
},
};

component.ngOnChanges(changes);

expect(component.isVisaRTFEnabled).toBeTrue();
expect(component.isMastercardRTFEnabled).toBeTrue();
});

describe('setupErrorMessages(): ', () => {
beforeEach(() => {
//@ts-ignore
spyOn(component, 'handleEnrollmentFailures');
});

it('should add the masked card number to failedCards', () => {
const mockError = new HttpErrorResponse({ status: 400, statusText: 'Bad Request' });
const mockCardNumber = '5432';

component.setupErrorMessages(mockError, mockCardNumber);

expect(component.cardsList.failedCards.length).toBe(1);
expect(component.cardsList.failedCards[0]).toBe('**** 5432');
});

it('should add multiple failed cards to failedCards', () => {
const mockError1 = new HttpErrorResponse({ status: 400, statusText: 'Bad Request' });
const mockCardNumber1 = '5432';

const mockError2 = new HttpErrorResponse({ status: 500, statusText: 'Internal Server Error' });
const mockCardNumber2 = '5678';

component.setupErrorMessages(mockError1, mockCardNumber1);
component.setupErrorMessages(mockError2, mockCardNumber2);

expect(component.cardsList.failedCards.length).toBe(2);
expect(component.cardsList.failedCards).toEqual(['**** 5432', '**** 5678']);
});

it('should handle cases where cardId is undefined', () => {
const mockError = new HttpErrorResponse({ status: 404, statusText: 'Not Found' });
const mockCardNumber = '4444';

component.setupErrorMessages(mockError, mockCardNumber);

//@ts-ignore
expect(component.handleEnrollmentFailures).toHaveBeenCalledWith(mockError, undefined);
expect(component.cardsList.failedCards.length).toBe(1);
expect(component.cardsList.failedCards[0]).toBe('**** 4444');
});
});

describe('enrollCards(): ', () => {
it('should call enrollMultipleCards if enrollableCards has items', () => {
component.enrollableCards = [statementUploadedCard];
const enrollSingularCardSpy = spyOn(component, 'enrollSingularCard');
const enrollMultipleCardsSpy = spyOn(component, 'enrollMultipleCards');

component.enrollCards();

expect(enrollMultipleCardsSpy).toHaveBeenCalledWith(component.enrollableCards);
expect(enrollSingularCardSpy).not.toHaveBeenCalled();
expect(component.cardsEnrolling).toBeTrue();
});

it('should call enrollSingularCard if enrollableCards is empty', () => {
// Arrange
component.enrollableCards = [];
const enrollSingularCardSpy = spyOn(component, 'enrollSingularCard');
const enrollMultipleCardsSpy = spyOn(component, 'enrollMultipleCards');

// Act
component.enrollCards();

// Assert
expect(enrollSingularCardSpy).toHaveBeenCalled();
expect(enrollMultipleCardsSpy).not.toHaveBeenCalled();
expect(component.cardsEnrolling).toBeTrue();
});
});

describe('enrollMultipleCards(): ', () => {
it('should handle successful card enrollment', fakeAsync(() => {
corporateCreditCardExpenseService.getCorporateCards.and.returnValue(
of([statementUploadedCard, { ...statementUploadedCard, id: 'bacc15bbrRGWzg' }])
);
component.ngOnInit();

const stepCompleteSpy = spyOn(component.isStepComplete, 'emit');
const showErrorPopoverSpy = spyOn(component, 'showErrorPopover');
const setupErrorMessagesSpy = spyOn(component, 'setupErrorMessages');
realTimeFeedService.enroll.and.returnValues(of(statementUploadedCard), of(statementUploadedCard));
component.enrollMultipleCards(component.enrollableCards);
tick();
expect(component.cardsList.successfulCards).toEqual(['**** 5555', '**** 5555']);
expect(component.cardsEnrolling).toBeFalse();
expect(stepCompleteSpy).toHaveBeenCalledWith(true);
expect(showErrorPopoverSpy).not.toHaveBeenCalled();
expect(setupErrorMessagesSpy).not.toHaveBeenCalled();
}));

it('should handle unsuccessful card enrollment', fakeAsync(() => {
corporateCreditCardExpenseService.getCorporateCards.and.returnValue(
of([statementUploadedCard, { ...statementUploadedCard, id: 'bacc15bbrRGWzg' }])
);
component.ngOnInit();

const stepCompleteSpy = spyOn(component.isStepComplete, 'emit');
const showErrorPopoverSpy = spyOn(component, 'showErrorPopover');
realTimeFeedService.enroll.and.returnValues(
of(statementUploadedCard),
throwError(() => new Error('This card already exists in the system'))
);
component.enrollMultipleCards(component.enrollableCards);
tick();
expect(component.cardsList.successfulCards).toEqual(['**** 5555']);
expect(component.cardsList.failedCards).toEqual(['**** 5555']);
expect(component.cardsEnrolling).toBeFalse();
expect(stepCompleteSpy).toHaveBeenCalledWith(true);
expect(showErrorPopoverSpy).toHaveBeenCalledTimes(1);
}));
});

describe('enrollSingularCard(): ', () => {
it('should handle successful card enrollment', fakeAsync(() => {
corporateCreditCardExpenseService.getCorporateCards.and.returnValue(of([]));
component.ngOnInit();

component.fg.controls.card_number.setValue('41111111111111111');
const stepCompleteSpy = spyOn(component.isStepComplete, 'emit');
const showErrorPopoverSpy = spyOn(component, 'showErrorPopover');
const setupErrorMessagesSpy = spyOn(component, 'setupErrorMessages');
realTimeFeedService.enroll.and.returnValues(of(statementUploadedCard));
component.enrollSingularCard();
tick();
expect(component.cardsList.successfulCards).toEqual(['**** 1111']);
expect(component.cardsEnrolling).toBeFalse();
expect(stepCompleteSpy).toHaveBeenCalledWith(true);
expect(showErrorPopoverSpy).not.toHaveBeenCalled();
expect(setupErrorMessagesSpy).not.toHaveBeenCalled();
}));

it('should handle unsuccessful card enrollment', fakeAsync(() => {
corporateCreditCardExpenseService.getCorporateCards.and.returnValue(of([]));
component.ngOnInit();

component.fg.controls.card_number.setValue('41111111111111111');
const stepCompleteSpy = spyOn(component.isStepComplete, 'emit');
const showErrorPopoverSpy = spyOn(component, 'showErrorPopover');
realTimeFeedService.enroll.and.returnValues(
throwError(() => new Error('This card already exists in the system'))
);
component.enrollSingularCard();
tick();
expect(component.cardsList.failedCards).toEqual(['**** 1111']);
expect(component.cardsEnrolling).toBeFalse();
expect(showErrorPopoverSpy).toHaveBeenCalledTimes(1);
}));
});
Comment on lines +155 to +232
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

The test coverage is powerful, but let's make it legendary!

The enrollment tests are solid like a punch sequence, but we could add more power with these additional test cases:

  • Network timeout scenarios
  • Multiple consecutive failures
  • Rate limiting responses

Would you like me to help craft these additional test cases?

});
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { HttpErrorResponse } from '@angular/common/http';
import {
ChangeDetectorRef,

Check failure on line 3 in src/app/fyle/spender-onboarding/spender-onboarding-connect-card-step/spender-onboarding-connect-card-step.component.ts

View workflow job for this annotation

GitHub Actions / Run linters

'ChangeDetectorRef' is defined but never used

Check failure on line 3 in src/app/fyle/spender-onboarding/spender-onboarding-connect-card-step/spender-onboarding-connect-card-step.component.ts

View workflow job for this annotation

GitHub Actions / Run linters

'ChangeDetectorRef' is defined but never used
Component,
EventEmitter,
Input,
Expand Down Expand Up @@ -88,7 +88,7 @@
this.cardsList.successfulCards.push(`**** ${card.card_number.slice(-4)}`);
}),
catchError((error: HttpErrorResponse) => {
this.setupErrorMessages(error, this.fg.controls[`card_number_${card.id}`].value as string, card.id);
this.setupErrorMessages(error, `${card.card_number.slice(-4)}`, card.id);
return of(error);
})
)
Expand All @@ -112,7 +112,7 @@
this.cardsList.successfulCards.push(`**** ${(this.fg.controls.card_number.value as string).slice(-4)}`);
}),
catchError((error: HttpErrorResponse) => {
this.setupErrorMessages(error, this.fg.controls.card_number.value as string);
this.setupErrorMessages(error, (this.fg.controls.card_number.value as string).slice(-4));
return of(error);
})
)
Expand Down Expand Up @@ -211,8 +211,8 @@
new FormControl('', [
Validators.required,
Validators.maxLength(12),
this.cardNumberValidator,
this.cardNetworkValidator,
this.cardNumberValidator.bind(this),
this.cardNetworkValidator.bind(this),
])
);
});
Expand Down
Loading