Skip to content
152 changes: 72 additions & 80 deletions frontend/src/test/task12/beneficiary.component.spec.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Component, DebugElement } from "@angular/core";
import { ComponentFixture, TestBed } from "@angular/core/testing";
import { ReactiveFormsModule } from "@angular/forms";
import { HttpClientTestingModule } from "@angular/common/http/testing";
Expand All @@ -6,67 +7,66 @@ import { BeneficiaryComponent } from "src/app/beneficiary/beneficiary.component"
import { BeneficiaryService } from "src/app/services/beneficiary.service";
import { AuthenticationService } from "src/app/services/authentication.service";
import { DarkThemeSelectorService } from "src/app/services/themeToggle.service";
import { DebugElement } from "@angular/core";
import { DragDropDirective } from "src/app/services/drag-drop.directive";
import { of } from "rxjs";
import { HttpErrorResponse } from "@angular/common/http";
import { By } from "@angular/platform-browser";
import { Renderer2 } from "@angular/core";

class MockBeneficiaryService {
getAllBeneficiaries() {
return of([
{
beneficiaryAccountId: "123",
name: "John Doe",
dateCreated: "2023-09-15",
},
]);
}

storeBeneficiary(accountId: number, beneficiaryAccountId: string) {
return of({ success: true });
}

getAllBeneficiaryIds() {
return of([{ beneficiary: "123" }, { beneficiary: "456" }]);
}
getAllBeneficiaries() { return of([{ beneficiaryAccountId: '123', name: 'John Doe' }]); }
storeBeneficiary(accountId: number, beneficiaryAccountId: string) { return of({ success: true }); }
getAllBeneficiaryIds() { return of([{ beneficiary: '123' }, { beneficiary: '456' }]); }
}

class MockAuthenticationService {
isAuthenticate() {
return of(true);
}

account() {
return of({ accountId: 1 });
}
isAuthenticate() { return of(true); }
account() { return of({ accountId: 1 }); }
}
class MockDarkThemeSelectorService { currentTheme = of('dark'); }

function makeDataTransfer(data: Record<string, string>) {
return {
getData: (k: string) => data[k] ?? '',
setData: () => {},
dropEffect: 'move' as const,
effectAllowed: 'move' as const,
} as unknown as DataTransfer;
}

class MockDarkThemeSelectorService {
currentTheme = of("dark");
// ===== Host just for directive tests =====
@Component({
template: `
<div
*ngFor="let b of beneficiaryList; trackBy: trackById"
[appDragDrop]
[draggableItem]="b"
[list]="beneficiaryList"
(listChange)="beneficiaryList = $event"
>
<span class="name">{{ b.name }}</span>
</div>
`
})
class HostComponent {
beneficiaryList = [
{ beneficiaryAccountId: '123', name: 'John Doe' },
{ beneficiaryAccountId: '456', name: 'Jane Smith' },
];
trackById(_: number, b: any) { return b.beneficiaryAccountId; }
}

describe("BeneficiaryComponent", () => {
let component: BeneficiaryComponent;
let fixture: ComponentFixture<BeneficiaryComponent>;
let debugElement: DebugElement;
let toastrService: ToastrService;

beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [BeneficiaryComponent, DragDropDirective],
imports: [
ReactiveFormsModule,
HttpClientTestingModule,
ToastrModule.forRoot(),
],
declarations: [BeneficiaryComponent, DragDropDirective, HostComponent],
imports: [ReactiveFormsModule, HttpClientTestingModule, ToastrModule.forRoot()],
providers: [
{ provide: BeneficiaryService, useClass: MockBeneficiaryService },
{ provide: AuthenticationService, useClass: MockAuthenticationService },
{
provide: DarkThemeSelectorService,
useClass: MockDarkThemeSelectorService,
},
{ provide: DarkThemeSelectorService, useClass: MockDarkThemeSelectorService },
],
}).compileComponents();
});
Expand All @@ -75,7 +75,6 @@ describe("BeneficiaryComponent", () => {
fixture = TestBed.createComponent(BeneficiaryComponent);
component = fixture.componentInstance;
toastrService = TestBed.inject(ToastrService);
debugElement = fixture.debugElement;
fixture.detectChanges();
});

Expand All @@ -88,52 +87,47 @@ describe("BeneficiaryComponent", () => {
);
});

// Successful form submission
it("should submit the form and add a new beneficiary", () => {
spyOn(toastrService, "success");
spyOn(component, "getAllBeneficiaries").and.callThrough();
it('should submit the form and add a new beneficiary', () => {
spyOn(toastrService, 'success');
spyOn(component, 'getAllBeneficiaries').and.callThrough();

component.beneficiaryForm.get("beneficiaryAccountId")?.setValue("123");
component.onSubmit();

expect(toastrService.success).toHaveBeenCalledWith(
"Beneficiary Added Successfully"
);
expect(toastrService.success).toHaveBeenCalledWith('Beneficiary Added Successfully');
expect(component.getAllBeneficiaries).toHaveBeenCalled();
});

it("should swap two adjacent beneficiaries correctly", () => {
const fixture = TestBed.createComponent(BeneficiaryComponent);
const component = fixture.componentInstance;
it('should swap two adjacent beneficiaries correctly', () => {
const hostFix = TestBed.createComponent(HostComponent);
hostFix.detectChanges();

// Initial beneficiaries setup
component.beneficiaryList = [
{
beneficiaryAccountId: "123",
name: "John Doe",
dateCreated: "2023-09-15",
},
{
beneficiaryAccountId: "456",
name: "Jane Smith",
dateCreated: "2023-09-16",
},
];
const des = hostFix.debugElement.queryAll(By.directive(DragDropDirective));
expect(des.length).toBeGreaterThanOrEqual(2);
const dropTargetDE = des[1];
const dir = dropTargetDE.injector.get(DragDropDirective) as DragDropDirective;

const host = hostFix.componentInstance;
dir.list = host.beneficiaryList;
dir.draggableItem = host.beneficiaryList[1];

// Simulate the drag and drop event to swap two adjacent beneficiaries
const dragIndex = 0; // John Doe is at index 0
const dropIndex = 1; // Jane Smith is at index 1
dir.onDrop({
preventDefault: () => {},
dataTransfer: makeDataTransfer({ 'text/plain': '0' }),
} as unknown as DragEvent);

// Perform the swap logic (swap two adjacent items)
const temp = component.beneficiaryList[dragIndex];
component.beneficiaryList[dragIndex] = component.beneficiaryList[dropIndex];
component.beneficiaryList[dropIndex] = temp;
host.beneficiaryList = host.beneficiaryList.filter(Boolean);
hostFix.detectChanges();

// Expect the items to be swapped
expect(component.beneficiaryList[0].beneficiaryAccountId).toBe("123"); // Jane Smith should now be at index 0
expect(component.beneficiaryList[1].beneficiaryAccountId).toBe("456"); // John Doe should now be at index 1
const actualIds = host.beneficiaryList.map(b => b.beneficiaryAccountId);

if (JSON.stringify(actualIds) === JSON.stringify(['456', '123'])) {
} else {
fail(`Beneficiary order mismatch: Expected sequence does not match actual sequence.`);
}
});


it("should not allow adding duplicate beneficiaries from the dropdown", () => {
const fixture = TestBed.createComponent(BeneficiaryComponent);
const component = fixture.componentInstance;
Expand All @@ -142,7 +136,6 @@ describe("BeneficiaryComponent", () => {
fixture.debugElement.injector.get(Renderer2)
);

// Existing beneficiary list with one beneficiary
component.beneficiaryList = [
{
beneficiaryAccountId: "123",
Expand All @@ -153,22 +146,21 @@ describe("BeneficiaryComponent", () => {

directive.list = component.beneficiaryList;

// Mocking a drop event from the dropdown
const dropEvent = new DragEvent("drop", {
dataTransfer: new DataTransfer(),
});

// Simulate dragging a duplicate beneficiary from the dropdown
dropEvent.dataTransfer?.setData("text/plain", "new-item");
dropEvent.dataTransfer?.setData(
"item",
JSON.stringify({ beneficiary: "123" })
);

// Simulate drop with the same beneficiary being dragged from the dropdown
directive.onDrop(dropEvent);

// Expect no duplicate beneficiary to be added
expect(component.beneficiaryList.length).toBe(1); // No new item added
expect(component.beneficiaryList.length).toBe(
1,
"A beneficiary dragged from the dropdown should not be added again if it's already in the list"
);
});
});