[React 18] Use `createRoot().render()` instead of `render()` (#48843)

* Use createRoot().render() instead of render()

* Add changefile(s) from automation for the following project(s): woocommerce-blocks

* Replace `unmountComponentAtNode()` with `createRoot().unmount()`

* Adjust JS unit tests

* Remove obsolete import

* Remove “ReactDOM.render is no longer supported” check

* Update pnpm-lock.yaml

* Migrate JS unit test to React 18

* Revert "Update pnpm-lock.yaml"

This reverts commit 18bfc967aa.

* Migrate JS unit test to React 18

* Migrate JS unit test to React 18

* Minor refactor

* Unskip previously skipped tests

* Fix JS unit test error related to root.unmount()

* Remove obsolete dependency

* Fix failing blocks e2e test

* Fix failing core e2e tests

* Optimise memoisation to prevent “Maximum update depth exceeded” error.

* Fix Filters JS tests in #48796 (#49973)

* Run cleanup in Rating Filter JS tests

* Run cleanup and wait user interactions in Stock Status Filter JS tests

* Rename constant

* Simplify conditional statement

* Introduce TS type

* Address lint error

* Rename TS type

* Optimise multiple TS types

---------

Co-authored-by: github-actions <github-actions@github.com>
Co-authored-by: Sam Seay <samueljseay@gmail.com>
Co-authored-by: Karol Manijak <20098064+kmanijak@users.noreply.github.com>
This commit is contained in:
Niels Lange 2024-07-26 09:18:48 +02:00 committed by GitHub
parent 33222c20e2
commit 752273e6ce
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 598 additions and 544 deletions

View File

@ -15,6 +15,7 @@ import {
hasInnerBlocks, hasInnerBlocks,
} from '@woocommerce/blocks-checkout'; } from '@woocommerce/blocks-checkout';
import BlockErrorBoundary from '@woocommerce/base-components/block-error-boundary'; import BlockErrorBoundary from '@woocommerce/base-components/block-error-boundary';
import type { ReactRootWithContainer } from '@woocommerce/base-utils';
/** /**
* This file contains logic used on the frontend to convert DOM elements (saved by the block editor) to React * This file contains logic used on the frontend to convert DOM elements (saved by the block editor) to React
@ -294,7 +295,7 @@ export const renderParentBlock = ( {
selector: string; selector: string;
// Function to generate the props object for the block. // Function to generate the props object for the block.
getProps: ( el: Element, i: number ) => Record< string, unknown >; getProps: ( el: Element, i: number ) => Record< string, unknown >;
} ): void => { } ): ReactRootWithContainer[] => {
/** /**
* In addition to getProps, we need to render and return the children. This adds children to props. * In addition to getProps, we need to render and return the children. This adds children to props.
*/ */
@ -310,7 +311,7 @@ export const renderParentBlock = ( {
/** /**
* The only difference between using renderParentBlock and renderFrontend is that here we provide children. * The only difference between using renderParentBlock and renderFrontend is that here we provide children.
*/ */
renderFrontend( { return renderFrontend( {
Block, Block,
selector, selector,
getProps: getPropsWithChildren, getProps: getPropsWithChildren,

View File

@ -21,16 +21,11 @@ jest.mock( '@wordpress/element', () => {
}; };
} ); } );
const renderInCheckoutProvider = ( ui, options = { legacyRoot: true } ) => { const renderInCheckoutProvider = ( ui, options = {} ) => {
const Wrapper = ( { children } ) => { const Wrapper = ( { children } ) => {
return <CheckoutProvider>{ children }</CheckoutProvider>; return <CheckoutProvider>{ children }</CheckoutProvider>;
}; };
const result = 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; return result;
}; };
@ -129,7 +124,7 @@ describe( 'Form Component', () => {
); );
}; };
it( 'updates context value when interacting with form elements', async () => { test( 'updates context value when interacting with form elements', async () => {
renderInCheckoutProvider( renderInCheckoutProvider(
<> <>
<WrappedAddressForm type="shipping" /> <WrappedAddressForm type="shipping" />
@ -155,7 +150,7 @@ describe( 'Form Component', () => {
); );
} ); } );
it( 'input fields update when changing the country', async () => { test( 'input fields update when changing the country', async () => {
renderInCheckoutProvider( <WrappedAddressForm type="shipping" /> ); renderInCheckoutProvider( <WrappedAddressForm type="shipping" /> );
await act( async () => { await act( async () => {
@ -182,7 +177,7 @@ describe( 'Form Component', () => {
expect( screen.getByLabelText( /Postal code/ ) ).toBeInTheDocument(); expect( screen.getByLabelText( /Postal code/ ) ).toBeInTheDocument();
} ); } );
it( 'input values are reset after changing the country', async () => { test( 'input values are reset after changing the country', async () => {
renderInCheckoutProvider( <WrappedAddressForm type="shipping" /> ); renderInCheckoutProvider( <WrappedAddressForm type="shipping" /> );
// First enter an address with no state, but fill the city. // First enter an address with no state, but fill the city.

View File

@ -1,8 +1,9 @@
/** /**
* External dependencies * External dependencies
*/ */
import { render, Suspense } from '@wordpress/element'; import { createRoot, useEffect, Suspense } from '@wordpress/element';
import BlockErrorBoundary from '@woocommerce/base-components/block-error-boundary'; import BlockErrorBoundary from '@woocommerce/base-components/block-error-boundary';
import type { Root } from 'react-dom/client';
// Some blocks take care of rendering their inner blocks automatically. For // Some blocks take care of rendering their inner blocks automatically. For
// example, the empty cart. In those cases, we don't want to trigger the render // example, the empty cart. In those cases, we don't want to trigger the render
@ -27,6 +28,11 @@ export type GetPropsFn<
TAttributes extends Record< string, unknown > TAttributes extends Record< string, unknown >
> = ( el: HTMLElement, i: number ) => BlockProps< TProps, TAttributes >; > = ( el: HTMLElement, i: number ) => BlockProps< TProps, TAttributes >;
export type ReactRootWithContainer = {
container: HTMLElement;
root: Root;
};
interface RenderBlockParams< interface RenderBlockParams<
TProps extends Record< string, unknown >, TProps extends Record< string, unknown >,
TAttributes extends Record< string, unknown > TAttributes extends Record< string, unknown >
@ -55,22 +61,34 @@ export const renderBlock = <
attributes = {} as TAttributes, attributes = {} as TAttributes,
props = {} as BlockProps< TProps, TAttributes >, props = {} as BlockProps< TProps, TAttributes >,
errorBoundaryProps = {}, errorBoundaryProps = {},
}: RenderBlockParams< TProps, TAttributes > ): void => { }: RenderBlockParams< TProps, TAttributes > ): Root => {
render( const BlockWrapper = () => {
<BlockErrorBoundary { ...errorBoundaryProps }> useEffect( () => {
<Suspense fallback={ <div className="wc-block-placeholder" /> }>
{ Block && <Block { ...props } attributes={ attributes } /> }
</Suspense>
</BlockErrorBoundary>,
container,
() => {
if ( container.classList ) { if ( container.classList ) {
container.classList.remove( 'is-loading' ); container.classList.remove( 'is-loading' );
} }
}, [] );
return (
<BlockErrorBoundary { ...errorBoundaryProps }>
<Suspense
fallback={
<div className="wc-block-placeholder">Loading...</div>
} }
>
{ Block && (
<Block { ...props } attributes={ attributes } />
) }
</Suspense>
</BlockErrorBoundary>
); );
}; };
const root = createRoot( container );
root.render( <BlockWrapper /> );
return root;
};
interface RenderBlockInContainersParams< interface RenderBlockInContainersParams<
TProps extends Record< string, unknown >, TProps extends Record< string, unknown >,
TAttributes extends Record< string, unknown > TAttributes extends Record< string, unknown >
@ -99,10 +117,14 @@ const renderBlockInContainers = <
containers, containers,
getProps = () => ( {} as BlockProps< TProps, TAttributes > ), getProps = () => ( {} as BlockProps< TProps, TAttributes > ),
getErrorBoundaryProps = () => ( {} ), getErrorBoundaryProps = () => ( {} ),
}: RenderBlockInContainersParams< TProps, TAttributes > ): void => { }: RenderBlockInContainersParams<
TProps,
TAttributes
> ): ReactRootWithContainer[] => {
if ( containers.length === 0 ) { if ( containers.length === 0 ) {
return; return [];
} }
const roots: ReactRootWithContainer[] = [];
// Use Array.forEach for IE11 compatibility. // Use Array.forEach for IE11 compatibility.
Array.prototype.forEach.call( containers, ( el, i ) => { Array.prototype.forEach.call( containers, ( el, i ) => {
@ -114,14 +136,19 @@ const renderBlockInContainers = <
...( props.attributes || {} ), ...( props.attributes || {} ),
}; };
renderBlock( { roots.push( {
container: el,
root: renderBlock( {
Block, Block,
container: el, container: el,
props, props,
attributes, attributes,
errorBoundaryProps, errorBoundaryProps,
} ),
} ); } );
} ); } );
return roots;
}; };
// Given an element and a list of wrappers, check if the element is inside at // Given an element and a list of wrappers, check if the element is inside at
@ -157,7 +184,10 @@ const renderBlockOutsideWrappers = <
getErrorBoundaryProps, getErrorBoundaryProps,
selector, selector,
wrappers, wrappers,
}: RenderBlockOutsideWrappersParams< TProps, TAttributes > ): void => { }: RenderBlockOutsideWrappersParams<
TProps,
TAttributes
> ): ReactRootWithContainer[] => {
const containers = document.body.querySelectorAll( selector ); const containers = document.body.querySelectorAll( selector );
// Filter out blocks inside the wrappers. // Filter out blocks inside the wrappers.
if ( wrappers && wrappers.length > 0 ) { if ( wrappers && wrappers.length > 0 ) {
@ -165,7 +195,8 @@ const renderBlockOutsideWrappers = <
return ! isElementInsideWrappers( el, wrappers ); return ! isElementInsideWrappers( el, wrappers );
} ); } );
} }
renderBlockInContainers( {
return renderBlockInContainers( {
Block, Block,
containers, containers,
getProps, getProps,
@ -234,20 +265,21 @@ export const renderFrontend = <
props: props:
| RenderBlockOutsideWrappersParams< TProps, TAttributes > | RenderBlockOutsideWrappersParams< TProps, TAttributes >
| RenderBlockInsideWrapperParams< TProps, TAttributes > | RenderBlockInsideWrapperParams< TProps, TAttributes >
): void => { ): ReactRootWithContainer[] => {
const wrappersToSkipOnLoad = document.body.querySelectorAll( const wrappersToSkipOnLoad = document.body.querySelectorAll(
selectorsToSkipOnLoad.join( ',' ) selectorsToSkipOnLoad.join( ',' )
); );
const { Block, getProps, getErrorBoundaryProps, selector } = props; const { Block, getProps, getErrorBoundaryProps, selector } = props;
renderBlockOutsideWrappers( { const roots = renderBlockOutsideWrappers( {
Block, Block,
getProps, getProps,
getErrorBoundaryProps, getErrorBoundaryProps,
selector, selector,
wrappers: wrappersToSkipOnLoad, wrappers: wrappersToSkipOnLoad,
} ); } );
// For each wrapper, add an event listener to render the inner blocks when // For each wrapper, add an event listener to render the inner blocks when
// `wc-blocks_render_blocks_frontend` event is triggered. // `wc-blocks_render_blocks_frontend` event is triggered.
Array.prototype.forEach.call( wrappersToSkipOnLoad, ( wrapper ) => { Array.prototype.forEach.call( wrappersToSkipOnLoad, ( wrapper ) => {
@ -255,6 +287,8 @@ export const renderFrontend = <
renderBlockInsideWrapper( { ...props, wrapper } ); renderBlockInsideWrapper( { ...props, wrapper } );
} ); } );
} ); } );
return roots;
}; };
export default renderFrontend; export default renderFrontend;

View File

@ -1,7 +1,7 @@
/** /**
* External dependencies * External dependencies
*/ */
import { render, screen } from '@testing-library/react'; import { act, render, screen } from '@testing-library/react';
import * as hooks from '@woocommerce/base-context/hooks'; import * as hooks from '@woocommerce/base-context/hooks';
import userEvent from '@testing-library/user-event'; import userEvent from '@testing-library/user-event';
@ -106,14 +106,8 @@ const setup = ( params: SetupParams ) => {
results: stubCollectionData(), results: stubCollectionData(),
isLoading: false, 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 applyButton = screen.getByRole( 'button', { name: /apply/i } );
const smallAttributeCheckbox = screen.getByRole( 'checkbox', { const smallAttributeCheckbox = screen.getByRole( 'checkbox', {
name: /small/i, name: /small/i,
@ -164,8 +158,10 @@ describe( 'Filter by Attribute block', () => {
test( 'should enable Apply button when filter attributes are changed', async () => { test( 'should enable Apply button when filter attributes are changed', async () => {
const { applyButton, smallAttributeCheckbox } = const { applyButton, smallAttributeCheckbox } =
setupWithoutSelectedFilterAttributes(); setupWithoutSelectedFilterAttributes();
await userEvent.click( smallAttributeCheckbox );
await act( async () => {
await userEvent.click( smallAttributeCheckbox );
} );
expect( applyButton ).not.toBeDisabled(); expect( applyButton ).not.toBeDisabled();
} ); } );
} ); } );
@ -180,18 +176,25 @@ describe( 'Filter by Attribute block', () => {
test( 'should enable Apply button when filter attributes are changed', async () => { test( 'should enable Apply button when filter attributes are changed', async () => {
const { applyButton, smallAttributeCheckbox } = const { applyButton, smallAttributeCheckbox } =
setupWithSelectedFilterAttributes(); setupWithSelectedFilterAttributes();
await userEvent.click( smallAttributeCheckbox );
await act( async () => {
await userEvent.click( smallAttributeCheckbox );
} );
expect( applyButton ).not.toBeDisabled(); expect( applyButton ).not.toBeDisabled();
} ); } );
test( 'should disable Apply button when deselecting the same previously selected attribute', async () => { test( 'should disable Apply button when deselecting the same previously selected attribute', async () => {
const { applyButton, smallAttributeCheckbox } = const { applyButton, smallAttributeCheckbox } =
setupWithSelectedFilterAttributes( { filterSize: 'small' } ); setupWithSelectedFilterAttributes( { filterSize: 'small' } );
await act( async () => {
await userEvent.click( smallAttributeCheckbox ); await userEvent.click( smallAttributeCheckbox );
} );
expect( applyButton ).not.toBeDisabled(); expect( applyButton ).not.toBeDisabled();
await act( async () => {
await userEvent.click( smallAttributeCheckbox ); await userEvent.click( smallAttributeCheckbox );
} );
expect( applyButton ).toBeDisabled(); expect( applyButton ).toBeDisabled();
} ); } );
} ); } );

View File

@ -6,6 +6,7 @@ import {
useState, useState,
useEffect, useEffect,
useCallback, useCallback,
useMemo,
createInterpolateElement, createInterpolateElement,
} from '@wordpress/element'; } from '@wordpress/element';
import { useShippingData, useStoreCart } from '@woocommerce/base-context/hooks'; import { useShippingData, useStoreCart } from '@woocommerce/base-context/hooks';
@ -138,10 +139,12 @@ const renderPickupLocation = (
const Block = (): JSX.Element | null => { const Block = (): JSX.Element | null => {
const { shippingRates, selectShippingRate } = useShippingData(); const { shippingRates, selectShippingRate } = useShippingData();
// Get pickup locations from the first shipping package. // Memoize pickup locations to prevent re-rendering when the shipping rates change.
const pickupLocations = ( shippingRates[ 0 ]?.shipping_rates || [] ).filter( const pickupLocations = useMemo( () => {
return ( shippingRates[ 0 ]?.shipping_rates || [] ).filter(
isPackageRateCollectable isPackageRateCollectable
); );
}, [ shippingRates ] );
const [ selectedOption, setSelectedOption ] = useState< string >( const [ selectedOption, setSelectedOption ] = useState< string >(
() => pickupLocations.find( ( rate ) => rate.selected )?.rate_id || '' () => pickupLocations.find( ( rate ) => rate.selected )?.rate_id || ''
@ -168,13 +171,19 @@ const Block = (): JSX.Element | null => {
renderPickupLocation, renderPickupLocation,
}; };
// Update the selected option if there is no rate selected on mount.
useEffect( () => { useEffect( () => {
if ( ! selectedOption && pickupLocations[ 0 ] ) { if (
! selectedOption &&
pickupLocations[ 0 ] &&
selectedOption !== pickupLocations[ 0 ].rate_id
) {
setSelectedOption( pickupLocations[ 0 ].rate_id ); setSelectedOption( pickupLocations[ 0 ].rate_id );
onSelectRate( pickupLocations[ 0 ].rate_id ); onSelectRate( pickupLocations[ 0 ].rate_id );
} }
}, [ onSelectRate, pickupLocations, selectedOption ] ); // Removing onSelectRate as it lead to an infinite loop when only one pickup location is available.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [ pickupLocations, selectedOption ] );
const packageCount = getShippingRatesPackageCount( shippingRates ); const packageCount = getShippingRatesPackageCount( shippingRates );
return ( return (
<> <>

View File

@ -26,6 +26,7 @@ import type {
} from '@woocommerce/types'; } from '@woocommerce/types';
import NoticeBanner from '@woocommerce/base-components/notice-banner'; import NoticeBanner from '@woocommerce/base-components/notice-banner';
import type { ReactElement } from 'react'; import type { ReactElement } from 'react';
import { useMemo } from '@wordpress/element';
/** /**
* Renders a shipping rate control option. * Renders a shipping rate control option.
@ -73,11 +74,13 @@ const Block = ( { noShippingPlaceholder = null } ): ReactElement | null => {
const { shippingAddress } = useCustomerData(); const { shippingAddress } = useCustomerData();
const filteredShippingRates = isCollectable const filteredShippingRates = useMemo( () => {
return isCollectable
? shippingRates.map( ( shippingRatesPackage ) => { ? shippingRates.map( ( shippingRatesPackage ) => {
return { return {
...shippingRatesPackage, ...shippingRatesPackage,
shipping_rates: shippingRatesPackage.shipping_rates.filter( shipping_rates:
shippingRatesPackage.shipping_rates.filter(
( shippingRatesPackageRate ) => ( shippingRatesPackageRate ) =>
! hasCollectableRate( ! hasCollectableRate(
shippingRatesPackageRate.method_id shippingRatesPackageRate.method_id
@ -86,6 +89,7 @@ const Block = ( { noShippingPlaceholder = null } ): ReactElement | null => {
}; };
} ) } )
: shippingRates; : shippingRates;
}, [ shippingRates, isCollectable ] );
if ( ! needsShipping ) { if ( ! needsShipping ) {
return null; return null;

View File

@ -20,15 +20,10 @@ import {
isCartResponseTotals, isCartResponseTotals,
isNumber, isNumber,
} from '@woocommerce/types'; } from '@woocommerce/types';
import { import { useCallback, useEffect, useRef, useState } from '@wordpress/element';
unmountComponentAtNode,
useCallback,
useEffect,
useRef,
useState,
} from '@wordpress/element';
import { sprintf, _n } from '@wordpress/i18n'; import { sprintf, _n } from '@wordpress/i18n';
import clsx from 'clsx'; import clsx from 'clsx';
import type { ReactRootWithContainer } from '@woocommerce/base-utils';
/** /**
* Internal dependencies * Internal dependencies
@ -110,6 +105,8 @@ const MiniCartBlock = ( attributes: Props ): JSX.Element => {
setContentsNode( node ); setContentsNode( node );
}, [] ); }, [] );
const rootRef = useRef< ReactRootWithContainer[] | null >( null );
useEffect( () => { useEffect( () => {
const body = document.querySelector( 'body' ); const body = document.querySelector( 'body' );
if ( body ) { if ( body ) {
@ -134,7 +131,7 @@ const MiniCartBlock = ( attributes: Props ): JSX.Element => {
return; return;
} }
if ( isOpen ) { if ( isOpen ) {
renderParentBlock( { const renderedBlock = renderParentBlock( {
Block: MiniCartContentsBlock, Block: MiniCartContentsBlock,
blockName, blockName,
getProps: ( el: Element ) => { getProps: ( el: Element ) => {
@ -151,16 +148,25 @@ const MiniCartBlock = ( attributes: Props ): JSX.Element => {
selector: '.wp-block-woocommerce-mini-cart-contents', selector: '.wp-block-woocommerce-mini-cart-contents',
blockMap: getRegisteredBlockComponents( blockName ), blockMap: getRegisteredBlockComponents( blockName ),
} ); } );
rootRef.current = renderedBlock;
} }
} }
return () => { return () => {
if ( contentsNode instanceof Element && isOpen ) { if ( contentsNode instanceof Element && isOpen ) {
const container = contentsNode.querySelector( const unmountingContainer = contentsNode.querySelector(
'.wp-block-woocommerce-mini-cart-contents' '.wp-block-woocommerce-mini-cart-contents'
); );
if ( container ) {
unmountComponentAtNode( container ); if ( unmountingContainer ) {
const foundRoot = rootRef?.current?.find(
( { container } ) => unmountingContainer === container
);
if ( typeof foundRoot?.root?.unmount === 'function' ) {
setTimeout( () => {
foundRoot.root.unmount();
} );
}
} }
} }
}; };

View File

@ -111,13 +111,6 @@ describe( 'Testing Mini-Cart', () => {
await waitFor( () => await waitFor( () =>
expect( screen.getByText( /your cart/i ) ).toBeInTheDocument() 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 () => { it( 'closes the drawer when clicking on the close button', async () => {
@ -132,9 +125,11 @@ describe( 'Testing Mini-Cart', () => {
// Close drawer. // Close drawer.
let closeButton = null; let closeButton = null;
await waitFor( () => { await waitFor( () => {
closeButton = screen.getByLabelText( /close/i ); closeButton = screen.getByLabelText( /close/i );
} ); } );
if ( closeButton ) { if ( closeButton ) {
await act( async () => { await act( async () => {
await user.click( closeButton ); await user.click( closeButton );
@ -146,13 +141,6 @@ describe( 'Testing Mini-Cart', () => {
screen.queryByText( /your cart/i ) screen.queryByText( /your cart/i )
).not.toBeInTheDocument(); ).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 () => { it( 'renders empty cart if there are no items in the cart', async () => {
@ -167,13 +155,6 @@ describe( 'Testing Mini-Cart', () => {
} ); } );
expect( fetchMock ).toHaveBeenCalledTimes( 1 ); 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 () => { it( 'updates contents when removed from cart event is triggered', async () => {

View File

@ -2,7 +2,14 @@
* External dependencies * External dependencies
*/ */
import React from '@wordpress/element'; import React from '@wordpress/element';
import { render, screen, waitFor, within } from '@testing-library/react'; import {
act,
cleanup,
render,
screen,
waitFor,
within,
} from '@testing-library/react';
import * as hooks from '@woocommerce/base-context/hooks'; import * as hooks from '@woocommerce/base-context/hooks';
import userEvent from '@testing-library/user-event'; import userEvent from '@testing-library/user-event';
@ -59,6 +66,7 @@ const selectors = {
}; };
const setup = ( params: SetupParams ) => { const setup = ( params: SetupParams ) => {
cleanup();
const url = `http://woo.local/${ const url = `http://woo.local/${
params.filterRating ? '?rating_filter=' + params.filterRating : '' params.filterRating ? '?rating_filter=' + params.filterRating : ''
}`; }`;
@ -78,14 +86,7 @@ const setup = ( params: SetupParams ) => {
} ); } );
const { container, ...utils } = render( 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 ); const getList = () => container.querySelector( selectors.list );
@ -203,11 +204,13 @@ describe( 'Filter by Rating block', () => {
describe( 'Single choice Dropdown', () => { describe( 'Single choice Dropdown', () => {
test( 'renders dropdown', () => { test( 'renders dropdown', () => {
const { getDropdown, getList } = setupSingleChoiceDropdown(); const { getDropdown, getList } = setupSingleChoiceDropdown();
expect( getDropdown() ).toBeInTheDocument(); expect( getDropdown() ).toBeInTheDocument();
expect( getList() ).toBeNull(); expect( getList() ).toBeNull();
} ); } );
test( 'renders chips based on URL params', () => { test( 'renders chips based on URL params', async () => {
await waitFor( async () => {
const ratingParam = '2'; const ratingParam = '2';
const { getRating2Chips, getRating4Chips, getRating5Chips } = const { getRating2Chips, getRating4Chips, getRating5Chips } =
setupSingleChoiceDropdown( ratingParam ); setupSingleChoiceDropdown( ratingParam );
@ -216,8 +219,10 @@ describe( 'Filter by Rating block', () => {
expect( getRating4Chips() ).toBeNull(); expect( getRating4Chips() ).toBeNull();
expect( getRating5Chips() ).toBeNull(); expect( getRating5Chips() ).toBeNull();
} ); } );
} );
test( 'replaces chosen option when another one is clicked', async () => { test( 'replaces chosen option when another one is clicked', async () => {
await waitFor( async () => {
const ratingParam = '2'; const ratingParam = '2';
const { const {
getDropdown, getDropdown,
@ -245,8 +250,10 @@ describe( 'Filter by Rating block', () => {
expect( getRating2Chips() ).toBeNull(); expect( getRating2Chips() ).toBeNull();
expect( getRating4Chips() ).toBeInTheDocument(); expect( getRating4Chips() ).toBeInTheDocument();
} ); } );
} );
test( 'removes the option when the X button is clicked', async () => { test( 'removes the option when the X button is clicked', async () => {
await waitFor( async () => {
const ratingParam = '4'; const ratingParam = '4';
const { const {
getRating2Chips, getRating2Chips,
@ -273,6 +280,7 @@ describe( 'Filter by Rating block', () => {
expect( getRating5Chips() ).toBeNull(); expect( getRating5Chips() ).toBeNull();
} ); } );
} ); } );
} );
describe( 'Multiple choice Dropdown', () => { describe( 'Multiple choice Dropdown', () => {
test( 'renders dropdown', () => { test( 'renders dropdown', () => {
@ -281,7 +289,8 @@ describe( 'Filter by Rating block', () => {
expect( getList() ).toBeNull(); expect( getList() ).toBeNull();
} ); } );
test( 'renders chips based on URL params', () => { test( 'renders chips based on URL params', async () => {
await waitFor( async () => {
const ratingParam = '2,4'; const ratingParam = '2,4';
const { getRating2Chips, getRating4Chips, getRating5Chips } = const { getRating2Chips, getRating4Chips, getRating5Chips } =
setupMultipleChoiceDropdown( ratingParam ); setupMultipleChoiceDropdown( ratingParam );
@ -290,8 +299,10 @@ describe( 'Filter by Rating block', () => {
expect( getRating4Chips() ).toBeInTheDocument(); expect( getRating4Chips() ).toBeInTheDocument();
expect( getRating5Chips() ).toBeNull(); expect( getRating5Chips() ).toBeNull();
} ); } );
} );
test( 'adds chosen option to another one that is clicked', async () => { test( 'adds chosen option to another one that is clicked', async () => {
await waitFor( async () => {
const ratingParam = '2'; const ratingParam = '2';
const { const {
getDropdown, getDropdown,
@ -333,8 +344,10 @@ describe( 'Filter by Rating block', () => {
expect( getRating4Chips() ).toBeInTheDocument(); expect( getRating4Chips() ).toBeInTheDocument();
expect( getRating5Chips() ).toBeInTheDocument(); expect( getRating5Chips() ).toBeInTheDocument();
} ); } );
} );
test( 'removes the option when the X button is clicked', async () => { test( 'removes the option when the X button is clicked', async () => {
await waitFor( async () => {
const ratingParam = '2,4,5'; const ratingParam = '2,4,5';
const { const {
getRating2Chips, getRating2Chips,
@ -360,6 +373,7 @@ describe( 'Filter by Rating block', () => {
expect( getRating5Chips() ).toBeInTheDocument(); expect( getRating5Chips() ).toBeInTheDocument();
} ); } );
} ); } );
} );
describe( 'Single choice List', () => { describe( 'Single choice List', () => {
test( 'renders list', () => { test( 'renders list', () => {
@ -368,7 +382,8 @@ describe( 'Filter by Rating block', () => {
expect( getList() ).toBeInTheDocument(); expect( getList() ).toBeInTheDocument();
} ); } );
test( 'renders checked options based on URL params', () => { test( 'renders checked options based on URL params', async () => {
await waitFor( async () => {
const ratingParam = '4'; const ratingParam = '4';
const { const {
getRating2Checkbox, getRating2Checkbox,
@ -380,8 +395,10 @@ describe( 'Filter by Rating block', () => {
expect( getRating4Checkbox()?.checked ).toBeTruthy(); expect( getRating4Checkbox()?.checked ).toBeTruthy();
expect( getRating5Checkbox()?.checked ).toBeFalsy(); expect( getRating5Checkbox()?.checked ).toBeFalsy();
} ); } );
} );
test( 'replaces chosen option when another one is clicked', async () => { test( 'replaces chosen option when another one is clicked', async () => {
await waitFor( async () => {
const ratingParam = '2'; const ratingParam = '2';
const { const {
getRating2Checkbox, getRating2Checkbox,
@ -396,15 +413,19 @@ describe( 'Filter by Rating block', () => {
const rating4checkbox = getRating4Checkbox(); const rating4checkbox = getRating4Checkbox();
if ( rating4checkbox ) { if ( rating4checkbox ) {
await act( async () => {
await userEvent.click( rating4checkbox ); await userEvent.click( rating4checkbox );
} );
} }
expect( getRating2Checkbox()?.checked ).toBeFalsy(); expect( getRating2Checkbox()?.checked ).toBeFalsy();
expect( getRating4Checkbox()?.checked ).toBeTruthy(); expect( getRating4Checkbox()?.checked ).toBeTruthy();
expect( getRating5Checkbox()?.checked ).toBeFalsy(); expect( getRating5Checkbox()?.checked ).toBeFalsy();
} ); } );
} );
test( 'removes the option when it is clicked again', async () => { test( 'removes the option when it is clicked again', async () => {
await waitFor( async () => {
const ratingParam = '4'; const ratingParam = '4';
const { const {
getRating2Checkbox, getRating2Checkbox,
@ -422,7 +443,6 @@ describe( 'Filter by Rating block', () => {
await userEvent.click( rating4checkbox ); await userEvent.click( rating4checkbox );
} }
await waitFor( () => {
expect( getRating2Checkbox()?.checked ).toBeFalsy(); expect( getRating2Checkbox()?.checked ).toBeFalsy();
expect( getRating4Checkbox()?.checked ).toBeFalsy(); expect( getRating4Checkbox()?.checked ).toBeFalsy();
expect( getRating5Checkbox()?.checked ).toBeFalsy(); expect( getRating5Checkbox()?.checked ).toBeFalsy();
@ -437,7 +457,8 @@ describe( 'Filter by Rating block', () => {
expect( getList() ).toBeInTheDocument(); expect( getList() ).toBeInTheDocument();
} ); } );
test( 'renders chips based on URL params', () => { test( 'renders chips based on URL params', async () => {
await waitFor( async () => {
const ratingParam = '4,5'; const ratingParam = '4,5';
const { const {
getRating2Checkbox, getRating2Checkbox,
@ -449,8 +470,10 @@ describe( 'Filter by Rating block', () => {
expect( getRating4Checkbox()?.checked ).toBeTruthy(); expect( getRating4Checkbox()?.checked ).toBeTruthy();
expect( getRating5Checkbox()?.checked ).toBeTruthy(); expect( getRating5Checkbox()?.checked ).toBeTruthy();
} ); } );
} );
test( 'adds chosen option to another one that is clicked', async () => { test( 'adds chosen option to another one that is clicked', async () => {
await waitFor( async () => {
const ratingParam = '2,4'; const ratingParam = '2,4';
const { const {
getRating2Checkbox, getRating2Checkbox,
@ -468,7 +491,6 @@ describe( 'Filter by Rating block', () => {
await userEvent.click( rating5checkbox ); await userEvent.click( rating5checkbox );
} }
await waitFor( () => {
expect( getRating2Checkbox()?.checked ).toBeTruthy(); expect( getRating2Checkbox()?.checked ).toBeTruthy();
expect( getRating4Checkbox()?.checked ).toBeTruthy(); expect( getRating4Checkbox()?.checked ).toBeTruthy();
expect( getRating5Checkbox()?.checked ).toBeTruthy(); expect( getRating5Checkbox()?.checked ).toBeTruthy();
@ -476,6 +498,7 @@ describe( 'Filter by Rating block', () => {
} ); } );
test( 'removes the option when it is clicked again', async () => { test( 'removes the option when it is clicked again', async () => {
await waitFor( async () => {
const ratingParam = '2,4'; const ratingParam = '2,4';
const { const {
getRating2Checkbox, getRating2Checkbox,
@ -493,7 +516,6 @@ describe( 'Filter by Rating block', () => {
await userEvent.click( rating2checkbox ); await userEvent.click( rating2checkbox );
} }
await waitFor( () => {
expect( getRating2Checkbox()?.checked ).toBeFalsy(); expect( getRating2Checkbox()?.checked ).toBeFalsy();
expect( getRating4Checkbox()?.checked ).toBeTruthy(); expect( getRating4Checkbox()?.checked ).toBeTruthy();
expect( getRating5Checkbox()?.checked ).toBeFalsy(); expect( getRating5Checkbox()?.checked ).toBeFalsy();

View File

@ -2,7 +2,14 @@
* External dependencies * External dependencies
*/ */
import React from '@wordpress/element'; import React from '@wordpress/element';
import { act, render, screen, within, waitFor } from '@testing-library/react'; import {
act,
cleanup,
render,
screen,
within,
waitFor,
} from '@testing-library/react';
import { default as fetchMock } from 'jest-fetch-mock'; import { default as fetchMock } from 'jest-fetch-mock';
import userEvent from '@testing-library/user-event'; import userEvent from '@testing-library/user-event';
@ -68,6 +75,7 @@ const selectors = {
}; };
const setup = ( params: SetupParams = {} ) => { const setup = ( params: SetupParams = {} ) => {
cleanup();
const url = `http://woo.local/${ const url = `http://woo.local/${
params.filterStock ? '?filter_stock_status=' + params.filterStock : '' params.filterStock ? '?filter_stock_status=' + params.filterStock : ''
}`; }`;
@ -87,14 +95,7 @@ const setup = ( params: SetupParams = {} ) => {
}; };
const { container, ...utils } = render( 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 ); const getList = () => container.querySelector( selectors.list );
@ -227,7 +228,7 @@ describe( 'Filter by Stock block', () => {
fetchMock.resetMocks(); fetchMock.resetMocks();
} ); } );
it( 'renders the stock filter block', async () => { test( 'renders the stock filter block', async () => {
const { container } = setup( { const { container } = setup( {
showFilterButton: false, showFilterButton: false,
showCounts: false, showCounts: false,
@ -235,7 +236,7 @@ describe( 'Filter by Stock block', () => {
expect( container ).toMatchSnapshot(); expect( container ).toMatchSnapshot();
} ); } );
it( 'renders the stock filter block with the filter button', async () => { test( 'renders the stock filter block with the filter button', async () => {
const { container } = setup( { const { container } = setup( {
showFilterButton: true, showFilterButton: true,
showCounts: false, showCounts: false,
@ -243,7 +244,7 @@ describe( 'Filter by Stock block', () => {
expect( container ).toMatchSnapshot(); expect( container ).toMatchSnapshot();
} ); } );
it( 'renders the stock filter block with the product counts', async () => { test( 'renders the stock filter block with the product counts', async () => {
const { container } = setup( { const { container } = setup( {
showFilterButton: false, showFilterButton: false,
showCounts: true, showCounts: true,
@ -254,21 +255,28 @@ describe( 'Filter by Stock block', () => {
describe( 'Single choice Dropdown', () => { describe( 'Single choice Dropdown', () => {
test( 'renders dropdown', () => { test( 'renders dropdown', () => {
const { getDropdown, getList } = setupSingleChoiceDropdown(); const { getDropdown, getList } = setupSingleChoiceDropdown();
expect( getDropdown() ).toBeInTheDocument(); expect( getDropdown() ).toBeInTheDocument();
expect( getList() ).toBeNull(); expect( getList() ).toBeNull();
} ); } );
test( 'renders chips based on URL params', () => { test( 'renders chips based on URL params', async () => {
await waitFor( async () => {
const ratingParam = 'instock'; const ratingParam = 'instock';
const { getInStockChips, getOutOfStockChips, getOnBackorderChips } = const {
setupSingleChoiceDropdown( ratingParam ); getInStockChips,
getOutOfStockChips,
getOnBackorderChips,
} = setupSingleChoiceDropdown( ratingParam );
expect( getInStockChips() ).toBeInTheDocument(); expect( getInStockChips() ).toBeInTheDocument();
expect( getOutOfStockChips() ).toBeNull(); expect( getOutOfStockChips() ).toBeNull();
expect( getOnBackorderChips() ).toBeNull(); expect( getOnBackorderChips() ).toBeNull();
} ); } );
} );
test( 'replaces chosen option when another one is clicked', async () => { test( 'replaces chosen option when another one is clicked', async () => {
await waitFor( async () => {
const user = userEvent.setup(); const user = userEvent.setup();
const ratingParam = 'instock'; const ratingParam = 'instock';
const { const {
@ -300,8 +308,10 @@ describe( 'Filter by Stock block', () => {
expect( getInStockChips() ).toBeNull(); expect( getInStockChips() ).toBeNull();
expect( getOutOfStockChips() ).toBeInTheDocument(); expect( getOutOfStockChips() ).toBeInTheDocument();
} ); } );
} );
test( 'removes the option when the X button is clicked', async () => { test( 'removes the option when the X button is clicked', async () => {
await waitFor( async () => {
const user = userEvent.setup(); const user = userEvent.setup();
const ratingParam = 'outofstock'; const ratingParam = 'outofstock';
const { const {
@ -311,23 +321,20 @@ describe( 'Filter by Stock block', () => {
getRemoveButtonFromChips, getRemoveButtonFromChips,
} = setupMultipleChoiceDropdown( ratingParam ); } = setupMultipleChoiceDropdown( ratingParam );
await waitFor( () => {
expect( getInStockChips() ).toBeNull(); expect( getInStockChips() ).toBeNull();
expect( getOutOfStockChips() ).toBeInTheDocument(); expect( getOutOfStockChips() ).toBeInTheDocument();
expect( getOnBackorderChips() ).toBeNull(); expect( getOnBackorderChips() ).toBeNull();
} );
const removeOutOfStockButton = getRemoveButtonFromChips( const removeOutOfStockButton = getRemoveButtonFromChips(
getOutOfStockChips() getOutOfStockChips()
); );
if ( removeOutOfStockButton ) { if ( removeOutOfStockButton ) {
act( async () => { await act( async () => {
await user.click( removeOutOfStockButton ); await user.click( removeOutOfStockButton );
} ); } );
} }
await waitFor( () => {
expect( getInStockChips() ).toBeNull(); expect( getInStockChips() ).toBeNull();
expect( getOutOfStockChips() ).toBeNull(); expect( getOutOfStockChips() ).toBeNull();
expect( getOnBackorderChips() ).toBeNull(); expect( getOnBackorderChips() ).toBeNull();
@ -342,17 +349,23 @@ describe( 'Filter by Stock block', () => {
expect( getList() ).toBeNull(); expect( getList() ).toBeNull();
} ); } );
test( 'renders chips based on URL params', () => { test( 'renders chips based on URL params', async () => {
await waitFor( async () => {
const ratingParam = 'instock,onbackorder'; const ratingParam = 'instock,onbackorder';
const { getInStockChips, getOutOfStockChips, getOnBackorderChips } = const {
setupMultipleChoiceDropdown( ratingParam ); getInStockChips,
getOutOfStockChips,
getOnBackorderChips,
} = setupMultipleChoiceDropdown( ratingParam );
expect( getInStockChips() ).toBeInTheDocument(); expect( getInStockChips() ).toBeInTheDocument();
expect( getOutOfStockChips() ).toBeNull(); expect( getOutOfStockChips() ).toBeNull();
expect( getOnBackorderChips() ).toBeInTheDocument(); expect( getOnBackorderChips() ).toBeInTheDocument();
} ); } );
} );
test( 'adds chosen option to another one that is clicked', async () => { test( 'adds chosen option to another one that is clicked', async () => {
await waitFor( async () => {
const user = userEvent.setup(); const user = userEvent.setup();
const ratingParam = 'onbackorder'; const ratingParam = 'onbackorder';
const { const {
@ -364,25 +377,20 @@ describe( 'Filter by Stock block', () => {
getOutOfStockSuggestion, getOutOfStockSuggestion,
} = setupMultipleChoiceDropdown( ratingParam ); } = setupMultipleChoiceDropdown( ratingParam );
await waitFor( () => {
expect( getInStockChips() ).toBeNull(); expect( getInStockChips() ).toBeNull();
expect( getOutOfStockChips() ).toBeNull(); expect( getOutOfStockChips() ).toBeNull();
expect( getOnBackorderChips() ).toBeInTheDocument(); expect( getOnBackorderChips() ).toBeInTheDocument();
} );
const dropdown = getDropdown(); const dropdown = getDropdown();
if ( dropdown ) { if ( dropdown ) {
await act( async () => {
await user.click( dropdown ); await user.click( dropdown );
} );
} }
const inStockSuggestion = getInStockSuggestion(); const inStockSuggestion = getInStockSuggestion();
if ( inStockSuggestion ) { if ( inStockSuggestion ) {
await act( async () => {
await user.click( inStockSuggestion ); await user.click( inStockSuggestion );
} );
} }
expect( getInStockChips() ).toBeInTheDocument(); expect( getInStockChips() ).toBeInTheDocument();
@ -391,18 +399,15 @@ describe( 'Filter by Stock block', () => {
const freshDropdown = getDropdown(); const freshDropdown = getDropdown();
if ( freshDropdown ) { if ( freshDropdown ) {
await act( async () => {
await user.click( freshDropdown ); await user.click( freshDropdown );
} );
} }
const outOfStockSuggestion = getOutOfStockSuggestion(); const outOfStockSuggestion = getOutOfStockSuggestion();
if ( outOfStockSuggestion ) { if ( outOfStockSuggestion ) {
userEvent.click( outOfStockSuggestion ); await userEvent.click( outOfStockSuggestion );
} }
await waitFor( () => {
expect( getInStockChips() ).toBeInTheDocument(); expect( getInStockChips() ).toBeInTheDocument();
expect( getOutOfStockChips() ).toBeInTheDocument(); expect( getOutOfStockChips() ).toBeInTheDocument();
expect( getOnBackorderChips() ).toBeInTheDocument(); expect( getOnBackorderChips() ).toBeInTheDocument();
@ -410,6 +415,7 @@ describe( 'Filter by Stock block', () => {
} ); } );
test( 'removes the option when the X button is clicked', async () => { test( 'removes the option when the X button is clicked', async () => {
await waitFor( async () => {
const user = userEvent.setup(); const user = userEvent.setup();
const ratingParam = 'instock,outofstock,onbackorder'; const ratingParam = 'instock,outofstock,onbackorder';
const { const {
@ -419,23 +425,20 @@ describe( 'Filter by Stock block', () => {
getRemoveButtonFromChips, getRemoveButtonFromChips,
} = setupMultipleChoiceDropdown( ratingParam ); } = setupMultipleChoiceDropdown( ratingParam );
await waitFor( () => {
expect( getInStockChips() ).toBeInTheDocument(); expect( getInStockChips() ).toBeInTheDocument();
expect( getOutOfStockChips() ).toBeInTheDocument(); expect( getOutOfStockChips() ).toBeInTheDocument();
expect( getOnBackorderChips() ).toBeInTheDocument(); expect( getOnBackorderChips() ).toBeInTheDocument();
} );
const removeOutOfStockButton = getRemoveButtonFromChips( const removeOutOfStockButton = getRemoveButtonFromChips(
getOutOfStockChips() getOutOfStockChips()
); );
if ( removeOutOfStockButton ) { if ( removeOutOfStockButton ) {
act( async () => { await act( async () => {
await user.click( removeOutOfStockButton ); await user.click( removeOutOfStockButton );
} ); } );
} }
await waitFor( () => {
expect( getInStockChips() ).toBeInTheDocument(); expect( getInStockChips() ).toBeInTheDocument();
expect( getOutOfStockChips() ).toBeNull(); expect( getOutOfStockChips() ).toBeNull();
expect( getOnBackorderChips() ).toBeInTheDocument(); expect( getOnBackorderChips() ).toBeInTheDocument();
@ -450,7 +453,8 @@ describe( 'Filter by Stock block', () => {
expect( getList() ).toBeInTheDocument(); expect( getList() ).toBeInTheDocument();
} ); } );
test( 'renders checked options based on URL params', () => { test( 'renders checked options based on URL params', async () => {
await waitFor( async () => {
const ratingParam = 'instock'; const ratingParam = 'instock';
const { const {
getInStockCheckbox, getInStockCheckbox,
@ -462,8 +466,10 @@ describe( 'Filter by Stock block', () => {
expect( getOutOfStockCheckbox()?.checked ).toBeFalsy(); expect( getOutOfStockCheckbox()?.checked ).toBeFalsy();
expect( getOnBackorderCheckbox()?.checked ).toBeFalsy(); expect( getOnBackorderCheckbox()?.checked ).toBeFalsy();
} ); } );
} );
test( 'replaces chosen option when another one is clicked', async () => { test( 'replaces chosen option when another one is clicked', async () => {
await waitFor( async () => {
const user = userEvent.setup(); const user = userEvent.setup();
const ratingParam = 'outofstock'; const ratingParam = 'outofstock';
const { const {
@ -488,8 +494,10 @@ describe( 'Filter by Stock block', () => {
expect( getOutOfStockCheckbox()?.checked ).toBeFalsy(); expect( getOutOfStockCheckbox()?.checked ).toBeFalsy();
expect( getOnBackorderCheckbox()?.checked ).toBeTruthy(); expect( getOnBackorderCheckbox()?.checked ).toBeTruthy();
} ); } );
} );
test( 'removes the option when it is clicked again', async () => { test( 'removes the option when it is clicked again', async () => {
await waitFor( async () => {
const ratingParam = 'onbackorder'; const ratingParam = 'onbackorder';
const { const {
getInStockCheckbox, getInStockCheckbox,
@ -514,6 +522,7 @@ describe( 'Filter by Stock block', () => {
} ); } );
} ); } );
} ); } );
} );
describe( 'Multiple choice List', () => { describe( 'Multiple choice List', () => {
test( 'renders list', () => { test( 'renders list', () => {
@ -522,7 +531,8 @@ describe( 'Filter by Stock block', () => {
expect( getList() ).toBeInTheDocument(); expect( getList() ).toBeInTheDocument();
} ); } );
test( 'renders chips based on URL params', () => { test( 'renders chips based on URL params', async () => {
await waitFor( async () => {
const ratingParam = 'instock,onbackorder'; const ratingParam = 'instock,onbackorder';
const { const {
getInStockCheckbox, getInStockCheckbox,
@ -534,8 +544,10 @@ describe( 'Filter by Stock block', () => {
expect( getOutOfStockCheckbox()?.checked ).toBeFalsy(); expect( getOutOfStockCheckbox()?.checked ).toBeFalsy();
expect( getOnBackorderCheckbox()?.checked ).toBeTruthy(); expect( getOnBackorderCheckbox()?.checked ).toBeTruthy();
} ); } );
} );
test( 'adds chosen option to another one that is clicked', async () => { test( 'adds chosen option to another one that is clicked', async () => {
await waitFor( async () => {
const ratingParam = 'outofstock,onbackorder'; const ratingParam = 'outofstock,onbackorder';
const { const {
getInStockCheckbox, getInStockCheckbox,
@ -559,8 +571,10 @@ describe( 'Filter by Stock block', () => {
expect( getOnBackorderCheckbox()?.checked ).toBeTruthy(); expect( getOnBackorderCheckbox()?.checked ).toBeTruthy();
} ); } );
} ); } );
} );
test( 'removes the option when it is clicked again', async () => { test( 'removes the option when it is clicked again', async () => {
await waitFor( async () => {
const ratingParam = 'instock,outofstock'; const ratingParam = 'instock,outofstock';
const { const {
getInStockCheckbox, getInStockCheckbox,
@ -586,3 +600,4 @@ describe( 'Filter by Stock block', () => {
} ); } );
} ); } );
} ); } );
} );

View File

@ -0,0 +1,4 @@
Significance: patch
Type: dev
Switch `render()` to `createRoot().render()` to use React 18 features.

View File

@ -137,9 +137,7 @@ test.describe(
await page await page
.getByRole( 'button', { name: 'Add a coupon' } ) .getByRole( 'button', { name: 'Add a coupon' } )
.click(); .click();
await page await page.getByLabel( 'Enter code' ).fill( coupons[ i ].code );
.locator( '#wc-block-components-totals-coupon__input-0' )
.fill( coupons[ i ].code );
await page.getByText( 'Apply', { exact: true } ).click(); await page.getByText( 'Apply', { exact: true } ).click();
await expect( await expect(
page page
@ -182,9 +180,7 @@ test.describe(
await page await page
.getByRole( 'button', { name: 'Add a coupon' } ) .getByRole( 'button', { name: 'Add a coupon' } )
.click(); .click();
await page await page.getByLabel( 'Enter code' ).fill( coupons[ i ].code );
.locator( '#wc-block-components-totals-coupon__input-0' )
.fill( coupons[ i ].code );
await page.getByText( 'Apply', { exact: true } ).click(); await page.getByText( 'Apply', { exact: true } ).click();
await expect( await expect(
page page
@ -224,9 +220,7 @@ test.describe(
} ) => { } ) => {
// try to add two same coupons and verify the error message // try to add two same coupons and verify the error message
await page.getByRole( 'button', { name: 'Add a coupon' } ).click(); await page.getByRole( 'button', { name: 'Add a coupon' } ).click();
await page await page.getByLabel( 'Enter code' ).fill( coupons[ 0 ].code );
.locator( '#wc-block-components-totals-coupon__input-0' )
.fill( coupons[ 0 ].code );
await page.getByText( 'Apply', { exact: true } ).click(); await page.getByText( 'Apply', { exact: true } ).click();
await expect( await expect(
page page
@ -236,9 +230,7 @@ test.describe(
) )
).toBeVisible(); ).toBeVisible();
await page.getByRole( 'button', { name: 'Add a coupon' } ).click(); await page.getByRole( 'button', { name: 'Add a coupon' } ).click();
await page await page.getByLabel( 'Enter code' ).fill( coupons[ 0 ].code );
.locator( '#wc-block-components-totals-coupon__input-0' )
.fill( coupons[ 0 ].code );
await page.getByText( 'Apply', { exact: true } ).click(); await page.getByText( 'Apply', { exact: true } ).click();
await expect( await expect(
page page
@ -254,9 +246,7 @@ test.describe(
} ) => { } ) => {
// add coupon with usage limit // add coupon with usage limit
await page.getByRole( 'button', { name: 'Add a coupon' } ).click(); await page.getByRole( 'button', { name: 'Add a coupon' } ).click();
await page await page.getByLabel( 'Enter code' ).fill( couponLimitedCode );
.locator( '#wc-block-components-totals-coupon__input-0' )
.fill( couponLimitedCode );
await page.getByText( 'Apply', { exact: true } ).click(); await page.getByText( 'Apply', { exact: true } ).click();
await expect( await expect(
page page

View File

@ -138,9 +138,7 @@ test.describe(
await page await page
.getByRole( 'button', { name: 'Add a coupon' } ) .getByRole( 'button', { name: 'Add a coupon' } )
.click(); .click();
await page await page.getByLabel( 'Enter code' ).fill( coupons[ i ].code );
.locator( '#wc-block-components-totals-coupon__input-0' )
.fill( coupons[ i ].code );
await page.getByText( 'Apply', { exact: true } ).click(); await page.getByText( 'Apply', { exact: true } ).click();
await expect( await expect(
page page
@ -183,9 +181,7 @@ test.describe(
await page await page
.getByRole( 'button', { name: 'Add a coupon' } ) .getByRole( 'button', { name: 'Add a coupon' } )
.click(); .click();
await page await page.getByLabel( 'Enter code' ).fill( coupons[ i ].code );
.locator( '#wc-block-components-totals-coupon__input-0' )
.fill( coupons[ i ].code );
await page.getByText( 'Apply', { exact: true } ).click(); await page.getByText( 'Apply', { exact: true } ).click();
await expect( await expect(
page page
@ -225,9 +221,7 @@ test.describe(
} ) => { } ) => {
// try to add two same coupons and verify the error message // try to add two same coupons and verify the error message
await page.getByRole( 'button', { name: 'Add a coupon' } ).click(); await page.getByRole( 'button', { name: 'Add a coupon' } ).click();
await page await page.getByLabel( 'Enter code' ).fill( coupons[ 0 ].code );
.locator( '#wc-block-components-totals-coupon__input-0' )
.fill( coupons[ 0 ].code );
await page.getByText( 'Apply', { exact: true } ).click(); await page.getByText( 'Apply', { exact: true } ).click();
await expect( await expect(
page page
@ -237,9 +231,7 @@ test.describe(
) )
).toBeVisible(); ).toBeVisible();
await page.getByRole( 'button', { name: 'Add a coupon' } ).click(); await page.getByRole( 'button', { name: 'Add a coupon' } ).click();
await page await page.getByLabel( 'Enter code' ).fill( coupons[ 0 ].code );
.locator( '#wc-block-components-totals-coupon__input-0' )
.fill( coupons[ 0 ].code );
await page.getByText( 'Apply', { exact: true } ).click(); await page.getByText( 'Apply', { exact: true } ).click();
await expect( await expect(
page page
@ -255,9 +247,7 @@ test.describe(
} ) => { } ) => {
// add coupon with usage limit // add coupon with usage limit
await page.getByRole( 'button', { name: 'Add a coupon' } ).click(); await page.getByRole( 'button', { name: 'Add a coupon' } ).click();
await page await page.getByLabel( 'Enter code' ).fill( couponLimitedCode );
.locator( '#wc-block-components-totals-coupon__input-0' )
.fill( couponLimitedCode );
await page.getByText( 'Apply', { exact: true } ).click(); await page.getByText( 'Apply', { exact: true } ).click();
await expect( await expect(
page page