diff --git a/libs/remission/feature/remission-return-receipt-details/src/lib/remission-return-receipt-details-item.component.spec.ts b/libs/remission/feature/remission-return-receipt-details/src/lib/remission-return-receipt-details-item.component.spec.ts
index d757b9631..8561ba2a3 100644
--- a/libs/remission/feature/remission-return-receipt-details/src/lib/remission-return-receipt-details-item.component.spec.ts
+++ b/libs/remission/feature/remission-return-receipt-details/src/lib/remission-return-receipt-details-item.component.spec.ts
@@ -1,8 +1,7 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { describe, it, expect, beforeEach, vi } from 'vitest';
-import { MockComponent, MockDirective, MockProvider } from 'ng-mocks';
-import { of } from 'rxjs';
+import { MockComponent, MockDirective } from 'ng-mocks';
import { RemissionReturnReceiptDetailsItemComponent } from './remission-return-receipt-details-item.component';
import { ProductFormatComponent } from '@isa/shared/product-foramt';
import { ProductImageDirective } from '@isa/shared/product-image';
@@ -13,10 +12,6 @@ import {
RemissionReturnReceiptService,
} from '@isa/remission/data-access';
import { IconButtonComponent } from '@isa/ui/buttons';
-import {
- BulletListComponent,
- BulletListItemComponent,
-} from '@isa/ui/bullet-list';
describe('RemissionReturnReceiptDetailsItemComponent', () => {
let component: RemissionReturnReceiptDetailsItemComponent;
@@ -90,6 +85,10 @@ describe('RemissionReturnReceiptDetailsItemComponent', () => {
component = fixture.componentInstance;
});
+ afterEach(() => {
+ mockRemissionReturnReceiptService.removeReturnItemFromReturnReceipt.mockClear();
+ });
+
describe('Component Setup', () => {
it('should create', () => {
fixture.componentRef.setInput('item', mockReceiptItem);
diff --git a/libs/remission/feature/remission-return-receipt-list/src/lib/remission-return-receipt-list.component.spec.ts b/libs/remission/feature/remission-return-receipt-list/src/lib/remission-return-receipt-list.component.spec.ts
index 568374a93..b52a474bf 100644
--- a/libs/remission/feature/remission-return-receipt-list/src/lib/remission-return-receipt-list.component.spec.ts
+++ b/libs/remission/feature/remission-return-receipt-list/src/lib/remission-return-receipt-list.component.spec.ts
@@ -1,14 +1,15 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { describe, it, expect, beforeEach, vi } from 'vitest';
-import { MockComponent, MockProvider } from 'ng-mocks';
+import { Component, signal } from '@angular/core';
+import { MockProvider } from 'ng-mocks';
import { RemissionReturnReceiptListComponent } from './remission-return-receipt-list.component';
-import { ReturnReceiptListItemComponent } from './return-receipt-list-item/return-receipt-list-item.component';
import {
RemissionReturnReceiptService,
Return,
+ Receipt,
} from '@isa/remission/data-access';
-import { OrderByToolbarComponent, FilterService } from '@isa/shared/filter';
-import { signal } from '@angular/core';
+import { FilterService } from '@isa/shared/filter';
+import { of } from 'rxjs';
// Mock the filter providers
vi.mock('@isa/shared/filter', async () => {
@@ -21,100 +22,111 @@ vi.mock('@isa/shared/filter', async () => {
};
});
+// Mock child components
+@Component({
+ selector: 'remi-return-receipt-list-item',
+ template: '
Mock Return Receipt List Item
',
+ standalone: true,
+})
+class MockReturnReceiptListItemComponent {}
+
+@Component({
+ selector: 'isa-order-by-toolbar',
+ template: 'Mock Order By Toolbar
',
+ standalone: true,
+})
+class MockOrderByToolbarComponent {}
+
describe('RemissionReturnReceiptListComponent', () => {
let component: RemissionReturnReceiptListComponent;
let fixture: ComponentFixture;
let mockRemissionReturnReceiptService: {
- fetchCompletedRemissionReturnReceipts: ReturnType;
- fetchIncompletedRemissionReturnReceipts: ReturnType;
- };
- let mockFilterService: {
- orderBy: any;
+ fetchRemissionReturnReceipts: ReturnType;
};
+ let mockFilterService: any;
- const mockCompletedReturns: Return[] = [
- {
- id: 1,
- receipts: [
- {
+ const mockCompletedReturn: Return = {
+ id: 1,
+ completed: '2024-01-15T10:30:00.000Z',
+ receipts: [
+ {
+ id: 101,
+ data: {
id: 101,
- data: {
- id: 101,
- receiptNumber: 'REC-2024-001',
- created: '2024-01-15T09:00:00.000Z',
- completed: '2024-01-15T10:30:00.000Z',
- items: [],
- },
- },
- ],
- } as Return,
- {
- id: 2,
- receipts: [
- {
- id: 102,
- data: {
- id: 102,
- receiptNumber: 'REC-2024-002',
- created: '2024-01-16T13:00:00.000Z',
- completed: '2024-01-16T14:45:00.000Z',
- items: [],
- },
- },
- ],
- } as Return,
- ];
+ receiptNumber: 'REC-2024-001',
+ created: '2024-01-15T09:00:00.000Z',
+ completed: '2024-01-15T10:30:00.000Z',
+ items: [],
+ } as Receipt,
+ },
+ ],
+ } as Return;
- const mockIncompletedReturns: Return[] = [
- {
- id: 3,
- receipts: [
- {
- id: 103,
- data: {
- id: 103,
- receiptNumber: 'REC-2024-003',
- created: '2024-01-17T08:00:00.000Z',
- completed: undefined,
- items: [],
- },
- },
- ],
- } as Return,
- ];
+ const mockIncompletedReturn: Return = {
+ id: 2,
+ completed: undefined,
+ receipts: [
+ {
+ id: 102,
+ data: {
+ id: 102,
+ receiptNumber: 'REC-2024-002',
+ created: '2024-01-16T08:00:00.000Z',
+ completed: undefined,
+ items: [],
+ } as Receipt,
+ },
+ ],
+ } as Return;
+
+ const mockReturnWithoutReceiptData: Return = {
+ id: 3,
+ completed: '2024-01-17T10:30:00.000Z',
+ receipts: [
+ {
+ id: 103,
+ data: undefined,
+ },
+ ],
+ } as Return;
+
+ const mockReturns = [mockCompletedReturn, mockIncompletedReturn];
beforeEach(async () => {
+ // Arrange: Setup mocks
mockRemissionReturnReceiptService = {
- fetchCompletedRemissionReturnReceipts: vi
- .fn()
- .mockResolvedValue(mockCompletedReturns),
- fetchIncompletedRemissionReturnReceipts: vi
- .fn()
- .mockResolvedValue(mockIncompletedReturns),
+ fetchRemissionReturnReceipts: vi.fn().mockReturnValue(of(mockReturns)),
};
mockFilterService = {
- orderBy: signal([{ selected: false, by: 'created', dir: 'asc' }]),
+ orderBy: signal([]),
};
await TestBed.configureTestingModule({
- imports: [RemissionReturnReceiptListComponent],
+ imports: [
+ RemissionReturnReceiptListComponent,
+ MockReturnReceiptListItemComponent,
+ MockOrderByToolbarComponent,
+ ],
providers: [
MockProvider(
RemissionReturnReceiptService,
- mockRemissionReturnReceiptService,
+ mockRemissionReturnReceiptService
),
- MockProvider(FilterService, mockFilterService),
+ {
+ provide: FilterService,
+ useValue: mockFilterService,
+ },
],
})
.overrideComponent(RemissionReturnReceiptListComponent, {
remove: {
- imports: [ReturnReceiptListItemComponent, OrderByToolbarComponent],
+ imports: [],
},
add: {
imports: [
- MockComponent(ReturnReceiptListItemComponent),
- MockComponent(OrderByToolbarComponent),
+ MockReturnReceiptListItemComponent,
+ MockOrderByToolbarComponent,
],
},
})
@@ -124,391 +136,490 @@ describe('RemissionReturnReceiptListComponent', () => {
component = fixture.componentInstance;
});
- describe('Component Setup', () => {
- it('should create', () => {
+ describe('Component Initialization', () => {
+ it('should create the component', () => {
+ // Assert
expect(component).toBeTruthy();
});
- it('should have correct configuration', () => {
+ it('should inject dependencies correctly', () => {
+ // Assert - Private fields cannot be directly tested
+ // Instead, we verify the component initializes correctly with dependencies
expect(component).toBeDefined();
+ expect(component.remissionReturnsResource).toBeDefined();
+ expect(component.orderDateBy).toBeDefined();
+ });
+
+ it('should render the component', () => {
+ // Act
+ fixture.detectChanges();
+
+ // Assert
+ expect(fixture.nativeElement).toBeTruthy();
expect(fixture.componentInstance).toBe(component);
});
});
- describe('Resources Loading', () => {
- it('should initialize resources on creation', () => {
- // Resources are created in the component constructor
- expect(component.completedRemissionReturnsResource).toBeDefined();
- expect(component.incompletedRemissionReturnsResource).toBeDefined();
+ describe('Resource Loading', () => {
+ it('should initialize remissionReturnsResource', () => {
+ // Assert
+ expect(component.remissionReturnsResource).toBeDefined();
});
- it('should call service methods when resources load', async () => {
- // Create a new component instance to test fresh loading
- const newFixture = TestBed.createComponent(
- RemissionReturnReceiptListComponent,
- );
+ it('should fetch remission return receipts on component initialization', () => {
+ // Arrange
+ mockRemissionReturnReceiptService.fetchRemissionReturnReceipts.mockClear();
- // Clear previous calls
- mockRemissionReturnReceiptService.fetchCompletedRemissionReturnReceipts.mockClear();
- mockRemissionReturnReceiptService.fetchIncompletedRemissionReturnReceipts.mockClear();
+ // Act
+ fixture.detectChanges();
- // Initialize the component to trigger resource loading
- newFixture.detectChanges();
- await newFixture.whenStable();
-
- // Verify that both service methods were called when resources load
+ // Assert
expect(
- mockRemissionReturnReceiptService.fetchCompletedRemissionReturnReceipts,
- ).toHaveBeenCalled();
- expect(
- mockRemissionReturnReceiptService.fetchIncompletedRemissionReturnReceipts,
+ mockRemissionReturnReceiptService.fetchRemissionReturnReceipts
).toHaveBeenCalled();
});
it('should handle loading state', () => {
- // Check loading state
- expect(
- component.completedRemissionReturnsResource.isLoading(),
- ).toBeDefined();
- expect(
- component.incompletedRemissionReturnsResource.isLoading(),
- ).toBeDefined();
+ // Arrange
+ mockRemissionReturnReceiptService.fetchRemissionReturnReceipts.mockReturnValue(
+ new Promise(() => undefined) // Never resolving promise to simulate loading
+ );
+
+ // Act
+ const newFixture = TestBed.createComponent(
+ RemissionReturnReceiptListComponent
+ );
+ const newComponent = newFixture.componentInstance;
+
+ // Assert
+ expect(newComponent.remissionReturnsResource.isLoading()).toBeDefined();
});
it('should handle error state when service fails', async () => {
- // Mock service to throw errors
- mockRemissionReturnReceiptService.fetchCompletedRemissionReturnReceipts.mockRejectedValue(
- new Error('Completed returns service failed')
- );
- mockRemissionReturnReceiptService.fetchIncompletedRemissionReturnReceipts.mockRejectedValue(
- new Error('Incomplete returns service failed')
+ // Arrange
+ const errorMessage = 'Service failed';
+ mockRemissionReturnReceiptService.fetchRemissionReturnReceipts.mockReturnValue(
+ Promise.reject(new Error(errorMessage))
);
- // Create a new component to test error handling
- const errorFixture = TestBed.createComponent(RemissionReturnReceiptListComponent);
- const errorComponent = errorFixture.componentInstance;
-
- // Trigger change detection to initiate resource loading
+ // Act
+ const errorFixture = TestBed.createComponent(
+ RemissionReturnReceiptListComponent
+ );
errorFixture.detectChanges();
await errorFixture.whenStable();
- // Check that resources have error signals available
- expect(errorComponent.completedRemissionReturnsResource.error).toBeDefined();
- expect(errorComponent.incompletedRemissionReturnsResource.error).toBeDefined();
-
- // Check that status signals indicate error states
- expect(errorComponent.completedRemissionReturnsResource.status).toBeDefined();
- expect(errorComponent.incompletedRemissionReturnsResource.status).toBeDefined();
+ // Assert
+ const errorComponent = errorFixture.componentInstance;
+ expect(errorComponent.remissionReturnsResource.error).toBeDefined();
});
});
describe('returns computed signal', () => {
- it('should combine completed and incompleted returns with incompleted first', async () => {
- // Mock the resource values
- (component.completedRemissionReturnsResource as any).value =
- signal(mockCompletedReturns);
- (component.incompletedRemissionReturnsResource as any).value = signal(
- mockIncompletedReturns,
+ it('should combine returns with incompleted first', () => {
+ // Arrange
+ vi.spyOn(component.remissionReturnsResource, 'value').mockReturnValue(
+ mockReturns
);
+ // Act
const returns = component.returns();
- expect(returns).toHaveLength(3);
- // Returns should be tuples [Return, Receipt]
- expect(returns[0][0]).toBe(mockIncompletedReturns[0]); // Incompleted first
- expect(returns[0][1]).toBe(mockIncompletedReturns[0].receipts[0].data);
- expect(returns[1][0]).toBe(mockCompletedReturns[0]);
- expect(returns[1][1]).toBe(mockCompletedReturns[0].receipts[0].data);
- expect(returns[2][0]).toBe(mockCompletedReturns[1]);
- expect(returns[2][1]).toBe(mockCompletedReturns[1].receipts[0].data);
- });
-
- it('should handle empty completed returns', () => {
- (component.completedRemissionReturnsResource as any).value = signal([]);
- (component.incompletedRemissionReturnsResource as any).value = signal(
- mockIncompletedReturns,
- );
-
- const returns = component.returns();
-
- expect(returns).toHaveLength(1);
- expect(returns[0][0]).toBe(mockIncompletedReturns[0]);
- expect(returns[0][1]).toBe(mockIncompletedReturns[0].receipts[0].data);
- });
-
- it('should handle empty incompleted returns', () => {
- (component.completedRemissionReturnsResource as any).value =
- signal(mockCompletedReturns);
- (component.incompletedRemissionReturnsResource as any).value = signal([]);
-
- const returns = component.returns();
-
+ // Assert
expect(returns).toHaveLength(2);
- expect(returns[0][0]).toBe(mockCompletedReturns[0]);
- expect(returns[0][1]).toBe(mockCompletedReturns[0].receipts[0].data);
- expect(returns[1][0]).toBe(mockCompletedReturns[1]);
- expect(returns[1][1]).toBe(mockCompletedReturns[1].receipts[0].data);
+ expect(returns[0][0]).toBe(mockIncompletedReturn);
+ expect(returns[0][1]).toBe(mockIncompletedReturn.receipts[0].data);
+ expect(returns[1][0]).toBe(mockCompletedReturn);
+ expect(returns[1][1]).toBe(mockCompletedReturn.receipts[0].data);
});
- it('should handle both empty returns', () => {
- (component.completedRemissionReturnsResource as any).value = signal([]);
- (component.incompletedRemissionReturnsResource as any).value = signal([]);
-
- const returns = component.returns();
-
- expect(returns).toHaveLength(0);
- expect(returns).toEqual([]);
- });
-
- it('should handle null values from resources', () => {
- (component.completedRemissionReturnsResource as any).value = signal(null);
- (component.incompletedRemissionReturnsResource as any).value =
- signal(null);
-
- const returns = component.returns();
-
- expect(returns).toHaveLength(0);
- expect(returns).toEqual([]);
- });
-
- it('should handle undefined values from resources', () => {
- (component.completedRemissionReturnsResource as any).value =
- signal(undefined);
- (component.incompletedRemissionReturnsResource as any).value =
- signal(undefined);
-
- const returns = component.returns();
-
- expect(returns).toHaveLength(0);
- expect(returns).toEqual([]);
- });
-
- it('should handle mixed null and valid values', () => {
- (component.completedRemissionReturnsResource as any).value =
- signal(mockCompletedReturns);
- (component.incompletedRemissionReturnsResource as any).value =
- signal(null);
-
- const returns = component.returns();
-
- expect(returns).toHaveLength(2);
- expect(returns[0][0]).toBe(mockCompletedReturns[0]);
- expect(returns[0][1]).toBe(mockCompletedReturns[0].receipts[0].data);
- expect(returns[1][0]).toBe(mockCompletedReturns[1]);
- expect(returns[1][1]).toBe(mockCompletedReturns[1].receipts[0].data);
- });
- });
-
- describe('Query Settings', () => {
- it('should have correct filter configuration', () => {
- // This is tested indirectly through the component setup
- // The actual filter behavior would be tested in integration tests
- expect(component).toBeDefined();
- });
-
- it('should define order by options', () => {
- // The QUERY_SETTINGS constant is private, but we can verify
- // that the component is configured with filter providers
- expect(component).toBeDefined();
- });
- });
-
- describe('Component Lifecycle', () => {
- it('should handle component destruction', () => {
- fixture.destroy();
- expect(component).toBeDefined();
- });
-
- it('should update when input changes', async () => {
- // Simulate resource updates
- const newCompletedReturns = [
- {
- id: 4,
- receipts: [
- {
- id: 104,
- data: {
- id: 104,
- receiptNumber: 'REC-2024-004',
- completed: true,
- },
- },
- ],
- } as Return,
- ];
-
- mockRemissionReturnReceiptService.fetchCompletedRemissionReturnReceipts.mockResolvedValue(
- newCompletedReturns,
- );
-
- // Mock resource value update
- (component.completedRemissionReturnsResource as any).value =
- signal(newCompletedReturns);
- (component.incompletedRemissionReturnsResource as any).value = signal(
- mockIncompletedReturns,
+ it('should filter out receipts without data', () => {
+ // Arrange
+ const returnsWithNullData = [mockCompletedReturn, mockReturnWithoutReceiptData];
+ vi.spyOn(component.remissionReturnsResource, 'value').mockReturnValue(
+ returnsWithNullData
);
+ // Act
const returns = component.returns();
- // Check that the tuple contains the new return
- expect(
- returns.some(
- ([returnData, _]) => returnData === newCompletedReturns[0],
- ),
- ).toBe(true);
- });
- });
-
- describe('Error Handling', () => {
- it('should handle service errors gracefully', async () => {
- // Mock one service to succeed and one to fail
- mockRemissionReturnReceiptService.fetchCompletedRemissionReturnReceipts.mockRejectedValue(
- new Error('Network error')
- );
- mockRemissionReturnReceiptService.fetchIncompletedRemissionReturnReceipts.mockResolvedValue(
- mockIncompletedReturns
- );
-
- // Create a new component to test graceful error handling
- const errorFixture = TestBed.createComponent(RemissionReturnReceiptListComponent);
- const errorComponent = errorFixture.componentInstance;
-
- // Trigger resource loading
- errorFixture.detectChanges();
- await errorFixture.whenStable();
-
- // Verify that the component handles errors gracefully
- // The component should still function with partial data
- expect(errorComponent).toBeTruthy();
- expect(errorComponent.completedRemissionReturnsResource).toBeDefined();
- expect(errorComponent.incompletedRemissionReturnsResource).toBeDefined();
-
- // Mock successful resource values for the returns computed signal test
- (errorComponent.completedRemissionReturnsResource as any).value = signal(null);
- (errorComponent.incompletedRemissionReturnsResource as any).value = signal(mockIncompletedReturns);
-
- // The returns computed signal should handle null/error states gracefully
- const returns = errorComponent.returns();
+ // Assert
expect(returns).toHaveLength(1);
- expect(returns[0][0]).toBe(mockIncompletedReturns[0]);
+ expect(returns[0][0]).toBe(mockCompletedReturn);
+ expect(returns[0][1]).toBe(mockCompletedReturn.receipts[0].data);
});
- it('should handle partial failures', async () => {
- mockRemissionReturnReceiptService.fetchCompletedRemissionReturnReceipts.mockRejectedValue(
- new Error('Failed'),
- );
- mockRemissionReturnReceiptService.fetchIncompletedRemissionReturnReceipts.mockResolvedValue(
- mockIncompletedReturns,
+ it('should handle empty returns array', () => {
+ // Arrange
+ vi.spyOn(component.remissionReturnsResource, 'value').mockReturnValue([]);
+
+ // Act
+ const returns = component.returns();
+
+ // Assert
+ expect(returns).toHaveLength(0);
+ expect(returns).toEqual([]);
+ });
+
+ it('should handle null value from resource', () => {
+ // Arrange
+ vi.spyOn(component.remissionReturnsResource, 'value').mockReturnValue(
+ null as any
);
+ // Act
+ const returns = component.returns();
+
+ // Assert
+ expect(returns).toHaveLength(0);
+ expect(returns).toEqual([]);
+ });
+
+ it('should handle undefined value from resource', () => {
+ // Arrange
+ vi.spyOn(component.remissionReturnsResource, 'value').mockReturnValue(
+ undefined as Return[] | undefined
+ );
+
+ // Act
+ const returns = component.returns();
+
+ // Assert
+ expect(returns).toHaveLength(0);
+ expect(returns).toEqual([]);
+ });
+
+ it('should flatten multiple receipts per return', () => {
+ // Arrange
+ const returnWithMultipleReceipts: Return = {
+ id: 4,
+ completed: '2024-01-15T10:00:00.000Z',
+ receipts: [
+ {
+ id: 201,
+ data: {
+ id: 201,
+ receiptNumber: 'REC-2024-201',
+ created: '2024-01-15T09:00:00.000Z',
+ completed: '2024-01-15T10:00:00.000Z',
+ items: [],
+ } as Receipt,
+ },
+ {
+ id: 202,
+ data: {
+ id: 202,
+ receiptNumber: 'REC-2024-202',
+ created: '2024-01-15T10:00:00.000Z',
+ completed: '2024-01-15T11:00:00.000Z',
+ items: [],
+ } as Receipt,
+ },
+ ],
+ } as Return;
+
+ vi.spyOn(component.remissionReturnsResource, 'value').mockReturnValue([
+ returnWithMultipleReceipts,
+ ]);
+
+ // Act
+ const returns = component.returns();
+
+ // Assert
+ expect(returns).toHaveLength(2);
+ expect(returns[0][0]).toBe(returnWithMultipleReceipts);
+ expect(returns[0][1]).toBe(returnWithMultipleReceipts.receipts[0].data);
+ expect(returns[1][0]).toBe(returnWithMultipleReceipts);
+ expect(returns[1][1]).toBe(returnWithMultipleReceipts.receipts[1].data);
+ });
+ });
+
+ describe('orderDateBy computed signal', () => {
+ it('should return undefined when no order is selected', () => {
+ // Arrange
+ mockFilterService.orderBy = signal([]);
+
+ // Act
+ const orderBy = component.orderDateBy();
+
+ // Assert
+ expect(orderBy).toBeUndefined();
+ });
+
+ it('should return selected order option', () => {
+ // Arrange
+ const selectedOrder = { selected: true, by: 'created', dir: 'desc' };
+ const notSelectedOrder = { selected: false, by: 'completed', dir: 'asc' };
+
+ // Update the existing mockFilterService signal
+ mockFilterService.orderBy.set([notSelectedOrder, selectedOrder]);
+
const newFixture = TestBed.createComponent(
- RemissionReturnReceiptListComponent,
+ RemissionReturnReceiptListComponent
);
const newComponent = newFixture.componentInstance;
- await newFixture.whenStable();
+ // Act
+ const orderBy = newComponent.orderDateBy();
- // Mock the resource values for testing
- (newComponent.completedRemissionReturnsResource as any).value =
- signal(null);
- (newComponent.incompletedRemissionReturnsResource as any).value = signal(
- mockIncompletedReturns,
+ // Assert
+ expect(orderBy).toBe(selectedOrder);
+ });
+ });
+
+ describe('Sorting functionality', () => {
+ it('should sort returns by created date in descending order', () => {
+ // Arrange
+ const orderOption = { selected: true, by: 'created', dir: 'desc' };
+ mockFilterService.orderBy.set([orderOption]);
+
+ const olderReturn: Return = {
+ id: 10,
+ completed: '2024-01-15T10:00:00.000Z',
+ created: '2024-01-10T09:00:00.000Z',
+ receipts: [
+ {
+ id: 301,
+ data: {
+ id: 301,
+ receiptNumber: 'REC-2024-301',
+ created: '2024-01-10T09:00:00.000Z',
+ completed: '2024-01-10T10:00:00.000Z',
+ items: [],
+ } as Receipt,
+ },
+ ],
+ } as Return;
+
+ const newerReturn: Return = {
+ id: 11,
+ completed: '2024-01-15T10:00:00.000Z',
+ created: '2024-01-20T09:00:00.000Z',
+ receipts: [
+ {
+ id: 302,
+ data: {
+ id: 302,
+ receiptNumber: 'REC-2024-302',
+ created: '2024-01-20T09:00:00.000Z',
+ completed: '2024-01-20T10:00:00.000Z',
+ items: [],
+ } as Receipt,
+ },
+ ],
+ } as Return;
+
+ vi.spyOn(component.remissionReturnsResource, 'value').mockReturnValue([
+ olderReturn,
+ newerReturn,
+ ]);
+
+ // Act
+ const returns = component.returns();
+
+ // Assert
+ expect(returns).toHaveLength(2);
+ expect(returns[0][0]).toBe(newerReturn); // Newer date should come first in desc order
+ expect(returns[1][0]).toBe(olderReturn);
+ });
+
+ it('should sort returns by created date in ascending order', () => {
+ // Arrange
+ const orderOption = { selected: true, by: 'created', dir: 'asc' };
+ mockFilterService.orderBy.set([orderOption]);
+
+ const sortedFixture = TestBed.createComponent(
+ RemissionReturnReceiptListComponent
);
+ const sortedComponent = sortedFixture.componentInstance;
+
+ const olderReturn: Return = {
+ id: 10,
+ completed: '2024-01-15T10:00:00.000Z',
+ created: '2024-01-10T09:00:00.000Z',
+ receipts: [
+ {
+ id: 301,
+ data: {
+ id: 301,
+ receiptNumber: 'REC-2024-301',
+ created: '2024-01-10T09:00:00.000Z',
+ completed: '2024-01-10T10:00:00.000Z',
+ items: [],
+ } as Receipt,
+ },
+ ],
+ } as Return;
- const returns = newComponent.returns();
+ const newerReturn: Return = {
+ id: 11,
+ completed: '2024-01-15T10:00:00.000Z',
+ created: '2024-01-20T09:00:00.000Z',
+ receipts: [
+ {
+ id: 302,
+ data: {
+ id: 302,
+ receiptNumber: 'REC-2024-302',
+ created: '2024-01-20T09:00:00.000Z',
+ completed: '2024-01-20T10:00:00.000Z',
+ items: [],
+ } as Receipt,
+ },
+ ],
+ } as Return;
- expect(returns).toHaveLength(1);
- expect(returns[0][0]).toBe(mockIncompletedReturns[0]);
- expect(returns[0][1]).toBe(mockIncompletedReturns[0].receipts[0].data);
+ vi.spyOn(sortedComponent.remissionReturnsResource, 'value').mockReturnValue([
+ newerReturn,
+ olderReturn,
+ ]);
+
+ // Act
+ const returns = sortedComponent.returns();
+
+ // Assert
+ expect(returns).toHaveLength(2);
+ expect(returns[0][0]).toBe(olderReturn); // Older date should come first in asc order
+ expect(returns[1][0]).toBe(newerReturn);
+ });
+
+ it('should handle sorting with undefined dates', () => {
+ // Arrange
+ const orderOption = { selected: true, by: 'created', dir: 'desc' };
+ mockFilterService.orderBy = signal([orderOption]);
+
+ const returnWithDate: Return = {
+ id: 10,
+ completed: '2024-01-15T10:00:00.000Z',
+ created: '2024-01-10T09:00:00.000Z',
+ receipts: [
+ {
+ id: 301,
+ data: {
+ id: 301,
+ receiptNumber: 'REC-2024-301',
+ created: '2024-01-10T09:00:00.000Z',
+ completed: '2024-01-10T10:00:00.000Z',
+ items: [],
+ } as Receipt,
+ },
+ ],
+ } as Return;
+
+ const returnWithoutDate: Return = {
+ id: 11,
+ completed: '2024-01-15T10:00:00.000Z',
+ created: undefined,
+ receipts: [
+ {
+ id: 302,
+ data: {
+ id: 302,
+ receiptNumber: 'REC-2024-302',
+ created: '2024-01-20T09:00:00.000Z',
+ completed: '2024-01-20T10:00:00.000Z',
+ items: [],
+ } as Receipt,
+ },
+ ],
+ } as Return;
+
+ vi.spyOn(component.remissionReturnsResource, 'value').mockReturnValue([
+ returnWithDate,
+ returnWithoutDate,
+ ]);
+
+ // Act
+ const returns = component.returns();
+
+ // Assert
+ expect(returns).toHaveLength(2);
+ expect(returns[0][0]).toBe(returnWithDate); // Item with date should come first
+ expect(returns[1][0]).toBe(returnWithoutDate); // Undefined date goes to end
+ });
+ });
+
+ describe('Component Destruction', () => {
+ it('should handle component destruction gracefully', () => {
+ // Act
+ fixture.destroy();
+
+ // Assert
+ expect(component).toBeDefined();
});
});
describe('Edge Cases', () => {
- it('should handle very large return lists', () => {
- const largeCompletedReturns = Array.from(
- { length: 1000 },
- (_, i) =>
- ({
- id: i,
- receipts: [],
- }) as Return,
- );
+ it('should handle returns with empty receipts array', () => {
+ // Arrange
+ const returnWithEmptyReceipts: Return = {
+ id: 100,
+ completed: '2024-01-15T10:00:00.000Z',
+ receipts: [],
+ } as Return;
- const largeIncompletedReturns = Array.from(
- { length: 500 },
- (_, i) =>
- ({
- id: i + 1000,
- receipts: [],
- }) as Return,
- );
-
- (component.completedRemissionReturnsResource as any).value = signal(
- largeCompletedReturns,
- );
- (component.incompletedRemissionReturnsResource as any).value = signal(
- largeIncompletedReturns,
- );
+ vi.spyOn(component.remissionReturnsResource, 'value').mockReturnValue([
+ returnWithEmptyReceipts,
+ ]);
+ // Act
const returns = component.returns();
- // With no receipts, the flattened result should be empty
+ // Assert
expect(returns).toHaveLength(0);
});
- it('should maintain order when resources update', () => {
- // Test that the order logic correctly maintains incompleted first, then completed
- const newCompletedReturns: Return[] = [
- {
- id: 5,
- receipts: [
- {
- id: 105,
- data: {
- id: 105,
- receiptNumber: 'REC-2024-005',
- created: '2024-01-18T10:00:00.000Z',
- completed: '2024-01-18T11:00:00.000Z',
- items: [],
- },
- },
- ],
- } as Return,
+ it('should handle mixed returns with and without receipt data', () => {
+ // Arrange
+ const mixedReturns = [
+ mockCompletedReturn,
+ mockReturnWithoutReceiptData,
+ mockIncompletedReturn,
];
- const newIncompletedReturns: Return[] = [
- {
- id: 6,
- receipts: [
- {
- id: 106,
- data: {
- id: 106,
- receiptNumber: 'REC-2024-006',
- created: '2024-01-19T08:00:00.000Z',
- completed: undefined,
- items: [],
- },
- },
- ],
- } as Return,
- ];
-
- // Simulate resource updates by mocking the resource values
- (component.completedRemissionReturnsResource as any).value = signal(newCompletedReturns);
- (component.incompletedRemissionReturnsResource as any).value = signal(newIncompletedReturns);
+ vi.spyOn(component.remissionReturnsResource, 'value').mockReturnValue(
+ mixedReturns
+ );
+ // Act
const returns = component.returns();
- expect(returns).toHaveLength(2);
-
- // Verify that incompleted returns come first
- expect(returns[0][0]).toBe(newIncompletedReturns[0]);
- expect(returns[0][1]).toBe(newIncompletedReturns[0].receipts[0].data);
-
- // Then completed returns
- expect(returns[1][0]).toBe(newCompletedReturns[0]);
- expect(returns[1][1]).toBe(newCompletedReturns[0].receipts[0].data);
+ // Assert
+ expect(returns).toHaveLength(2); // Only returns with receipt data
+ expect(returns[0][0]).toBe(mockIncompletedReturn); // Incompleted first
+ expect(returns[1][0]).toBe(mockCompletedReturn);
+ });
+
+ it('should handle very large number of receipts per return', () => {
+ // Arrange
+ const returnWithManyReceipts: Return = {
+ id: 200,
+ completed: '2024-01-15T10:00:00.000Z',
+ receipts: Array.from({ length: 100 }, (_, i) => ({
+ id: 1000 + i,
+ data: {
+ id: 1000 + i,
+ receiptNumber: `REC-2024-${1000 + i}`,
+ created: '2024-01-15T09:00:00.000Z',
+ completed: '2024-01-15T10:00:00.000Z',
+ items: [],
+ } as Receipt,
+ })),
+ } as Return;
+
+ vi.spyOn(component.remissionReturnsResource, 'value').mockReturnValue([
+ returnWithManyReceipts,
+ ]);
+
+ // Act
+ const returns = component.returns();
+
+ // Assert
+ expect(returns).toHaveLength(100);
+ returns.forEach(([returnData, receipt]) => {
+ expect(returnData).toBe(returnWithManyReceipts);
+ expect(receipt).toBeDefined();
+ });
});
});
});
diff --git a/nx.json b/nx.json
index 2d41c4465..27893935d 100644
--- a/nx.json
+++ b/nx.json
@@ -1,165 +1,164 @@
-{
- "$schema": "./node_modules/nx/schemas/nx-schema.json",
- "cli": {
- "packageManager": "npm"
- },
- "targetDefaults": {
- "build": {
- "cache": true,
- "dependsOn": ["^build"],
- "inputs": ["production", "^production"]
- },
- "test": {
- "cache": true,
- "inputs": ["default", "^production", "{workspaceRoot}/karma.conf.js"]
- },
- "@nx/eslint:lint": {
- "cache": true,
- "inputs": [
- "default",
- "{workspaceRoot}/.eslintrc.json",
- "{workspaceRoot}/.eslintignore",
- "{workspaceRoot}/eslint.config.js"
- ]
- },
- "@nx/jest:jest": {
- "cache": true,
- "inputs": ["default", "^production", "{workspaceRoot}/jest.preset.js"],
- "options": {
- "passWithNoTests": true
- },
- "configurations": {
- "ci": {
- "ci": true,
- "codeCoverage": true
- }
- }
- },
- "@nx/js:tsc": {
- "cache": true,
- "dependsOn": ["^build"],
- "inputs": ["production", "^production"]
- },
- "@angular-devkit/build-angular:application": {
- "cache": true,
- "dependsOn": ["^build"],
- "inputs": ["production", "^production"]
- },
- "build-storybook": {
- "cache": true
- },
- "@nx/angular:ng-packagr-lite": {
- "cache": true,
- "dependsOn": ["^build"],
- "inputs": ["production", "^production"]
- },
- "@nx/vite:test": {
- "cache": true,
- "inputs": ["default", "^production"]
- }
- },
- "defaultBase": "develop",
- "namedInputs": {
- "sharedGlobals": [],
- "default": ["{projectRoot}/**/*", "sharedGlobals"],
- "production": [
- "default",
- "!{projectRoot}/tsconfig.spec.json",
- "!{projectRoot}/**/*.spec.[jt]s",
- "!{projectRoot}/.eslintrc.json",
- "!{projectRoot}/eslint.config.js",
- "!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)",
- "!{projectRoot}/jest.config.[jt]s",
- "!{projectRoot}/src/test-setup.[jt]s",
- "!{projectRoot}/test-setup.[jt]s",
- "!{projectRoot}/**/*.stories.@(js|jsx|ts|tsx|mdx)",
- "!{projectRoot}/.storybook/**/*",
- "!{projectRoot}/tsconfig.storybook.json"
- ]
- },
- "generators": {
- "@nx/angular:library": {
- "linter": "eslint",
- "unitTestRunner": "none"
- },
- "@nx/angular:component": {
- "style": "css",
- "type": "component"
- },
- "@nx/angular:application": {
- "e2eTestRunner": "none",
- "linter": "eslint",
- "style": "css",
- "unitTestRunner": "jest"
- },
- "@schematics/angular:component": {
- "type": "component"
- },
- "@nx/angular:directive": {
- "type": "directive"
- },
- "@schematics/angular:directive": {
- "type": "directive"
- },
- "@nx/angular:service": {
- "type": "service"
- },
- "@schematics/angular:service": {
- "type": "service"
- },
- "@nx/angular:scam": {
- "type": "component"
- },
- "@nx/angular:scam-directive": {
- "type": "directive"
- },
- "@nx/angular:guard": {
- "typeSeparator": "."
- },
- "@schematics/angular:guard": {
- "typeSeparator": "."
- },
- "@nx/angular:interceptor": {
- "typeSeparator": "."
- },
- "@schematics/angular:interceptor": {
- "typeSeparator": "."
- },
- "@nx/angular:module": {
- "typeSeparator": "."
- },
- "@schematics/angular:module": {
- "typeSeparator": "."
- },
- "@nx/angular:pipe": {
- "typeSeparator": "."
- },
- "@schematics/angular:pipe": {
- "typeSeparator": "."
- },
- "@nx/angular:resolver": {
- "typeSeparator": "."
- },
- "@schematics/angular:resolver": {
- "typeSeparator": "."
- }
- },
- "plugins": [
- {
- "plugin": "@nx/eslint/plugin",
- "options": {
- "targetName": "eslint:lint"
- }
- },
- {
- "plugin": "@nx/storybook/plugin",
- "options": {
- "serveStorybookTargetName": "storybook",
- "buildStorybookTargetName": "build-storybook",
- "testStorybookTargetName": "test-storybook",
- "staticStorybookTargetName": "static-storybook"
- }
- }
- ],
- "nxCloudId": "686ee1ec0f8935752d36306a"
-}
+{
+ "$schema": "./node_modules/nx/schemas/nx-schema.json",
+ "cli": {
+ "packageManager": "npm"
+ },
+ "targetDefaults": {
+ "build": {
+ "cache": true,
+ "dependsOn": ["^build"],
+ "inputs": ["production", "^production"]
+ },
+ "test": {
+ "cache": true,
+ "inputs": ["default", "^production", "{workspaceRoot}/karma.conf.js"]
+ },
+ "@nx/eslint:lint": {
+ "cache": true,
+ "inputs": [
+ "default",
+ "{workspaceRoot}/.eslintrc.json",
+ "{workspaceRoot}/.eslintignore",
+ "{workspaceRoot}/eslint.config.js"
+ ]
+ },
+ "@nx/jest:jest": {
+ "cache": true,
+ "inputs": ["default", "^production", "{workspaceRoot}/jest.preset.js"],
+ "options": {
+ "passWithNoTests": true
+ },
+ "configurations": {
+ "ci": {
+ "ci": true,
+ "codeCoverage": true
+ }
+ }
+ },
+ "@nx/js:tsc": {
+ "cache": true,
+ "dependsOn": ["^build"],
+ "inputs": ["production", "^production"]
+ },
+ "@angular-devkit/build-angular:application": {
+ "cache": true,
+ "dependsOn": ["^build"],
+ "inputs": ["production", "^production"]
+ },
+ "build-storybook": {
+ "cache": true
+ },
+ "@nx/angular:ng-packagr-lite": {
+ "cache": true,
+ "dependsOn": ["^build"],
+ "inputs": ["production", "^production"]
+ },
+ "@nx/vite:test": {
+ "cache": true,
+ "inputs": ["default", "^production"]
+ }
+ },
+ "defaultBase": "develop",
+ "namedInputs": {
+ "sharedGlobals": [],
+ "default": ["{projectRoot}/**/*", "sharedGlobals"],
+ "production": [
+ "default",
+ "!{projectRoot}/tsconfig.spec.json",
+ "!{projectRoot}/**/*.spec.[jt]s",
+ "!{projectRoot}/.eslintrc.json",
+ "!{projectRoot}/eslint.config.js",
+ "!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)",
+ "!{projectRoot}/jest.config.[jt]s",
+ "!{projectRoot}/src/test-setup.[jt]s",
+ "!{projectRoot}/test-setup.[jt]s",
+ "!{projectRoot}/**/*.stories.@(js|jsx|ts|tsx|mdx)",
+ "!{projectRoot}/.storybook/**/*",
+ "!{projectRoot}/tsconfig.storybook.json"
+ ]
+ },
+ "generators": {
+ "@nx/angular:library": {
+ "linter": "eslint",
+ "unitTestRunner": "none"
+ },
+ "@nx/angular:component": {
+ "style": "css",
+ "type": "component"
+ },
+ "@nx/angular:application": {
+ "e2eTestRunner": "none",
+ "linter": "eslint",
+ "style": "css",
+ "unitTestRunner": "jest"
+ },
+ "@schematics/angular:component": {
+ "type": "component"
+ },
+ "@nx/angular:directive": {
+ "type": "directive"
+ },
+ "@schematics/angular:directive": {
+ "type": "directive"
+ },
+ "@nx/angular:service": {
+ "type": "service"
+ },
+ "@schematics/angular:service": {
+ "type": "service"
+ },
+ "@nx/angular:scam": {
+ "type": "component"
+ },
+ "@nx/angular:scam-directive": {
+ "type": "directive"
+ },
+ "@nx/angular:guard": {
+ "typeSeparator": "."
+ },
+ "@schematics/angular:guard": {
+ "typeSeparator": "."
+ },
+ "@nx/angular:interceptor": {
+ "typeSeparator": "."
+ },
+ "@schematics/angular:interceptor": {
+ "typeSeparator": "."
+ },
+ "@nx/angular:module": {
+ "typeSeparator": "."
+ },
+ "@schematics/angular:module": {
+ "typeSeparator": "."
+ },
+ "@nx/angular:pipe": {
+ "typeSeparator": "."
+ },
+ "@schematics/angular:pipe": {
+ "typeSeparator": "."
+ },
+ "@nx/angular:resolver": {
+ "typeSeparator": "."
+ },
+ "@schematics/angular:resolver": {
+ "typeSeparator": "."
+ }
+ },
+ "plugins": [
+ {
+ "plugin": "@nx/eslint/plugin",
+ "options": {
+ "targetName": "eslint:lint"
+ }
+ },
+ {
+ "plugin": "@nx/storybook/plugin",
+ "options": {
+ "serveStorybookTargetName": "storybook",
+ "buildStorybookTargetName": "build-storybook",
+ "testStorybookTargetName": "test-storybook",
+ "staticStorybookTargetName": "static-storybook"
+ }
+ }
+ ]
+}