Skip to content

Commit cd49a8d

Browse files
test: intacct export settings watchers (#1002)
* test: intacct export settings initialization * test: intacct export settings save functionality + misc tests * test: intacct export settings watchers * refactor: linting
1 parent 6d0540c commit cd49a8d

File tree

1 file changed

+233
-4
lines changed

1 file changed

+233
-4
lines changed

src/app/integrations/intacct/intacct-shared/intacct-export-settings/intacct-export-settings.component.spec.ts

Lines changed: 233 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
/* eslint-disable dot-notation */
12
import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
2-
import { FormBuilder, ReactiveFormsModule } from '@angular/forms';
3+
import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
34
import { provideRouter, Router, RouterModule } from '@angular/router';
45
import { of, throwError } from 'rxjs';
56
import { IntacctExportSettingsComponent } from './intacct-export-settings.component';
@@ -9,10 +10,13 @@ import { SiWorkspaceService } from 'src/app/core/services/si/si-core/si-workspac
910
import { IntegrationsToastService } from 'src/app/core/services/common/integrations-toast.service';
1011
import { TrackingService } from 'src/app/core/services/integration/tracking.service';
1112
import { mockExportSettings, mockPaginatedDestinationAttributes } from '../../intacct.fixture';
12-
import { IntacctOnboardingState, Page, ToastSeverity } from 'src/app/core/models/enum/enum.model';
13-
import { ExportSettingOptionSearch } from 'src/app/core/models/common/export-settings.model';
13+
import { EmployeeFieldMapping, ExpenseGroupingFieldOption, ExportDateType, FyleField, IntacctCorporateCreditCardExpensesObject, IntacctOnboardingState, IntacctReimbursableExpensesObject, Page, ToastSeverity } from 'src/app/core/models/enum/enum.model';
14+
import { ExportSettingOptionSearch, ExportSettingModel } from 'src/app/core/models/common/export-settings.model';
1415
import { IntacctDestinationAttribute, PaginatedintacctDestinationAttribute } from 'src/app/core/models/intacct/db/destination-attribute.model';
1516
import { SharedModule } from 'src/app/shared/shared.module';
17+
import { brandingConfig } from 'src/app/branding/branding-config';
18+
import { BrandingConfiguration } from 'src/app/core/models/branding/branding-configuration.model';
19+
import { ExportSettingModel as IntacctExportSettingModel } from 'src/app/core/models/intacct/intacct-configuration/export-settings.model';
1620

1721
describe('IntacctExportSettingsComponent', () => {
1822
let component: IntacctExportSettingsComponent;
@@ -57,7 +61,7 @@ describe('IntacctExportSettingsComponent', () => {
5761
mappingService.refreshSageIntacctDimensions.and.returnValue(of(null));
5862
mappingService.refreshFyleDimensions.and.returnValue(of(null));
5963

60-
const copy = structuredClone(mockPaginatedDestinationAttributes);
64+
const copy = structuredClone(mockPaginatedDestinationAttributes);
6165
mappingService.getPaginatedDestinationAttributes.and.returnValues(
6266
of(copy.ACCOUNT as unknown as PaginatedintacctDestinationAttribute),
6367
of(copy.EXPENSE_PAYMENT_TYPE as unknown as PaginatedintacctDestinationAttribute),
@@ -178,4 +182,229 @@ describe('IntacctExportSettingsComponent', () => {
178182
component.navigateToPreviousStep();
179183
expect(router.navigate).toHaveBeenCalledWith(['/integrations/intacct/onboarding/connector']);
180184
});
185+
186+
describe('Watchers', () => {
187+
beforeEach(() => {
188+
fixture.detectChanges();
189+
});
190+
191+
describe('Reimbursable Expense Toggle Watcher', () => {
192+
it('should enable fields on enabling reimbursable expenses', fakeAsync(() => {
193+
component.exportSettingsForm.get('reimbursableExpense')?.setValue(true);
194+
tick();
195+
196+
expect(component.exportSettingsForm.get('reimbursableExportType')?.hasValidator(Validators.required)).toBeTrue();
197+
expect(component.exportSettingsForm.get('reimbursableExportGroup')?.hasValidator(Validators.required)).toBeTrue();
198+
expect(component.exportSettingsForm.get('reimbursableExportDate')?.hasValidator(Validators.required)).toBeTrue();
199+
}));
200+
201+
it('should disable fields on disabling reimbursable expenses', fakeAsync(() => {
202+
component.exportSettingsForm.get('reimbursableExpense')?.setValue(false);
203+
tick();
204+
205+
expect(component.exportSettingsForm.get('reimbursableExportType')?.hasValidator(Validators.required)).toBeFalse();
206+
expect(component.exportSettingsForm.get('reimbursableExportGroup')?.hasValidator(Validators.required)).toBeFalse();
207+
expect(component.exportSettingsForm.get('reimbursableExportDate')?.hasValidator(Validators.required)).toBeFalse();
208+
expect(component.exportSettingsForm.get('reimbursableExportType')?.value).toBeNull();
209+
}));
210+
});
211+
212+
describe('Reimbursable Export Type Watchers', () => {
213+
214+
it('should handle reimbursableExportType being changed to Journal Entry', fakeAsync(() => {
215+
component.exportSettingsForm.get('reimbursableExportType')?.setValue(IntacctReimbursableExpensesObject.JOURNAL_ENTRY);
216+
tick();
217+
218+
expect(component.exportSettingsForm.get('glAccount')?.hasValidator(Validators.required)).toBeTrue();
219+
expect(component.exportSettingsForm.get('employeeFieldMapping')?.enabled).toBeTrue();
220+
}));
221+
222+
it('should handle reimbursableExportType being changed to Expense Report', fakeAsync(() => {
223+
component.exportSettingsForm.get('reimbursableExportType')?.setValue(IntacctReimbursableExpensesObject.EXPENSE_REPORT);
224+
tick();
225+
226+
expect(component.exportSettingsForm.get('employeeFieldMapping')?.value).toBe(FyleField.EMPLOYEE);
227+
expect(component.exportSettingsForm.get('employeeFieldMapping')?.disabled).toBeTrue();
228+
}));
229+
230+
it('should handle reimbursableExportType being changed to Bill', fakeAsync(() => {
231+
component.exportSettingsForm.get('reimbursableExportType')?.setValue(IntacctReimbursableExpensesObject.BILL);
232+
tick();
233+
234+
expect(component.exportSettingsForm.get('employeeFieldMapping')?.value).toBe(FyleField.VENDOR);
235+
expect(component.exportSettingsForm.get('employeeFieldMapping')?.disabled).toBeTrue();
236+
}));
237+
});
238+
239+
describe('Credit Card Expense Toggle Watcher', () => {
240+
it('should enable fields on enabling CCC expenses', fakeAsync(() => {
241+
component.exportSettingsForm.get('creditCardExpense')?.setValue(true);
242+
tick();
243+
244+
expect(component.exportSettingsForm.get('cccExportType')?.hasValidator(Validators.required)).toBeTrue();
245+
expect(component.exportSettingsForm.get('cccExportGroup')?.hasValidator(Validators.required)).toBeTrue();
246+
expect(component.exportSettingsForm.get('cccExportDate')?.hasValidator(Validators.required)).toBeTrue();
247+
}));
248+
249+
it('should disable fields on disabling CCC expenses', fakeAsync(() => {
250+
component.exportSettingsForm.get('creditCardExpense')?.setValue(false);
251+
tick();
252+
253+
expect(component.exportSettingsForm.get('cccExportType')?.hasValidator(Validators.required)).toBeFalse();
254+
expect(component.exportSettingsForm.get('cccExportGroup')?.hasValidator(Validators.required)).toBeFalse();
255+
expect(component.exportSettingsForm.get('cccExportDate')?.hasValidator(Validators.required)).toBeFalse();
256+
expect(component.exportSettingsForm.get('cccExportType')?.value).toBeNull();
257+
}));
258+
});
259+
260+
describe('CCC Export Type Watchers', () => {
261+
it('should handle cccExportType being changed to Charge Card Transaction', fakeAsync(() => {
262+
component.exportSettingsForm.get('cccExportType')?.setValue(IntacctCorporateCreditCardExpensesObject.CHARGE_CARD_TRANSACTION);
263+
tick();
264+
265+
expect(component.exportSettingsForm.get('chargeCard')?.hasValidator(Validators.required)).toBeTrue();
266+
expect(component.exportSettingsForm.get('cccExportGroup')?.disabled).toBeTrue();
267+
expect(component.exportSettingsForm.get('cccExportGroup')?.value).toBe(ExpenseGroupingFieldOption.EXPENSE_ID);
268+
}));
269+
270+
it('should handle cccExportType being changed to Bill', fakeAsync(() => {
271+
component.exportSettingsForm.get('cccExportType')?.setValue(IntacctCorporateCreditCardExpensesObject.BILL);
272+
tick();
273+
274+
expect(component.exportSettingsForm.get('creditCardVendor')?.hasValidator(Validators.required)).toBeTrue();
275+
}));
276+
277+
it('should handle cccExportType being changed to Expense Report', fakeAsync(() => {
278+
component.exportSettingsForm.get('cccExportType')?.setValue(IntacctCorporateCreditCardExpensesObject.EXPENSE_REPORT);
279+
tick();
280+
281+
expect(component.exportSettingsForm.get('employeeFieldMapping')?.value).toBe(EmployeeFieldMapping.EMPLOYEE);
282+
expect(component.exportSettingsForm.get('cccExpensePaymentType')?.hasValidator(Validators.required)).toBeTrue();
283+
}));
284+
});
285+
286+
describe('Custom Watchers', () => {
287+
beforeEach(() => {
288+
brandingConfig.brandId = 'fyle';
289+
});
290+
291+
it('should update reimbursable expense grouping date options when group changes', fakeAsync(() => {
292+
fixture.detectChanges();
293+
component.exportSettingsForm.get('reimbursableExportGroup')?.setValue(ExpenseGroupingFieldOption.CLAIM_NUMBER);
294+
tick();
295+
296+
expect(component.reimbursableExpenseGroupingDateOptions).not.toContain({
297+
label: 'Spend date',
298+
value: ExportDateType.SPENT_AT
299+
});
300+
}));
301+
302+
it('should update CCC expense grouping date options when group changes', fakeAsync(() => {
303+
spyOn<IntacctExportSettingsComponent, any>(component, 'setCCExpenseDateOptions').and.callThrough();
304+
spyOn(IntacctExportSettingModel, 'getExpenseGroupingDateOptions').and.callThrough();
305+
spyOn(ExportSettingModel, 'constructGroupingDateOptions').and.callThrough();
306+
307+
component.exportSettingsForm.get('cccExportType')?.setValue(IntacctCorporateCreditCardExpensesObject.CHARGE_CARD_TRANSACTION);
308+
component.exportSettingsForm.get('cccExportGroup')?.setValue(ExpenseGroupingFieldOption.CLAIM_NUMBER);
309+
310+
tick();
311+
312+
expect(IntacctExportSettingModel.getExpenseGroupingDateOptions).toHaveBeenCalledWith();
313+
expect(ExportSettingModel.constructGroupingDateOptions).toHaveBeenCalledWith(
314+
ExpenseGroupingFieldOption.CLAIM_NUMBER,
315+
IntacctExportSettingModel.getExpenseGroupingDateOptions()
316+
);
317+
expect(component['setCCExpenseDateOptions']).toHaveBeenCalled();
318+
}));
319+
});
320+
321+
describe('Export Selection Validator', () => {
322+
beforeEach(() => {
323+
fixture.detectChanges();
324+
});
325+
326+
it('should invalidate form when neither reimbursable nor credit card expense is selected', () => {
327+
component.exportSettingsForm.get('reimbursableExpense')?.setValue(false);
328+
component.exportSettingsForm.get('creditCardExpense')?.setValue(false);
329+
330+
expect(component.exportSettingsForm.valid).toBeFalse();
331+
});
332+
333+
it('should validate the form when at least one export type is selected', () => {
334+
component.exportSettingsForm.get('reimbursableExpense')?.setValue(true);
335+
component.exportSettingsForm.get('creditCardExpense')?.setValue(false);
336+
337+
expect(component.exportSettingsForm.valid).toBeTrue();
338+
});
339+
});
340+
341+
describe('Destination Options Handling', () => {
342+
beforeEach(() => {
343+
fixture.detectChanges();
344+
});
345+
346+
it('should handle option search for reimbursable expense payment type', fakeAsync(() => {
347+
const searchEvent = {
348+
searchTerm: 'test',
349+
destinationOptionKey: 'EXPENSE_PAYMENT_TYPE'
350+
} as ExportSettingOptionSearch;
351+
352+
mappingService.getPaginatedDestinationAttributes.and.returnValue(
353+
of(mockPaginatedDestinationAttributes.EXPENSE_PAYMENT_TYPE as unknown as PaginatedintacctDestinationAttribute)
354+
);
355+
356+
component.searchOptionsDropdown(searchEvent);
357+
tick(1000);
358+
359+
const isReimbursable = (option: IntacctDestinationAttribute) => (
360+
option.detail ? option.detail.is_reimbursable : true
361+
);
362+
363+
expect(mappingService.getPaginatedDestinationAttributes).toHaveBeenCalledWith('EXPENSE_PAYMENT_TYPE', 'test');
364+
expect(component.destinationOptions.EXPENSE_PAYMENT_TYPE.every(isReimbursable)).toBeTrue();
365+
expect(component.isOptionSearchInProgress).toBeFalse();
366+
}));
367+
368+
it('should handle option search for CCC expense payment type', fakeAsync(() => {
369+
const searchEvent = {
370+
searchTerm: 'test',
371+
destinationOptionKey: 'CCC_EXPENSE_PAYMENT_TYPE'
372+
};
373+
374+
mappingService.getPaginatedDestinationAttributes.and.returnValue(
375+
of(mockPaginatedDestinationAttributes.EXPENSE_PAYMENT_TYPE as unknown as PaginatedintacctDestinationAttribute)
376+
);
377+
378+
component.searchOptionsDropdown(searchEvent as ExportSettingOptionSearch);
379+
tick(1000);
380+
381+
expect(mappingService.getPaginatedDestinationAttributes).toHaveBeenCalledWith('EXPENSE_PAYMENT_TYPE', 'test');
382+
expect(component.destinationOptions.CCC_EXPENSE_PAYMENT_TYPE.every(option => (
383+
option.detail ? !option.detail.is_reimbursable : true
384+
))).toBeTrue();
385+
expect(component.isOptionSearchInProgress).toBeFalse();
386+
}));
387+
});
388+
389+
390+
});
391+
392+
393+
describe('C1 Specific Behavior', () => {
394+
it('should handle setup with c1 branding', () => {
395+
brandingConfig.brandId = 'co';
396+
397+
fixture = TestBed.createComponent(IntacctExportSettingsComponent);
398+
component = fixture.componentInstance;
399+
fixture.detectChanges();
400+
401+
expect(component.exportSettingsForm.get('creditCardExpense')?.value).toBeTrue();
402+
expect(component.exportSettingsForm.get('employeeFieldMapping')?.value).toBe(FyleField.VENDOR);
403+
expect(component.isMultiLineOption).toBeFalse();
404+
});
405+
406+
afterAll(() => {
407+
brandingConfig.brandId = 'fyle';
408+
});
409+
});
181410
});

0 commit comments

Comments
 (0)