/**
* Return Shipment Test Utilities
*
* Comprehensive test utilities for testing return label functionality,
* including mock setups, error scenarios, and integration test helpers.
*/
import { useState } from 'react';
import {
render,
renderHook,
fireEvent,
// eslint-disable-next-line import/named
screen,
// eslint-disable-next-line import/named
waitFor,
} from '@testing-library/react';
import { SHIPMENT_TYPE } from 'utils/shipment';
import { LABEL_PURCHASE_STATUS } from 'data/constants';
import { address, destinationAddress } from './test-utils';
export interface ReturnShipmentTestConfig {
shipmentId?: string;
isReturn?: boolean;
hasExistingShipments?: boolean;
isDomestic?: boolean;
hasValidAddresses?: boolean;
}
export interface ErrorScenario {
type: 'api_error' | 'network_error' | 'validation_error' | 'address_error';
message: string;
statusCode?: number;
}
/**
* Creates comprehensive mock setup for return shipment testing
*/
export const createReturnShipmentMocks = (
config: ReturnShipmentTestConfig = {}
) => {
const {
shipmentId = '1',
isReturn = true,
hasExistingShipments = true,
isDomestic = true,
hasValidAddresses = true,
} = config;
const mockSelectedRates = hasExistingShipments
? {
'0': { rate: { isReturn: false } },
[ shipmentId ]: { rate: { isReturn } },
}
: {
[ shipmentId ]: { rate: { isReturn } },
};
const mockOriginAddress = {
...address,
country: isDomestic ? 'US' : 'CA',
};
const mockDestinationAddress = {
...destinationAddress,
country: isDomestic ? 'US' : 'CA',
isVerified: hasValidAddresses,
};
return {
wordPressMocks: {
useSelect: jest.fn().mockImplementation( ( selector ) =>
selector( () => ( {
getSelectedRates: jest
.fn()
.mockReturnValue( mockSelectedRates ),
getPurchasedLabel: jest.fn().mockReturnValue( null ),
getOriginAddresses: jest
.fn()
.mockReturnValue( [ mockOriginAddress ] ),
getLabelDestinations: jest
.fn()
.mockReturnValue( [ mockDestinationAddress ] ),
getCurrentOrderShipments: jest
.fn()
.mockReturnValue(
hasExistingShipments
? { 0: [], [ shipmentId ]: [] }
: {}
),
getRefundedLabel: jest.fn().mockReturnValue( null ),
getOrderStatus: jest.fn().mockReturnValue( 'processing' ),
} ) )
),
select: jest.fn().mockReturnValue( {
getPurchasedLabel: jest.fn().mockReturnValue( null ),
getSelectedRates: jest
.fn()
.mockReturnValue( mockSelectedRates ),
getOriginAddresses: jest
.fn()
.mockReturnValue( [ mockOriginAddress ] ),
} ),
dispatch: jest.fn().mockReturnValue( {
updateShipments: jest.fn(),
purchaseLabel: jest.fn(),
refundLabel: jest.fn(),
} ),
},
shipmentMocks: {
getShipmentOrigin: jest.fn().mockReturnValue( mockOriginAddress ),
getShipmentPurchaseOrigin: jest
.fn()
.mockReturnValue( mockOriginAddress ),
getShipmentDestination: jest
.fn()
.mockReturnValue( mockDestinationAddress ),
getCurrentShipmentIsReturn: jest.fn().mockReturnValue( isReturn ),
isReturnShipment: jest
.fn()
.mockImplementation( ( id ) => id === shipmentId && isReturn ),
getShipmentType: jest
.fn()
.mockImplementation( ( id ) =>
id === shipmentId && isReturn
? SHIPMENT_TYPE.RETURN
: SHIPMENT_TYPE.OUTBOUND
),
getCurrentShipmentDate: jest.fn().mockReturnValue( {
shippingDate: isReturn ? null : new Date( '2025-02-26' ),
estimatedDeliveryDate: new Date( '2025-02-28' ),
} ),
returnShipments: isReturn
? { [ shipmentId ]: { isReturn: true } }
: {},
setReturnShipments: jest.fn(),
},
contextMocks: {
storeCurrency: {
formatMoney: jest.fn( ( amount ) => `$${ amount }` ),
},
rates: {
getSelectedRate: jest.fn().mockReturnValue( null ),
getSelectedRateOptions: jest.fn().mockReturnValue( [] ),
selectRateOption: jest.fn(),
getCurrentRateOptions: jest.fn().mockReturnValue( [] ),
isFetching: false,
errors: [],
},
labels: {
hasPurchasedLabel: jest.fn().mockReturnValue( false ),
getCurrentShipmentLabel: jest.fn().mockReturnValue( {
isLegacy: false,
status: LABEL_PURCHASE_STATUS.PURCHASED,
} ),
isPurchasing: false,
errors: [],
},
},
apiMocks: {
getRates: jest.fn(),
purchaseLabel: jest.fn(),
refundLabel: jest.fn(),
},
addresses: {
origin: mockOriginAddress,
destination: mockDestinationAddress,
},
};
};
/**
* Setup mock error scenarios for return shipment testing
*/
export const setupErrorScenarios = {
apiError: ( scenario: ErrorScenario ) => {
const mockError = new Error( scenario.message ) as Error & {
status?: number;
};
if ( scenario.statusCode ) {
mockError.status = scenario.statusCode;
}
switch ( scenario.type ) {
case 'api_error':
global.fetch = jest.fn().mockRejectedValue( mockError );
break;
case 'network_error':
global.fetch = jest
.fn()
.mockRejectedValue( new Error( 'Network Error' ) );
break;
case 'validation_error':
return {
validation: {
isValid: false,
errors: [ scenario.message ],
},
};
case 'address_error':
return {
address: {
isVerified: false,
errors: [ scenario.message ],
},
};
}
return mockError;
},
clearApiMocks: () => {
const fetchMock = global.fetch as
| jest.MockedFunction< typeof fetch >
| undefined;
if ( fetchMock && typeof fetchMock.mockRestore === 'function' ) {
fetchMock.mockRestore();
}
},
};
/**
* Enhanced test utilities for return shipment integration testing
*/
export const returnShipmentIntegrationUtils = {
/**
* Setup complete return label purchase flow test
*/
setupReturnLabelPurchaseFlow: async (
config: ReturnShipmentTestConfig = {}
) => {
const mocks = createReturnShipmentMocks( config );
// Mock successful API responses
mocks.apiMocks.getRates.mockResolvedValue( {
rates: [
{
id: 'rate_1',
service: 'USPS Ground',
rate: 5.99,
isReturn: config.isReturn ?? true,
},
],
} );
mocks.apiMocks.purchaseLabel.mockResolvedValue( {
label: {
id: 'label_1',
status: LABEL_PURCHASE_STATUS.PURCHASED,
tracking_number: 'TRACK123',
label_url: 'https://example.com/label.pdf',
},
} );
return mocks;
},
/**
* Test return label creation with address swapping
*/
testAddressSwapping: async ( component: JSX.Element ) => {
render( component );
// For return shipments, "Ship from" should show destination address
// and "Return to" should show origin address
await waitFor( () => {
expect( screen.getByText( 'Ship from' ) ).toBeInTheDocument();
expect( screen.getByText( 'Return to' ) ).toBeInTheDocument();
} );
// Verify address order is swapped for returns
const shipFromSection = screen
.getByText( 'Ship from' )
.closest( '.components-base-control' );
const returnToSection = screen
.getByText( 'Return to' )
.closest( '.components-base-control' );
expect( shipFromSection ).toBeInTheDocument();
expect( returnToSection ).toBeInTheDocument();
// Ship from should appear before Return to in the DOM for returns
const sections = [ shipFromSection, returnToSection ];
expect( sections[ 0 ] ).toBe( shipFromSection );
expect( sections[ 1 ] ).toBe( returnToSection );
},
/**
* Test domestic-only restriction for return labels
*/
testDomesticRestriction: async ( component: JSX.Element ) => {
render( component );
const returnButton = await screen.findByRole( 'button', {
name: /return label/i,
} );
// International orders should have disabled return button
expect( returnButton ).toBeDisabled();
// Should show tooltip explaining restriction
fireEvent.mouseOver( returnButton );
await waitFor( () => {
expect(
screen.getByText(
/return labels are currently only available for domestic shipments/i
)
).toBeInTheDocument();
} );
},
/**
* Test return shipment validation
*/
validateReturnShipment: ( shipmentData: {
isReturn?: boolean;
origin?: { country?: string };
destination?: { country?: string };
items?: unknown[];
} ) => {
const validationErrors: string[] = [];
if ( ! shipmentData.isReturn ) {
validationErrors.push( 'Shipment must be marked as return' );
}
if ( ! shipmentData.origin || ! shipmentData.destination ) {
validationErrors.push(
'Both origin and destination addresses are required'
);
}
if (
shipmentData.origin?.country !== shipmentData.destination?.country
) {
validationErrors.push(
'Return labels only support domestic shipments'
);
}
if ( ! shipmentData.items || shipmentData.items.length === 0 ) {
validationErrors.push(
'At least one item must be selected for return'
);
}
return {
isValid: validationErrors.length === 0,
errors: validationErrors,
};
},
/**
* Mock successful return label purchase
*/
mockSuccessfulReturnPurchase: ( config: ReturnShipmentTestConfig = {} ) => {
const mocks = createReturnShipmentMocks( config );
mocks.apiMocks.purchaseLabel.mockResolvedValue( {
success: true,
data: {
label: {
id: `return_label_${ config.shipmentId ?? '1' }`,
status: LABEL_PURCHASE_STATUS.PURCHASED,
tracking_number: `RET${ Date.now() }`,
label_url: 'https://example.com/return-label.pdf',
isReturn: true,
},
},
} );
return mocks;
},
/**
* Mock failed return label purchase
*/
mockFailedReturnPurchase: (
errorType: ErrorScenario[ 'type' ] = 'api_error'
) => {
const mocks = createReturnShipmentMocks( { isReturn: true } );
const errorMessage = {
api_error: 'API service unavailable',
network_error: 'Network connection failed',
validation_error: 'Invalid return shipment data',
address_error: 'Return address validation failed',
}[ errorType ];
mocks.apiMocks.purchaseLabel.mockRejectedValue(
new Error( errorMessage )
);
return mocks;
},
};
/**
* Focused assertion utilities instead of large snapshots
*/
export const returnShipmentAssertions = {
/**
* Assert return label UI elements are present and correct
*/
assertReturnLabelUI: async () => {
await waitFor( () => {
expect(
screen.getByRole( 'button', { name: /return label/i } )
).toBeInTheDocument();
} );
},
/**
* Assert address fields are in correct order for return shipments
*/
assertReturnAddressOrder: async () => {
await waitFor( () => {
const shipFromLabel = screen.getByText( 'Ship from' );
const returnToLabel = screen.getByText( 'Return to' );
expect( shipFromLabel ).toBeInTheDocument();
expect( returnToLabel ).toBeInTheDocument();
// Verify proper labeling
expect( shipFromLabel ).toHaveAttribute( 'for', 'ship-to' ); // Customer address for returns
expect( returnToLabel ).toHaveAttribute( 'for', 'ship-from' ); // Merchant address for returns
} );
},
/**
* Assert return shipment doesn't show shipping date field
*/
assertNoShippingDateForReturns: () => {
expect(
screen.queryByLabelText( /ship date/i )
).not.toBeInTheDocument();
},
/**
* Assert return label button state and behavior
*/
assertReturnButtonBehavior: async ( isDomestic = true ) => {
const returnButton = screen.getByRole( 'button', {
name: /return label/i,
} );
if ( isDomestic ) {
expect( returnButton ).toBeEnabled();
} else {
expect( returnButton ).toBeDisabled();
// Check for international restriction tooltip
fireEvent.mouseOver( returnButton );
await waitFor( () => {
expect(
screen.getByText( /domestic shipments/i )
).toBeInTheDocument();
} );
}
},
/**
* Assert return shipment tab labeling
*/
assertReturnShipmentTabs: async ( totalShipments = 2 ) => {
// Wait for tabs to render
await waitFor( () => {
// Should see "Return X/Y" format for return tabs
expect(
screen.getByText( `Return 1/${ totalShipments }` )
).toBeInTheDocument();
} );
},
/**
* Assert purchase button text for returns
*/
assertReturnPurchaseButton: async ( returnIndex = 1, totalReturns = 1 ) => {
await waitFor( () => {
const purchaseButton = screen.getByRole( 'button', {
name: new RegExp(
`return ${ returnIndex }/${ totalReturns }`,
'i'
),
} );
expect( purchaseButton ).toBeInTheDocument();
} );
},
/**
* Assert error handling for return shipments
*/
assertReturnErrorHandling: async ( expectedError: string ) => {
await waitFor( () => {
expect( screen.getByText( expectedError ) ).toBeInTheDocument();
} );
},
};
/**
* Hook testing utilities for return shipments
*/
export const returnShipmentHookUtils = {
/**
* Test return shipment state management
*/
testReturnShipmentState: () => {
const { result } = renderHook( () => {
// Mock implementation for testing
const [ returnShipments, setReturnShipments ] = useState<
Record< string, { isReturn?: boolean } >
>( {} );
return {
returnShipments,
setReturnShipments,
isReturnShipment: ( id: string ) =>
returnShipments[ id ]?.isReturn ?? false,
getCurrentShipmentIsReturn: () =>
returnShipments[ '1' ]?.isReturn ?? false,
getShipmentType: ( id: string ) =>
returnShipments[ id ]?.isReturn
? SHIPMENT_TYPE.RETURN
: SHIPMENT_TYPE.OUTBOUND,
};
} );
return result;
},
/**
* Test return shipment validation hooks
*/
testReturnShipmentValidation: ( shipmentData: {
isReturn?: boolean;
origin?: { country?: string };
destination?: { country?: string };
items?: unknown[];
} ) => {
const { result } = renderHook( () => {
const validate = () =>
returnShipmentIntegrationUtils.validateReturnShipment(
shipmentData
);
return { validate };
} );
return result;
},
};
/**
* Common test setup for return shipment components
* Note: This should be called in the main describe block, not inside individual tests
*/
export const setupReturnShipmentTest = (
config: ReturnShipmentTestConfig = {}
) => {
const mocks = createReturnShipmentMocks( config );
return mocks;
};