import { createRoot } from 'react-dom/client';
import { act } from 'react';
import PackagingSection from '../features/order-ship/packaging-section';
import type { Order } from '../types';
( globalThis as any ).IS_REACT_ACT_ENVIRONMENT = true;
const order = {
id: 323,
shipping: { country: 'GB', postcode: 'YO22 5HS' },
units: { weight: 'kg', dimension: 'cm' },
items: [
{
name: 'Mini Pouch',
quantity: 1,
total: '10.00',
dimensions: { length: 10, width: 10, height: 10, weight: 1 },
},
],
} as unknown as Order;
function deferred() {
let resolve!: () => void;
const promise = new Promise< void >( ( r ) => ( resolve = r ) );
return { promise, resolve };
}
// Set a React-controlled input's value the way a user typing would.
function typeInto( input: HTMLInputElement, value: string ) {
const setter = Object.getOwnPropertyDescriptor( HTMLInputElement.prototype, 'value' )!.set!;
setter.call( input, value );
input.dispatchEvent( new Event( 'input', { bubbles: true } ) );
}
describe( 'PackagingSection', () => {
it( 'keeps an edit made while the quotes for the applied parcel are loading', async () => {
const quotes = deferred();
const onFetchQuotes = jest.fn( () => quotes.promise );
const onParcelChange = jest.fn();
const container = document.createElement( 'div' );
document.body.appendChild( container );
const root = createRoot( container );
act( () =>
root.render(
)
);
const button = ( text: string ) =>
Array.from( container.querySelectorAll( 'button' ) ).find( ( b ) =>
b.textContent?.includes( text )
) as HTMLButtonElement;
const weightInput = () =>
Array.from( container.querySelectorAll< HTMLInputElement >( 'input[type=number]' ) )[ 3 ];
act( () => button( 'Parcel' ).click() );
act( () => typeInto( weightInput(), '3' ) );
act( () => button( 'Apply' ).click() );
// The merchant keeps editing before the quotes come back.
act( () => typeInto( weightInput(), '4' ) );
await act( async () => {
quotes.resolve();
await quotes.promise;
} );
expect( onParcelChange ).toHaveBeenCalledWith(
expect.objectContaining( { weight: 3 } )
);
expect( weightInput().value ).toBe( '4' );
expect( container.textContent ).toContain( 'changes that are not applied' );
act( () => root.unmount() );
container.remove();
} );
it( 'allows one Apply at a time, so an earlier request cannot set the parcel last', async () => {
const quotes = deferred();
const onFetchQuotes = jest.fn( () => quotes.promise );
const onParcelChange = jest.fn();
const onApplyingChange = jest.fn();
const container = document.createElement( 'div' );
document.body.appendChild( container );
const root = createRoot( container );
act( () =>
root.render(
)
);
const button = ( text: string ) =>
Array.from( container.querySelectorAll( 'button' ) ).find( ( b ) =>
b.textContent?.includes( text )
) as HTMLButtonElement;
const weightInput = () =>
Array.from( container.querySelectorAll< HTMLInputElement >( 'input[type=number]' ) )[ 3 ];
act( () => button( 'Parcel' ).click() );
act( () => typeInto( weightInput(), '3' ) );
act( () => button( 'Apply' ).click() );
// The page is told, so it can hold booking until the new quotes arrive.
expect( onApplyingChange ).toHaveBeenLastCalledWith( true );
// Editing still works while the first request runs, but Apply does not.
act( () => typeInto( weightInput(), '4' ) );
expect( button( 'Apply' ).disabled ).toBe( true );
act( () => button( 'Apply' ).click() );
expect( onFetchQuotes ).toHaveBeenCalledTimes( 1 );
await act( async () => {
quotes.resolve();
await quotes.promise;
} );
expect( button( 'Apply' ).disabled ).toBe( false );
expect( onApplyingChange ).toHaveBeenLastCalledWith( false );
expect( weightInput().value ).toBe( '4' );
act( () => root.unmount() );
container.remove();
} );
} );