From 8d1d20c576ea31ee39565eb418390e74e6f90e55 Mon Sep 17 00:00:00 2001 From: Suyash Patil <127177049+suyashpatil78@users.noreply.github.com> Date: Mon, 16 Oct 2023 17:54:58 +0530 Subject: [PATCH] test: removing unnecessary optionals and increasing branch cov - 1 (#2503) * test: removing unnecessary optionals and increasing branch cov - 1 * pr comments * test: removing unnecessary optionals and increasing branch cov - 2 (#2504) * test: removing unnecessary optionals and increasing branch cov - 2 * pr comments * test: removing unnecessary optionals and increasing branch cov - 3 (#2505) * test: removing unnecessary optionals and increasing branch cov - 3 * minor * minor * test: removing unnecessary optionals and increasing branch cov - 4 (#2506) * test: removing unnecessary optionals and increasing branch cov - 4 * minor --- .../core/mock-data/expense-fields-map.data.ts | 51 ++++ .../mock-data/unflattened-expense.data.ts | 12 + .../services/merge-expenses.service.spec.ts | 148 +++++++++- .../core/services/merge-expenses.service.ts | 10 +- .../add-edit-expense-1.spec.ts | 73 ++++- .../add-edit-expense-3.spec.ts | 77 ++++++ .../add-edit-expense-5.spec.ts | 258 +++++++++++++++++- .../add-edit-expense/add-edit-expense.page.ts | 16 +- 8 files changed, 605 insertions(+), 40 deletions(-) diff --git a/src/app/core/mock-data/expense-fields-map.data.ts b/src/app/core/mock-data/expense-fields-map.data.ts index 73341d882b..8a97c4eafd 100644 --- a/src/app/core/mock-data/expense-fields-map.data.ts +++ b/src/app/core/mock-data/expense-fields-map.data.ts @@ -1174,3 +1174,54 @@ export const txnFieldsMap2: Partial = { field: 'org_category_id', }, }; + +export const txnFieldsFlightData: Partial = { + ...txnFieldsData2, + flight_journey_travel_class: { + id: 2, + created_at: new Date('2018-01-31T23:50:27.221Z'), + created_by: { + user_id: 'SYSTEM', + org_user_id: null, + org_id: null, + roles: [], + scopes: [], + allowed_cidrs: [], + cluster_domain: null, + proxy_org_user_id: null, + tpa_id: null, + tpa_name: null, + name: null, + }, + updated_at: new Date('2023-01-13T11:45:00.655Z'), + updated_by: { + user_id: 'usEyHSLj6aHw', + org_user_id: 'ou9KVQTZWIFk', + org_id: 'orNVthTo2Zyo', + roles: ['FYLER', 'ADMIN', 'APPROVER', 'VERIFIER', 'PAYMENT_PROCESSOR', 'FINANCE', 'SUPER_ADMIN', 'HOP'], + scopes: [], + allowed_cidrs: [], + cluster_domain: '"https://staging.fyle.tech"', + proxy_org_user_id: null, + tpa_id: null, + tpa_name: null, + name: 'ou9KVQTZWIFk', + }, + org_id: 'orNVthTo2Zyo', + column_name: 'flight_journey_travel_class', + field_name: 'Flight Travel Class\\/', + seq: 1, + type: 'SELECT', + is_custom: false, + is_enabled: true, + is_mandatory: true, + placeholder: 'TEST', + default_value: null, + options: ['economy', 'business'], + org_category_ids: [ + 17283, 49621, 83060, 87409, 87441, 101148, 101149, 101151, 102676, 102757, 105705, 106574, 110168, 110597, 114563, + ], + code: null, + roles_editable: ['FYLER', 'APPROVER', 'TRAVEL_ADMIN', 'VERIFIER', 'PAYMENT_PROCESSOR', 'FINANCE', 'ADMIN'], + }, +}; diff --git a/src/app/core/mock-data/unflattened-expense.data.ts b/src/app/core/mock-data/unflattened-expense.data.ts index 718ab60a66..0e6610a666 100644 --- a/src/app/core/mock-data/unflattened-expense.data.ts +++ b/src/app/core/mock-data/unflattened-expense.data.ts @@ -561,6 +561,18 @@ export const draftUnflattendedTxn3 = { }, }; +export const draftUnflattendedTxn4: UnflattenedTransaction = { + ...unflattenedExpData, + tx: { + ...unflattenedExpData.tx, + id: null, + source: 'MOBILE', + state: 'DRAFT', + org_category_id: 212690, + fyle_category: null, + }, +}; + export const unflattenedTxnDataPerDiem = { tx: { // TODO: Enum for state and source diff --git a/src/app/core/services/merge-expenses.service.spec.ts b/src/app/core/services/merge-expenses.service.spec.ts index 3dafbdc2b5..cef21423b8 100644 --- a/src/app/core/services/merge-expenses.service.spec.ts +++ b/src/app/core/services/merge-expenses.service.spec.ts @@ -94,7 +94,7 @@ import { projectsV1Data } from '../test-data/projects.spec.data'; import { corporateCardExpenseData } from '../mock-data/corporate-card-expense.data'; import { customInputData } from '../test-data/custom-inputs.spec.data'; import * as dayjs from 'dayjs'; -import { orgCategoryData1 } from '../mock-data/org-category.data'; +import { expectedOrgCategoryByName2, orgCategoryData1 } from '../mock-data/org-category.data'; import { taxGroupData } from '../mock-data/tax-group.data'; describe('MergeExpensesService', () => { @@ -412,6 +412,15 @@ describe('MergeExpensesService', () => { }); }); + it('should return the project options with label as project name if id matches with option', (done) => { + projectService.getAllActive.and.returnValue(of(projectsV1Data)); + // @ts-ignore + mergeExpensesService.formatProjectOptions({ ...projectOptionsData, value: 257528 }).subscribe((res) => { + expect(res).toEqual({ label: 'Customer Mapped Project', value: 257528 }); + done(); + }); + }); + it('should return the project options when project is not present', (done) => { projectService.getAllActive.and.returnValue(of([])); // @ts-ignore @@ -447,7 +456,7 @@ describe('MergeExpensesService', () => { }); describe('getCorporateCardTransactions(): ', () => { - it('should return the corportate card transactions', (done) => { + it('should return the corporate card transactions', (done) => { const params = { queryParams: { group_id: ['in.(,)'], @@ -468,6 +477,29 @@ describe('MergeExpensesService', () => { done(); }); }); + + it('should return the corporate card transactions if expenses are undefined', (done) => { + const params = { + queryParams: { + group_id: ['in.(,)'], + }, + offset: 0, + limit: 1, + }; + + customInputsService.getAll.withArgs(true).and.returnValue(of(customInputData)); + corporateCreditCardExpenseService.getv2CardTransactions + .withArgs(params) + .and.returnValue(of(corporateCardExpenseData)); + + const mockExpense = [undefined, undefined]; + mergeExpensesService.getCorporateCardTransactions(mockExpense).subscribe((res) => { + expect(res).toEqual(corporateCardExpenseData.data); + expect(corporateCreditCardExpenseService.getv2CardTransactions).toHaveBeenCalledOnceWith(params); + expect(customInputsService.getAll).toHaveBeenCalledOnceWith(true); + done(); + }); + }); }); it('should return empty list if there are no expenses', (done) => { @@ -669,6 +701,16 @@ describe('MergeExpensesService', () => { it('should return null when options are not passed', () => { expect(mergeExpensesService.getFieldValue(optionsData13)).toBeNull(); }); + + it('should return null when optionsData is null', () => { + expect(mergeExpensesService.getFieldValue(null)).toBeNull(); + }); + + it('should return undefined if option is empty array', () => { + expect( + mergeExpensesService.getFieldValue({ ...optionsData13, areSameValues: true, options: [] }) + ).toBeUndefined(); + }); }); it('generateLocationOptions(): should return the location options', (done) => { @@ -782,12 +824,32 @@ describe('MergeExpensesService', () => { }); }); - it('getCategoryName(): should return the category name', () => { - categoriesService.getAll.and.returnValue(of(orgCategoryData1)); - const categoryId = '201952'; - mergeExpensesService.getCategoryName(categoryId).subscribe((res) => { - expect(res).toEqual('Food'); - expect(categoriesService.getAll).toHaveBeenCalledTimes(1); + describe('getCategoryName():', () => { + it('should return the category name', () => { + categoriesService.getAll.and.returnValue(of(orgCategoryData1)); + const categoryId = '201952'; + mergeExpensesService.getCategoryName(categoryId).subscribe((res) => { + expect(res).toEqual('Food'); + expect(categoriesService.getAll).toHaveBeenCalledTimes(1); + }); + }); + + it('should return undefined if category id does not match with params', () => { + categoriesService.getAll.and.returnValue(of(orgCategoryData1)); + const categoryId = '201951'; + mergeExpensesService.getCategoryName(categoryId).subscribe((res) => { + expect(res).toEqual(undefined); + expect(categoriesService.getAll).toHaveBeenCalledTimes(1); + }); + }); + + it('should return undefined if category id is undefined', () => { + categoriesService.getAll.and.returnValue(of([expectedOrgCategoryByName2])); + const categoryId = '201951'; + mergeExpensesService.getCategoryName(categoryId).subscribe((res) => { + expect(res).toEqual(undefined); + expect(categoriesService.getAll).toHaveBeenCalledTimes(1); + }); }); }); @@ -815,6 +877,16 @@ describe('MergeExpensesService', () => { expect(res).toEqual(mergeExpensesOptionData4[0].value); }); + it('should return the field value on change when value is untouched and optionsData is undefined', () => { + const res = mergeExpensesService.getFieldValueOnChange( + undefined, + null, + mergeExpensesOptionData4[0].value, + mergeExpensesOptionData4[0].value + ); + expect(res).toEqual(mergeExpensesOptionData4[0].value); + }); + it('should return the field value on change when value is touched', () => { const res = mergeExpensesService.getFieldValueOnChange( mergeExpensesOptionData4[0], @@ -826,13 +898,29 @@ describe('MergeExpensesService', () => { }); }); - it('formatTaxGroupOption(): should return the formatted tax group option', (done) => { - taxGroupService.get.and.returnValue(of(taxGroupData)); - // @ts-ignore - mergeExpensesService.formatTaxGroupOption(optionsData11.options[0]).subscribe((res) => { - expect(res).toEqual(mergeExpensesOptionData5[0]); - expect(taxGroupService.get).toHaveBeenCalledTimes(1); - done(); + describe('formatTaxGroupOption():', () => { + it('formatTaxGroupOption(): should return the formatted tax group option', (done) => { + taxGroupService.get.and.returnValue(of(taxGroupData)); + // @ts-ignore + mergeExpensesService.formatTaxGroupOption(optionsData11.options[0]).subscribe((res) => { + expect(res).toEqual(mergeExpensesOptionData5[0]); + expect(taxGroupService.get).toHaveBeenCalledTimes(1); + done(); + }); + }); + + it('formatTaxGroupOption(): should return the formatted tax group option with label as undefined if id does not matches with options', (done) => { + taxGroupService.get.and.returnValue(of(taxGroupData)); + const mockOptions = { ...optionsData11.options[0], value: 'tgp6JA6tgoZ1' }; + // @ts-ignore + mergeExpensesService.formatTaxGroupOption(mockOptions).subscribe((res) => { + expect(res).toEqual({ + label: undefined, + value: 'tgp6JA6tgoZ1', + }); + expect(taxGroupService.get).toHaveBeenCalledTimes(1); + done(); + }); }); }); @@ -912,4 +1000,34 @@ describe('MergeExpensesService', () => { const mockDate = '2021-03-10T05:31:00.000Z'; expect(mergeExpensesService.setFormattedDate(mockDate)).toEqual(dayjs(mockDate).format('MMM DD, YYYY')); }); + + describe('formatCategoryOption():', () => { + beforeEach(() => { + categoriesService.getAll.and.returnValue(of(orgCategoryData1)); + categoriesService.filterRequired.and.returnValue(orgCategoryData1); + }); + + it('should return the formatted category option', (done) => { + // @ts-ignore + mergeExpensesService.formatCategoryOption(mergeExpensesOptionData4[0]).subscribe((res) => { + expect(res).toEqual(mergeExpensesOptionData4[0]); + expect(categoriesService.getAll).toHaveBeenCalledTimes(1); + expect(categoriesService.filterRequired).toHaveBeenCalledOnceWith(orgCategoryData1); + done(); + }); + }); + + it('should return the formatted category option with label as Unspecified if id does not matches with options', (done) => { + // @ts-ignore + mergeExpensesService.formatCategoryOption({ ...mergeExpensesOptionData4[0], value: 201951 }).subscribe((res) => { + expect(res).toEqual({ + label: 'Unspecified', + value: 201951, + }); + expect(categoriesService.getAll).toHaveBeenCalledTimes(1); + expect(categoriesService.filterRequired).toHaveBeenCalledOnceWith(orgCategoryData1); + done(); + }); + }); + }); }); diff --git a/src/app/core/services/merge-expenses.service.ts b/src/app/core/services/merge-expenses.service.ts index f8fd45ea40..2e9fcb143d 100644 --- a/src/app/core/services/merge-expenses.service.ts +++ b/src/app/core/services/merge-expenses.service.ts @@ -135,7 +135,7 @@ export class MergeExpensesService { switchMap(() => { const CCCGroupIds = expenses.map((expense) => expense?.tx_corporate_credit_card_expense_group_id); - if (CCCGroupIds?.length > 0) { + if (CCCGroupIds.length > 0) { const queryParams = { group_id: ['in.(' + CCCGroupIds + ')'], }; @@ -297,7 +297,7 @@ export class MergeExpensesService { return from(expenses).pipe( filter((expense) => !!expense.tx_project_id), map((expense) => ({ - label: expense.tx_project_id?.toString(), + label: expense.tx_project_id.toString(), value: expense.tx_project_id, })), mergeMap((option) => this.formatProjectOptions(option)), @@ -395,7 +395,7 @@ export class MergeExpensesService { return from(expenses).pipe( filter((expense) => !!expense.tx_locations[locationIndex]), map((expense) => ({ - label: expense.tx_locations[locationIndex]?.formatted_address, + label: expense.tx_locations[locationIndex].formatted_address, value: expense.tx_locations[locationIndex], })), reduce((acc: MergeExpensesOption[], curr) => { @@ -562,7 +562,7 @@ export class MergeExpensesService { getCategoryName(categoryId: string): Observable { return this.categoriesService.getAll().pipe( map((categories) => { - const category = categories.find((category) => category?.id?.toString() === categoryId); + const category = categories.find((category) => category.id?.toString() === categoryId); return category?.name; }) ); @@ -641,7 +641,7 @@ export class MergeExpensesService { getFieldValue(optionsData: MergeExpensesOptionsData): T { if (optionsData?.areSameValues) { - return optionsData?.options[0]?.value; + return optionsData.options[0]?.value; } else { return null; } diff --git a/src/app/fyle/add-edit-expense/add-edit-expense-1.spec.ts b/src/app/fyle/add-edit-expense/add-edit-expense-1.spec.ts index 6dee584f95..3cfd27db28 100644 --- a/src/app/fyle/add-edit-expense/add-edit-expense-1.spec.ts +++ b/src/app/fyle/add-edit-expense/add-edit-expense-1.spec.ts @@ -12,7 +12,7 @@ import { actionSheetOptionsData } from 'src/app/core/mock-data/action-sheet-opti import { expectedECccResponse } from 'src/app/core/mock-data/corporate-card-expense-unflattened.data'; import { costCenterApiRes1, expectedCCdata } from 'src/app/core/mock-data/cost-centers.data'; import { customFieldData1 } from 'src/app/core/mock-data/custom-field.data'; -import { expenseFieldObjData } from 'src/app/core/mock-data/expense-field-obj.data'; +import { expenseFieldObjData, txnFieldData } from 'src/app/core/mock-data/expense-field-obj.data'; import { txnFieldsMap2 } from 'src/app/core/mock-data/expense-fields-map.data'; import { apiExpenseRes, expenseData1 } from 'src/app/core/mock-data/expense.data'; import { categorieListRes } from 'src/app/core/mock-data/org-category-list-item.data'; @@ -445,7 +445,7 @@ export function TestCases1(getTestBed) { component.etxn$ = of(unflattenedExpData); component.fg.controls.paymentMode.setValue({ ...unflattenedAccount1Data, - acc: { ...unflattenedAccount1Data.acc, type: AccountType.ADVANCE, id: 'acc5APeygFjRd' }, + acc: { ...unflattenedAccount1Data.acc, type: AccountType.ADVANCE, id: 'accZ1IWjhjLv4' }, }); component.fg.controls.currencyObj.setValue({ currency: 'USD', @@ -709,6 +709,24 @@ export function TestCases1(getTestBed) { }); })); + it('should go back to my expenses page if etxn is undefined', fakeAsync(() => { + component.etxn$ = of(undefined); + spyOn(component, 'goBack'); + transactionService.getRemoveCardExpenseDialogBody.and.returnValue('removed'); + spyOn(component, 'getRemoveCCCExpModalParams'); + spyOn(component, 'showSnackBarToast'); + + const deletePopoverSpy = jasmine.createSpyObj('deletePopover', ['present', 'onDidDismiss']); + deletePopoverSpy.onDidDismiss.and.resolveTo({ data: { status: 'success' } }); + + popoverController.create.and.resolveTo(deletePopoverSpy); + + component.removeCorporateCardExpense(); + tick(500); + + expect(component.goBack).toHaveBeenCalledTimes(1); + })); + it('navigate back to report if redirected from report after removing txn', fakeAsync(() => { const txn = { ...unflattenedTxn, tx: { ...unflattenedTxn.tx, report_id: 'rpFE5X1Pqi9P' } }; component.etxn$ = of(txn); @@ -1040,6 +1058,57 @@ export function TestCases1(getTestBed) { done(); }); + it('should get all action sheet options and call titleCasePipe transform method if project_id is defined', (done) => { + orgSettingsService.get.and.returnValue( + of({ + ...orgSettingsData, + expense_settings: { ...orgSettingsData.expense_settings, split_expense_settings: { enabled: true } }, + }) + ); + component.costCenters$ = of(expectedCCdata); + projectsService.getAllActive.and.returnValue(of(projectsV1Data)); + component.filteredCategories$ = of(categorieListRes); + component.txnFields$ = of(txnFieldData); + component.isCccExpense = 'tx3qHxFNgRcZ'; + component.canDismissCCCE = true; + component.isCorporateCreditCardEnabled = true; + component.canRemoveCardExpense = true; + component.isExpenseMatchedForDebitCCCE = true; + spyOn(component, 'splitExpCategoryHandler'); + spyOn(component, 'splitExpProjectHandler'); + spyOn(component, 'splitExpCostCenterHandler'); + spyOn(component, 'markPersonalHandler'); + spyOn(component, 'markDismissHandler'); + spyOn(component, 'removeCCCHandler'); + launchDarklyService.getVariation.and.returnValue(of(true)); + fixture.detectChanges(); + + component.getActionSheetOptions().subscribe((actionSheetOptionsResponse) => { + const actionSheetOptions = actionSheetOptionsResponse; + expect(actionSheetOptionsResponse.length).toEqual(6); + expect(titleCasePipe.transform).toHaveBeenCalledOnceWith('Project'); + expect(orgSettingsService.get).toHaveBeenCalledTimes(1); + expect(projectsService.getAllActive).toHaveBeenCalledTimes(1); + expect(launchDarklyService.getVariation).toHaveBeenCalledOnceWith( + 'show_project_mapped_categories_in_split_expense', + false + ); + actionSheetOptions[0].handler(); + expect(component.splitExpCategoryHandler).toHaveBeenCalledTimes(1); + actionSheetOptions[1].handler(); + expect(component.splitExpProjectHandler).toHaveBeenCalledTimes(1); + actionSheetOptions[2].handler(); + expect(component.splitExpCostCenterHandler).toHaveBeenCalledTimes(1); + actionSheetOptions[3].handler(); + expect(component.markPersonalHandler).toHaveBeenCalledTimes(1); + actionSheetOptions[4].handler(); + expect(component.markDismissHandler).toHaveBeenCalledTimes(1); + actionSheetOptions[5].handler(); + expect(component.removeCCCHandler).toHaveBeenCalledTimes(1); + done(); + }); + }); + it('should get action sheet options when split expense is not allowed', (done) => { orgSettingsService.get.and.returnValue( of({ diff --git a/src/app/fyle/add-edit-expense/add-edit-expense-3.spec.ts b/src/app/fyle/add-edit-expense/add-edit-expense-3.spec.ts index 8bd55c7d7e..774c94a9d7 100644 --- a/src/app/fyle/add-edit-expense/add-edit-expense-3.spec.ts +++ b/src/app/fyle/add-edit-expense/add-edit-expense-3.spec.ts @@ -44,6 +44,7 @@ import { draftUnflattendedTxn, draftUnflattendedTxn2, draftUnflattendedTxn3, + draftUnflattendedTxn4, unflattenedExpData, unflattenedTxn, } from 'src/app/core/mock-data/unflattened-expense.data'; @@ -382,6 +383,20 @@ export function TestCases3(getTestBed) { expect(component.presetCategoryId).toEqual(expectedAutoFillCategory2.id); }); + it('should return category passed as arguments for DRAFT expense added via webapp bulk upload or bulk instafyle if recent category is undefined', () => { + const result = component.getAutofillCategory({ + isAutofillsEnabled: true, + recentValue: recentlyUsedRes, + recentCategories: undefined, + etxn: draftUnflattendedTxn3, + category: orgCategoryData, + }); + + expect(result).toEqual(orgCategoryData); + expect(component.recentCategories).toEqual(undefined); + expect(component.presetCategoryId).toEqual(undefined); + }); + it('return auto fill category if recent categories is not present and expense category is empty', () => { const result = component.getAutofillCategory({ isAutofillsEnabled: true, @@ -393,6 +408,18 @@ export function TestCases3(getTestBed) { expect(result).toEqual(expectedAutoFillCategory3); }); + + it('should return config category if recent categories is not present, expense category is empty and fyle_category is undefined', () => { + const result = component.getAutofillCategory({ + isAutofillsEnabled: true, + recentValue: null, + recentCategories: null, + etxn: draftUnflattendedTxn4, + category: orgCategoryData, + }); + + expect(result).toEqual(expectedAutoFillCategory3); + }); }); describe('getExpenseAttachments():', () => { @@ -1063,6 +1090,28 @@ export function TestCases3(getTestBed) { }); }); + it('should return selected category for draft expense if fyle_category is undefined', (done) => { + orgUserSettingsService.get.and.returnValue(of(orgUserSettingsData)); + orgSettingsService.get.and.returnValue(of(orgSettingsData)); + component.recentlyUsedValues$ = of(recentlyUsedRes); + component.recentlyUsedCategories$ = of(recentUsedCategoriesRes); + component.etxn$ = of({ + ...unflattenedPaidExp, + tx: { ...unflattenedPaidExp.tx, fyle_category: undefined, state: 'DRAFT' }, + }); + component.initialFetch = true; + categoriesService.getCategoryById.and.returnValue(of(orgCategoryData1[0])); + + fixture.detectChanges(); + component.getCategoryOnEdit(orgCategoryData1[0]).subscribe((res) => { + expect(res).toEqual(orgCategoryPaginated1[0]); + expect(orgUserSettingsService.get).toHaveBeenCalledTimes(1); + expect(orgSettingsService.get).toHaveBeenCalledTimes(1); + expect(categoriesService.getCategoryById).toHaveBeenCalledOnceWith(unflattenedDraftExp.tx.org_category_id); + done(); + }); + }); + it('should get autofill category for draft expense when category is unspecified', (done) => { orgUserSettingsService.get.and.returnValue(of(orgUserSettingsData)); orgSettingsService.get.and.returnValue(of(orgSettingsData)); @@ -1232,6 +1281,34 @@ export function TestCases3(getTestBed) { }); }); + it('should get new expense observable without autofill and set currency equal to homeCurrency if recently used currency is undefined', (done) => { + orgSettingsService.get.and.returnValue(of(orgSettingsWithoutAutofill)); + authService.getEou.and.resolveTo(apiEouRes); + component.orgUserSettings$ = of(orgUserSettingsData); + categoriesService.getAll.and.returnValue(of(orgCategoryData1)); + component.homeCurrency$ = of('USD'); + dateService.getUTCDate.and.returnValue(new Date('2023-01-24T11:30:00.000Z')); + spyOn(component, 'getInstaFyleImageData').and.returnValue(of(instaFyleData1)); + recentLocalStorageItemsService.get.and.resolveTo(undefined); + component.recentlyUsedValues$ = of(recentlyUsedRes); + fixture.detectChanges(); + + component.getNewExpenseObservable().subscribe((newExpense) => { + expect(newExpense).toEqual(expectedExpenseObservable3); + expect(component.source).toEqual('MOBILE_DASHCAM_SINGLE'); + expect(component.isExpenseBankTxn).toBeFalse(); + expect(component.instaFyleCancelled).toBeFalse(); + expect(orgSettingsService.get).toHaveBeenCalledTimes(1); + + expect(authService.getEou).toHaveBeenCalledTimes(1); + expect(categoriesService.getCategoryByName).toHaveBeenCalledTimes(1); + expect(recentLocalStorageItemsService.get).toHaveBeenCalledOnceWith('recent-currency-cache'); + expect(component.getInstaFyleImageData).toHaveBeenCalledTimes(1); + expect(dateService.getUTCDate).toHaveBeenCalledTimes(2); + done(); + }); + }); + it('should get new expense observable from personal card txn and home currency does not match extracted data', (done) => { activatedRoute.snapshot.params.personalCardTxn = JSON.stringify(apiPersonalCardTxnsRes.data); orgSettingsService.get.and.returnValue(of(orgSettingsData)); diff --git a/src/app/fyle/add-edit-expense/add-edit-expense-5.spec.ts b/src/app/fyle/add-edit-expense/add-edit-expense-5.spec.ts index e3b0e1c9d8..09a34a0b87 100644 --- a/src/app/fyle/add-edit-expense/add-edit-expense-5.spec.ts +++ b/src/app/fyle/add-edit-expense/add-edit-expense-5.spec.ts @@ -105,7 +105,7 @@ import { txnCustomProperties, txnCustomProperties2 } from 'src/app/core/test-dat import { apiV2ResponseMultiple, expectedProjectsResponse } from 'src/app/core/test-data/projects.spec.data'; import { getEstatusApiResponse } from 'src/app/core/test-data/status.service.spec.data'; import { AddEditExpensePage } from './add-edit-expense.page'; -import { txnFieldsData2 } from 'src/app/core/mock-data/expense-fields-map.data'; +import { txnFieldsData2, txnFieldsFlightData } from 'src/app/core/mock-data/expense-fields-map.data'; export function TestCases5(getTestBed) { return describe('AddEditExpensePage-5', () => { @@ -187,11 +187,11 @@ export function TestCases5(getTestBed) { popupService = TestBed.inject(PopupService) as jasmine.SpyObj; navController = TestBed.inject(NavController) as jasmine.SpyObj; corporateCreditCardExpenseService = TestBed.inject( - CorporateCreditCardExpenseService, + CorporateCreditCardExpenseService ) as jasmine.SpyObj; trackingService = TestBed.inject(TrackingService) as jasmine.SpyObj; recentLocalStorageItemsService = TestBed.inject( - RecentLocalStorageItemsService, + RecentLocalStorageItemsService ) as jasmine.SpyObj; recentlyUsedItemsService = TestBed.inject(RecentlyUsedItemsService) as jasmine.SpyObj; tokenService = TestBed.inject(TokenService) as jasmine.SpyObj; @@ -270,7 +270,7 @@ export function TestCases5(getTestBed) { ctaText: 'Done', ctaLoadingText: 'Loading', }, - true, + true ) .componentProps.deleteMethod() .subscribe(() => { @@ -295,7 +295,7 @@ export function TestCases5(getTestBed) { ctaText: 'Done', ctaLoadingText: 'Loading', }, - false, + false ) .componentProps.deleteMethod() .subscribe(() => { @@ -320,7 +320,7 @@ export function TestCases5(getTestBed) { ctaText: 'Done', ctaLoadingText: 'Loading', }, - false, + false ) .componentProps.deleteMethod() .subscribe(() => { @@ -345,7 +345,7 @@ export function TestCases5(getTestBed) { ctaText: 'Done', ctaLoadingText: 'Loading', }, - false, + false ) .componentProps.deleteMethod() .subscribe(() => { @@ -644,7 +644,7 @@ export function TestCases5(getTestBed) { expect(res).toEqual(unflattenedAccount1Data); expect(accountsService.getEtxnSelectedPaymentMode).toHaveBeenCalledOnceWith( unflattenedTxnData, - accountOptionData1, + accountOptionData1 ); done(); }); @@ -869,6 +869,128 @@ export function TestCases5(getTestBed) { expect(component.setCategoryOnValueChange).toHaveBeenCalledTimes(1); })); + //TODO: Reduce the number of test by moving it to beforeEach + it('should setup form and set payment mode as default payment mode if selectedPaymentMode is undefined', fakeAsync(() => { + spyOn(component, 'getSelectedProjects').and.returnValue(of(expectedProjectsResponse[0])); + spyOn(component, 'getSelectedCategory').and.returnValue(of(orgCategoryData)); + spyOn(component, 'getSelectedReport').and.returnValue(of(expectedErpt[0])); + spyOn(component, 'getSelectedPaymentModes').and.returnValue(of(undefined)); + spyOn(component, 'getRecentCostCenters').and.returnValue(of(recentlyUsedCostCentersRes)); + spyOn(component, 'getRecentProjects').and.returnValue(of(recentlyUsedProjectRes)); + spyOn(component, 'getRecentCurrencies').and.returnValue(of(recentCurrencyRes)); + spyOn(component, 'getDefaultPaymentModes').and.returnValue(of(accountOptionData1[1].value)); + spyOn(component, 'getSelectedCostCenters').and.returnValue(of(costCentersData[0])); + spyOn(component, 'getReceiptCount').and.returnValue(of(1)); + currencyService.getHomeCurrency.and.returnValue(of('USD')); + orgSettingsService.get.and.returnValue(of(orgSettingsData)); + customInputsService.getAll.and.returnValue(of(expenseFieldResponse)); + loaderService.hideLoader.and.resolveTo(); + loaderService.showLoader.and.resolveTo(); + component.etxn$ = of(unflattenedTxnData); + component.taxGroups$ = of(taxGroupData); + component.orgUserSettings$ = of(orgUserSettingsData); + component.recentlyUsedValues$ = of(recentlyUsedRes); + component.recentlyUsedProjects$ = of(recentlyUsedProjectRes); + component.recentlyUsedCurrencies$ = of(recentCurrencyRes); + component.recentlyUsedCostCenters$ = of(recentlyUsedCostCentersRes); + component.recentlyUsedCategories$ = of(recentUsedCategoriesRes); + component.selectedCostCenter$ = new BehaviorSubject(costCentersData[0]); + spyOn(component, 'setCategoryOnValueChange'); + spyOn(component, 'getAutofillCategory'); + customFieldsService.standardizeCustomFields.and.returnValue(txnCustomProperties); + customInputsService.filterByCategory.and.returnValue(expenseFieldResponse); + fixture.detectChanges(); + + component.setupFormInit(); + tick(1000); + + expect(customFieldsService.standardizeCustomFields).toHaveBeenCalledTimes(1); + expect(customInputsService.filterByCategory).toHaveBeenCalledOnceWith(expenseFieldResponse, 16577); + expect(component.getAutofillCategory).toHaveBeenCalledOnceWith({ + isAutofillsEnabled: true, + recentValue: recentlyUsedRes, + recentCategories: recentUsedCategoriesRes, + etxn: unflattenedTxnData, + category: orgCategoryData, + }); + expect(component.setCategoryOnValueChange).toHaveBeenCalledTimes(1); + expect(component.fg.value.paymentMode).toEqual(accountOptionData1[1].value); + })); + + it('should setup form if custom field has a date type field', fakeAsync(() => { + spyOn(component, 'getSelectedProjects').and.returnValue(of(expectedProjectsResponse[0])); + spyOn(component, 'getSelectedCategory').and.returnValue(of(orgCategoryData)); + spyOn(component, 'getSelectedReport').and.returnValue(of(expectedErpt[0])); + spyOn(component, 'getSelectedPaymentModes').and.returnValue(of(unflattenedAccount1Data)); + spyOn(component, 'getRecentCostCenters').and.returnValue(of(recentlyUsedCostCentersRes)); + spyOn(component, 'getRecentProjects').and.returnValue(of(recentlyUsedProjectRes)); + spyOn(component, 'getRecentCurrencies').and.returnValue(of(recentCurrencyRes)); + spyOn(component, 'getDefaultPaymentModes').and.returnValue(of(accountOptionData1[1].value)); + spyOn(component, 'getSelectedCostCenters').and.returnValue(of(costCentersData[0])); + spyOn(component, 'getReceiptCount').and.returnValue(of(1)); + currencyService.getHomeCurrency.and.returnValue(of('USD')); + orgSettingsService.get.and.returnValue(of(orgSettingsData)); + customInputsService.getAll.and.returnValue(of(expenseFieldResponse)); + loaderService.hideLoader.and.resolveTo(); + loaderService.showLoader.and.resolveTo(); + component.etxn$ = of({ + ...unflattenedTxnData, + tx: { + ...unflattenedTxnData.tx, + custom_properties: [ + { + name: 'Arrival', + value: '2021-06-01', + }, + ], + }, + }); + component.taxGroups$ = of(taxGroupData); + component.orgUserSettings$ = of(orgUserSettingsData); + component.recentlyUsedValues$ = of(recentlyUsedRes); + component.recentlyUsedProjects$ = of(recentlyUsedProjectRes); + component.recentlyUsedCurrencies$ = of(recentCurrencyRes); + component.recentlyUsedCostCenters$ = of(recentlyUsedCostCentersRes); + component.recentlyUsedCategories$ = of(recentUsedCategoriesRes); + component.selectedCostCenter$ = new BehaviorSubject(costCentersData[0]); + spyOn(component, 'setCategoryOnValueChange'); + spyOn(component, 'getAutofillCategory'); + customFieldsService.standardizeCustomFields.and.returnValue([ + ...txnCustomProperties, + { + type: 'DATE', + name: 'Arrival', + value: '2023-06-01', + }, + ]); + customInputsService.filterByCategory.and.returnValue(expenseFieldResponse); + fixture.detectChanges(); + + component.setupFormInit(); + tick(1000); + expect(customFieldsService.standardizeCustomFields).toHaveBeenCalledTimes(1); + expect(customInputsService.filterByCategory).toHaveBeenCalledOnceWith(expenseFieldResponse, 16577); + expect(component.getAutofillCategory).toHaveBeenCalledOnceWith({ + isAutofillsEnabled: true, + recentValue: recentlyUsedRes, + recentCategories: recentUsedCategoriesRes, + etxn: { + ...unflattenedTxnData, + tx: { + ...unflattenedTxnData.tx, + custom_properties: [ + { + name: 'Arrival', + value: '2021-06-01', + }, + ], + }, + }, + category: orgCategoryData, + }); + expect(component.setCategoryOnValueChange).toHaveBeenCalledTimes(1); + })); + it('should setup up form for a draft expense with policy violation', fakeAsync(() => { spyOn(component, 'getSelectedProjects').and.returnValue(of(expectedProjectsResponse[0])); spyOn(component, 'getSelectedCategory').and.returnValue(of(orgCategoryData)); @@ -1105,6 +1227,52 @@ export function TestCases5(getTestBed) { }); expect(component.setCategoryOnValueChange).toHaveBeenCalledTimes(1); })); + + it('setup form for an expense with different currencies and DRAFT state if recently used categories are undefined', fakeAsync(() => { + spyOn(component, 'getSelectedProjects').and.returnValue(of(expectedProjectsResponse[0])); + spyOn(component, 'getSelectedCategory').and.returnValue(of(orgCategoryData)); + spyOn(component, 'getSelectedReport').and.returnValue(of(expectedErpt[0])); + spyOn(component, 'getSelectedPaymentModes').and.returnValue(of(unflattenedAccount1Data)); + spyOn(component, 'getRecentCostCenters').and.returnValue(of(recentlyUsedCostCentersRes)); + spyOn(component, 'getRecentProjects').and.returnValue(of(recentlyUsedProjectRes)); + spyOn(component, 'getRecentCurrencies').and.returnValue(of(recentCurrencyRes)); + spyOn(component, 'getDefaultPaymentModes').and.returnValue(of(accountOptionData1[1].value)); + spyOn(component, 'getSelectedCostCenters').and.returnValue(of(costCentersData[0])); + spyOn(component, 'getReceiptCount').and.returnValue(of(1)); + currencyService.getHomeCurrency.and.returnValue(of('USD')); + orgSettingsService.get.and.returnValue(of(orgSettingsWithProjectAndAutofill)); + customInputsService.getAll.and.returnValue(of(expenseFieldResponse)); + loaderService.hideLoader.and.resolveTo(); + loaderService.showLoader.and.resolveTo(); + component.etxn$ = of(setupFormExpenseWoCurrency3); + component.taxGroups$ = of(taxGroupData); + component.orgUserSettings$ = of(orgUserSettingsData); + component.recentlyUsedValues$ = of(recentlyUsedRes); + component.recentlyUsedProjects$ = of(recentlyUsedProjectRes); + component.recentlyUsedCurrencies$ = of(recentCurrencyRes); + component.recentlyUsedCostCenters$ = of(recentlyUsedCostCentersRes); + component.recentlyUsedCategories$ = of(undefined); + component.selectedCostCenter$ = new BehaviorSubject(costCentersData[0]); + spyOn(component, 'setCategoryOnValueChange'); + spyOn(component, 'getAutofillCategory'); + customFieldsService.standardizeCustomFields.and.returnValue(TxnCustomProperties3); + customInputsService.filterByCategory.and.returnValue(expenseFieldResponse); + fixture.detectChanges(); + + component.setupFormInit(); + tick(1000); + + expect(customFieldsService.standardizeCustomFields).toHaveBeenCalledTimes(1); + expect(customInputsService.filterByCategory).toHaveBeenCalledOnceWith(expenseFieldResponse, 16577); + expect(component.getAutofillCategory).toHaveBeenCalledOnceWith({ + isAutofillsEnabled: true, + recentValue: recentlyUsedRes, + recentCategories: undefined, + etxn: setupFormExpenseWoCurrency3, + category: orgCategoryData, + }); + expect(component.setCategoryOnValueChange).toHaveBeenCalledTimes(1); + })); }); it('getProjectDependentFields(): should get project dependent fields', () => { @@ -1351,6 +1519,80 @@ export function TestCases5(getTestBed) { done(); }); + it('should set flightJourneyTravelClassOptions$', (done) => { + component.isConnected$ = of(true); + component.txnFields$ = of(txnFieldsFlightData); + component.filteredCategories$ = of(); + + spyOn(component, 'initClassObservables').and.returnValue(null); + tokenService.getClusterDomain.and.resolveTo('domain'); + categoriesService.getSystemCategories.and.returnValue(['Bus', 'Airlines', 'Lodging', 'Train']); + categoriesService.getBreakfastSystemCategories.and.returnValue(['Lodging']); + reportService.getAutoSubmissionReportName.and.returnValue(of('Jun #23')); + spyOn(component, 'setupSelectedProjectObservable'); + spyOn(component, 'setupSelectedCostCenterObservable'); + spyOn(component, 'getCCCpaymentMode'); + spyOn(component, 'setUpTaxCalculations'); + orgSettingsService.get.and.returnValue(of(orgSettingsData)); + orgUserSettingsService.get.and.returnValue(of(orgUserSettingsData)); + currencyService.getHomeCurrency.and.returnValue(of('USD')); + accountsService.getEMyAccounts.and.returnValue(of(multiplePaymentModesData)); + spyOn(component, 'setupNetworkWatcher'); + taxGroupService.get.and.returnValue(of(taxGroupData)); + recentlyUsedItemsService.getRecentlyUsed.and.returnValue(of(recentlyUsedRes)); + component.individualProjectIds$ = of([]); + component.isIndividualProjectsEnabled$ = of(false); + projectsService.getProjectCount.and.returnValue(of(2)); + spyOn(component, 'setupCostCenters'); + storageService.get.and.resolveTo(true); + spyOn(component, 'setupBalanceFlag'); + statusService.find.and.returnValue(of(getEstatusApiResponse)); + spyOn(component, 'getActiveCategories').and.returnValue(of(sortedCategory)); + spyOn(component, 'getNewExpenseObservable').and.returnValue(of(expectedExpenseObservable)); + spyOn(component, 'getEditExpenseObservable').and.returnValue(of(expectedUnflattendedTxnData1)); + fileService.findByTransactionId.and.returnValue(of(expectedFileData1)); + fileService.downloadUrl.and.returnValue(of('url')); + spyOn(component, 'getReceiptDetails').and.returnValue({ + type: 'jpeg', + thumbnail: 'thumbnail', + }); + spyOn(component, 'getPaymentModes'); + spyOn(component, 'setupFilteredCategories'); + spyOn(component, 'setupExpenseFields'); + + reportService.getFilteredPendingReports.and.returnValue(of(expectedErpt)); + recentlyUsedItemsService.getRecentCategories.and.returnValue(of(recentUsedCategoriesRes)); + + spyOn(component, 'setupFormInit'); + spyOn(component, 'setupCustomFields'); + spyOn(component, 'clearCategoryOnValueChange'); + spyOn(component, 'getActionSheetOptions').and.returnValue(of([])); + spyOn(component, 'getPolicyDetails'); + spyOn(component, 'getDuplicateExpenses'); + + activatedRoute.snapshot.params.bankTxn = JSON.stringify(expectedECccResponse); + activatedRoute.snapshot.params.txnIds = JSON.stringify(['id_1']); + component.reviewList = ['id_1']; + component.activeIndex = 0; + fixture.detectChanges(); + + component.ionViewWillEnter(); + + component.flightJourneyTravelClassOptions$.subscribe((res) => { + expect(res).toEqual([ + { + label: 'economy', + value: 'economy', + }, + { + label: 'business', + value: 'business', + }, + ]); + done(); + }); + }); + it('should setup class variables for offline mode', (done) => { component.isConnected$ = of(false); component.txnFields$ = of(expenseFieldObjData); diff --git a/src/app/fyle/add-edit-expense/add-edit-expense.page.ts b/src/app/fyle/add-edit-expense/add-edit-expense.page.ts index 12fce15204..2c1f2316f4 100644 --- a/src/app/fyle/add-edit-expense/add-edit-expense.page.ts +++ b/src/app/fyle/add-edit-expense/add-edit-expense.page.ts @@ -590,15 +590,15 @@ export class AddEditExpensePage implements OnInit { combineLatest(this.fg.controls.currencyObj.valueChanges, this.fg.controls.tax_group.valueChanges).subscribe(() => { if ( this.fg.controls.tax_group.value && - isNumber(taxGroupControl.value?.percentage) && + isNumber(taxGroupControl.value.percentage) && this.fg.controls.currencyObj.value ) { const amount = - currencyObjControl.value?.amount - currencyObjControl.value?.amount / (taxGroupControl.value?.percentage + 1); + currencyObjControl.value.amount - currencyObjControl.value.amount / (taxGroupControl.value.percentage + 1); const formattedAmount = this.currencyService.getAmountWithCurrencyFraction( amount, - currencyObjControl.value?.currency + currencyObjControl.value.currency ); this.fg.controls.tax_amount.setValue(formattedAmount); @@ -1984,7 +1984,7 @@ export class AddEditExpensePage implements OnInit { recentCategories: OrgCategoryListItem[]; etxn: UnflattenedTransaction; }) => { - const isExpenseCategoryUnspecified = etxn.tx?.fyle_category?.toLowerCase() === 'unspecified'; + const isExpenseCategoryUnspecified = etxn.tx.fyle_category?.toLowerCase() === 'unspecified'; if (this.initialFetch && etxn.tx.org_category_id && !isExpenseCategoryUnspecified) { return this.categoriesService.getCategoryById(etxn.tx.org_category_id).pipe( map((selectedCategory) => ({ @@ -2029,11 +2029,7 @@ export class AddEditExpensePage implements OnInit { } else { return selectedCategory; } - } else if ( - etxn.tx.state === 'DRAFT' && - !isCategoryExtracted && - (!etxn.tx.org_category_id || etxn.tx.fyle_category?.toLowerCase() === 'unspecified') - ) { + } else if (etxn.tx.state === 'DRAFT' && !isCategoryExtracted && !etxn.tx.org_category_id) { return this.getAutofillCategory({ isAutofillsEnabled, recentValue: recentValues, @@ -3149,7 +3145,7 @@ export class AddEditExpensePage implements OnInit { getSkipRemibursement(): boolean { const formValue = this.getFormValues(); - return formValue?.paymentMode?.acc?.type === AccountType.PERSONAL && !formValue.paymentMode.acc?.isReimbursable; + return formValue?.paymentMode?.acc?.type === AccountType.PERSONAL && !formValue.paymentMode.acc.isReimbursable; } getTxnDate(): Date {