Update blocks JS tests to React 18 (#47383)

This commit is contained in:
Sam Seay 2024-05-15 17:33:36 +08:00 committed by GitHub
parent 02c640480d
commit 3ef7a01840
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
29 changed files with 2829 additions and 81507 deletions

View File

@ -72,8 +72,8 @@
"pnpm": {
"overrides": {
"@types/react": "^17.0.71",
"react": "^17.0.2",
"react-resize-aware": "3.1.1"
"react-resize-aware": "3.1.1",
"@automattic/tour-kit>@wordpress/element": "4.4.1"
}
}
}

View File

@ -0,0 +1,4 @@
Significance: patch
Type: dev
Update the Blocks JS tests to React 18

View File

@ -79,6 +79,7 @@
"@babel/runtime": "^7.23.5",
"@testing-library/react": "12.1.3",
"@testing-library/react-hooks": "7.0.2",
"react-test-renderer": "17.0.2",
"@types/jest": "^27.5.2",
"@types/lodash": "^4.14.202",
"@types/md5": "^2.3.5",

View File

@ -121,6 +121,7 @@
"@testing-library/react": "12.1.3",
"@testing-library/react-hooks": "7.0.2",
"@testing-library/user-event": "13.5.0",
"react-test-renderer": "17.0.2",
"@types/cookie": "^0.4.1",
"@types/dompurify": "^2.4.0",
"@types/expect-puppeteer": "^4.4.7",

View File

@ -1,7 +1,7 @@
/**
* External dependencies
*/
import { render, screen } from '@testing-library/react';
import { act, render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { CheckoutProvider } from '@woocommerce/base-context';
import { useCheckoutAddress } from '@woocommerce/base-context/hooks';
@ -21,11 +21,18 @@ jest.mock( '@wordpress/element', () => {
};
} );
const renderInCheckoutProvider = ( ui, options = {} ) => {
const renderInCheckoutProvider = ( ui, options = { legacyRoot: true } ) => {
const Wrapper = ( { children } ) => {
return <CheckoutProvider>{ children }</CheckoutProvider>;
};
return render( ui, { wrapper: Wrapper, ...options } );
const result = render( ui, { wrapper: Wrapper, ...options } );
// We need to switch to React 17 rendering to allow these tests to keep passing, but as a result the React
// rendering error will be shown.
expect( console ).toHaveErroredWith(
`Warning: ReactDOM.render is no longer supported in React 18. Use createRoot instead. Until you switch to the new API, your app will behave as if it's running React 17. Learn more: https://reactjs.org/link/switch-to-createroot`
);
return result;
};
// Countries used in testing addresses must be in the wcSettings global.
@ -63,29 +70,34 @@ const inputAddress = async ( {
postcode = null,
} ) => {
if ( country ) {
const countryInput = screen.getByLabelText( countryRegExp );
userEvent.type( countryInput, country + '{arrowdown}{enter}' );
const countryInput = screen.queryByRole( 'combobox', {
name: countryRegExp,
} );
await userEvent.type( countryInput, country + '{arrowdown}{enter}' );
}
if ( city ) {
const cityInput = screen.getByLabelText( cityRegExp );
userEvent.type( cityInput, city );
await userEvent.type( cityInput, city );
}
if ( state ) {
const stateButton = screen.queryByRole( 'combobox', {
name: stateRegExp,
} );
// State input might be a select or a text input.
if ( stateButton ) {
userEvent.click( stateButton );
userEvent.click( screen.getByRole( 'option', { name: state } ) );
await userEvent.click( stateButton );
await userEvent.click(
screen.getByRole( 'option', { name: state } )
);
} else {
const stateInput = screen.getByLabelText( stateRegExp );
userEvent.type( stateInput, state );
await userEvent.type( stateInput, state );
}
}
if ( postcode ) {
const postcodeInput = screen.getByLabelText( postalCodeRegExp );
userEvent.type( postcodeInput, postcode );
await userEvent.type( postcodeInput, postcode );
}
};
@ -114,7 +126,7 @@ describe( 'Form Component', () => {
);
};
it( 'updates context value when interacting with form elements', () => {
it( 'updates context value when interacting with form elements', async () => {
renderInCheckoutProvider(
<>
<WrappedAddressForm type="shipping" />
@ -122,9 +134,11 @@ describe( 'Form Component', () => {
</>
);
inputAddress( primaryAddress );
await act( async () => {
await inputAddress( primaryAddress );
} );
expect( screen.getByText( /country/ ) ).toHaveTextContent(
expect( screen.getByText( /country:/ ) ).toHaveTextContent(
`country: ${ primaryAddress.countryKey }`
);
expect( screen.getByText( /city/ ) ).toHaveTextContent(
@ -138,38 +152,54 @@ describe( 'Form Component', () => {
);
} );
it( 'input fields update when changing the country', () => {
it( 'input fields update when changing the country', async () => {
renderInCheckoutProvider( <WrappedAddressForm type="shipping" /> );
inputAddress( primaryAddress );
await act( async () => {
await inputAddress( primaryAddress );
} );
// Verify correct labels are used.
expect( screen.getByLabelText( /City/ ) ).toBeInTheDocument();
expect( screen.getByLabelText( /County/ ) ).toBeInTheDocument();
expect( screen.getByLabelText( /Postcode/ ) ).toBeInTheDocument();
inputAddress( secondaryAddress );
await act( async () => {
await inputAddress( secondaryAddress );
} );
// Verify state input has been removed.
expect( screen.queryByText( stateRegExp ) ).not.toBeInTheDocument();
inputAddress( tertiaryAddress );
await act( async () => {
await inputAddress( tertiaryAddress );
} );
// Verify postal code input label changed.
expect( screen.getByLabelText( /Postal code/ ) ).toBeInTheDocument();
} );
it( 'input values are reset after changing the country', () => {
it( 'input values are reset after changing the country', async () => {
renderInCheckoutProvider( <WrappedAddressForm type="shipping" /> );
inputAddress( secondaryAddress );
await act( async () => {
await inputAddress( secondaryAddress );
} );
// Only update `country` to verify other values are reset.
inputAddress( { country: primaryAddress.country } );
await act( async () => {
await inputAddress( { country: primaryAddress.country } );
} );
expect( screen.getByLabelText( stateRegExp ).value ).toBe( '' );
// Repeat the test with an address which has a select for the state.
inputAddress( tertiaryAddress );
inputAddress( { country: primaryAddress.country } );
await act( async () => {
await inputAddress( tertiaryAddress );
} );
await act( async () => {
await inputAddress( { country: primaryAddress.country } );
} );
expect( screen.getByLabelText( stateRegExp ).value ).toBe( '' );
} );
} );

View File

@ -102,7 +102,7 @@ describe( 'LocalPickupSelect', () => {
expect( screen.getByText( 'Package 1' ) ).toBeInTheDocument();
} );
it( 'Calls the correct functions when changing selected option', () => {
it( 'Calls the correct functions when changing selected option', async () => {
const setSelectedOption = jest.fn();
const onSelectRate = jest.fn();
render(
@ -111,10 +111,10 @@ describe( 'LocalPickupSelect', () => {
onSelectRateOverride={ onSelectRate }
/>
);
userEvent.click( screen.getByText( 'Store 2' ) );
await userEvent.click( screen.getByText( 'Store 2' ) );
expect( setSelectedOption ).toHaveBeenLastCalledWith( '2' );
expect( onSelectRate ).toHaveBeenLastCalledWith( '2' );
userEvent.click( screen.getByText( 'Store 1' ) );
await userEvent.click( screen.getByText( 'Store 1' ) );
expect( setSelectedOption ).toHaveBeenLastCalledWith( '1' );
expect( onSelectRate ).toHaveBeenLastCalledWith( '1' );
} );

View File

@ -66,7 +66,6 @@ exports[`ProductDetails should render details 1`] = `
<li
className=""
>
<span
className="wc-block-components-product-details__value"
>

View File

@ -12,12 +12,15 @@ import { VALIDATION_STORE_KEY } from '@woocommerce/block-data';
import { TotalsCoupon } from '..';
describe( 'TotalsCoupon', () => {
it( "Shows a validation error when one is in the wc/store/validation data store and doesn't show one when there isn't", () => {
it( "Shows a validation error when one is in the wc/store/validation data store and doesn't show one when there isn't", async () => {
const user = userEvent.setup();
const { rerender } = render( <TotalsCoupon instanceId={ 'coupon' } /> );
const openCouponFormButton = screen.getByText( 'Add a coupon' );
expect( openCouponFormButton ).toBeInTheDocument();
userEvent.click( openCouponFormButton );
await act( async () => {
await user.click( openCouponFormButton );
} );
expect(
screen.queryByText( 'Invalid coupon code' )
).not.toBeInTheDocument();

View File

@ -1,7 +1,8 @@
/**
* External dependencies
*/
import { render, fireEvent, findByText } from '@testing-library/react';
import { render, findByText, act } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
/**
* Internal dependencies
@ -49,7 +50,7 @@ describe( 'NoticeBanner', () => {
expect( summaryElement ).toBeInTheDocument();
} );
test( 'can be dismissed when isDismissible prop is true', () => {
test( 'can be dismissed when isDismissible prop is true', async () => {
const onRemoveMock = jest.fn();
const { getByRole } = render(
<NoticeBanner
@ -61,11 +62,13 @@ describe( 'NoticeBanner', () => {
</NoticeBanner>
);
const closeButton = getByRole( 'button' );
fireEvent.click( closeButton );
await act( async () => {
await userEvent.click( closeButton );
} );
expect( onRemoveMock ).toHaveBeenCalled();
} );
test( 'calls onRemove function when the notice is dismissed', () => {
test( 'calls onRemove function when the notice is dismissed', async () => {
const onRemoveMock = jest.fn();
const { getByRole } = render(
<NoticeBanner status="info" isDismissible onRemove={ onRemoveMock }>
@ -73,7 +76,9 @@ describe( 'NoticeBanner', () => {
</NoticeBanner>
);
const closeButton = getByRole( 'button' );
fireEvent.click( closeButton );
await act( async () => {
await userEvent.click( closeButton );
} );
expect( onRemoveMock ).toHaveBeenCalled();
} );

View File

@ -3,7 +3,8 @@
*/
import { useCartEventsContext } from '@woocommerce/base-context';
import { useEffect } from '@wordpress/element';
import { render, screen, waitFor } from '@testing-library/react';
import { act, render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
/**
* Internal dependencies
@ -13,13 +14,15 @@ import Block from '../../../../../../blocks/cart/inner-blocks/proceed-to-checkou
describe( 'CartEventsProvider', () => {
it( 'allows observers to unsubscribe', async () => {
const user = userEvent.setup();
const mockObserver = jest.fn().mockReturnValue( { type: 'error' } );
const MockObserverComponent = () => {
const { onProceedToCheckout } = useCartEventsContext();
useEffect( () => {
const unsubscribe = onProceedToCheckout( () => {
mockObserver();
unsubscribe();
mockObserver();
} );
}, [ onProceedToCheckout ] );
return <div>Mock observer</div>;
@ -33,6 +36,7 @@ describe( 'CartEventsProvider', () => {
</div>
</CartEventsProvider>
);
// TODO: Fix a recent deprecation of showSpinner prop of Button called in this component.
expect( console ).toHaveWarned();
@ -42,9 +46,14 @@ describe( 'CartEventsProvider', () => {
// Forcibly set the button URL to # to prevent JSDOM error: `["Error: Not implemented: navigation (except hash changes)`
button.parentElement?.removeAttribute( 'href' );
// Click twice. The observer should unsubscribe after the first click.
button.click();
button.click();
await act( async () => {
await user.click( button );
} );
await act( async () => {
await user.click( button );
} );
await waitFor( () => {
expect( mockObserver ).toHaveBeenCalledTimes( 1 );
} );

View File

@ -106,7 +106,14 @@ const setup = ( params: SetupParams ) => {
results: stubCollectionData(),
isLoading: false,
} );
const utils = render( <AttributeFilterBlock attributes={ attributes } /> );
const utils = render( <AttributeFilterBlock attributes={ attributes } />, {
legacyRoot: true,
} );
// We need to switch to React 17 rendering to allow these tests to keep passing, but as a result the React
// rendering error will be shown.
expect( console ).toHaveErroredWith(
`Warning: ReactDOM.render is no longer supported in React 18. Use createRoot instead. Until you switch to the new API, your app will behave as if it's running React 17. Learn more: https://reactjs.org/link/switch-to-createroot`
);
const applyButton = screen.getByRole( 'button', { name: /apply/i } );
const smallAttributeCheckbox = screen.getByRole( 'checkbox', {
name: /small/i,
@ -154,10 +161,10 @@ describe( 'Filter by Attribute block', () => {
expect( applyButton ).toBeDisabled();
} );
test( 'should enable Apply button when filter attributes are changed', () => {
test( 'should enable Apply button when filter attributes are changed', async () => {
const { applyButton, smallAttributeCheckbox } =
setupWithoutSelectedFilterAttributes();
userEvent.click( smallAttributeCheckbox );
await userEvent.click( smallAttributeCheckbox );
expect( applyButton ).not.toBeDisabled();
} );
@ -170,21 +177,21 @@ describe( 'Filter by Attribute block', () => {
expect( applyButton ).toBeDisabled();
} );
test( 'should enable Apply button when filter attributes are changed', () => {
test( 'should enable Apply button when filter attributes are changed', async () => {
const { applyButton, smallAttributeCheckbox } =
setupWithSelectedFilterAttributes();
userEvent.click( smallAttributeCheckbox );
await userEvent.click( smallAttributeCheckbox );
expect( applyButton ).not.toBeDisabled();
} );
test( 'should disable Apply button when deselecting the same previously selected attribute', () => {
test( 'should disable Apply button when deselecting the same previously selected attribute', async () => {
const { applyButton, smallAttributeCheckbox } =
setupWithSelectedFilterAttributes( { filterSize: 'small' } );
userEvent.click( smallAttributeCheckbox );
await userEvent.click( smallAttributeCheckbox );
expect( applyButton ).not.toBeDisabled();
userEvent.click( smallAttributeCheckbox );
await userEvent.click( smallAttributeCheckbox );
expect( applyButton ).toBeDisabled();
} );
} );

View File

@ -133,6 +133,8 @@ describe( 'PaymentMethods', () => {
} );
test( 'selecting new payment method', async () => {
const user = userEvent.setup();
const ShowActivePaymentMethod = () => {
const { activePaymentMethod, activeSavedToken } =
wpDataFunctions.useSelect( ( select ) => {
@ -192,7 +194,9 @@ describe( 'PaymentMethods', () => {
expect( savedToken ).toBeNull();
} );
userEvent.click( screen.getByText( 'Select new payment' ) );
await act( async () => {
await user.click( screen.getByText( 'Select new payment' ) );
} );
await waitFor( () => {
const activePaymentMethod = screen.queryByText(

View File

@ -231,6 +231,13 @@ describe( 'Testing cart', () => {
} );
expect( quantityInput.value ).toBe( '5' );
// React Transition Group uses deprecated findDOMNode, so we need to suppress the warning. This will have to be fixed in React 19.
expect( console ).toHaveErroredWith(
`Warning: findDOMNode is deprecated and will be removed in the next major release. Instead, add a ref directly to the element you want to reference. Learn more about using refs safely here: https://reactjs.org/link/strict-mode-find-node%s`,
// The stack trace
expect.any( String )
);
} );
it( 'does not show the remove item button when a filter prevents this', async () => {

View File

@ -5,6 +5,7 @@ import {
render,
findByLabelText,
queryByLabelText,
act,
} from '@testing-library/react';
import userEvent from '@testing-library/user-event';
@ -60,6 +61,7 @@ describe( 'FrontendBlock', () => {
} );
it( 'Clears any validation errors when the checkbox is checked', async () => {
const user = userEvent.setup();
const { container } = render(
<FrontendBlock
checkbox={ true }
@ -70,7 +72,9 @@ describe( 'FrontendBlock', () => {
container,
'I agree to the terms and conditions'
);
userEvent.click( checkbox );
await act( async () => {
await user.click( checkbox );
} );
expect( actionCreators.clearValidationError ).toHaveBeenLastCalledWith(
expect.stringMatching( /terms-and-conditions-\d/ )
);

View File

@ -8,6 +8,7 @@ import {
fireEvent,
findByPlaceholderText,
queryByPlaceholderText,
act,
} from '@testing-library/react';
import userEvent from '@testing-library/user-event';
@ -18,6 +19,8 @@ import OrderNotes from '../index';
describe( 'Checkout order notes', () => {
it( 'Shows a textarea when the checkbox to add order notes is toggled', async () => {
const user = userEvent.setup();
const { container } = render(
<OrderNotes
disabled={ false }
@ -31,7 +34,10 @@ describe( 'Checkout order notes', () => {
'Add a note to your order'
);
await userEvent.click( checkbox );
await act( async () => {
await user.click( checkbox );
} );
const textarea = await findByPlaceholderText(
container,
'Enter a note'
@ -59,6 +65,7 @@ describe( 'Checkout order notes', () => {
} );
it( 'Retains the order note when toggling the textarea on and off', async () => {
const user = userEvent.setup();
const onChange = jest.fn();
const { container, rerender } = render(
<OrderNotes
@ -74,7 +81,9 @@ describe( 'Checkout order notes', () => {
'Add a note to your order'
);
await userEvent.click( checkbox );
await act( async () => {
await user.click( checkbox );
} );
// The onChange handler should not have been called because the value is the same as what was stored
expect( onChange ).not.toHaveBeenCalled();
@ -83,7 +92,10 @@ describe( 'Checkout order notes', () => {
container,
'Enter a note'
);
// eslint-disable-next-line testing-library/no-unnecessary-act -- Act warnings still happen without wrapping in act.
await act( async () => {
fireEvent.change( textarea, { target: { value: 'Test message' } } );
} );
expect( onChange ).toHaveBeenLastCalledWith( 'Test message' );
// Rerender here with the new value to simulate the onChange updating the value
@ -97,7 +109,10 @@ describe( 'Checkout order notes', () => {
);
// Toggle off.
await userEvent.click( checkbox );
await act( async () => {
await user.click( checkbox );
} );
expect( onChange ).toHaveBeenLastCalledWith( '' );
// Rerender here with an empty value to simulate the onChange updating the value
@ -111,7 +126,9 @@ describe( 'Checkout order notes', () => {
);
// Toggle back on.
await userEvent.click( checkbox );
await act( async () => {
await user.click( checkbox );
} );
expect( onChange ).toHaveBeenLastCalledWith( 'Test message' );
} );
} );

View File

@ -100,21 +100,35 @@ describe( 'Testing Mini-Cart', () => {
} );
it( 'opens Mini-Cart drawer when clicking on button', async () => {
const user = userEvent.setup();
render( <MiniCartBlock /> );
await waitFor( () => expect( fetchMock ).toHaveBeenCalled() );
userEvent.click( screen.getByLabelText( /items/i ) );
await act( async () => {
await user.click( screen.getByLabelText( /items/i ) );
} );
await waitFor( () =>
expect( screen.getByText( /your cart/i ) ).toBeInTheDocument()
);
// The opening of the drawer uses deprecated ReactDOM.render.
expect( console ).toHaveErroredWith(
`Warning: ReactDOM.render is no longer supported in React 18. Use createRoot instead. Until you switch to the new API, your app will behave as if it's running React 17. Learn more: https://reactjs.org/link/switch-to-createroot%s`,
// The stack trace
expect.any( String )
);
} );
it( 'closes the drawer when clicking on the close button', async () => {
const user = userEvent.setup();
render( <MiniCartBlock /> );
await waitFor( () => expect( fetchMock ).toHaveBeenCalled() );
// Open drawer.
userEvent.click( screen.getByLabelText( /items/i ) );
await act( async () => {
await user.click( screen.getByLabelText( /items/i ) );
} );
// Close drawer.
let closeButton = null;
@ -122,7 +136,9 @@ describe( 'Testing Mini-Cart', () => {
closeButton = screen.getByLabelText( /close/i );
} );
if ( closeButton ) {
userEvent.click( closeButton );
await act( async () => {
await user.click( closeButton );
} );
}
await waitFor( () => {
@ -130,16 +146,34 @@ describe( 'Testing Mini-Cart', () => {
screen.queryByText( /your cart/i )
).not.toBeInTheDocument();
} );
// The opening of the drawer uses deprecated ReactDOM.render.
expect( console ).toHaveErroredWith(
`Warning: ReactDOM.render is no longer supported in React 18. Use createRoot instead. Until you switch to the new API, your app will behave as if it's running React 17. Learn more: https://reactjs.org/link/switch-to-createroot%s`,
// The stack trace
expect.any( String )
);
} );
it( 'renders empty cart if there are no items in the cart', async () => {
const user = userEvent.setup();
mockEmptyCart();
render( <MiniCartBlock /> );
await waitFor( () => expect( fetchMock ).toHaveBeenCalled() );
userEvent.click( screen.getByLabelText( /items/i ) );
await act( async () => {
await user.click( screen.getByLabelText( /items/i ) );
} );
expect( fetchMock ).toHaveBeenCalledTimes( 1 );
// The opening of the drawer uses deprecated ReactDOM.render.
expect( console ).toHaveErroredWith(
`Warning: ReactDOM.render is no longer supported in React 18. Use createRoot instead. Until you switch to the new API, your app will behave as if it's running React 17. Learn more: https://reactjs.org/link/switch-to-createroot%s`,
// The stack trace
expect.any( String )
);
} );
it( 'updates contents when removed from cart event is triggered', async () => {

View File

@ -2,7 +2,7 @@
* External dependencies
*/
import React from '@wordpress/element';
import { act, render, screen, waitFor, within } from '@testing-library/react';
import { render, screen, waitFor, within } from '@testing-library/react';
import * as hooks from '@woocommerce/base-context/hooks';
import userEvent from '@testing-library/user-event';
@ -78,7 +78,14 @@ const setup = ( params: SetupParams ) => {
} );
const { container, ...utils } = render(
<RatingFilterBlock attributes={ attributes } />
<RatingFilterBlock attributes={ attributes } />,
{ legacyRoot: true }
);
// We need to switch to React 17 rendering to allow these tests to keep passing, but as a result the React
// rendering error will be shown.
expect( console ).toHaveErroredWith(
`Warning: ReactDOM.render is no longer supported in React 18. Use createRoot instead. Until you switch to the new API, your app will behave as if it's running React 17. Learn more: https://reactjs.org/link/switch-to-createroot`
);
const getList = () => container.querySelector( selectors.list );
@ -210,7 +217,7 @@ describe( 'Filter by Rating block', () => {
expect( getRating5Chips() ).toBeNull();
} );
test( 'replaces chosen option when another one is clicked', () => {
test( 'replaces chosen option when another one is clicked', async () => {
const ratingParam = '2';
const {
getDropdown,
@ -225,21 +232,21 @@ describe( 'Filter by Rating block', () => {
const dropdown = getDropdown();
if ( dropdown ) {
userEvent.click( dropdown );
await userEvent.click( dropdown );
acceptErrorWithDuplicatedKeys();
}
const rating4Suggestion = getRating4Suggestion();
if ( rating4Suggestion ) {
userEvent.click( rating4Suggestion );
await userEvent.click( rating4Suggestion );
}
expect( getRating2Chips() ).toBeNull();
expect( getRating4Chips() ).toBeInTheDocument();
} );
test( 'removes the option when the X button is clicked', () => {
test( 'removes the option when the X button is clicked', async () => {
const ratingParam = '4';
const {
getRating2Chips,
@ -257,9 +264,7 @@ describe( 'Filter by Rating block', () => {
);
if ( removeRating4Button ) {
act( () => {
userEvent.click( removeRating4Button );
} );
await userEvent.click( removeRating4Button );
acceptErrorWithDuplicatedKeys();
}
@ -286,7 +291,7 @@ describe( 'Filter by Rating block', () => {
expect( getRating5Chips() ).toBeNull();
} );
test( 'adds chosen option to another one that is clicked', () => {
test( 'adds chosen option to another one that is clicked', async () => {
const ratingParam = '2';
const {
getDropdown,
@ -304,14 +309,14 @@ describe( 'Filter by Rating block', () => {
const dropdown = getDropdown();
if ( dropdown ) {
userEvent.click( dropdown );
await userEvent.click( dropdown );
acceptErrorWithDuplicatedKeys();
}
const rating4Suggestion = getRating4Suggestion();
if ( rating4Suggestion ) {
userEvent.click( rating4Suggestion );
await userEvent.click( rating4Suggestion );
}
expect( getRating2Chips() ).toBeInTheDocument();
@ -321,7 +326,7 @@ describe( 'Filter by Rating block', () => {
const rating5Suggestion = getRating5Suggestion();
if ( rating5Suggestion ) {
userEvent.click( rating5Suggestion );
await userEvent.click( rating5Suggestion );
}
expect( getRating2Chips() ).toBeInTheDocument();
@ -329,7 +334,7 @@ describe( 'Filter by Rating block', () => {
expect( getRating5Chips() ).toBeInTheDocument();
} );
test( 'removes the option when the X button is clicked', () => {
test( 'removes the option when the X button is clicked', async () => {
const ratingParam = '2,4,5';
const {
getRating2Chips,
@ -347,9 +352,7 @@ describe( 'Filter by Rating block', () => {
);
if ( removeRating4Button ) {
act( () => {
userEvent.click( removeRating4Button );
} );
await userEvent.click( removeRating4Button );
}
expect( getRating2Chips() ).toBeInTheDocument();
@ -393,7 +396,7 @@ describe( 'Filter by Rating block', () => {
const rating4checkbox = getRating4Checkbox();
if ( rating4checkbox ) {
userEvent.click( rating4checkbox );
await userEvent.click( rating4checkbox );
}
expect( getRating2Checkbox()?.checked ).toBeFalsy();
@ -416,7 +419,7 @@ describe( 'Filter by Rating block', () => {
const rating4checkbox = getRating4Checkbox();
if ( rating4checkbox ) {
userEvent.click( rating4checkbox );
await userEvent.click( rating4checkbox );
}
await waitFor( () => {
@ -462,7 +465,7 @@ describe( 'Filter by Rating block', () => {
const rating5checkbox = getRating5Checkbox();
if ( rating5checkbox ) {
userEvent.click( rating5checkbox );
await userEvent.click( rating5checkbox );
}
await waitFor( () => {
@ -487,7 +490,7 @@ describe( 'Filter by Rating block', () => {
const rating2checkbox = getRating2Checkbox();
if ( rating2checkbox ) {
userEvent.click( rating2checkbox );
await userEvent.click( rating2checkbox );
}
await waitFor( () => {

View File

@ -15,7 +15,7 @@ jest.mock( '@woocommerce/settings', () => ( {
/**
* External dependencies
*/
import { render } from '@testing-library/react';
import { act, render } from '@testing-library/react';
import { getSetting } from '@woocommerce/settings';
/**
@ -48,7 +48,7 @@ describe( 'ReviewsFrontendBlock', () => {
rating: 1,
};
it( 'Does not render when there are no reviews', () => {
it( 'Does not render when there are no reviews', async () => {
const { container } = render(
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore - withReviews HOC will need refactoring to TS to fix this.
@ -62,8 +62,9 @@ describe( 'ReviewsFrontendBlock', () => {
onChangeOrderby={ jest.fn() }
/>
);
expect( container.firstChild ).toBeNull();
await act( async () => {
expect( container ).toBeEmptyDOMElement();
} );
} );
it( 'Shows load more button when there are more reviews than displayed.', async () => {

View File

@ -2,7 +2,6 @@
exports[`Filter by Stock block renders the stock filter block 1`] = `
<div>
<div
class="wc-block-stock-filter style-list"
>
@ -112,7 +111,6 @@ exports[`Filter by Stock block renders the stock filter block 1`] = `
exports[`Filter by Stock block renders the stock filter block with the filter button 1`] = `
<div>
<div
class="wc-block-stock-filter style-list"
>
@ -238,7 +236,6 @@ exports[`Filter by Stock block renders the stock filter block with the filter bu
exports[`Filter by Stock block renders the stock filter block with the product counts 1`] = `
<div>
<div
class="wc-block-stock-filter style-list"
>

View File

@ -87,7 +87,14 @@ const setup = ( params: SetupParams = {} ) => {
};
const { container, ...utils } = render(
<Block attributes={ attributes } />
<Block attributes={ attributes } />,
{ legacyRoot: true }
);
// We need to switch to React 17 rendering to allow these tests to keep passing, but as a result the React
// rendering error will be shown.
expect( console ).toHaveErroredWith(
`Warning: ReactDOM.render is no longer supported in React 18. Use createRoot instead. Until you switch to the new API, your app will behave as if it's running React 17. Learn more: https://reactjs.org/link/switch-to-createroot`
);
const getList = () => container.querySelector( selectors.list );
@ -261,7 +268,8 @@ describe( 'Filter by Stock block', () => {
expect( getOnBackorderChips() ).toBeNull();
} );
test( 'replaces chosen option when another one is clicked', () => {
test( 'replaces chosen option when another one is clicked', async () => {
const user = userEvent.setup();
const ratingParam = 'instock';
const {
getDropdown,
@ -276,20 +284,25 @@ describe( 'Filter by Stock block', () => {
const dropdown = getDropdown();
if ( dropdown ) {
userEvent.click( dropdown );
await act( async () => {
await user.click( dropdown );
} );
}
const outOfStockSuggestion = getOutOfStockSuggestion();
if ( outOfStockSuggestion ) {
userEvent.click( outOfStockSuggestion );
await act( async () => {
await user.click( outOfStockSuggestion );
} );
}
expect( getInStockChips() ).toBeNull();
expect( getOutOfStockChips() ).toBeInTheDocument();
} );
test( 'removes the option when the X button is clicked', () => {
test( 'removes the option when the X button is clicked', async () => {
const user = userEvent.setup();
const ratingParam = 'outofstock';
const {
getInStockChips,
@ -298,25 +311,29 @@ describe( 'Filter by Stock block', () => {
getRemoveButtonFromChips,
} = setupMultipleChoiceDropdown( ratingParam );
await waitFor( () => {
expect( getInStockChips() ).toBeNull();
expect( getOutOfStockChips() ).toBeInTheDocument();
expect( getOnBackorderChips() ).toBeNull();
} );
const removeOutOfStockButton = getRemoveButtonFromChips(
getOutOfStockChips()
);
if ( removeOutOfStockButton ) {
act( () => {
userEvent.click( removeOutOfStockButton );
act( async () => {
await user.click( removeOutOfStockButton );
} );
}
await waitFor( () => {
expect( getInStockChips() ).toBeNull();
expect( getOutOfStockChips() ).toBeNull();
expect( getOnBackorderChips() ).toBeNull();
} );
} );
} );
describe( 'Multiple choice Dropdown', () => {
test( 'renders dropdown', () => {
@ -336,6 +353,7 @@ describe( 'Filter by Stock block', () => {
} );
test( 'adds chosen option to another one that is clicked', async () => {
const user = userEvent.setup();
const ratingParam = 'onbackorder';
const {
getDropdown,
@ -346,20 +364,25 @@ describe( 'Filter by Stock block', () => {
getOutOfStockSuggestion,
} = setupMultipleChoiceDropdown( ratingParam );
await waitFor( () => {
expect( getInStockChips() ).toBeNull();
expect( getOutOfStockChips() ).toBeNull();
expect( getOnBackorderChips() ).toBeInTheDocument();
} );
const dropdown = getDropdown();
if ( dropdown ) {
userEvent.click( dropdown );
await act( async () => {
await user.click( dropdown );
} );
}
const inStockSuggestion = getInStockSuggestion();
if ( inStockSuggestion ) {
userEvent.click( inStockSuggestion );
await act( async () => {
await user.click( inStockSuggestion );
} );
}
expect( getInStockChips() ).toBeInTheDocument();
@ -368,7 +391,9 @@ describe( 'Filter by Stock block', () => {
const freshDropdown = getDropdown();
if ( freshDropdown ) {
userEvent.click( freshDropdown );
await act( async () => {
await user.click( freshDropdown );
} );
}
const outOfStockSuggestion = getOutOfStockSuggestion();
@ -384,7 +409,8 @@ describe( 'Filter by Stock block', () => {
} );
} );
test( 'removes the option when the X button is clicked', () => {
test( 'removes the option when the X button is clicked', async () => {
const user = userEvent.setup();
const ratingParam = 'instock,outofstock,onbackorder';
const {
getInStockChips,
@ -393,25 +419,29 @@ describe( 'Filter by Stock block', () => {
getRemoveButtonFromChips,
} = setupMultipleChoiceDropdown( ratingParam );
await waitFor( () => {
expect( getInStockChips() ).toBeInTheDocument();
expect( getOutOfStockChips() ).toBeInTheDocument();
expect( getOnBackorderChips() ).toBeInTheDocument();
} );
const removeOutOfStockButton = getRemoveButtonFromChips(
getOutOfStockChips()
);
if ( removeOutOfStockButton ) {
act( () => {
userEvent.click( removeOutOfStockButton );
act( async () => {
await user.click( removeOutOfStockButton );
} );
}
await waitFor( () => {
expect( getInStockChips() ).toBeInTheDocument();
expect( getOutOfStockChips() ).toBeNull();
expect( getOnBackorderChips() ).toBeInTheDocument();
} );
} );
} );
describe( 'Single choice List', () => {
test( 'renders list', () => {
@ -434,6 +464,7 @@ describe( 'Filter by Stock block', () => {
} );
test( 'replaces chosen option when another one is clicked', async () => {
const user = userEvent.setup();
const ratingParam = 'outofstock';
const {
getInStockCheckbox,
@ -448,7 +479,9 @@ describe( 'Filter by Stock block', () => {
const onBackorderCheckbox = getOnBackorderCheckbox();
if ( onBackorderCheckbox ) {
userEvent.click( onBackorderCheckbox );
await act( async () => {
await user.click( onBackorderCheckbox );
} );
}
expect( getInStockCheckbox()?.checked ).toBeFalsy();

View File

@ -3712,7 +3712,6 @@ Object {
<strong>
berry
</strong>
</label>
</div>
</div>
@ -3748,7 +3747,6 @@ Object {
<strong>
berry
</strong>
</label>
</div>
</div>
@ -3830,7 +3828,6 @@ Object {
<strong>
berry
</strong>
</label>
</div>
</div>
@ -3866,7 +3863,6 @@ Object {
<strong>
berry
</strong>
</label>
</div>
</div>

View File

@ -99,8 +99,8 @@ const getAlias = ( options = {} ) => {
__dirname,
`../assets/js/templates/`
),
'react/jsx-dev-runtime': require.resolve( 'react/jsx-dev-runtime.js' ),
'react/jsx-runtime': require.resolve( 'react/jsx-runtime.js' ),
'react/jsx-dev-runtime': require.resolve( 'react/jsx-dev-runtime' ),
'react/jsx-runtime': require.resolve( 'react/jsx-runtime' ),
};
};

File diff suppressed because it is too large Load Diff

View File

@ -137,10 +137,9 @@
"@storybook/react": "7.5.2",
"@storybook/react-webpack5": "^7.6.4",
"@testing-library/dom": "9.3.3",
"@testing-library/jest-dom": "5.16.5",
"@testing-library/react": "12.1.5",
"@testing-library/react-hooks": "7.0.2",
"@testing-library/user-event": "13.5.0",
"@testing-library/jest-dom": "6.4.5",
"@testing-library/react": "15.0.7",
"@testing-library/user-event": "14.5.2",
"@types/classnames": "2.3.0",
"@types/dinero.js": "1.9.0",
"@types/dompurify": "2.3.4",
@ -252,7 +251,7 @@
"puppeteer": "17.1.3",
"react-docgen": "5.4.3",
"react-docgen-typescript-plugin": "^1.0.5",
"react-test-renderer": "17.0.2",
"react-test-renderer": "18.3.1",
"redux": "4.2.1",
"request-promise": "4.2.6",
"rimraf": "5.0.5",
@ -319,8 +318,8 @@
"wordpress-components": "npm:@wordpress/components@14.2.0"
},
"peerDependencies": {
"react": "^17.0.0",
"react-dom": "^17.0.0"
"react": "^18.3.0",
"react-dom": "^18.3.0"
},
"optionalDependencies": {
"ndb": "1.1.5"

View File

@ -1,7 +1,3 @@
/**
* External dependencies
*/
import { renderHook } from '@testing-library/react-hooks';
/**
* Internal dependencies
*/
@ -19,6 +15,7 @@ jest.mock( '@woocommerce/settings', () => {
describe( 'Checkout registry (as admin user)', () => {
test( 'should throw if the filter throws and user is an admin', () => {
expect.assertions( 1 );
const filterName = 'ErrorTestFilter';
const value = 'Hello World';
registerCheckoutFilters( filterName, {
@ -27,30 +24,35 @@ describe( 'Checkout registry (as admin user)', () => {
},
} );
const { result } = renderHook( () =>
try {
applyCheckoutFilter( {
filterName,
defaultValue: value,
} )
);
expect( result.error ).toEqual( Error( 'test error' ) );
} );
} catch ( e ) {
// eslint-disable-next-line -- The toThrow helper does not stop wordpress/jest-console from erroring.
expect( e.message ).toBe( 'test error' );
}
} );
test( 'should throw if validation throws and user is an admin', () => {
expect.assertions( 1 );
const filterName = 'ValidationTestFilter';
const value = 'Hello World';
registerCheckoutFilters( filterName, {
[ filterName ]: ( val ) => val,
} );
const { result } = renderHook( () =>
try {
applyCheckoutFilter( {
filterName,
defaultValue: value,
validation: () => {
throw Error( 'validation error' );
},
} )
);
expect( result.error ).toEqual( Error( 'validation error' ) );
} );
} catch ( e ) {
// eslint-disable-next-line -- The toThrow helper does not stop wordpress/jest-console from erroring.
expect( e.message ).toBe( 'validation error' );
}
} );
} );

View File

@ -1,7 +1,7 @@
/**
* External dependencies
*/
import { renderHook } from '@testing-library/react-hooks';
import { renderHook } from '@testing-library/react';
/**
* Internal dependencies
*/

View File

@ -23,6 +23,8 @@ jest.mock( '@wordpress/data', () => ( {
describe( 'ValidatedTextInput', () => {
it( 'Removes related validation error on change', async () => {
const user = userEvent.setup();
render(
<ValidatedTextInput
instanceId={ '0' }
@ -46,9 +48,14 @@ describe( 'ValidatedTextInput', () => {
await expect(
select( VALIDATION_STORE_KEY ).getValidationError( 'test-input' )
).not.toBe( undefined );
const textInputElement = await screen.getByLabelText( 'Test Input' );
await userEvent.type( textInputElement, 'New value' );
await expect(
await act( async () => {
await user.type( textInputElement, 'New value' );
} );
expect(
select( VALIDATION_STORE_KEY ).getValidationError( 'test-input' )
).toBe( undefined );
} );
@ -75,7 +82,11 @@ describe( 'ValidatedTextInput', () => {
select( VALIDATION_STORE_KEY ).getValidationError( 'textinput-1' )
).not.toBe( undefined );
const textInputElement = await screen.getByLabelText( 'Test Input' );
await act( async () => {
await userEvent.type( textInputElement, 'New value' );
} );
await expect(
select( VALIDATION_STORE_KEY ).getValidationError( 'textinput-1' )
).toBe( undefined );
@ -129,6 +140,7 @@ describe( 'ValidatedTextInput', () => {
await expect( errorMessageElement ).toBeInTheDocument();
} );
it( 'Runs custom validation on the input', async () => {
const user = userEvent.setup();
const TestComponent = () => {
const [ inputValue, setInputValue ] = useState( 'Test' );
return (
@ -147,17 +159,26 @@ describe( 'ValidatedTextInput', () => {
render( <TestComponent /> );
const textInputElement = await screen.getByLabelText( 'Test Input' );
await userEvent.type( textInputElement, 'Invalid Value' );
await act( async () => {
await user.type( textInputElement, 'Invalid Value' );
} );
await expect(
select( VALIDATION_STORE_KEY ).getValidationError( 'test-input' )
).not.toBe( undefined );
await userEvent.type( textInputElement, '{selectall}{del}Valid Value' );
await act( async () => {
await user.clear( textInputElement );
await user.type( textInputElement, 'Valid Value' );
} );
await expect( textInputElement.value ).toBe( 'Valid Value' );
await expect(
select( VALIDATION_STORE_KEY ).getValidationError( 'test-input' )
).toBe( undefined );
} );
it( 'Shows a custom error message for an invalid required input', async () => {
const user = userEvent.setup();
const TestComponent = () => {
const [ inputValue, setInputValue ] = useState( '' );
return (
@ -173,9 +194,13 @@ describe( 'ValidatedTextInput', () => {
};
render( <TestComponent /> );
const textInputElement = await screen.getByLabelText( 'Test Input' );
await userEvent.type( textInputElement, 'test' );
await userEvent.type( textInputElement, '{selectall}{del}' );
await act( async () => {
await user.type( textInputElement, 'test' );
await user.clear( textInputElement );
await textInputElement.blur();
} );
await expect(
screen.queryByText( 'Please enter a valid test input' )
).not.toBeNull();

View File

@ -0,0 +1,4 @@
Significance: patch
Type: dev
Update the Blocks JS tests to React 18

File diff suppressed because it is too large Load Diff