Skip to content

Commit d6035f6

Browse files
test: unit test intacct import settings initialization (#1007)
* test: unit test intacct import settings * refactor: move to fixtures * refactor: comments
1 parent 1514f40 commit d6035f6

File tree

2 files changed

+317
-6
lines changed

2 files changed

+317
-6
lines changed
Lines changed: 189 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,206 @@
1-
import { ComponentFixture, TestBed } from '@angular/core/testing';
1+
import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
2+
import { AbstractControl, FormArray, FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
3+
import { provideRouter, Router, RouterModule } from '@angular/router';
4+
import { of, throwError } from 'rxjs';
25

36
import { IntacctImportSettingsComponent } from './intacct-import-settings.component';
7+
import { SiImportSettingService } from 'src/app/core/services/si/si-configuration/si-import-setting.service';
8+
import { SiMappingsService } from 'src/app/core/services/si/si-core/si-mappings.service';
9+
import { IntacctConnectorService } from 'src/app/core/services/si/si-core/intacct-connector.service';
10+
import { OrgService } from 'src/app/core/services/org/org.service';
11+
import { TrackingService } from 'src/app/core/services/integration/tracking.service';
12+
import { IntegrationsToastService } from 'src/app/core/services/common/integrations-toast.service';
13+
import { StorageService } from 'src/app/core/services/common/storage.service';
14+
import { SiWorkspaceService } from 'src/app/core/services/si/si-core/si-workspace.service';
15+
import { HelperService } from 'src/app/core/services/common/helper.service';
416

5-
xdescribe('IntacctImportSettingsComponent', () => {
17+
import { configuration, costCodeFieldValue, costTypeFieldValue, fyleFields, groupedDestinationAttributes, importSettings, importSettingsWithProject, intacctImportCodeConfig, locationEntityMapping, sageIntacctFields, sageIntacctFieldsSortedByPriority, settingsWithDependentFields } from '../../intacct.fixture';
18+
import { IntacctCategoryDestination, IntacctOnboardingState, ToastSeverity } from 'src/app/core/models/enum/enum.model';
19+
import { SharedModule } from 'src/app/shared/shared.module';
20+
import { Org } from 'src/app/core/models/org/org.model';
21+
import { HttpClientTestingModule } from '@angular/common/http/testing';
22+
import { LocationEntityMapping } from 'src/app/core/models/intacct/db/location-entity-mapping.model';
23+
import { IntacctSharedModule } from '../intacct-shared.module';
24+
import { ExpenseField } from 'src/app/core/models/intacct/db/expense-field.model';
25+
import { ImportSettingGet } from 'src/app/core/models/intacct/intacct-configuration/import-settings.model';
26+
import { IntacctConfiguration } from 'src/app/core/models/db/configuration.model';
27+
28+
describe('IntacctImportSettingsComponent', () => {
629
let component: IntacctImportSettingsComponent;
730
let fixture: ComponentFixture<IntacctImportSettingsComponent>;
31+
let siImportSettingService: jasmine.SpyObj<SiImportSettingService>;
32+
let siMappingsService: jasmine.SpyObj<SiMappingsService>;
33+
let intacctConnectorService: jasmine.SpyObj<IntacctConnectorService>;
34+
let orgService: jasmine.SpyObj<OrgService>;
35+
let trackingService: jasmine.SpyObj<TrackingService>;
36+
let toastService: jasmine.SpyObj<IntegrationsToastService>;
37+
let storageService: jasmine.SpyObj<StorageService>;
38+
let siWorkspaceService: jasmine.SpyObj<SiWorkspaceService>;
39+
let router: Router;
840

941
beforeEach(async () => {
42+
const siImportSettingServiceSpy = jasmine.createSpyObj('SiImportSettingService', ['getImportSettings', 'postImportSettings', 'getImportCodeFieldConfig']);
43+
const siMappingsServiceSpy = jasmine.createSpyObj('SiMappingsService', ['getSageIntacctFields', 'getFyleFields', 'getGroupedDestinationAttributes', 'getConfiguration', 'refreshSageIntacctDimensions', 'refreshFyleDimensions']);
44+
const intacctConnectorServiceSpy = jasmine.createSpyObj('IntacctConnectorService', ['getLocationEntityMapping']);
45+
const orgServiceSpy = jasmine.createSpyObj('OrgService', ['getCachedOrg']);
46+
const trackingServiceSpy = jasmine.createSpyObj('TrackingService', ['integrationsOnboardingCompletion', 'intacctUpdateEvent', 'trackTimeSpent']);
47+
const toastServiceSpy = jasmine.createSpyObj('IntegrationsToastService', ['displayToastMessage']);
48+
const storageServiceSpy = jasmine.createSpyObj('StorageService', ['get']);
49+
const siWorkspaceServiceSpy = jasmine.createSpyObj('SiWorkspaceService', ['getIntacctOnboardingState', 'setIntacctOnboardingState']);
50+
1051
await TestBed.configureTestingModule({
11-
declarations: [ IntacctImportSettingsComponent ]
52+
declarations: [IntacctImportSettingsComponent],
53+
imports: [SharedModule, IntacctSharedModule, ReactiveFormsModule, RouterModule.forRoot([]), HttpClientTestingModule],
54+
providers: [
55+
FormBuilder,
56+
{ provide: SiImportSettingService, useValue: siImportSettingServiceSpy },
57+
{ provide: SiMappingsService, useValue: siMappingsServiceSpy },
58+
{ provide: IntacctConnectorService, useValue: intacctConnectorServiceSpy },
59+
{ provide: OrgService, useValue: orgServiceSpy },
60+
{ provide: TrackingService, useValue: trackingServiceSpy },
61+
{ provide: IntegrationsToastService, useValue: toastServiceSpy },
62+
{ provide: StorageService, useValue: storageServiceSpy },
63+
{ provide: SiWorkspaceService, useValue: siWorkspaceServiceSpy },
64+
provideRouter([])
65+
]
1266
})
13-
.compileComponents();
67+
.compileComponents();
68+
69+
siImportSettingService = TestBed.inject(SiImportSettingService) as jasmine.SpyObj<SiImportSettingService>;
70+
siMappingsService = TestBed.inject(SiMappingsService) as jasmine.SpyObj<SiMappingsService>;
71+
intacctConnectorService = TestBed.inject(IntacctConnectorService) as jasmine.SpyObj<IntacctConnectorService>;
72+
orgService = TestBed.inject(OrgService) as jasmine.SpyObj<OrgService>;
73+
trackingService = TestBed.inject(TrackingService) as jasmine.SpyObj<TrackingService>;
74+
toastService = TestBed.inject(IntegrationsToastService) as jasmine.SpyObj<IntegrationsToastService>;
75+
storageService = TestBed.inject(StorageService) as jasmine.SpyObj<StorageService>;
76+
siWorkspaceService = TestBed.inject(SiWorkspaceService) as jasmine.SpyObj<SiWorkspaceService>;
77+
router = TestBed.inject(Router);
78+
79+
spyOn(router, 'navigate');
80+
spyOnProperty(router, 'url').and.returnValue('/onboarding');
81+
siImportSettingService.getImportSettings.and.returnValue(of(importSettings));
82+
siImportSettingService.getImportCodeFieldConfig.and.returnValue(of(intacctImportCodeConfig));
83+
siMappingsService.getSageIntacctFields.and.returnValue(of(sageIntacctFields));
84+
siMappingsService.getFyleFields.and.returnValue(of(fyleFields));
85+
siMappingsService.getGroupedDestinationAttributes.and.returnValue(of(groupedDestinationAttributes));
86+
siMappingsService.getConfiguration.and.returnValue(of(configuration));
87+
intacctConnectorService.getLocationEntityMapping.and.returnValue(of(locationEntityMapping));
88+
orgService.getCachedOrg.and.returnValue({ created_at: new Date() } as Org);
89+
siWorkspaceService.getIntacctOnboardingState.and.returnValue(IntacctOnboardingState.IMPORT_SETTINGS);
90+
storageService.get.and.returnValue(366);
1491

1592
fixture = TestBed.createComponent(IntacctImportSettingsComponent);
1693
component = fixture.componentInstance;
17-
fixture.detectChanges();
94+
1895
});
1996

2097
it('should create', () => {
2198
expect(component).toBeTruthy();
2299
});
23-
});
100+
101+
describe('Form Initialization', () => {
102+
beforeEach(() => {
103+
component.ngOnInit();
104+
fixture.detectChanges();
105+
});
106+
107+
it('should set initial loading state', () => {
108+
expect(component.isLoading).toBeFalsy();
109+
});
110+
111+
it('should fetch and set all required data', () => {
112+
expect(siMappingsService.getSageIntacctFields).toHaveBeenCalled();
113+
expect(siMappingsService.getFyleFields).toHaveBeenCalled();
114+
expect(siMappingsService.getGroupedDestinationAttributes).toHaveBeenCalledWith(['TAX_DETAIL']);
115+
expect(siImportSettingService.getImportSettings).toHaveBeenCalled();
116+
expect(siMappingsService.getConfiguration).toHaveBeenCalled();
117+
expect(intacctConnectorService.getLocationEntityMapping).toHaveBeenCalled();
118+
expect(siImportSettingService.getImportCodeFieldConfig).toHaveBeenCalled();
119+
});
120+
121+
it('should correctly transform and set sageIntacctFields', () => {
122+
expect(component.sageIntacctFields).toEqual(sageIntacctFieldsSortedByPriority);
123+
});
124+
125+
it('should set Fyle fields with custom field option', () => {
126+
expect(component.fyleFields).toEqual(fyleFields as ExpenseField[]);
127+
});
128+
129+
it('should set intacctCategoryDestination based on configuration', () => {
130+
expect(component.intacctCategoryDestination).toEqual(IntacctCategoryDestination.GL_ACCOUNT);
131+
132+
const updatedConfig = {...configuration, reimbursable_expenses_object: 'EXPENSE_REPORT'};
133+
siMappingsService.getConfiguration.and.returnValue(of(updatedConfig as IntacctConfiguration));
134+
135+
component.ngOnInit();
136+
fixture.detectChanges();
137+
138+
expect(component.intacctCategoryDestination).toEqual(IntacctCategoryDestination.EXPENSE_TYPE);
139+
});
140+
141+
describe('Form Group Initialization', () => {
142+
it('should initialize the form with correct controls', () => {
143+
expect(component.importSettingsForm.get('importVendorAsMerchant')).toBeTruthy();
144+
expect(component.importSettingsForm.get('importCategories')).toBeTruthy();
145+
expect(component.importSettingsForm.get('importTaxCodes')).toBeTruthy();
146+
expect(component.importSettingsForm.get('costCodes')).toBeTruthy();
147+
expect(component.importSettingsForm.get('dependentFieldImportToggle')).toBeTruthy();
148+
expect(component.importSettingsForm.get('workspaceId')).toBeTruthy();
149+
expect(component.importSettingsForm.get('costTypes')).toBeTruthy();
150+
expect(component.importSettingsForm.get('isDependentImportEnabled')).toBeTruthy();
151+
expect(component.importSettingsForm.get('sageIntacctTaxCodes')).toBeTruthy();
152+
expect(component.importSettingsForm.get('expenseFields')).toBeTruthy();
153+
expect(component.importSettingsForm.get('searchOption')).toBeTruthy();
154+
expect(component.importSettingsForm.get('importCodeField')).toBeTruthy();
155+
expect(component.importSettingsForm.get('importCodeFields')).toBeTruthy();
156+
});
157+
158+
it('should initialize form with correct values', () => {
159+
expect(component.importSettingsForm.get('importVendorAsMerchant')?.value).toEqual(importSettings.configurations.import_vendors_as_merchants || null || null);
160+
expect(component.importSettingsForm.get('importCategories')?.value).toEqual(importSettings.configurations.import_categories || null);
161+
expect(component.importSettingsForm.get('importTaxCodes')?.value).toEqual(importSettings.configurations.import_tax_codes || null);
162+
expect(component.importSettingsForm.get('workspaceId')?.value).toEqual(366);
163+
expect(component.importSettingsForm.get('isDependentImportEnabled')?.value).toBeFalsy();
164+
expect(component.importSettingsForm.get('searchOption')?.value).toEqual('');
165+
expect(component.importSettingsForm.get('importCodeField')?.value).toEqual(importSettings.configurations.import_code_fields);
166+
});
167+
168+
it('should initialize expenseFields FormArray correctly', () => {
169+
const expenseFieldsArray = component.importSettingsForm.get('expenseFields') as FormArray;
170+
expect(expenseFieldsArray.length).toBeGreaterThan(0);
171+
172+
const testForFields = (control: AbstractControl<any, any>) => {
173+
expect(control.get('source_field')).toBeTruthy();
174+
expect(control.get('destination_field')).toBeTruthy();
175+
expect(control.get('import_to_fyle')).toBeTruthy();
176+
expect(control.get('is_custom')).toBeTruthy();
177+
expect(control.get('source_placeholder')).toBeTruthy();
178+
};
179+
expenseFieldsArray.controls.forEach(testForFields);
180+
});
181+
});
182+
183+
describe('Dependent Fields Setup', () => {
184+
it('should handle dependent fields when project mapping exists', fakeAsync(() => {
185+
siImportSettingService.getImportSettings.and.returnValue(of(importSettingsWithProject));
186+
187+
fixture.detectChanges();
188+
tick();
189+
190+
expect(component.costCodeFieldOption.length).toBe(1);
191+
expect(component.costTypeFieldOption.length).toBe(1);
192+
}));
193+
194+
it('should handle dependent field settings', () => {
195+
siImportSettingService.getImportSettings.and.returnValue(of(settingsWithDependentFields));
196+
197+
component.ngOnInit();
198+
fixture.detectChanges();
199+
200+
expect(component.importSettingsForm.get('isDependentImportEnabled')?.value).toBeTrue();
201+
expect(component.importSettingsForm.get('costCodes')?.value).toEqual(costCodeFieldValue);
202+
expect(component.importSettingsForm.get('costTypes')?.value).toEqual(costTypeFieldValue);
203+
});
204+
});
205+
});
206+
});

src/app/integrations/intacct/intacct.fixture.ts

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ import { ExportSettingGet } from "src/app/core/models/intacct/intacct-configurat
88
import { ExpenseGroup, ExpenseGroupDescription, ExpenseGroupResponse } from 'src/app/core/models/db/expense-group.model';
99
import { Paginator } from 'src/app/core/models/misc/paginator.model';
1010
import { SkipExportLogResponse } from "src/app/core/models/intacct/db/expense-group.model";
11+
import { ExpenseField } from 'src/app/core/models/intacct/db/expense-field.model';
12+
import { DependentFieldSetting, ImportSettingGet } from 'src/app/core/models/intacct/intacct-configuration/import-settings.model';
13+
import { LocationEntityMapping } from 'src/app/core/models/intacct/db/location-entity-mapping.model';
14+
import { GroupedDestinationAttribute } from "src/app/core/models/intacct/db/destination-attribute.model";
15+
import { IntacctConfiguration } from "src/app/core/models/db/configuration.model";
1116

1217
export const workspaceResponse: IntacctWorkspace[] = [{
1318
"id": 1,
@@ -734,4 +739,127 @@ export const mockPaginatedDestinationAttributes = {
734739
}
735740
]
736741
}
742+
};
743+
744+
export const intacctImportCodeConfig = {
745+
PROJECT: true,
746+
DEPARTMENT: true,
747+
ACCOUNT: true,
748+
EXPENSE_TYPE: true
749+
};
750+
751+
export const fyleFields = [
752+
{
753+
attribute_type: 'COST_CENTER',
754+
display_name: 'Cost Center',
755+
is_dependent: false
756+
},
757+
{
758+
attribute_type: 'PROJECT',
759+
display_name: 'Project',
760+
is_dependent: false
761+
}
762+
] as ExpenseField[];
763+
764+
export const sageIntacctFields = [
765+
{
766+
attribute_type: 'CUSTOMER',
767+
display_name: 'customer'
768+
},
769+
{
770+
attribute_type: 'ITEM',
771+
display_name: 'item'
772+
},
773+
{
774+
attribute_type: 'PROJECT',
775+
display_name: 'Project'
776+
}
777+
] as ExpenseField[];
778+
779+
export const importSettings = {
780+
configurations: {
781+
import_categories: false,
782+
import_tax_codes: false,
783+
import_vendors_as_merchants: false,
784+
import_code_fields: []
785+
},
786+
general_mappings: {
787+
default_tax_code: {
788+
name: null,
789+
id: null
790+
}
791+
},
792+
mapping_settings: [],
793+
dependent_field_settings: null as unknown as DependentFieldSetting,
794+
workspace_id: 366
795+
} as ImportSettingGet;
796+
797+
export const configuration = {
798+
reimbursable_expenses_object: null,
799+
corporate_credit_card_expenses_object: 'CHARGE_CARD_TRANSACTION',
800+
import_code_fields: []
801+
} as unknown as IntacctConfiguration;
802+
803+
export const locationEntityMapping: LocationEntityMapping = {
804+
id: 327,
805+
location_entity_name: 'Top Level',
806+
country_name: null,
807+
destination_id: 'top_level',
808+
created_at: new Date('2024-08-26T11:54:23.640893Z'),
809+
updated_at: new Date('2024-08-26T11:54:23.640913Z'),
810+
workspace: 366
811+
};
812+
813+
export const groupedDestinationAttributes = {
814+
ACCOUNT: [],
815+
EXPENSE_TYPE: [],
816+
EXPENSE_PAYMENT_TYPE: [],
817+
VENDOR: [],
818+
EMPLOYEE: [],
819+
CHARGE_CARD_NUMBER: [],
820+
TAX_DETAIL: []
821+
} as unknown as GroupedDestinationAttribute;
822+
823+
824+
export const sageIntacctFieldsSortedByPriority = [
825+
{
826+
attribute_type: 'PROJECT',
827+
display_name: 'Project'
828+
},
829+
{
830+
attribute_type: 'CUSTOMER',
831+
display_name: 'Customer'
832+
},
833+
{
834+
attribute_type: 'ITEM',
835+
display_name: 'Item'
836+
}
837+
] as ExpenseField[];
838+
839+
export const importSettingsWithProject = {...importSettings, mapping_settings: [{
840+
source_field: 'PROJECT',
841+
destination_field: 'PROJECT',
842+
import_to_fyle: true
843+
}]} as ImportSettingGet;
844+
845+
export const settingsWithDependentFields = {...importSettings, dependent_field_settings: {
846+
is_import_enabled: true,
847+
cost_code_field_name: 'COST_CODE',
848+
cost_code_placeholder: 'Enter Cost Code',
849+
cost_type_field_name: 'COST_TYPE',
850+
cost_type_placeholder: 'Enter Cost Type'
851+
}} as ImportSettingGet;
852+
853+
export const costCodeFieldValue = {
854+
attribute_type: 'COST_CODE',
855+
display_name: 'COST_CODE',
856+
source_placeholder: 'Enter Cost Code',
857+
is_dependent: true
858+
};
859+
860+
export const costTypeFieldValue = {
861+
attribute_type: 'COST_TYPE',
862+
display_name: 'COST_TYPE',
863+
source_placeholder: 'Enter Cost Type',
864+
is_dependent: true
737865
};

0 commit comments

Comments
 (0)