Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<div class="dialog-container-confirm">
<div class="title-bar">
<div class="mask-icon" [ngClass]="{ 'delete': isDeleteDialog, 'warning': !isDeleteDialog }" aria-hidden="true">
<div class="mask-icon" [ngClass]="{ 'delete': isDeleteOrChangeDialog , 'warning': !isDeleteOrChangeDialog }" aria-hidden="true">
</div>
{{ dialogTitle }}
</div>
Expand All @@ -11,11 +11,11 @@
</div>
<div class="footer">
<div class="button-row">
<button [ngClass]="isDeleteDialog ? 'cancel-button' : 'secondary'" (click)="onGoBack()">
<button [ngClass]="isDeleteOrChangeDialog ? 'cancel-button' : 'secondary'" (click)="onGoBack()">
Cancel
</button>

<button [ngClass]="isDeleteDialog ? 'delete-button' : 'primary'" (click)="onConfirm()">
<button [ngClass]="isDeleteOrChangeDialog ? 'delete-button' : 'primary'" (click)="onConfirm()">
{{ confirmButtonText }}
</button>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,15 +116,15 @@ describe('ConfirmationDialogComponent', () => {
});
});

describe('isDeleteDialog', () => {
describe('isDeleteOrChangeDialog ', () => {
it('should return true if dialogUsage starts with "delete-"', async () => {
await setupComponentWithData({ indicator: 'delete-fiscal-year' });
expect(component.isDeleteDialog).toBeTrue();
expect(component.isDeleteOrChangeDialog ).toBeTrue();
});

it('should return false if dialogUsage does not start with "delete-"', async () => {
await setupComponentWithData({ indicator: 'confirm-cancel' });
expect(component.isDeleteDialog).toBeFalse();
expect(component.isDeleteOrChangeDialog ).toBeFalse();
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ export class ConfirmationDialogComponent {
return 'Continue';
}

get isDeleteDialog(): boolean {
return this.dialogUsage.startsWith('delete-');
get isDeleteOrChangeDialog (): boolean {
return this.dialogUsage.startsWith('delete-') || this.dialogUsage.startsWith('change-');
}

constructor(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,8 @@ describe('EditProjectComponent', () => {
const mockInstance = {
focusedFiscalId: null,
loadProjectFiscals: jasmine.createSpy('loadProjectFiscals'),
getFirstFiscalGuid: jasmine.createSpy('getFirstFiscalGuid').and.returnValue('guid-123')
getFirstFiscalGuid: jasmine.createSpy('getFirstFiscalGuid').and.returnValue('guid-123'),
fiscalsUpdated: { subscribe: jasmine.createSpy('subscribe') }
};

const mockComponentRef = { instance: mockInstance } as any;
Expand Down Expand Up @@ -485,7 +486,8 @@ describe('EditProjectComponent (extended coverage)', () => {
it('should initialize ProjectFiscalsComponent in loadFiscalComponent', async () => {
const mockInstance = {
focusedFiscalId: null,
loadProjectFiscals: jasmine.createSpy('loadProjectFiscals')
loadProjectFiscals: jasmine.createSpy('loadProjectFiscals'),
fiscalsUpdated: { subscribe: jasmine.createSpy('subscribe') }
};

const mockComponentRef = { instance: mockInstance };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,9 @@ export class EditProjectComponent implements CanComponentDeactivate, OnInit {
this.projectFiscalsComponentRef = this.fiscalsContainer.createComponent(ProjectFiscalsComponent);
this.projectFiscalsComponentRef.instance.focusedFiscalId = this.focusedFiscalId;
this.projectFiscalsComponentRef.instance.loadProjectFiscals();
this.projectFiscalsComponentRef.instance.fiscalsUpdated.subscribe(() => {
this.projectDetailsComponent.reloadFiscals();
});
}
);
} else if (event.index === 0) {
Expand Down Expand Up @@ -186,6 +189,9 @@ export class EditProjectComponent implements CanComponentDeactivate, OnInit {
this.projectFiscalsComponentRef = this.fiscalsContainer.createComponent(ProjectFiscalsComponent);
this.projectFiscalsComponentRef.instance.focusedFiscalId = this.focusedFiscalId;
this.projectFiscalsComponentRef.instance.loadProjectFiscals();
this.projectFiscalsComponentRef.instance.fiscalsUpdated.subscribe(() => {
this.projectDetailsComponent.reloadFiscals();
});
});
}
return Promise.resolve();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@
[options]="projectTypeCode"
[optionValueField]="'projectTypeCode'"
[optionLabelField]="'description'"
[tooltip]="getCodeDescription(CodeTableKeys.PROJECT_TYPE_CODE)"
[tooltip]="projectTypeLocked
? messages.projectTypeCannotUpdateAfterEndorsed
: getCodeDescription(CodeTableKeys.PROJECT_TYPE_CODE)"
[disabled]="projectTypeLocked"
id="projectTypeCode"
placeholder="Select"
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { ActivatedRoute } from '@angular/router';
import { formatLatLong } from 'src/app/utils/tools';
import { CodeTableKeys } from 'src/app/utils/constants';
import * as toolUtils from 'src/app/utils/tools'
import { EvaluationCriteriaSummaryModel, ProjectFiscal } from 'src/app/components/models';
const mockApplicationConfig = {
application: {
baseUrl: 'http://test.com',
Expand Down Expand Up @@ -76,7 +77,8 @@ describe('ProjectDetailsComponent', () => {
'getProjectByProjectGuid',
'getProjectFiscalsByProjectGuid',
'getFiscalActivities',
'getActivityBoundaries'
'getActivityBoundaries',
'deleteEvaluationCriteriaSummary'
]);
mockSnackbar = jasmine.createSpyObj('MatSnackBar', ['open']);

Expand Down Expand Up @@ -1184,6 +1186,145 @@ describe('ProjectDetailsComponent', () => {
});
});

describe('onSave projectType change confirmation', () => {
let dialogSpy: jasmine.SpyObj<any>;

beforeEach(() => {
dialogSpy = jasmine.createSpyObj('MatDialog', ['open']);
component['dialog'] = dialogSpy;

component.originalFormValues = { projectTypeCode: 'OLD_TYPE' };
component.detailsForm.patchValue({ projectTypeCode: 'NEW_TYPE' });

component.evaluationCriteriaComponent = {
evaluationCriteriaSummary: {
evaluationCriteriaSummaryGuid: 'guid-123'
}
} as any;

mockProjectService.deleteEvaluationCriteriaSummary.and.returnValue(of({}));
});

it('should open confirmation dialog when projectType changes and evaluationCriteriaSummary exists', fakeAsync(() => {
const afterClosedSpy = jasmine.createSpy().and.returnValue(of(true));
dialogSpy.open.and.returnValue({ afterClosed: afterClosedSpy });

spyOn(component, 'onSave').and.callThrough();

component.onSave();
tick();

expect(dialogSpy.open).toHaveBeenCalledWith(jasmine.any(Function), jasmine.objectContaining({
data: jasmine.objectContaining({ indicator: 'change-project-type' })
}));
expect(afterClosedSpy).toHaveBeenCalled();
expect(mockProjectService.deleteEvaluationCriteriaSummary)
.toHaveBeenCalledWith(component.projectGuid, 'guid-123');
expect(component.onSave).toHaveBeenCalledTimes(2); // recursive call after delete
}));

it('should log warning and skip delete if summaryGuid is missing', fakeAsync(() => {
component.evaluationCriteriaComponent = {
evaluationCriteriaSummary: {} as EvaluationCriteriaSummaryModel
} as any;
const afterClosedSpy = jasmine.createSpy().and.returnValue(of(true));
dialogSpy.open.and.returnValue({ afterClosed: afterClosedSpy });

const consoleSpy = spyOn(console, 'warn');

component.onSave();
tick();

expect(consoleSpy).toHaveBeenCalledWith('No evaluationCriteriaSummaryGuid found, skipping delete.');
expect(mockProjectService.deleteEvaluationCriteriaSummary).not.toHaveBeenCalled();
}));

it('should log when user cancels the dialog', fakeAsync(() => {
const afterClosedSpy = jasmine.createSpy().and.returnValue(of(false));
dialogSpy.open.and.returnValue({ afterClosed: afterClosedSpy });

const consoleSpy = spyOn(console, 'log');

component.onSave();
tick();

expect(consoleSpy).toHaveBeenCalledWith('User canceled project type change, save aborted.');
expect(mockProjectService.deleteEvaluationCriteriaSummary).not.toHaveBeenCalled();
}));

it('should log error if deleteEvaluationCriteriaSummary fails', fakeAsync(() => {
const afterClosedSpy = jasmine.createSpy().and.returnValue(of(true));
dialogSpy.open.and.returnValue({ afterClosed: afterClosedSpy });
mockProjectService.deleteEvaluationCriteriaSummary.and.returnValue(
throwError(() => new Error('delete failed'))
);

const consoleErrorSpy = spyOn(console, 'error');

component.onSave();
tick();

expect(consoleErrorSpy).toHaveBeenCalledWith(
'Failed to delete evaluation criteria summary',
jasmine.any(Error)
);
}));
});

describe('hasApprovedFiscals Method', () => {
it('should return true if any fiscal has a locked status', () => {
const fiscals = [
{ planFiscalStatusCode: { planFiscalStatusCode: 'PREPARED' } },
{ planFiscalStatusCode: { planFiscalStatusCode: 'DRAFT' } }
] as ProjectFiscal[];

expect(component.hasApprovedFiscals(fiscals)).toBeTrue();
});

it('should return false if no fiscals have a locked status', () => {
const fiscals = [
{ planFiscalStatusCode: { planFiscalStatusCode: 'DRAFT' } },
{ planFiscalStatusCode: { planFiscalStatusCode: '' } }
] as ProjectFiscal[];

expect(component.hasApprovedFiscals(fiscals)).toBeFalse();
});

});

describe('reloadFiscals Method', () => {
it('should call getProjectFiscalsByProjectGuid and pass response to handleFiscalsResponse', () => {
const mockData = { _embedded: { projectFiscals: [] } };
spyOn(component as any, 'handleFiscalsResponse');
mockProjectService.getProjectFiscalsByProjectGuid.and.returnValue(of(mockData));

component.projectGuid = 'test-guid';
component.reloadFiscals();

expect(mockProjectService.getProjectFiscalsByProjectGuid).toHaveBeenCalledWith('test-guid');
expect((component as any).handleFiscalsResponse).toHaveBeenCalledWith(mockData);
});

it('should not call service if projectGuid is empty', () => {
component.projectGuid = '';
component.reloadFiscals();

expect(mockProjectService.getProjectFiscalsByProjectGuid).not.toHaveBeenCalled();
});

it('should log error when service call fails', () => {
spyOn(console, 'error');
mockProjectService.getProjectFiscalsByProjectGuid.and.returnValue(
throwError(() => new Error('Service error'))
);

component.projectGuid = 'test-guid';
component.reloadFiscals();

expect(console.error).toHaveBeenCalledWith('Error reloading fiscals:', jasmine.any(Error));
});
});

it('should set duplicate error and show snackbar when updateProject returns 409', () => {
component.projectGuid = 'test-guid';
component.projectDetail = {
Expand Down Expand Up @@ -1225,6 +1366,4 @@ describe('ProjectDetailsComponent', () => {
);
});



});
Loading
Loading