From 63d2f38a94292ea68bf2bc0ae6e678069e9df0d2 Mon Sep 17 00:00:00 2001 From: Viswas Date: Fri, 4 Oct 2024 04:22:17 +0530 Subject: [PATCH 1/4] test: intacct export settings initialization --- .../intacct-dashboard.component.spec.ts | 2 +- .../intacct-export-settings.component.spec.ts | 121 ++++- .../integrations/intacct/intacct.fixture.ts | 412 +++++++++++++++++- 3 files changed, 512 insertions(+), 23 deletions(-) diff --git a/src/app/integrations/intacct/intacct-main/intacct-dashboard/intacct-dashboard.component.spec.ts b/src/app/integrations/intacct/intacct-main/intacct-dashboard/intacct-dashboard.component.spec.ts index 17ddaf49f..485ae6cd2 100644 --- a/src/app/integrations/intacct/intacct-main/intacct-dashboard/intacct-dashboard.component.spec.ts +++ b/src/app/integrations/intacct/intacct-main/intacct-dashboard/intacct-dashboard.component.spec.ts @@ -10,7 +10,7 @@ import { SiExportSettingService } from 'src/app/core/services/si/si-configuratio import { MinimalUser } from 'src/app/core/models/db/user.model'; import { of } from 'rxjs'; import { AccountingExportSummary, AccountingExportSummaryModel } from 'src/app/core/models/db/accounting-export-summary.model'; -import { mockAccountingExportSummary, mockCompletedTasksWithFailures, mockConfiguration, mockErrors, mockExportableAccountingExportIds, mockExportSettingGet, mockExportSettings, mockTasksInProgress } from '../../intacct.fixture'; +import { mockAccountingExportSummary, mockCompletedTasksWithFailures, mockConfiguration, mockErrors, mockExportableAccountingExportIds, mockExportSettingGet, mockTasksInProgress } from '../../intacct.fixture'; import { SharedModule } from 'src/app/shared/shared.module'; import { Error } from 'src/app/core/models/db/error.model'; import { AccountingErrorType, AppName, CCCImportState, IntacctCategoryDestination, ReimbursableImportState, TaskLogState } from 'src/app/core/models/enum/enum.model'; diff --git a/src/app/integrations/intacct/intacct-shared/intacct-export-settings/intacct-export-settings.component.spec.ts b/src/app/integrations/intacct/intacct-shared/intacct-export-settings/intacct-export-settings.component.spec.ts index 6943290a6..4ff7c568d 100644 --- a/src/app/integrations/intacct/intacct-shared/intacct-export-settings/intacct-export-settings.component.spec.ts +++ b/src/app/integrations/intacct/intacct-shared/intacct-export-settings/intacct-export-settings.component.spec.ts @@ -1,23 +1,128 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - +import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; +import { FormBuilder, ReactiveFormsModule } from '@angular/forms'; +import { provideRouter, Router, RouterModule } from '@angular/router'; +import { of, throwError } from 'rxjs'; import { IntacctExportSettingsComponent } from './intacct-export-settings.component'; +import { SiExportSettingService } from 'src/app/core/services/si/si-configuration/si-export-setting.service'; +import { SiMappingsService } from 'src/app/core/services/si/si-core/si-mappings.service'; +import { SiWorkspaceService } from 'src/app/core/services/si/si-core/si-workspace.service'; +import { IntegrationsToastService } from 'src/app/core/services/common/integrations-toast.service'; +import { TrackingService } from 'src/app/core/services/integration/tracking.service'; +import { mockExportSettings, mockPaginatedDestinationAttributes } from '../../intacct.fixture'; +import { IntacctOnboardingState, Page, ToastSeverity } from 'src/app/core/models/enum/enum.model'; +import { ExportSettingOptionSearch } from 'src/app/core/models/common/export-settings.model'; +import { IntacctDestinationAttribute, PaginatedintacctDestinationAttribute } from 'src/app/core/models/intacct/db/destination-attribute.model'; +import { SharedModule } from 'src/app/shared/shared.module'; -xdescribe('IntacctExportSettingsComponent', () => { +describe('IntacctExportSettingsComponent', () => { let component: IntacctExportSettingsComponent; let fixture: ComponentFixture; + let exportSettingService: jasmine.SpyObj; + let mappingService: jasmine.SpyObj; + let workspaceService: jasmine.SpyObj; + let toastService: jasmine.SpyObj; + let trackingService: jasmine.SpyObj; + let router: Router; beforeEach(async () => { + const exportSettingServiceSpy = jasmine.createSpyObj('SiExportSettingService', ['getExportSettings', 'postExportSettings']); + const mappingServiceSpy = jasmine.createSpyObj('SiMappingsService', ['getPaginatedDestinationAttributes', 'refreshSageIntacctDimensions', 'refreshFyleDimensions']); + const workspaceServiceSpy = jasmine.createSpyObj('SiWorkspaceService', ['getIntacctOnboardingState', 'setIntacctOnboardingState']); + const toastServiceSpy = jasmine.createSpyObj('IntegrationsToastService', ['displayToastMessage']); + const trackingServiceSpy = jasmine.createSpyObj('TrackingService', ['trackTimeSpent', 'integrationsOnboardingCompletion', 'intacctUpdateEvent']); + await TestBed.configureTestingModule({ - declarations: [ IntacctExportSettingsComponent ] - }) - .compileComponents(); + declarations: [ IntacctExportSettingsComponent ], + imports: [ SharedModule, ReactiveFormsModule, RouterModule.forRoot([]) ], + providers: [ + FormBuilder, + { provide: SiExportSettingService, useValue: exportSettingServiceSpy }, + { provide: SiMappingsService, useValue: mappingServiceSpy }, + { provide: SiWorkspaceService, useValue: workspaceServiceSpy }, + { provide: IntegrationsToastService, useValue: toastServiceSpy }, + { provide: TrackingService, useValue: trackingServiceSpy }, + provideRouter([]) + ] + }).compileComponents(); + + exportSettingService = TestBed.inject(SiExportSettingService) as jasmine.SpyObj; + mappingService = TestBed.inject(SiMappingsService) as jasmine.SpyObj; + workspaceService = TestBed.inject(SiWorkspaceService) as jasmine.SpyObj; + toastService = TestBed.inject(IntegrationsToastService) as jasmine.SpyObj; + trackingService = TestBed.inject(TrackingService) as jasmine.SpyObj; + router = TestBed.inject(Router); + spyOn(router, 'navigate'); + + exportSettingService.getExportSettings.and.returnValue(of(mockExportSettings)); + mappingService.refreshSageIntacctDimensions.and.returnValue(of(null)); + mappingService.refreshFyleDimensions.and.returnValue(of(null)); + + const copy = structuredClone(mockPaginatedDestinationAttributes); + mappingService.getPaginatedDestinationAttributes.and.returnValues( + of(copy.ACCOUNT as unknown as PaginatedintacctDestinationAttribute), + of(copy.EXPENSE_PAYMENT_TYPE as unknown as PaginatedintacctDestinationAttribute), + of(copy.VENDOR as unknown as PaginatedintacctDestinationAttribute), + of(copy.CHARGE_CARD_NUMBER as unknown as PaginatedintacctDestinationAttribute) + ); fixture = TestBed.createComponent(IntacctExportSettingsComponent); component = fixture.componentInstance; - fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); -}); + + describe('Initialization', () => { + beforeEach(fakeAsync(() => { + fixture.detectChanges(); + tick(); + })); + + it('should initialize destination options correctly', () => { + + expect(component.destinationOptions.ACCOUNT).toEqual(jasmine.arrayContaining( + mockPaginatedDestinationAttributes.ACCOUNT.results + ) as unknown as IntacctDestinationAttribute[]); + + expect(component.destinationOptions.EXPENSE_PAYMENT_TYPE).toEqual(jasmine.arrayContaining( + (mockPaginatedDestinationAttributes.EXPENSE_PAYMENT_TYPE.results) + .filter((attr) => attr.detail.is_reimbursable) + ) as unknown as IntacctDestinationAttribute[]); + + expect(component.destinationOptions.CCC_EXPENSE_PAYMENT_TYPE).toEqual(jasmine.arrayContaining( + (mockPaginatedDestinationAttributes.EXPENSE_PAYMENT_TYPE.results) + .filter((attr) => !attr.detail.is_reimbursable) + ) as unknown as IntacctDestinationAttribute[]); + + expect(component.destinationOptions.VENDOR).toEqual(jasmine.arrayContaining( + mockPaginatedDestinationAttributes.VENDOR.results + ) as unknown as IntacctDestinationAttribute[]); + + expect(component.destinationOptions.CHARGE_CARD).toEqual(jasmine.arrayContaining( + mockPaginatedDestinationAttributes.CHARGE_CARD_NUMBER.results as unknown as IntacctDestinationAttribute[] + ) as unknown as IntacctDestinationAttribute[]); + }); + + it('should add missing destination options', () => { + + expect(component.destinationOptions.ACCOUNT).toContain({ + destination_id: mockExportSettings.general_mappings.default_gl_account.id, + value: mockExportSettings.general_mappings.default_gl_account.name + } as unknown as IntacctDestinationAttribute); + + expect(component.destinationOptions.ACCOUNT).toContain({ + destination_id: mockExportSettings.general_mappings.default_credit_card.id, + value: mockExportSettings.general_mappings.default_credit_card.name + } as unknown as IntacctDestinationAttribute); + + }); + + it('should fetch and store export settings', () => { + expect(exportSettingService.getExportSettings).toHaveBeenCalled(); + expect(component.exportSettings).toEqual(mockExportSettings); + expect(component.exportSettingsForm).toBeDefined(); + expect(component.isLoading).toBeFalse(); + }); + }); +}); \ No newline at end of file diff --git a/src/app/integrations/intacct/intacct.fixture.ts b/src/app/integrations/intacct/intacct.fixture.ts index 165c738e9..23707afda 100644 --- a/src/app/integrations/intacct/intacct.fixture.ts +++ b/src/app/integrations/intacct/intacct.fixture.ts @@ -2,10 +2,12 @@ import { minimalUser } from "src/app/core/interceptor/jwt.fixture"; import { AccountingExportSummary } from "src/app/core/models/db/accounting-export-summary.model"; import { Error } from "src/app/core/models/db/error.model"; import { MinimalUser } from "src/app/core/models/db/user.model"; -import { AccountingErrorType, CCCExpenseState, ExpenseState, ExportDateType, FyleField, IntacctCorporateCreditCardExpensesObject, IntacctOnboardingState, IntacctReimbursableExpensesObject, SplitExpenseGrouping, TaskLogState, TaskLogType } from "src/app/core/models/enum/enum.model"; +import { AccountingErrorType, CCCExpenseState, ExpenseGroupingFieldOption, ExpenseState, ExportDateType, FyleField, IntacctCorporateCreditCardExpensesObject, IntacctOnboardingState, IntacctReimbursableExpensesObject, SplitExpenseGrouping, TaskLogState, TaskLogType } from "src/app/core/models/enum/enum.model"; import { IntacctWorkspace } from "src/app/core/models/intacct/db/workspaces.model"; import { ExportSettingGet } from "src/app/core/models/intacct/intacct-configuration/export-settings.model"; - +import { ExpenseGroup, ExpenseGroupDescription, ExpenseGroupResponse } from 'src/app/core/models/db/expense-group.model'; +import { Paginator } from 'src/app/core/models/misc/paginator.model'; +import { SkipExportLogResponse } from "src/app/core/models/intacct/db/expense-group.model"; export const workspaceResponse: IntacctWorkspace[] = [{ "id": 1, @@ -297,18 +299,6 @@ export const mockErrors = [ { id: 3, type: AccountingErrorType.ACCOUNTING_ERROR, error_title: 'Accounting error' } ] as Error[]; -export const mockExportSettings = { - configurations: { - reimbursable_expenses_object: IntacctReimbursableExpensesObject.EXPENSE_REPORT, - corporate_credit_card_expenses_object: IntacctCorporateCreditCardExpensesObject.BILL - }, - expense_group_settings: { - expense_state: ExpenseState.PAYMENT_PROCESSING, - ccc_expense_state: ExpenseState.PAID - } -} as unknown as ExportSettingGet; - - export const mockMappingSettingsResponse = { results: [ { source_field: FyleField.EMPLOYEE, destination_field: 'EMPLOYEE' }, @@ -351,3 +341,397 @@ export const mockDestinationAttributesResponse = { } ] }; + +export const mockExpenseGroupResponse: ExpenseGroupResponse = { + count: 2, + next: null, + previous: null, + results: [ + { + id: 7715, + expenses: [ + { + updated_at: new Date('2024-08-12T12:17:27.837958Z'), + claim_number: 'C/2024/08/R/8', + employee_email: 'ashwin.t@fyle.in', + employee_name: 'Ashwin', + fund_source: 'CCC', + expense_number: 'E/2024/08/T/8', + payment_number: 'P/2024/08/T/P/2024/08/R/7', + category: 'ABN Withholding', + amount: -460.0, + expense_id: 'txPpUSwko5es' + } + ], + description: { + spent_at: new Date("2024-08-12T00:00:00"), + expense_id: "txPpUSwko5es", + fund_source: "CCC", + employee_email: "ashwin.t@fyle.in" + } as unknown as ExpenseGroupDescription, + fund_source: 'CCC', + export_type: 'CHARGE_CARD_TRANSACTION', + exported_at: new Date('2024-08-27T16:51:07.206898Z'), + employee_name: 'Ashwin', + export_url: 'https://example.com/export/7715' + }, + { + id: 7714, + expenses: [ + { + updated_at: new Date('2024-08-12T12:17:27.698968Z'), + claim_number: 'C/2024/08/R/9', + employee_email: 'ashwin.t@fyle.in', + employee_name: 'Ashwin', + fund_source: 'CCC', + expense_number: 'E/2024/08/T/9', + payment_number: 'P/2024/08/T/P/2024/08/R/8', + category: 'ABN Withholding', + amount: -550.0, + expense_id: 'txoerMCRLkJ4' + } + ], + description: { + spent_at: new Date("2024-08-12T00:00:00"), + expense_id: "txPpUSwko5es", + fund_source: "CCC", + employee_email: "ashwin.t@fyle.in" + } as unknown as ExpenseGroupDescription, + fund_source: 'CCC', + export_type: 'CHARGE_CARD_TRANSACTION', + exported_at: new Date('2024-08-27T16:50:57.620969Z'), + employee_name: 'Ashwin', + export_url: 'https://example.com/export/7714' + } + ] as ExpenseGroup[] +}; + +export const mockSkipExportLogResponse = { + count: 2, + next: null, + previous: null, + results: [ + { + id: 1, + expenses: [ + { + updated_at: '2024-08-12T12:17:27.837958Z', + claim_number: 'C/2024/08/R/8', + employee_email: 'ashwin.t@fyle.in', + employee_name: 'Ashwin', + fund_source: 'CCC', + expense_number: 'E/2024/08/T/8', + category: 'ABN Withholding', + amount: -460.0, + expense_id: 'txPpUSwko5es' + } + ], + fund_source: 'CCC', + created_at: '2024-08-12T12:17:27.916749Z', + updated_at: '2024-08-27T16:51:07.218092Z', + workspace: 240 + }, + { + id: 2, + expenses: [ + { + updated_at: '2024-08-12T12:17:27.698968Z', + claim_number: 'C/2024/08/R/9', + employee_email: 'ashwin.t@fyle.in', + employee_name: 'Ashwin', + fund_source: 'CCC', + expense_number: 'E/2024/08/T/9', + category: 'ABN Withholding', + amount: -550.0, + expense_id: 'txoerMCRLkJ4' + } + ], + fund_source: 'CCC', + created_at: '2024-08-12T12:17:27.903912Z', + updated_at: '2024-08-27T16:50:57.633246Z', + workspace: 240 + } + ] +} as unknown as SkipExportLogResponse; + +export const mockPaginator: Paginator = { + limit: 50, + offset: 0 +}; + + +export const mockExportSettings = { + configurations: { + reimbursable_expenses_object: IntacctReimbursableExpensesObject.EXPENSE_REPORT, + corporate_credit_card_expenses_object: IntacctCorporateCreditCardExpensesObject.CHARGE_CARD_TRANSACTION, + employee_field_mapping: FyleField.EMPLOYEE, + auto_map_employees: 'EMAIL', + use_merchant_in_journal_line: true + }, + expense_group_settings: { + reimbursable_expense_group_fields: [ExpenseGroupingFieldOption.EXPENSE_ID], + reimbursable_export_date_type: ExportDateType.CURRENT_DATE, + expense_state: ExpenseState.PAYMENT_PROCESSING, + corporate_credit_card_expense_group_fields: [ExpenseGroupingFieldOption.EXPENSE_ID], + ccc_export_date_type: ExportDateType.SPENT_AT, + ccc_expense_state: CCCExpenseState.PAID, + split_expense_grouping: null + }, + general_mappings: { + default_gl_account: { id: '1', name: 'Account 1' }, + default_charge_card: { id: '2', name: 'Card 1' }, + default_reimbursable_expense_payment_type: { id: '3', name: 'Type 1' }, + default_ccc_expense_payment_type: { id: '4', name: 'Type 2' }, + default_ccc_vendor: { id: '5', name: 'Vendor 1' }, + default_credit_card: { id: '6', name: 'Credit Card 1' } + }, + workspace_id: 1 +} as unknown as ExportSettingGet; + + +export const mockPaginatedDestinationAttributes = { + ACCOUNT: { + "count": 4, + "next": "http://intacct-api.staging-integrations:8000/api/workspaces/366/sage_intacct/paginated_destination_attributes/?attribute_type=ACCOUNT&limit=100&offset=100", + "previous": null, + "results": [ + { + "id": 250084, + "attribute_type": "ACCOUNT", + "display_name": "account", + "value": "Accounts Payable", + "destination_id": "2000", + "auto_created": false, + "active": true, + "detail": { + "account_type": "balancesheet" + }, + "code": "2000", + "created_at": "2024-08-26T11:54:56.743019Z", + "updated_at": "2024-09-23T08:32:57.719332Z", + "workspace": 366 + }, + { + "id": 250077, + "attribute_type": "ACCOUNT", + "display_name": "account", + "value": "Accounts Receivable - trade", + "destination_id": "1100", + "auto_created": false, + "active": true, + "detail": { + "account_type": "balancesheet" + }, + "code": "1100", + "created_at": "2024-08-26T11:54:56.737413Z", + "updated_at": "2024-09-23T08:32:57.719266Z", + "workspace": 366 + }, + { + "id": 250078, + "attribute_type": "ACCOUNT", + "display_name": "account", + "value": "Accounts Receivable - unbilled", + "destination_id": "1110", + "auto_created": false, + "active": true, + "detail": { + "account_type": "balancesheet" + }, + "code": "1110", + "created_at": "2024-08-26T11:54:56.742752Z", + "updated_at": "2024-09-23T08:32:57.719286Z", + "workspace": 366 + }, + { + "id": 249997, + "attribute_type": "ACCOUNT", + "display_name": "account", + "value": "Accrued Bonus", + "destination_id": "2022", + "auto_created": false, + "active": true, + "detail": { + "account_type": "balancesheet" + }, + "code": "2022", + "created_at": "2024-08-26T11:54:56.719668Z", + "updated_at": "2024-09-23T08:32:57.718161Z", + "workspace": 366 + } + ] + }, + EXPENSE_PAYMENT_TYPE: { + "count": 2, + "next": null, + "previous": null, + "results": [ + { + "id": 249873, + "attribute_type": "EXPENSE_PAYMENT_TYPE", + "display_name": "expense payment type", + "value": "Elon Baba CCC", + "destination_id": "2", + "auto_created": false, + "active": true, + "detail": { + "is_reimbursable": false + }, + "code": null, + "created_at": "2024-08-26T11:54:41.504318Z", + "updated_at": "2024-08-26T11:54:41.504329Z", + "workspace": 366 + }, + { + "id": 249872, + "attribute_type": "EXPENSE_PAYMENT_TYPE", + "display_name": "expense payment type", + "value": "Elon musk", + "destination_id": "1", + "auto_created": false, + "active": true, + "detail": { + "is_reimbursable": true + }, + "code": null, + "created_at": "2024-08-26T11:54:41.504228Z", + "updated_at": "2024-08-26T11:54:41.504262Z", + "workspace": 366 + } + ] + }, + VENDOR: { + "count": 4, + "next": null, + "previous": null, + "results": [ + { + "id": 249883, + "attribute_type": "VENDOR", + "display_name": "vendor", + "value": "A-1 Electric Company", + "destination_id": "V100", + "auto_created": false, + "active": true, + "detail": { + "email": null + }, + "code": null, + "created_at": "2024-08-26T11:54:51.315724Z", + "updated_at": "2024-08-26T11:54:51.315731Z", + "workspace": 366 + }, + { + "id": 249886, + "attribute_type": "VENDOR", + "display_name": "vendor", + "value": "AAA Insurance and Bonding", + "destination_id": "V104", + "auto_created": false, + "active": true, + "detail": { + "email": "andree@abuckley.COM" + }, + "code": null, + "created_at": "2024-08-26T11:54:51.315883Z", + "updated_at": "2024-08-26T11:54:51.315893Z", + "workspace": 366 + }, + { + "id": 249943, + "attribute_type": "VENDOR", + "display_name": "vendor", + "value": "ABC Electric", + "destination_id": "V164", + "auto_created": false, + "active": true, + "detail": { + "email": null + }, + "code": null, + "created_at": "2024-08-26T11:54:51.338322Z", + "updated_at": "2024-08-26T11:54:51.338330Z", + "workspace": 366 + }, + { + "id": 249885, + "attribute_type": "VENDOR", + "display_name": "vendor", + "value": "Ace Drywall", + "destination_id": "V103", + "auto_created": false, + "active": true, + "detail": { + "email": null + }, + "code": null, + "created_at": "2024-08-26T11:54:51.315818Z", + "updated_at": "2024-08-26T11:54:51.315827Z", + "workspace": 366 + } + ] + }, + CHARGE_CARD_NUMBER: { + "count": 4, + "next": null, + "previous": null, + "results": [ + { + "id": 249875, + "attribute_type": "CHARGE_CARD_NUMBER", + "display_name": "Charge Card Account", + "value": "1234", + "destination_id": "1234", + "auto_created": false, + "active": true, + "detail": null, + "code": null, + "created_at": "2024-08-26T11:54:46.642086Z", + "updated_at": "2024-08-26T11:54:46.642093Z", + "workspace": 366 + }, + { + "id": 249876, + "attribute_type": "CHARGE_CARD_NUMBER", + "display_name": "Charge Card Account", + "value": "Mastercard - 6789", + "destination_id": "Mastercard - 6789", + "auto_created": false, + "active": true, + "detail": null, + "code": null, + "created_at": "2024-08-26T11:54:46.642117Z", + "updated_at": "2024-08-26T11:54:46.642125Z", + "workspace": 366 + }, + { + "id": 249874, + "attribute_type": "CHARGE_CARD_NUMBER", + "display_name": "Charge Card Account", + "value": "Nilesh Credit Card", + "destination_id": "Nilesh Credit Card", + "auto_created": false, + "active": true, + "detail": null, + "code": null, + "created_at": "2024-08-26T11:54:46.642035Z", + "updated_at": "2024-08-26T11:54:46.642058Z", + "workspace": 366 + }, + { + "id": 249877, + "attribute_type": "CHARGE_CARD_NUMBER", + "display_name": "Charge Card Account", + "value": "Visa - 1234", + "destination_id": "Visa - 1234", + "auto_created": false, + "active": true, + "detail": null, + "code": null, + "created_at": "2024-08-26T11:54:46.642147Z", + "updated_at": "2024-08-26T11:54:46.642154Z", + "workspace": 366 + } + ] + } +}; \ No newline at end of file From bbda337537b912a7c219f55a3d4f34e59fc2a20a Mon Sep 17 00:00:00 2001 From: Viswas Date: Fri, 4 Oct 2024 04:24:22 +0530 Subject: [PATCH 2/4] test: intacct export settings save functionality + misc tests --- .../intacct-export-settings.component.spec.ts | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/src/app/integrations/intacct/intacct-shared/intacct-export-settings/intacct-export-settings.component.spec.ts b/src/app/integrations/intacct/intacct-shared/intacct-export-settings/intacct-export-settings.component.spec.ts index 4ff7c568d..238f5266a 100644 --- a/src/app/integrations/intacct/intacct-shared/intacct-export-settings/intacct-export-settings.component.spec.ts +++ b/src/app/integrations/intacct/intacct-shared/intacct-export-settings/intacct-export-settings.component.spec.ts @@ -125,4 +125,57 @@ describe('IntacctExportSettingsComponent', () => { expect(component.isLoading).toBeFalse(); }); }); + + describe('Form Save', () => { + it('should save export settings successfully during onboarding', fakeAsync(() => { + workspaceService.getIntacctOnboardingState.and.returnValue(IntacctOnboardingState.EXPORT_SETTINGS); + exportSettingService.postExportSettings.and.returnValue(of(mockExportSettings)); + spyOnProperty(router, 'url').and.returnValue('/integrations/intacct/onboarding/export_settings'); + + fixture.detectChanges(); + component.save(); + tick(); + + expect(exportSettingService.postExportSettings).toHaveBeenCalled(); + expect(toastService.displayToastMessage).toHaveBeenCalledWith(ToastSeverity.SUCCESS, 'Export settings saved successfully'); + expect(trackingService.integrationsOnboardingCompletion).toHaveBeenCalled(); + expect(workspaceService.setIntacctOnboardingState).toHaveBeenCalledWith(IntacctOnboardingState.IMPORT_SETTINGS); + expect(router.navigate).toHaveBeenCalledWith(['/integrations/intacct/onboarding/import_settings']); + })); + + it('should save export settings successfully post onboarding', () => { + workspaceService.getIntacctOnboardingState.and.returnValue(IntacctOnboardingState.COMPLETE); + exportSettingService.postExportSettings.and.returnValue(of(mockExportSettings)); + + fixture.detectChanges(); + component.save(); + + expect(exportSettingService.postExportSettings).toHaveBeenCalled(); + expect(toastService.displayToastMessage).toHaveBeenCalledWith(ToastSeverity.SUCCESS, 'Export settings saved successfully'); + expect(trackingService.intacctUpdateEvent).toHaveBeenCalled(); + expect(router.navigate).not.toHaveBeenCalled(); + }); + + it('should handle save failure', () => { + exportSettingService.postExportSettings.and.returnValue(throwError(() => new Error('API Error'))); + + fixture.detectChanges(); + component.save(); + + expect(toastService.displayToastMessage).toHaveBeenCalledWith(ToastSeverity.ERROR, 'Error saving export settings, please try again later'); + expect(component.saveInProgress).toBeFalse(); + }); + }); + + it('should handle refresh dimensions', () => { + component.refreshDimensions(true); + expect(mappingService.refreshSageIntacctDimensions).toHaveBeenCalled(); + expect(mappingService.refreshFyleDimensions).toHaveBeenCalled(); + expect(toastService.displayToastMessage).toHaveBeenCalledWith(ToastSeverity.SUCCESS, 'Syncing data dimensions from Sage Intacct'); + }); + + it('should navigate to previous step', () => { + component.navigateToPreviousStep(); + expect(router.navigate).toHaveBeenCalledWith(['/integrations/intacct/onboarding/connector']); + }); }); \ No newline at end of file From 93810e133d8fe25147e25bd72f3d28f99525823d Mon Sep 17 00:00:00 2001 From: Viswas Date: Sat, 5 Oct 2024 00:39:27 +0530 Subject: [PATCH 3/4] test: intacct export settings watchers --- .../intacct-export-settings.component.spec.ts | 237 +++++++++++++++++- 1 file changed, 230 insertions(+), 7 deletions(-) diff --git a/src/app/integrations/intacct/intacct-shared/intacct-export-settings/intacct-export-settings.component.spec.ts b/src/app/integrations/intacct/intacct-shared/intacct-export-settings/intacct-export-settings.component.spec.ts index 238f5266a..952263e20 100644 --- a/src/app/integrations/intacct/intacct-shared/intacct-export-settings/intacct-export-settings.component.spec.ts +++ b/src/app/integrations/intacct/intacct-shared/intacct-export-settings/intacct-export-settings.component.spec.ts @@ -1,5 +1,5 @@ import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; -import { FormBuilder, ReactiveFormsModule } from '@angular/forms'; +import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms'; import { provideRouter, Router, RouterModule } from '@angular/router'; import { of, throwError } from 'rxjs'; import { IntacctExportSettingsComponent } from './intacct-export-settings.component'; @@ -9,10 +9,13 @@ import { SiWorkspaceService } from 'src/app/core/services/si/si-core/si-workspac import { IntegrationsToastService } from 'src/app/core/services/common/integrations-toast.service'; import { TrackingService } from 'src/app/core/services/integration/tracking.service'; import { mockExportSettings, mockPaginatedDestinationAttributes } from '../../intacct.fixture'; -import { IntacctOnboardingState, Page, ToastSeverity } from 'src/app/core/models/enum/enum.model'; -import { ExportSettingOptionSearch } from 'src/app/core/models/common/export-settings.model'; +import { EmployeeFieldMapping, ExpenseGroupingFieldOption, ExportDateType, FyleField, IntacctCorporateCreditCardExpensesObject, IntacctOnboardingState, IntacctReimbursableExpensesObject, Page, ToastSeverity } from 'src/app/core/models/enum/enum.model'; +import { ExportSettingOptionSearch, ExportSettingModel } from 'src/app/core/models/common/export-settings.model'; import { IntacctDestinationAttribute, PaginatedintacctDestinationAttribute } from 'src/app/core/models/intacct/db/destination-attribute.model'; import { SharedModule } from 'src/app/shared/shared.module'; +import { brandingConfig } from 'src/app/branding/branding-config'; +import { BrandingConfiguration } from 'src/app/core/models/branding/branding-configuration.model'; +import { ExportSettingModel as IntacctExportSettingModel } from 'src/app/core/models/intacct/intacct-configuration/export-settings.model'; describe('IntacctExportSettingsComponent', () => { let component: IntacctExportSettingsComponent; @@ -32,8 +35,8 @@ describe('IntacctExportSettingsComponent', () => { const trackingServiceSpy = jasmine.createSpyObj('TrackingService', ['trackTimeSpent', 'integrationsOnboardingCompletion', 'intacctUpdateEvent']); await TestBed.configureTestingModule({ - declarations: [ IntacctExportSettingsComponent ], - imports: [ SharedModule, ReactiveFormsModule, RouterModule.forRoot([]) ], + declarations: [IntacctExportSettingsComponent], + imports: [SharedModule, ReactiveFormsModule, RouterModule.forRoot([])], providers: [ FormBuilder, { provide: SiExportSettingService, useValue: exportSettingServiceSpy }, @@ -57,7 +60,7 @@ describe('IntacctExportSettingsComponent', () => { mappingService.refreshSageIntacctDimensions.and.returnValue(of(null)); mappingService.refreshFyleDimensions.and.returnValue(of(null)); - const copy = structuredClone(mockPaginatedDestinationAttributes); + const copy = structuredClone(mockPaginatedDestinationAttributes); mappingService.getPaginatedDestinationAttributes.and.returnValues( of(copy.ACCOUNT as unknown as PaginatedintacctDestinationAttribute), of(copy.EXPENSE_PAYMENT_TYPE as unknown as PaginatedintacctDestinationAttribute), @@ -105,7 +108,6 @@ describe('IntacctExportSettingsComponent', () => { }); it('should add missing destination options', () => { - expect(component.destinationOptions.ACCOUNT).toContain({ destination_id: mockExportSettings.general_mappings.default_gl_account.id, value: mockExportSettings.general_mappings.default_gl_account.name @@ -178,4 +180,225 @@ describe('IntacctExportSettingsComponent', () => { component.navigateToPreviousStep(); expect(router.navigate).toHaveBeenCalledWith(['/integrations/intacct/onboarding/connector']); }); + + describe('Watchers', () => { + beforeEach(() => { + fixture.detectChanges(); + }); + + describe('Reimbursable Expense Toggle Watcher', () => { + it('should enable fields on enabling reimbursable expenses', fakeAsync(() => { + component.exportSettingsForm.get('reimbursableExpense')?.setValue(true); + tick(); + + expect(component.exportSettingsForm.get('reimbursableExportType')?.hasValidator(Validators.required)).toBeTrue(); + expect(component.exportSettingsForm.get('reimbursableExportGroup')?.hasValidator(Validators.required)).toBeTrue(); + expect(component.exportSettingsForm.get('reimbursableExportDate')?.hasValidator(Validators.required)).toBeTrue(); + })); + + it('should disable fields on disabling reimbursable expenses', fakeAsync(() => { + component.exportSettingsForm.get('reimbursableExpense')?.setValue(false); + tick(); + + expect(component.exportSettingsForm.get('reimbursableExportType')?.hasValidator(Validators.required)).toBeFalse(); + expect(component.exportSettingsForm.get('reimbursableExportGroup')?.hasValidator(Validators.required)).toBeFalse(); + expect(component.exportSettingsForm.get('reimbursableExportDate')?.hasValidator(Validators.required)).toBeFalse(); + expect(component.exportSettingsForm.get('reimbursableExportType')?.value).toBeNull(); + })); + }); + + describe('Reimbursable Export Type Watchers', () => { + + it('should handle reimbursableExportType being changed to Journal Entry', fakeAsync(() => { + component.exportSettingsForm.get('reimbursableExportType')?.setValue(IntacctReimbursableExpensesObject.JOURNAL_ENTRY); + tick(); + + expect(component.exportSettingsForm.get('glAccount')?.hasValidator(Validators.required)).toBeTrue(); + expect(component.exportSettingsForm.get('employeeFieldMapping')?.enabled).toBeTrue(); + })); + + it('should handle reimbursableExportType being changed to Expense Report', fakeAsync(() => { + component.exportSettingsForm.get('reimbursableExportType')?.setValue(IntacctReimbursableExpensesObject.EXPENSE_REPORT); + tick(); + + expect(component.exportSettingsForm.get('employeeFieldMapping')?.value).toBe(FyleField.EMPLOYEE); + expect(component.exportSettingsForm.get('employeeFieldMapping')?.disabled).toBeTrue(); + })); + + it('should handle reimbursableExportType being changed to Bill', fakeAsync(() => { + component.exportSettingsForm.get('reimbursableExportType')?.setValue(IntacctReimbursableExpensesObject.BILL); + tick(); + + expect(component.exportSettingsForm.get('employeeFieldMapping')?.value).toBe(FyleField.VENDOR); + expect(component.exportSettingsForm.get('employeeFieldMapping')?.disabled).toBeTrue(); + })); + }); + + describe('Credit Card Expense Toggle Watcher', () => { + it('should enable fields on enabling CCC expenses', fakeAsync(() => { + component.exportSettingsForm.get('creditCardExpense')?.setValue(true); + tick(); + + expect(component.exportSettingsForm.get('cccExportType')?.hasValidator(Validators.required)).toBeTrue(); + expect(component.exportSettingsForm.get('cccExportGroup')?.hasValidator(Validators.required)).toBeTrue(); + expect(component.exportSettingsForm.get('cccExportDate')?.hasValidator(Validators.required)).toBeTrue(); + })); + + it('should disable fields on disabling CCC expenses', fakeAsync(() => { + component.exportSettingsForm.get('creditCardExpense')?.setValue(false); + tick(); + + expect(component.exportSettingsForm.get('cccExportType')?.hasValidator(Validators.required)).toBeFalse(); + expect(component.exportSettingsForm.get('cccExportGroup')?.hasValidator(Validators.required)).toBeFalse(); + expect(component.exportSettingsForm.get('cccExportDate')?.hasValidator(Validators.required)).toBeFalse(); + expect(component.exportSettingsForm.get('cccExportType')?.value).toBeNull(); + })); + }); + + describe('CCC Export Type Watchers', () => { + it('should handle cccExportType being changed to Charge Card Transaction', fakeAsync(() => { + component.exportSettingsForm.get('cccExportType')?.setValue(IntacctCorporateCreditCardExpensesObject.CHARGE_CARD_TRANSACTION); + tick(); + + expect(component.exportSettingsForm.get('chargeCard')?.hasValidator(Validators.required)).toBeTrue(); + expect(component.exportSettingsForm.get('cccExportGroup')?.disabled).toBeTrue(); + expect(component.exportSettingsForm.get('cccExportGroup')?.value).toBe(ExpenseGroupingFieldOption.EXPENSE_ID); + })); + + it('should handle cccExportType being changed to Bill', fakeAsync(() => { + component.exportSettingsForm.get('cccExportType')?.setValue(IntacctCorporateCreditCardExpensesObject.BILL); + tick(); + + expect(component.exportSettingsForm.get('creditCardVendor')?.hasValidator(Validators.required)).toBeTrue(); + })); + + it('should handle cccExportType being changed to Expense Report', fakeAsync(() => { + component.exportSettingsForm.get('cccExportType')?.setValue(IntacctCorporateCreditCardExpensesObject.EXPENSE_REPORT); + tick(); + + expect(component.exportSettingsForm.get('employeeFieldMapping')?.value).toBe(EmployeeFieldMapping.EMPLOYEE); + expect(component.exportSettingsForm.get('cccExpensePaymentType')?.hasValidator(Validators.required)).toBeTrue(); + })); + }); + + describe('Custom Watchers', () => { + beforeEach(() => { + brandingConfig.brandId = 'fyle'; + }); + + it('should update reimbursable expense grouping date options when group changes', fakeAsync(() => { + fixture.detectChanges(); + component.exportSettingsForm.get('reimbursableExportGroup')?.setValue(ExpenseGroupingFieldOption.CLAIM_NUMBER); + tick(); + + expect(component.reimbursableExpenseGroupingDateOptions).not.toContain({ + label: 'Spend date', + value: ExportDateType.SPENT_AT + }); + })); + + it('should update CCC expense grouping date options when group changes', fakeAsync(() => { + spyOn(component, 'setCCExpenseDateOptions').and.callThrough(); + spyOn(IntacctExportSettingModel, 'getExpenseGroupingDateOptions').and.callThrough(); + spyOn(ExportSettingModel, 'constructGroupingDateOptions').and.callThrough(); + + component.exportSettingsForm.get('cccExportType')?.setValue(IntacctCorporateCreditCardExpensesObject.CHARGE_CARD_TRANSACTION); + component.exportSettingsForm.get('cccExportGroup')?.setValue(ExpenseGroupingFieldOption.CLAIM_NUMBER); + + tick(); + + expect(IntacctExportSettingModel.getExpenseGroupingDateOptions).toHaveBeenCalledWith(); + expect(ExportSettingModel.constructGroupingDateOptions).toHaveBeenCalledWith( + ExpenseGroupingFieldOption.CLAIM_NUMBER, + IntacctExportSettingModel.getExpenseGroupingDateOptions() + ); + expect(component['setCCExpenseDateOptions']).toHaveBeenCalled(); + })); + }); + + describe('Export Selection Validator', () => { + beforeEach(() => { + fixture.detectChanges(); + }); + + it('should invalidate form when neither reimbursable nor credit card expense is selected', () => { + component.exportSettingsForm.get('reimbursableExpense')?.setValue(false); + component.exportSettingsForm.get('creditCardExpense')?.setValue(false); + + expect(component.exportSettingsForm.valid).toBeFalse(); + }); + + it('should validate the form when at least one export type is selected', () => { + component.exportSettingsForm.get('reimbursableExpense')?.setValue(true); + component.exportSettingsForm.get('creditCardExpense')?.setValue(false); + + expect(component.exportSettingsForm.valid).toBeTrue(); + }); + }); + + describe('Destination Options Handling', () => { + beforeEach(() => { + fixture.detectChanges(); + }); + + it('should handle option search for reimbursable expense payment type', fakeAsync(() => { + const searchEvent = { + searchTerm: 'test', + destinationOptionKey: 'EXPENSE_PAYMENT_TYPE' + } as ExportSettingOptionSearch; + + mappingService.getPaginatedDestinationAttributes.and.returnValue( + of(mockPaginatedDestinationAttributes.EXPENSE_PAYMENT_TYPE as unknown as PaginatedintacctDestinationAttribute) + ); + + component.searchOptionsDropdown(searchEvent); + tick(1000); + + expect(mappingService.getPaginatedDestinationAttributes).toHaveBeenCalledWith('EXPENSE_PAYMENT_TYPE', 'test'); + expect(component.destinationOptions.EXPENSE_PAYMENT_TYPE.every(option => (option.detail ? option.detail.is_reimbursable : true))).toBeTrue(); + expect(component.isOptionSearchInProgress).toBeFalse(); + })); + + it('should handle option search for CCC expense payment type', fakeAsync(() => { + const searchEvent = { + searchTerm: 'test', + destinationOptionKey: 'CCC_EXPENSE_PAYMENT_TYPE' + }; + + mappingService.getPaginatedDestinationAttributes.and.returnValue( + of(mockPaginatedDestinationAttributes.EXPENSE_PAYMENT_TYPE as unknown as PaginatedintacctDestinationAttribute) + ); + + component.searchOptionsDropdown(searchEvent as ExportSettingOptionSearch); + tick(1000); + + expect(mappingService.getPaginatedDestinationAttributes).toHaveBeenCalledWith('EXPENSE_PAYMENT_TYPE', 'test'); + expect(component.destinationOptions.CCC_EXPENSE_PAYMENT_TYPE.every(option => ( + option.detail ? !option.detail.is_reimbursable : true + ))).toBeTrue(); + expect(component.isOptionSearchInProgress).toBeFalse(); + })); + }); + + + }); + + + describe('C1 Specific Behavior', () => { + it('should handle setup with c1 branding', () => { + brandingConfig.brandId = 'co'; + + fixture = TestBed.createComponent(IntacctExportSettingsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + + expect(component.exportSettingsForm.get('creditCardExpense')?.value).toBeTrue(); + expect(component.exportSettingsForm.get('employeeFieldMapping')?.value).toBe(FyleField.VENDOR); + expect(component.isMultiLineOption).toBeFalse(); + }); + + afterAll(() => { + brandingConfig.brandId = 'fyle'; + }); + }); }); \ No newline at end of file From e0e35eb4ef35e211c2ce70424f8f8d946d800df5 Mon Sep 17 00:00:00 2001 From: Viswas Date: Mon, 7 Oct 2024 21:50:14 +0530 Subject: [PATCH 4/4] refactor: linting --- .../intacct-export-settings.component.spec.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/app/integrations/intacct/intacct-shared/intacct-export-settings/intacct-export-settings.component.spec.ts b/src/app/integrations/intacct/intacct-shared/intacct-export-settings/intacct-export-settings.component.spec.ts index 4fbf6cd74..ffa6f5e4f 100644 --- a/src/app/integrations/intacct/intacct-shared/intacct-export-settings/intacct-export-settings.component.spec.ts +++ b/src/app/integrations/intacct/intacct-shared/intacct-export-settings/intacct-export-settings.component.spec.ts @@ -1,3 +1,4 @@ +/* eslint-disable dot-notation */ import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms'; import { provideRouter, Router, RouterModule } from '@angular/router'; @@ -355,8 +356,12 @@ describe('IntacctExportSettingsComponent', () => { component.searchOptionsDropdown(searchEvent); tick(1000); + const isReimbursable = (option: IntacctDestinationAttribute) => ( + option.detail ? option.detail.is_reimbursable : true + ); + expect(mappingService.getPaginatedDestinationAttributes).toHaveBeenCalledWith('EXPENSE_PAYMENT_TYPE', 'test'); - expect(component.destinationOptions.EXPENSE_PAYMENT_TYPE.every(option => (option.detail ? option.detail.is_reimbursable : true))).toBeTrue(); + expect(component.destinationOptions.EXPENSE_PAYMENT_TYPE.every(isReimbursable)).toBeTrue(); expect(component.isOptionSearchInProgress).toBeFalse(); }));