WEB-341 Add Integration Tests for CreateClientComponent

This commit is contained in:
Gopikishan
2025-10-06 11:35:53 +05:30
parent 2462ea5380
commit 7a1bcbc4e9
6 changed files with 1076 additions and 1006 deletions
@@ -0,0 +1,552 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ActivatedRoute, Router } from '@angular/router';
import { of, BehaviorSubject } from 'rxjs';
import { CreateClientComponent } from './create-client.component';
import { ClientsService } from '../clients.service';
import { SettingsService } from '../../settings/settings.service';
import { DatePipe, DecimalPipe } from '@angular/common';
import { TranslateModule } from '@ngx-translate/core';
import { provideNativeDateAdapter } from '@angular/material/core';
import { FaIconLibrary } from '@fortawesome/angular-fontawesome';
import * as solidIcons from '@fortawesome/free-solid-svg-icons';
import { describe, it, expect, jest, beforeEach } from '@jest/globals';
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
describe('CreateClientComponent - Integration Tests', () => {
let component: CreateClientComponent;
let fixture: ComponentFixture<CreateClientComponent>;
let mockRouter: jest.Mocked<Router>;
let mockClientsService: jest.Mocked<ClientsService>;
let mockSettingsService: jest.Mocked<SettingsService>;
let routeDataSubject: BehaviorSubject<any>;
const mockClientTemplate: any = {
isAddressEnabled: true,
datatables: [
{
registeredTableName: 'PersonDatatable',
entitySubType: 'person',
columnHeaderData: [] as any[]
},
{
registeredTableName: 'EntityDatatable',
entitySubType: 'entity',
columnHeaderData: [] as any[]
}
],
officeOptions: [
{ id: 1, name: 'Head Office' }],
staffOptions: [
{ id: 1, displayName: 'John Doe' }],
savingsProductOptions: [] as any[],
genderOptions: [] as any[],
clientTypeOptions: [] as any[],
clientClassificationOptions: [] as any[],
clientNonPersonConstitutionOptions: [] as any[],
clientNonPersonMainBusinessLineOptions: [] as any[],
clientLegalFormOptions: [
{ id: 1, value: 'Person' },
{ id: 2, value: 'Entity' }
]
};
const mockAddressConfig = {
addressTypeIdOptions: [
{ id: 1, name: 'Home' }]
};
beforeEach(async () => {
mockRouter = {
navigate: jest.fn()
} as any;
routeDataSubject = new BehaviorSubject({
clientTemplate: { ...mockClientTemplate },
clientAddressFieldConfig: { ...mockAddressConfig }
});
mockClientsService = {
createClient: jest.fn(() => of({ resourceId: 123, clientId: 123 }))
} as unknown as jest.Mocked<ClientsService>;
mockSettingsService = {
language: { code: 'en' },
dateFormat: 'dd MMMM yyyy'
} as unknown as jest.Mocked<SettingsService>;
await TestBed.configureTestingModule({
imports: [
CreateClientComponent,
TranslateModule.forRoot()
],
providers: [
{ provide: Router, useValue: mockRouter },
{
provide: ActivatedRoute,
useValue: {
data: routeDataSubject.asObservable(),
snapshot: { data: routeDataSubject.value }
}
},
{ provide: ClientsService, useValue: mockClientsService },
{ provide: SettingsService, useValue: mockSettingsService },
DatePipe,
DecimalPipe,
provideNativeDateAdapter(),
provideAnimationsAsync()
]
}).compileComponents();
// Add all FontAwesome solid icons to handle all child component icon requirements
const faIconLibrary = TestBed.inject(FaIconLibrary);
const iconList = Object.keys(solidIcons)
.filter((key) => key !== 'fas' && key !== 'prefix' && key.startsWith('fa'))
.map((icon) => (solidIcons as any)[icon]);
faIconLibrary.addIcons(...iconList);
fixture = TestBed.createComponent(CreateClientComponent);
component = fixture.componentInstance;
});
describe('Component Initialization', () => {
it('should create the component', () => {
expect(component).toBeTruthy();
});
it('should initialize with default legal form type as Person (1)', () => {
expect(component.legalFormType).toBe(1);
});
it('should load client template and address config from route resolver', () => {
expect(component.clientTemplate).toEqual(mockClientTemplate);
expect(component.clientAddressFieldConfig).toEqual(mockAddressConfig);
});
it('should filter datatables based on initial legal form type', () => {
expect(component.datatables).toHaveLength(1);
expect(component.datatables[0].entitySubType).toBe('person');
});
});
describe('Legal Form Type Changes', () => {
it('should update legal form type when changed to Entity', () => {
component.legalFormChange({ legalForm: 2 });
expect(component.legalFormType).toBe(2);
});
it('should filter datatables for Entity when legal form is Entity', () => {
component.legalFormChange({ legalForm: 2 });
expect(component.datatables).toHaveLength(1);
expect(component.datatables[0].entitySubType).toBe('entity');
expect(component.datatables[0].registeredTableName).toBe('EntityDatatable');
});
it('should filter datatables for Person when legal form is Person', () => {
component.legalFormChange({ legalForm: 1 });
expect(component.datatables).toHaveLength(1);
expect(component.datatables[0].entitySubType).toBe('person');
expect(component.datatables[0].registeredTableName).toBe('PersonDatatable');
});
it('should handle no datatables when template has none', () => {
routeDataSubject.next({
clientTemplate: { ...mockClientTemplate, datatables: [] },
clientAddressFieldConfig: mockAddressConfig
});
component.legalFormChange({ legalForm: 2 });
expect(component.datatables).toHaveLength(0);
});
it('should re-filter datatables when switching between Person and Entity multiple times', () => {
component.legalFormChange({ legalForm: 2 });
expect(component.datatables[0].entitySubType).toBe('entity');
component.legalFormChange({ legalForm: 1 });
expect(component.datatables[0].entitySubType).toBe('person');
component.legalFormChange({ legalForm: 2 });
expect(component.datatables[0].entitySubType).toBe('entity');
});
});
describe('Client Data Aggregation', () => {
beforeEach(() => {
fixture.detectChanges();
});
it('should aggregate client data from general and family steps when address is disabled', () => {
routeDataSubject.next({
clientTemplate: { ...mockClientTemplate, isAddressEnabled: false },
clientAddressFieldConfig: mockAddressConfig
});
// Mock the getter methods on child components
if (component.clientGeneralStep) {
Object.defineProperty(component.clientGeneralStep, 'clientGeneralDetails', {
get: jest.fn(() => ({
firstname: 'John',
lastname: 'Doe',
officeId: 1,
active: true
})),
configurable: true
});
}
if (component.clientFamilyMembersStep) {
Object.defineProperty(component.clientFamilyMembersStep, 'familyMembers', {
get: jest.fn(() => ({
familyMembers: [
{ firstName: 'Jane', relationship: 'Spouse' }]
})),
configurable: true
});
}
const clientData = component.client;
expect(clientData.firstname).toBe('John');
expect(clientData.lastname).toBe('Doe');
expect(clientData.familyMembers).toBeDefined();
});
it('should aggregate client data including address when address is enabled', () => {
// Mock the getter methods on child components
if (component.clientGeneralStep) {
Object.defineProperty(component.clientGeneralStep, 'clientGeneralDetails', {
get: jest.fn(() => ({
firstname: 'John',
lastname: 'Doe',
officeId: 1,
active: true
})),
configurable: true
});
}
if (component.clientFamilyMembersStep) {
Object.defineProperty(component.clientFamilyMembersStep, 'familyMembers', {
get: jest.fn(() => ({
familyMembers: [
{ firstName: 'Jane', relationship: 'Spouse' }]
})),
configurable: true
});
}
if (component.clientAddressStep) {
Object.defineProperty(component.clientAddressStep, 'address', {
get: jest.fn(() => ({
address: [
{ addressTypeId: 1, street: '123 Main St' }]
})),
configurable: true
});
}
const clientData = component.client;
expect(clientData.firstname).toBe('John');
expect(clientData.address).toBeDefined();
});
});
describe('Form Validation', () => {
beforeEach(() => {
fixture.detectChanges();
});
it('should validate that general form is required', () => {
if (component.clientGeneralStep && component.clientGeneralStep.createClientForm) {
jest.spyOn(component.clientGeneralForm, 'valid', 'get').mockReturnValue(false);
expect(component.areFormvalids()).toBe(false);
}
});
it('should require address when address is enabled', () => {
component.clientTemplate.isAddressEnabled = true;
if (component.clientGeneralStep && component.clientGeneralStep.createClientForm) {
jest.spyOn(component.clientGeneralForm, 'valid', 'get').mockReturnValue(true);
}
if (component.clientAddressStep) {
Object.defineProperty(component.clientAddressStep, 'address', {
get: jest.fn(() => ({ address: [] })),
configurable: true
});
expect(component.areFormvalids()).toBe(false);
}
});
it('should validate all datatable forms when datatables exist', () => {
component.clientTemplate.datatables = mockClientTemplate.datatables;
component.clientTemplate.isAddressEnabled = false; // Disable address to simplify test
component.setDatatables();
if (component.clientGeneralStep && component.clientGeneralStep.createClientForm) {
jest.spyOn(component.clientGeneralForm, 'valid', 'get').mockReturnValue(true);
}
// When there are no clientDatatables ViewChildren, validation should pass
expect(component.areFormvalids()).toBe(true);
});
it('should pass validation when all forms are valid', () => {
component.clientTemplate.isAddressEnabled = false;
if (component.clientGeneralStep && component.clientGeneralStep.createClientForm) {
jest.spyOn(component.clientGeneralForm, 'valid', 'get').mockReturnValue(true);
}
expect(component.areFormvalids()).toBe(true);
});
});
describe('Client Submission', () => {
beforeEach(() => {
fixture.detectChanges();
// Mock child component getters
if (component.clientGeneralStep) {
Object.defineProperty(component.clientGeneralStep, 'clientGeneralDetails', {
get: jest.fn(() => ({
firstname: 'John',
lastname: 'Doe',
officeId: 1,
active: true,
externalId: 'EXT-001',
dateFormat: 'dd MMMM yyyy',
locale: 'en'
})),
configurable: true
});
}
if (component.clientFamilyMembersStep) {
Object.defineProperty(component.clientFamilyMembersStep, 'familyMembers', {
get: jest.fn(() => ({
familyMembers: []
})),
configurable: true
});
}
});
it('should submit client with locale and dateFormat from settings', () => {
component.submit();
expect(mockClientsService.createClient).toHaveBeenCalledWith(
expect.objectContaining({
dateFormat: 'dd MMMM yyyy',
locale: 'en'
})
);
});
it('should navigate to client detail page after successful creation', () => {
const mockActivatedRoute = TestBed.inject(ActivatedRoute);
component.submit();
expect(mockClientsService.createClient).toHaveBeenCalled();
expect(mockRouter.navigate).toHaveBeenCalledWith(
[
'../',
123
],
{ relativeTo: mockActivatedRoute }
);
});
it('should include address data in submission when address is enabled', () => {
component.clientTemplate.isAddressEnabled = true;
if (component.clientAddressStep) {
Object.defineProperty(component.clientAddressStep, 'address', {
get: jest.fn(() => ({
address: [
{ addressTypeId: 1, street: '123 Main St', city: 'New York' }]
})),
configurable: true
});
}
component.submit();
expect(mockClientsService.createClient).toHaveBeenCalledWith(
expect.objectContaining({
address: expect.arrayContaining([
expect.objectContaining({ street: '123 Main St' })
])
})
);
});
it('should submit without address when address is disabled', () => {
component.clientTemplate.isAddressEnabled = false;
component.submit();
const callArgs = mockClientsService.createClient.mock.calls[0][0];
expect(callArgs.address).toBeUndefined();
});
it('should include datatable data when datatables exist', () => {
component.clientTemplate.datatables = mockClientTemplate.datatables;
component.setDatatables();
// Note: In real scenario, clientDatatables would be populated by ViewChildren
// This test verifies the logic flow
component.submit();
expect(mockClientsService.createClient).toHaveBeenCalled();
});
it('should handle client creation with all data types', () => {
component.clientTemplate.isAddressEnabled = true;
component.clientTemplate.datatables = mockClientTemplate.datatables;
if (component.clientAddressStep) {
Object.defineProperty(component.clientAddressStep, 'address', {
get: jest.fn(() => ({
address: [{ addressTypeId: 1, street: 'Test St' }]
})),
configurable: true
});
}
component.submit();
expect(mockClientsService.createClient).toHaveBeenCalledWith(
expect.objectContaining({
firstname: 'John',
lastname: 'Doe',
officeId: 1,
dateFormat: 'dd MMMM yyyy',
locale: 'en'
})
);
});
});
describe('Edge Cases and Error Handling', () => {
it('should handle missing client template gracefully', () => {
routeDataSubject.next({
clientTemplate: null,
clientAddressFieldConfig: mockAddressConfig
});
// The component doesn't currently handle null template, which is actually a bug
// But this test documents the current behavior
expect(component.clientTemplate).toBeNull();
});
it('should handle undefined datatables in template', () => {
routeDataSubject.next({
clientTemplate: { ...mockClientTemplate, datatables: undefined },
clientAddressFieldConfig: mockAddressConfig
});
component.setDatatables();
expect(component.datatables).toEqual([]);
});
it('should handle datatables with case-insensitive entitySubType', () => {
routeDataSubject.next({
clientTemplate: {
...mockClientTemplate,
datatables: [
{ registeredTableName: 'Test1', entitySubType: 'PERSON' },
{ registeredTableName: 'Test2', entitySubType: 'Person' },
{ registeredTableName: 'Test3', entitySubType: 'person' }
]
},
clientAddressFieldConfig: mockAddressConfig
});
component.legalFormChange({ legalForm: 1 });
expect(component.datatables).toHaveLength(3);
});
it('should properly update when route data changes', () => {
const newTemplate = {
...mockClientTemplate,
isAddressEnabled: false
};
routeDataSubject.next({
clientTemplate: newTemplate,
clientAddressFieldConfig: mockAddressConfig
});
expect(component.clientTemplate.isAddressEnabled).toBe(false);
});
});
describe('Integration Flow - Complete Client Creation', () => {
it('should complete full client creation workflow', () => {
// 1. Component initializes
fixture.detectChanges();
expect(component).toBeTruthy();
expect(component.clientTemplate).toBeDefined();
// 2. User fills general information
if (component.clientGeneralStep) {
Object.defineProperty(component.clientGeneralStep, 'clientGeneralDetails', {
get: jest.fn(() => ({
firstname: 'Jane',
lastname: 'Smith',
officeId: 1,
active: true,
dateFormat: 'dd MMMM yyyy',
locale: 'en'
})),
configurable: true
});
}
// 3. User adds family members
if (component.clientFamilyMembersStep) {
Object.defineProperty(component.clientFamilyMembersStep, 'familyMembers', {
get: jest.fn(() => ({
familyMembers: [
{ firstName: 'John', relationship: 'Spouse' }]
})),
configurable: true
});
}
// 4. User adds address
if (component.clientAddressStep) {
Object.defineProperty(component.clientAddressStep, 'address', {
get: jest.fn(() => ({
address: [
{ addressTypeId: 1, street: '456 Oak Ave', city: 'Boston' }]
})),
configurable: true
});
}
// 5. Form validation passes
if (component.clientGeneralStep && component.clientGeneralStep.createClientForm) {
jest.spyOn(component.clientGeneralForm, 'valid', 'get').mockReturnValue(true);
}
// 6. User submits
component.submit();
// 7. Client is created
expect(mockClientsService.createClient).toHaveBeenCalled();
// 8. Navigation occurs
expect(mockRouter.navigate).toHaveBeenCalledWith(
expect.arrayContaining([
'../',
123
]),
expect.any(Object)
);
});
});
});
+64
View File
@@ -0,0 +1,64 @@
import { setupZoneTestEnv } from 'jest-preset-angular/setup-env/zone';
setupZoneTestEnv();
// Global test setup
Object.defineProperty(window, 'getComputedStyle', {
value: () => ({
getPropertyValue: () => ''
})
});
// Mock localStorage
const localStorageMock = {
getItem: jest.fn(),
setItem: jest.fn(),
removeItem: jest.fn(),
clear: jest.fn(),
length: 0,
key: jest.fn()
};
Object.defineProperty(window, 'localStorage', {
value: localStorageMock
});
// Mock sessionStorage
Object.defineProperty(window, 'sessionStorage', {
value: localStorageMock
});
// Mock matchMedia
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: jest.fn().mockImplementation((query) => ({
matches: false,
media: query,
onchange: null,
addListener: jest.fn(),
removeListener: jest.fn(),
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn()
}))
});
// Mock CSS.supports
Object.defineProperty(window, 'CSS', {
value: {
supports: jest.fn().mockReturnValue(false)
}
});
// Mock window.env for environment configuration
Object.defineProperty(window, 'env', {
value: {
oidcServerEnabled: false,
oidcBaseUrl: '',
oidcClientId: '',
oidcApiUrl: '',
apiUrl: 'http://localhost:8080',
tenantIdentifier: 'default'
},
writable: true
});
+7 -2
View File
@@ -2,9 +2,13 @@
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/spec",
"baseUrl": "./",
"baseUrl": "../",
"paths": {
"@pipes/*": ["src/app/pipes/*"],
"app/*": ["src/app/*"]
},
"types": [
"jasmine",
"jest",
"node"
]
},
@@ -14,6 +18,7 @@
],
"include": [
"**/*.spec.ts",
"**/*.test.ts",
"**/*.mock.ts",
"**/*.d.ts"
]