Skip to content

Commit

Permalink
feat: controller logic and tests for onboarding home page (#3387)
Browse files Browse the repository at this point in the history
  • Loading branch information
bistaastha authored Dec 26, 2024
1 parent b003ae1 commit 0c53949
Show file tree
Hide file tree
Showing 12 changed files with 700 additions and 13 deletions.
14 changes: 14 additions & 0 deletions src/app/core/mock-data/onboarding-status.data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import deepFreeze from 'deep-freeze-strict';
import { OnboardingStatus } from '../models/onboarding-status.model';
import { OnboardingState } from '../models/onboarding-state.enum';

export const onboardingStatusData: OnboardingStatus = deepFreeze({
user_id: 'us1ymEVgUKqb',
org_id: 'orOTDe765hQp',
step_connect_cards_is_configured: false,
step_connect_cards_is_skipped: false,
step_sms_opt_in_is_configured: false,
step_sms_opt_in_is_skipped: false,
step_show_welcome_modal_is_complete: false,
state: OnboardingState.YET_TO_START,
});
144 changes: 144 additions & 0 deletions src/app/core/services/spender-onboarding.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import { TestBed } from '@angular/core/testing';
import { SpenderOnboardingService } from './spender-onboarding.service';
import { SpenderPlatformV1ApiService } from './spender-platform-v1-api.service';
import { OnboardingStepStatus } from '../models/onboarding-step-status.model';
import { of } from 'rxjs';
import { onboardingStatusData } from '../mock-data/onboarding-status.data';
import { OnboardingWelcomeStepStatus } from '../models/onboarding-welcome-step-status.model';

describe('SpenderOnboardingService', () => {
let spenderOnboardingService: SpenderOnboardingService;
let spenderPlatformV1ApiService: jasmine.SpyObj<SpenderPlatformV1ApiService>;

beforeEach(() => {
const spenderPlatformV1ApiServiceSpy = jasmine.createSpyObj('SpenderPlatformV1ApiService', ['get', 'post']);
TestBed.configureTestingModule({
providers: [
SpenderOnboardingService,
[
{
provide: SpenderPlatformV1ApiService,
useValue: spenderPlatformV1ApiServiceSpy,
},
],
],
});
spenderOnboardingService = TestBed.inject(SpenderOnboardingService);
spenderPlatformV1ApiService = TestBed.inject(
SpenderPlatformV1ApiService
) as jasmine.SpyObj<SpenderPlatformV1ApiService>;
});

it('getOnboardingStatus(): should get onboarding status', (done) => {
const onboardingResponse = onboardingStatusData;
spenderPlatformV1ApiService.get.and.returnValue(of({ data: onboardingResponse }));

spenderOnboardingService.getOnboardingStatus().subscribe((res) => {
expect(res).toEqual(onboardingResponse);
expect(spenderPlatformV1ApiService.get).toHaveBeenCalledOnceWith('/onboarding');
done();
});
});

it('processConnnectCardsStep(): should process connect card step', (done) => {
const onboardingRequestResponse: OnboardingStepStatus = {
is_configured: true,
is_skipped: false,
};
spenderPlatformV1ApiService.post.and.returnValue(of({ data: onboardingRequestResponse }));

spenderOnboardingService.processConnectCardsStep(onboardingRequestResponse).subscribe((res) => {
expect(res).toEqual(onboardingRequestResponse);
expect(spenderPlatformV1ApiService.post).toHaveBeenCalledOnceWith('/onboarding/process_step_connect_cards', {
data: onboardingRequestResponse,
});
done();
});
});

it('processSmsOptInStep(): should process opt in step', (done) => {
const onboardingRequestResponse: OnboardingStepStatus = {
is_configured: true,
is_skipped: false,
};
spenderPlatformV1ApiService.post.and.returnValue(of({ data: onboardingRequestResponse }));

spenderOnboardingService.processSmsOptInStep(onboardingRequestResponse).subscribe((res) => {
expect(res).toEqual(onboardingRequestResponse);
expect(spenderPlatformV1ApiService.post).toHaveBeenCalledOnceWith('/onboarding/process_step_sms_opt_in', {
data: onboardingRequestResponse,
});
done();
});
});

it('processWelcomeModalStep(): should get category count', (done) => {
const onboardingRequestResponse: OnboardingWelcomeStepStatus = {
is_complete: true,
};
spenderPlatformV1ApiService.post.and.returnValue(of({ data: onboardingRequestResponse }));

spenderOnboardingService.processWelcomeModalStep(onboardingRequestResponse).subscribe((res) => {
expect(res).toEqual(onboardingRequestResponse);
expect(spenderPlatformV1ApiService.post).toHaveBeenCalledOnceWith('/onboarding/process_step_show_welcome_modal', {
data: onboardingRequestResponse,
});
done();
});
});

it('markWelcomeModalStepAsComplete(): should call processWelcomeModalStep with the correct data', (done) => {
const mockData: OnboardingWelcomeStepStatus = { is_complete: true };
spyOn(spenderOnboardingService, 'processWelcomeModalStep').and.returnValue(of(mockData));

spenderOnboardingService.markWelcomeModalStepAsComplete().subscribe((result) => {
expect(spenderOnboardingService.processWelcomeModalStep).toHaveBeenCalledWith(mockData);
expect(result).toEqual(mockData);
done();
});
});

it('markConnectCardsStepAsComplete(): should call processConnectCardsStep with the correct data', (done) => {
const mockData: OnboardingStepStatus = { is_configured: true, is_skipped: false };
spyOn(spenderOnboardingService, 'processConnectCardsStep').and.returnValue(of(mockData));

spenderOnboardingService.markConnectCardsStepAsComplete().subscribe((result) => {
expect(spenderOnboardingService.processConnectCardsStep).toHaveBeenCalledWith(mockData);
expect(result).toEqual(mockData);
done();
});
});

it('skipConnectCardsStep(): should call processConnectCardsStep with the correct data', (done) => {
const mockData: OnboardingStepStatus = { is_configured: false, is_skipped: true };
spyOn(spenderOnboardingService, 'processConnectCardsStep').and.returnValue(of(mockData));

spenderOnboardingService.skipConnectCardsStep().subscribe((result) => {
expect(spenderOnboardingService.processConnectCardsStep).toHaveBeenCalledWith(mockData);
expect(result).toEqual(mockData);
done();
});
});

it('markSmsOptInStepAsComplete(): should call processSmsOptInStep with the correct data', (done) => {
const mockData: OnboardingStepStatus = { is_configured: true, is_skipped: false };
spyOn(spenderOnboardingService, 'processSmsOptInStep').and.returnValue(of(mockData));

spenderOnboardingService.markSmsOptInStepAsComplete().subscribe((result) => {
expect(spenderOnboardingService.processSmsOptInStep).toHaveBeenCalledWith(mockData);
expect(result).toEqual(mockData);
done();
});
});

it('skipSmsOptInStep(): should call processSmsOptInStep with the correct data', (done) => {
const mockData: OnboardingStepStatus = { is_configured: false, is_skipped: true };
spyOn(spenderOnboardingService, 'processSmsOptInStep').and.returnValue(of(mockData));

spenderOnboardingService.skipSmsOptInStep().subscribe((result) => {
expect(spenderOnboardingService.processSmsOptInStep).toHaveBeenCalledWith(mockData);
expect(result).toEqual(mockData);
done();
});
});
});
13 changes: 7 additions & 6 deletions src/app/core/services/spender-onboarding.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,36 +4,37 @@ import { SpenderPlatformV1ApiService } from './spender-platform-v1-api.service';
import { PlatformApiResponse } from '../models/platform/platform-api-response.model';
import { OnboardingWelcomeStepStatus } from '../models/onboarding-welcome-step-status.model';
import { OnboardingStepStatus } from '../models/onboarding-step-status.model';
import { OnboardingStatus } from '../models/onboarding-status.model';

@Injectable({
providedIn: 'root',
})
export class OnboardingService {
export class SpenderOnboardingService {
constructor(private spenderPlatformV1ApiService: SpenderPlatformV1ApiService) {}

getOnboardingStatus(): Observable<OnboardingStepStatus> {
getOnboardingStatus(): Observable<OnboardingStatus> {
return this.spenderPlatformV1ApiService
.get<PlatformApiResponse<OnboardingStepStatus>>('/spender/onboarding')
.get<PlatformApiResponse<OnboardingStatus>>('/onboarding')
.pipe(map((res) => res.data));
}

processConnectCardsStep(data: OnboardingStepStatus): Observable<OnboardingStepStatus> {
return this.spenderPlatformV1ApiService
.post<PlatformApiResponse<OnboardingStepStatus>>('/spender/onboarding/process_step_connect_cards', {
.post<PlatformApiResponse<OnboardingStepStatus>>('/onboarding/process_step_connect_cards', {
data,
})
.pipe(map((res) => res.data));
}

processSmsOptInStep(data: OnboardingStepStatus): Observable<OnboardingStepStatus> {
return this.spenderPlatformV1ApiService
.post<PlatformApiResponse<OnboardingStepStatus>>('/spender/onboarding/process_step_sms_opt_in', { data })
.post<PlatformApiResponse<OnboardingStepStatus>>('/onboarding/process_step_sms_opt_in', { data })
.pipe(map((res) => res.data));
}

processWelcomeModalStep(data: OnboardingWelcomeStepStatus): Observable<OnboardingWelcomeStepStatus> {
return this.spenderPlatformV1ApiService
.post<PlatformApiResponse<OnboardingWelcomeStepStatus>>('/spender/onboarding/process_step_show_welcome_modal', {
.post<PlatformApiResponse<OnboardingWelcomeStepStatus>>('/onboarding/process_step_show_welcome_modal', {
data,
})
.pipe(map((res) => res.data));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export enum OnboardingStep {
CONNECT_CARD,
OPT_IN,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<div class="connect-card__body">
<div>
<div class="connect-card__heading">Connect corporate card</div>
<div class="connect-card__sub-heading">
This will help you bring your card transactions into Fyle as expenses instantly.
</div>

<div class="connect-card__input-label">
<span>Corporate card</span>
</div>

<div
class="connect-card__input-inner-container"
[ngClass]="{ 'connect-card__input-inner-container--error': cardForm.touched && cardForm.invalid }"
>
<input
class="smartlook-show connect-card__card-number-input pl-0"
inputmode="numeric"
[formControl]="cardForm"
mask="0000 0000 0000 0000"
data-testid="card-number-input"
appAutofocus
[timeout]="500"
required
placeholder="Enter corporate card number"
/>

<ion-icon
*ngIf="!cardType || cardType === cardNetworkTypes.OTHERS"
src="../../../../assets/svg/card.svg"
class="connect-card__input-default-icon"
data-testid="default-icon"
></ion-icon>

<img
*ngIf="cardType === cardNetworkTypes.VISA"
src="../../../../assets/images/visa-logo.png"
class="connect-card__input-visa-icon"
data-testid="visa-icon"
/>

<img
*ngIf="cardType === cardNetworkTypes.MASTERCARD"
src="../../../../assets/images/mastercard-logo.png"
class="connect-card__input-mastercard-icon"
data-testid="mastercard-icon"
/>
</div>

<div class="connect-card__input-error-space"></div>

<div *ngIf="cardForm.touched && cardForm.invalid" class="connect-card__input-errors" data-testid="error-message">
<span *ngIf="cardForm.errors.invalidCardNumber">Please enter a valid card number.</span>

<ng-container *ngIf="cardForm.errors.invalidCardNetwork">
<span *ngIf="isVisaRTFEnabled && isMastercardRTFEnabled; else visaOnlyOrg"
>Enter a valid Visa or Mastercard number. If you have other cards, please contact your admin.</span
>

<ng-template #visaOnlyOrg>
<!-- Check if only visa is enabled -->
<span *ngIf="cardForm.errors.invalidCardNetwork && isVisaRTFEnabled; else mastercardOnlyOrg"
>Enter a valid Visa number. If you have other cards, please contact your admin.</span
>
</ng-template>

<ng-template #mastercardOnlyOrg>
<!-- Check if only mastercard is enabled -->
<span *ngIf="cardForm.errors.invalidCardNetwork && isMastercardRTFEnabled"
>Enter a valid Mastercard number. If you have other cards, please contact your admin.</span
>
</ng-template>
</ng-container>

<span *ngIf="cardForm.errors.enrollmentError">
{{ enrollmentFailureMessage }}
</span>
</div>
</div>
<div class="connect-card__primary-cta-container">
<ion-button
class="btn-primary connect-card__primary-cta"
fill="clear"
aria-label="Navigate back to sign in page"
role="button"
>
Continue
</ion-button>
</div>
</div>
Loading

0 comments on commit 0c53949

Please sign in to comment.