Skip to content

Commit

Permalink
Merge branch 'main' into feature/WFPREV-127
Browse files Browse the repository at this point in the history
  • Loading branch information
ssylver93 authored Feb 3, 2025
2 parents bf3e5a4 + 7f71cc8 commit 257a0ab
Show file tree
Hide file tree
Showing 11 changed files with 321 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,7 @@
<div class="dialog-content">
<div class="icon-and-text">
<img alt="warning-icon" class="icon" src="/assets/warning.svg" />
<p *ngIf="dialogUsage === 'confirm-cancel'">
Are you sure you want to cancel?<br />
This information will not be saved.
</p>
<p *ngIf="dialogUsage === 'duplicate-project'">
This Project already exists:<br />
</p>
<p *ngIf="dialogUsage === 'confirm-delete'">
Are you sure you want to delete this fiscal year?<br />
This action cannot be undone.
</p>
<p *ngIf="dialogMessage" [innerHTML]="dialogMessage"></p>
</div>
</div>
<div class="footer">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,4 +124,67 @@ describe('ConfirmationDialogComponent', () => {
expect(component.onConfirm).toHaveBeenCalled();
});
});

describe('with "confirm-delete" data', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [ConfirmationDialogComponent],
providers: [
{ provide: MatDialogRef, useValue: mockDialogRef },
{ provide: MAT_DIALOG_DATA, useValue: { indicator: 'confirm-delete' } },
],
}).compileComponents();

fixture = TestBed.createComponent(ConfirmationDialogComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should set dialogUsage to "confirm-delete"', () => {
expect(component.dialogUsage).toBe('confirm-delete');
});

it('should render the correct title for "confirm-delete"', () => {
const titleElement = fixture.nativeElement.querySelector('.title-bar');
expect(titleElement.textContent.trim()).toBe('Confirm Delete');
});

it('should display the correct message for "confirm-delete"', () => {
const messageElement = fixture.nativeElement.querySelector('.dialog-content p');
expect(messageElement.textContent.trim()).toContain('Are you sure you want to delete this fiscal year?');
expect(messageElement.textContent.trim()).toContain('This action cannot be undone.');
});
});

describe('with "confirm-unsave" data', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [ConfirmationDialogComponent],
providers: [
{ provide: MatDialogRef, useValue: mockDialogRef },
{ provide: MAT_DIALOG_DATA, useValue: { indicator: 'confirm-unsave' } },
],
}).compileComponents();

fixture = TestBed.createComponent(ConfirmationDialogComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should set dialogUsage to "confirm-unsave"', () => {
expect(component.dialogUsage).toBe('confirm-unsave');
});

it('should render the correct title for "confirm-unsave"', () => {
const titleElement = fixture.nativeElement.querySelector('.title-bar');
expect(titleElement.textContent.trim()).toBe('Confirm Unsave');
});

it('should display the correct message for "confirm-unsave"', () => {
const messageElement = fixture.nativeElement.querySelector('.dialog-content p');
expect(messageElement.textContent.trim()).toContain('Are you sure you want to leave this page?');
expect(messageElement.textContent.trim()).toContain('The changes you made will not be saved.');
});
});

});
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,27 @@ import { MatIconModule } from '@angular/material/icon';
],
})
export class ConfirmationDialogComponent {
dialogUsage: string;
dialogUsage: string = 'confirm-cancel';

private readonly dialogMessages: Record<string, string> = {
'confirm-cancel': 'Are you sure you want to cancel?<br />This information will not be saved.',
'duplicate-project': 'This Project already exists:<br />',
'confirm-delete': 'Are you sure you want to delete this fiscal year?<br />This action cannot be undone.',
'confirm-unsave': 'Are you sure you want to leave this page?<br />The changes you made will not be saved.'
};

get dialogMessage(): string {
return this.dialogMessages[this.dialogUsage] || '';
}

get dialogTitle(): string {
switch (this.dialogUsage) {
case 'confirm-cancel':
return 'Confirm Cancel';
case 'confirm-delete':
return 'Confirm Delete';
case 'confirm-unsave':
return 'Confirm Unsave'
default:
return 'Duplicate Found';
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { EditProjectComponent } from 'src/app/components/edit-project/edit-project.component';

import { CanDeactivateGuard } from 'src/app/services/util/can-deactive.guard';

const routes: Routes = [
{ path: '', component: EditProjectComponent }
{
path: '',
component: EditProjectComponent,
canDeactivate: [CanDeactivateGuard]
}
];

@NgModule({
imports: [RouterModule.forChild(routes), EditProjectComponent]
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
providers: [CanDeactivateGuard]
})
export class EditProjectModule {}
export class EditProjectModule {}
Original file line number Diff line number Diff line change
Expand Up @@ -85,4 +85,39 @@ describe('EditProjectComponent', () => {
const projectDetailsComponent = fixture.debugElement.query(By.directive(ProjectDetailsComponent));
expect(projectDetailsComponent).toBeTruthy();
});
});

it('should not reload ProjectFiscalsComponent if it is already loaded', () => {
component.projectFiscalsComponentRef = {} as any; // Mock that the component is already loaded
component.onTabChange({ index: 1 });

expect(component.fiscalsContainer).toBeTruthy(); // Ensures container is still present
});

it('should return true from canDeactivate() if no unsaved changes exist', () => {
component.projectFiscalsComponentRef = {
instance: {
isFormDirty: () => false,
},
} as any;

expect(component.canDeactivate()).toBe(true);
});

it('should call canDeactivate of ProjectFiscalsComponent if forms are dirty', () => {
const mockCanDeactivate = jasmine.createSpy().and.returnValue(true);

component.projectFiscalsComponentRef = {
instance: {
isFormDirty: () => true,
canDeactivate: mockCanDeactivate,
},
} as any;

expect(component.canDeactivate()).toBe(true);
expect(mockCanDeactivate).toHaveBeenCalled();
});




});
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { Component, ComponentRef, ViewChild, ViewContainerRef } from '@angular/core';
import { MatTabsModule } from '@angular/material/tabs';
import { Observable } from 'rxjs';
import { ProjectDetailsComponent } from 'src/app/components/edit-project/project-details/project-details.component';
import { ProjectFiscalsComponent } from 'src/app/components/edit-project/project-fiscals/project-fiscals.component';
import { CanComponentDeactivate } from 'src/app/services/util/can-deactive.guard';

@Component({
selector: 'app-edit-project',
Expand All @@ -10,10 +12,10 @@ import { ProjectFiscalsComponent } from 'src/app/components/edit-project/project
templateUrl: './edit-project.component.html',
styleUrl: './edit-project.component.scss'
})
export class EditProjectComponent {
export class EditProjectComponent implements CanComponentDeactivate {
projectName: string | null = null;
@ViewChild('fiscalsContainer', { read: ViewContainerRef }) fiscalsContainer!: ViewContainerRef;
private projectFiscalsComponentRef: ComponentRef<any> | null = null;
projectFiscalsComponentRef: ComponentRef<any> | null = null;

constructor() {}

Expand All @@ -29,4 +31,11 @@ export class EditProjectComponent {
);
}
}

canDeactivate(): Observable<boolean> | boolean {
if (this.projectFiscalsComponentRef?.instance?.isFormDirty()) {
return this.projectFiscalsComponentRef.instance.canDeactivate();
}
return true;
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ProjectFiscalsComponent } from './project-fiscals.component';
import { ActivatedRoute } from '@angular/router';
import { of, throwError } from 'rxjs';
import { Observable, of, throwError } from 'rxjs';
import { ProjectService } from 'src/app/services/project-services';
import { CodeTableServices } from 'src/app/services/code-table-services';
import { MatSnackBar } from '@angular/material/snack-bar';
import { FormBuilder } from '@angular/forms';
import { FormBuilder, FormGroup } from '@angular/forms';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { ConfirmationDialogComponent } from 'src/app/components/confirmation-dialog/confirmation-dialog.component';

Expand Down Expand Up @@ -281,5 +281,67 @@ describe('ProjectFiscalsComponent', () => {
it('should return false for isUndeletable if form is null', () => {
expect(component.isUndeletable(null)).toBe(false);
});

it('should return false if no forms are dirty', () => {
component.fiscalForms = [new FormGroup({})]; // No fields, not dirty
expect(component.isFormDirty()).toBe(false);
});

it('should return true if at least one form is dirty', () => {
const form1 = new FormGroup({});
spyOnProperty(form1, 'dirty', 'get').and.returnValue(true);

const form2 = new FormGroup({});
spyOnProperty(form2, 'dirty', 'get').and.returnValue(false);

component.fiscalForms = [form1, form2];

expect(component.isFormDirty()).toBe(true);
});

it('should return true from canDeactivate() if no forms are dirty', () => {
spyOn(component, 'isFormDirty').and.returnValue(false);
expect(component.canDeactivate()).toBe(true);
});

it('should open a confirmation dialog if forms are dirty and return false when user cancels', (done) => {
spyOn(component, 'isFormDirty').and.returnValue(true);

const mockDialogRef = { afterClosed: () => of(false) };
spyOn(component.dialog, 'open').and.returnValue(mockDialogRef as any);

const result = component.canDeactivate();

if (result instanceof Observable) {
result.subscribe((value: boolean) => {
expect(component.dialog.open).toHaveBeenCalledWith(ConfirmationDialogComponent, {
data: { indicator: 'confirm-unsave' },
width: '500px',
});
expect(value).toBe(false);
done();
});
} else {
fail('Expected an Observable but received something else');
}
});

it('should allow navigation if user confirms in the dialog', (done) => {
spyOn(component, 'isFormDirty').and.returnValue(true);

const mockDialogRef = { afterClosed: () => of(true) };
spyOn(component.dialog, 'open').and.returnValue(mockDialogRef as any);

const result = component.canDeactivate();

if (result instanceof Observable) {
result.subscribe((value: boolean) => {
expect(value).toBe(true);
done();
});
} else {
fail('Expected an Observable but received something else');
}
});

});
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { MatMenuModule } from '@angular/material/menu';
import { Observable } from 'rxjs';
import { MatDialog } from '@angular/material/dialog';
import { ConfirmationDialogComponent } from 'src/app/components/confirmation-dialog/confirmation-dialog.component';
import { CanComponentDeactivate } from 'src/app/services/util/can-deactive.guard';

@Component({
selector: 'app-project-fiscals',
Expand All @@ -32,7 +33,7 @@ import { ConfirmationDialogComponent } from 'src/app/components/confirmation-dia
MatMenuModule
]
})
export class ProjectFiscalsComponent implements OnInit {
export class ProjectFiscalsComponent implements OnInit, CanComponentDeactivate {
projectGuid = '';
projectFiscals: any[] = [];
fiscalForms: FormGroup[] = [];
Expand All @@ -58,6 +59,22 @@ export class ProjectFiscalsComponent implements OnInit {
this.generateFiscalYears();
this.loadProjectFiscals();
}


canDeactivate(): Observable<boolean> | boolean {
if (this.isFormDirty()) {
const dialogRef = this.dialog.open(ConfirmationDialogComponent, {
data: { indicator: 'confirm-unsave' },
width: '500px',
});
return dialogRef.afterClosed();
}
return true;
}

isFormDirty(): boolean {
return this.fiscalForms.some(form => form.dirty);
}

generateFiscalYears(): void {
const currentYear = new Date().getFullYear();
Expand Down
Loading

0 comments on commit 257a0ab

Please sign in to comment.