Skip to content

Commit

Permalink
Migrated get of v2 expenses in my-view-report to platform
Browse files Browse the repository at this point in the history
  • Loading branch information
arjunaj5 committed Dec 1, 2023
1 parent 9523e37 commit 7897875
Show file tree
Hide file tree
Showing 9 changed files with 541 additions and 137 deletions.
3 changes: 3 additions & 0 deletions src/app/core/services/platform/v1/spender/expenses.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ export class ExpensesService {
.pipe(map((expenses) => expenses.data));
}

@Cacheable({
cacheBusterObserver: expensesCacheBuster$,
})
getAllExpenses(params: ExpensesQueryParams): Observable<Expense[]> {
return this.getExpensesCount(params.queryParams).pipe(
switchMap((count) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<ion-header mode="md">
<mat-icon class="fy-modal-top-bar" svgIcon="fy-rectangle"></mat-icon>
<ion-toolbar mode="md" class="fy-modal-toolbar">
<ion-title>
<div *ngIf="selectedElements?.length === 0" class="report-list--title text-center">Add Expenses</div>
<div *ngIf="selectedElements?.length > 0" class="add-expenses-to-report--title-container text-center">
<div class="add-expenses-to-report--title">
{{ selectedElements?.length }} {{ selectedElements?.length > 1 ? 'Expenses' : 'Expense' }} -
{{ selectedTotalAmount || 0 | humanizeCurrency : homeCurrency }}
</div>
</div>
</ion-title>
<ion-buttons slot="start">
<ion-button (click)="close()">
<mat-icon class="fy-icon-close" svgIcon="fy-close"></mat-icon>
</ion-button>
</ion-buttons>
<ion-buttons slot="end">
<ion-button (click)="addNewExpense()">
<ion-icon class="report-list--add-icon" src="../../../../../assets/svg/fy-plus.svg"></ion-icon>
</ion-button>
</ion-buttons>
</ion-toolbar>
</ion-header>

<ion-content>
<div class="fy-modal">
<div *ngIf="unreportedExpenses?.length > 0" class="add-expenses-to-report--select-all-checkbox">
<mat-checkbox
class="custom-mat-checkbox create-new-report--checkbox"
[(ngModel)]="isSelectedAll"
(ngModelChange)="toggleSelectAll($event)"
name="select all"
>
Select all</mat-checkbox
>
</div>

<div *ngFor="let expense of unreportedExpenses as list; let i = index">
<app-expense-card-v2
[expense]="expense"
[expenseIndex]="i"
[previousExpenseTxnDate]="list[i - 1]?.spent_at"
[previousExpenseCreatedAt]="list[i - 1]?.created_at"
[isSelectionModeEnabled]="true"
[selectedElements]="selectedElements"
(cardClickedForSelection)="toggleExpense(expense)"
[isFromViewReports]="true"
>
</app-expense-card-v2>
</div>
</div>
<ng-container *ngIf="unreportedExpenses?.length === 0">
<div class="add-expenses-to-report--zero-state">
<div class="text-center">
<img src="../../../assets/images/zero-states/expenses.png" alt="No expense in this report" />
<div class="add-expenses-to-report--zero-state--header text-center">
Looks like there are no complete expenses!
</div>

<div class="add-expenses-to-report--zero-state--sub-header">
Click on the
<mat-icon class="add-expenses-to-report--zero-state--sub-header--icon" svgIcon="plus"></mat-icon>
to add a new expense to this report
</div>
</div>
</div>
</ng-container>
</ion-content>

<ion-footer>
<ion-toolbar mode="md" class="add-expenses-to-report--footer">
<div>
<div class="fy-footer-cta-container">
<ion-button
class="fy-footer-cta fy-footer-cta--primary"
[disabled]="!(selectedTotalExpenses > 0)"
(click)="addExpensestoReport()"
>
Add to Report
</ion-button>
</div>
</div>
</ion-toolbar>
</ion-footer>
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
@import '.././../../../theme/colors.scss';

.add-expenses-to-report {
&--toolbar {
background-color: $pure-white;
border-bottom: 1px solid $grey-lighter;
}

&--title-container {
color: $black;
font-weight: 500;
margin-left: -10%;
}

&--title {
font-size: 20px;
line-height: 26px;
}

&--close {
margin-left: 16px;
}

&--select-all-checkbox {
font-size: 14px;
padding: 16px 16px;
background-color: $pure-white;
}

&--zero-state {
font-size: 14px;
display: flex;
flex-direction: column;
align-items: center;
color: $blue-black;
line-height: 1.5;
font-weight: 500;
margin: 20% 20px 10px;

&--header {
margin-top: 12px;
margin-bottom: 10px;
font-size: 15px;
color: $black;
font-weight: 500;
display: flex;
flex-direction: column;
align-items: center;
}
&--sub-header {
font-size: 14px;
text-align: center;
color: $dark-grey;
display: flex;
align-items: center;
justify-content: center;
gap: -1px;
&--icon {
height: 14px;
color: $dark-grey;
}
}
}

&--footer {
box-shadow: 0px -2px 40px rgba(215, 215, 215, 0.4);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
import { CurrencyPipe } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA } from '@angular/core';
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { Router } from '@angular/router';
import { IonicModule, ModalController } from '@ionic/angular';
import { of } from 'rxjs';
import { click, getElementBySelector, getTextContent } from 'src/app/core/dom-helpers';
import { CurrencyService } from 'src/app/core/services/currency.service';
import { FyCurrencyPipe } from 'src/app/shared/pipes/fy-currency.pipe';
import { HumanizeCurrencyPipe } from 'src/app/shared/pipes/humanize-currency.pipe';
import { AddExpensesToReportV2Component } from './add-expenses-to-report-v2.component';
import { expenseData } from 'src/app/core/mock-data/platform/v1/expense.data';

describe('AddExpensesToReportV2Component', () => {
let component: AddExpensesToReportV2Component;
let fixture: ComponentFixture<AddExpensesToReportV2Component>;
let modalController: jasmine.SpyObj<ModalController>;
let currencyService: jasmine.SpyObj<CurrencyService>;
let router: jasmine.SpyObj<Router>;

const expense1 = expenseData;
const expense2 = { ...expenseData, id: 'txcSFe6efB62' };
const expense3 = { ...expenseData, id: 'txcSFe6efB63' };

beforeEach(waitForAsync(() => {
const modalControllerSpy = jasmine.createSpyObj('ModalController', ['dismiss']);
const currencyServiceSpy = jasmine.createSpyObj('CurrencyService', ['getHomeCurrency']);
const routerSpy = jasmine.createSpyObj('Router', ['navigate']);

TestBed.configureTestingModule({
declarations: [AddExpensesToReportV2Component, HumanizeCurrencyPipe],
imports: [IonicModule.forRoot()],
providers: [
FyCurrencyPipe,
CurrencyPipe,
{
provide: ModalController,
useValue: modalControllerSpy,
},
{
provide: CurrencyService,
useValue: currencyServiceSpy,
},
{
provide: Router,
useValue: routerSpy,
},
],
schemas: [CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA],
}).compileComponents();
fixture = TestBed.createComponent(AddExpensesToReportV2Component);
component = fixture.componentInstance;

modalController = TestBed.inject(ModalController) as jasmine.SpyObj<ModalController>;
currencyService = TestBed.inject(CurrencyService) as jasmine.SpyObj<CurrencyService>;
router = TestBed.inject(Router) as jasmine.SpyObj<Router>;

currencyService.getHomeCurrency.and.returnValue(of('USD'));
component.selectedExpenseIds = ['txCYDX0peUw5', 'txfCdl3TEZ7K'];
component.selectedTotalAmount = 500;
component.selectedTotalExpenses = 2;
fixture.detectChanges();
}));

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

it('close(): should dismiss modal', () => {
const closeButton = getElementBySelector(fixture, '.fy-icon-close') as HTMLElement;
click(closeButton);

expect(modalController.dismiss).toHaveBeenCalledTimes(1);
});

it('addExpensestoReport(): should dismiss modal with new expense data', () => {
const addExpToReportButton = getElementBySelector(fixture, '.fy-footer-cta--primary') as HTMLElement;
click(addExpToReportButton);

expect(modalController.dismiss).toHaveBeenCalledOnceWith({
selectedExpenseIds: component.selectedExpenseIds,
});
});

describe('updateSelectedExpenses():', () => {
it('should update selected expenses', () => {
component.selectedElements = [
{ ...expense1, is_reimbursable: true },
{ ...expense2, is_reimbursable: true },
];
fixture.detectChanges();

component.updateSelectedExpenses();
expect(component.selectedExpenseIds).toEqual([expense1.id, expense2.id]);
expect(component.selectedTotalExpenses).toEqual(2);
expect(component.selectedTotalAmount).toEqual(expense1.amount + expense2.amount);
});

it('should update selected expenses if expense is non-reimbursable', () => {
component.selectedElements = [
{ ...expense1, is_reimbursable: true },
{ ...expense2, is_reimbursable: false },
];
fixture.detectChanges();

component.updateSelectedExpenses();
expect(component.selectedExpenseIds).toEqual([expense1.id, expense2.id]);
expect(component.selectedTotalExpenses).toEqual(2);
expect(component.selectedTotalAmount).toEqual(expense1.amount);
});
});

describe('toggleExpense():', () => {
it('should toggle expenses and filter expense if already selected', () => {
spyOn(component, 'updateSelectedExpenses');
component.unreportedExpenses = [expense1, expense2];
component.selectedElements = [expense1, expense2];
fixture.detectChanges();

component.toggleExpense(expense2);
expect(component.selectedElements).toEqual([expense1]);
expect(component.updateSelectedExpenses).toHaveBeenCalledTimes(1);
expect(component.isSelectedAll).toBeFalse();
});

it('should toggle expenses and add expense if not selected', () => {
spyOn(component, 'updateSelectedExpenses');
component.unreportedExpenses = [expense1, expense2, expense3];
component.selectedElements = [expense1, expense2];
fixture.detectChanges();

component.toggleExpense(expense3);
expect(component.selectedElements).toEqual([expense1, expense2, expense3]);
expect(component.updateSelectedExpenses).toHaveBeenCalledTimes(1);
expect(component.isSelectedAll).toBeTrue();
});
});

describe('toggleSelectAll():', () => {
it('should select all expenses if value is true', () => {
spyOn(component, 'updateSelectedExpenses');
component.unreportedExpenses = [expense1, expense2];
fixture.detectChanges();

component.toggleSelectAll(true);
expect(component.selectedElements).toEqual([expense1, expense2]);
expect(component.updateSelectedExpenses).toHaveBeenCalledTimes(1);
});

it('should unselect and reset all selected expenses if value is false', () => {
component.toggleSelectAll(false);

expect(component.selectedElements).toEqual([]);
expect(component.selectedTotalAmount).toEqual(0);
expect(component.selectedTotalExpenses).toEqual(0);
});
});

it('ionViewWillEnter():', () => {
spyOn(component, 'updateSelectedExpenses');
component.unreportedExpenses = [expense1, expense2];
fixture.detectChanges();

component.ionViewWillEnter();
expect(component.updateSelectedExpenses).toHaveBeenCalledTimes(1);
expect(currencyService.getHomeCurrency).toHaveBeenCalledTimes(2);
component.homeCurrency$.subscribe((res) => {
expect(res).toEqual('USD');
});
expect(component.isSelectedAll).toBeTrue();
expect(component.selectedElements).toEqual([expense1, expense2]);
});

it('addNewExpense(): should navigate to add expense page', () => {
component.reportId = 'rpFE5X1Pqi9P';
fixture.detectChanges();

const addNewExpenseButton = getElementBySelector(fixture, '.report-list--add-icon') as HTMLElement;
click(addNewExpenseButton);

expect(router.navigate).toHaveBeenCalledOnceWith([
'/',
'enterprise',
'add_edit_expense',
{ rp_id: component.reportId, remove_from_report: false, navigate_back: true },
]);
expect(modalController.dismiss).toHaveBeenCalledTimes(1);
});

it('should show header if no expenses are not selected', () => {
component.selectedElements = [];
fixture.detectChanges();

expect(getTextContent(getElementBySelector(fixture, '.report-list--title'))).toEqual('Add Expenses');
});

it('should show number of expenses and total amount', () => {
component.selectedElements = [expense1, expense2];
fixture.detectChanges();

expect(getTextContent(getElementBySelector(fixture, '.add-expenses-to-report--title'))).toEqual(
'2 Expenses - $500.00'
);
});

it('should zero state message if no unreported expense exist', () => {
component.unreportedExpenses = [];
fixture.detectChanges();

expect(getTextContent(getElementBySelector(fixture, '.add-expenses-to-report--zero-state--header'))).toEqual(
'Looks like there are no complete expenses!'
);
});
});
Loading

0 comments on commit 7897875

Please sign in to comment.