diff --git a/plugins/woocommerce-blocks/assets/js/atomic/utils/render-parent-block.tsx b/plugins/woocommerce-blocks/assets/js/atomic/utils/render-parent-block.tsx index 913371d0983..24b6c1bd296 100644 --- a/plugins/woocommerce-blocks/assets/js/atomic/utils/render-parent-block.tsx +++ b/plugins/woocommerce-blocks/assets/js/atomic/utils/render-parent-block.tsx @@ -15,6 +15,7 @@ import { hasInnerBlocks, } from '@woocommerce/blocks-checkout'; 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 @@ -294,7 +295,7 @@ export const renderParentBlock = ( { selector: string; // Function to generate the props object for the block. 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. */ @@ -310,7 +311,7 @@ export const renderParentBlock = ( { /** * The only difference between using renderParentBlock and renderFrontend is that here we provide children. */ - renderFrontend( { + return renderFrontend( { Block, selector, getProps: getPropsWithChildren, diff --git a/plugins/woocommerce-blocks/assets/js/base/components/cart-checkout/form/test/index.js b/plugins/woocommerce-blocks/assets/js/base/components/cart-checkout/form/test/index.js index 3b802cdb8dd..9151c6e39e7 100644 --- a/plugins/woocommerce-blocks/assets/js/base/components/cart-checkout/form/test/index.js +++ b/plugins/woocommerce-blocks/assets/js/base/components/cart-checkout/form/test/index.js @@ -21,16 +21,11 @@ jest.mock( '@wordpress/element', () => { }; } ); -const renderInCheckoutProvider = ( ui, options = { legacyRoot: true } ) => { +const renderInCheckoutProvider = ( ui, options = {} ) => { const Wrapper = ( { children } ) => { return { children }; }; 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; }; @@ -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( <> @@ -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( ); await act( async () => { @@ -182,7 +177,7 @@ describe( 'Form Component', () => { 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( ); // First enter an address with no state, but fill the city. diff --git a/plugins/woocommerce-blocks/assets/js/base/utils/render-frontend.tsx b/plugins/woocommerce-blocks/assets/js/base/utils/render-frontend.tsx index 4867fd9815f..0a1739a5407 100644 --- a/plugins/woocommerce-blocks/assets/js/base/utils/render-frontend.tsx +++ b/plugins/woocommerce-blocks/assets/js/base/utils/render-frontend.tsx @@ -1,8 +1,9 @@ /** * 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 type { Root } from 'react-dom/client'; // 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 @@ -27,6 +28,11 @@ export type GetPropsFn< TAttributes extends Record< string, unknown > > = ( el: HTMLElement, i: number ) => BlockProps< TProps, TAttributes >; +export type ReactRootWithContainer = { + container: HTMLElement; + root: Root; +}; + interface RenderBlockParams< TProps extends Record< string, unknown >, TAttributes extends Record< string, unknown > @@ -55,20 +61,32 @@ export const renderBlock = < attributes = {} as TAttributes, props = {} as BlockProps< TProps, TAttributes >, errorBoundaryProps = {}, -}: RenderBlockParams< TProps, TAttributes > ): void => { - render( - - }> - { Block && } - - , - container, - () => { +}: RenderBlockParams< TProps, TAttributes > ): Root => { + const BlockWrapper = () => { + useEffect( () => { if ( container.classList ) { container.classList.remove( 'is-loading' ); } - } - ); + }, [] ); + + return ( + + Loading... + } + > + { Block && ( + + ) } + + + ); + }; + + const root = createRoot( container ); + root.render( ); + return root; }; interface RenderBlockInContainersParams< @@ -99,10 +117,14 @@ const renderBlockInContainers = < containers, getProps = () => ( {} as BlockProps< TProps, TAttributes > ), getErrorBoundaryProps = () => ( {} ), -}: RenderBlockInContainersParams< TProps, TAttributes > ): void => { +}: RenderBlockInContainersParams< + TProps, + TAttributes +> ): ReactRootWithContainer[] => { if ( containers.length === 0 ) { - return; + return []; } + const roots: ReactRootWithContainer[] = []; // Use Array.forEach for IE11 compatibility. Array.prototype.forEach.call( containers, ( el, i ) => { @@ -114,14 +136,19 @@ const renderBlockInContainers = < ...( props.attributes || {} ), }; - renderBlock( { - Block, + roots.push( { container: el, - props, - attributes, - errorBoundaryProps, + root: renderBlock( { + Block, + container: el, + props, + attributes, + errorBoundaryProps, + } ), } ); } ); + + return roots; }; // Given an element and a list of wrappers, check if the element is inside at @@ -157,7 +184,10 @@ const renderBlockOutsideWrappers = < getErrorBoundaryProps, selector, wrappers, -}: RenderBlockOutsideWrappersParams< TProps, TAttributes > ): void => { +}: RenderBlockOutsideWrappersParams< + TProps, + TAttributes +> ): ReactRootWithContainer[] => { const containers = document.body.querySelectorAll( selector ); // Filter out blocks inside the wrappers. if ( wrappers && wrappers.length > 0 ) { @@ -165,7 +195,8 @@ const renderBlockOutsideWrappers = < return ! isElementInsideWrappers( el, wrappers ); } ); } - renderBlockInContainers( { + + return renderBlockInContainers( { Block, containers, getProps, @@ -234,20 +265,21 @@ export const renderFrontend = < props: | RenderBlockOutsideWrappersParams< TProps, TAttributes > | RenderBlockInsideWrapperParams< TProps, TAttributes > -): void => { +): ReactRootWithContainer[] => { const wrappersToSkipOnLoad = document.body.querySelectorAll( selectorsToSkipOnLoad.join( ',' ) ); const { Block, getProps, getErrorBoundaryProps, selector } = props; - renderBlockOutsideWrappers( { + const roots = renderBlockOutsideWrappers( { Block, getProps, getErrorBoundaryProps, selector, wrappers: wrappersToSkipOnLoad, } ); + // For each wrapper, add an event listener to render the inner blocks when // `wc-blocks_render_blocks_frontend` event is triggered. Array.prototype.forEach.call( wrappersToSkipOnLoad, ( wrapper ) => { @@ -255,6 +287,8 @@ export const renderFrontend = < renderBlockInsideWrapper( { ...props, wrapper } ); } ); } ); + + return roots; }; export default renderFrontend; diff --git a/plugins/woocommerce-blocks/assets/js/blocks/attribute-filter/test/block.tsx b/plugins/woocommerce-blocks/assets/js/blocks/attribute-filter/test/block.tsx index ecdc9b111ae..3b1b191e7ff 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/attribute-filter/test/block.tsx +++ b/plugins/woocommerce-blocks/assets/js/blocks/attribute-filter/test/block.tsx @@ -1,7 +1,7 @@ /** * 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 userEvent from '@testing-library/user-event'; @@ -106,14 +106,8 @@ const setup = ( params: SetupParams ) => { results: stubCollectionData(), isLoading: false, } ); - const utils = render( , { - 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 utils = render( ); + const applyButton = screen.getByRole( 'button', { name: /apply/i } ); const smallAttributeCheckbox = screen.getByRole( 'checkbox', { name: /small/i, @@ -164,8 +158,10 @@ describe( 'Filter by Attribute block', () => { test( 'should enable Apply button when filter attributes are changed', async () => { const { applyButton, smallAttributeCheckbox } = setupWithoutSelectedFilterAttributes(); - await userEvent.click( smallAttributeCheckbox ); + await act( async () => { + await userEvent.click( smallAttributeCheckbox ); + } ); expect( applyButton ).not.toBeDisabled(); } ); } ); @@ -180,18 +176,25 @@ describe( 'Filter by Attribute block', () => { test( 'should enable Apply button when filter attributes are changed', async () => { const { applyButton, smallAttributeCheckbox } = setupWithSelectedFilterAttributes(); - await userEvent.click( smallAttributeCheckbox ); + await act( async () => { + await userEvent.click( smallAttributeCheckbox ); + } ); expect( applyButton ).not.toBeDisabled(); } ); test( 'should disable Apply button when deselecting the same previously selected attribute', async () => { const { applyButton, smallAttributeCheckbox } = setupWithSelectedFilterAttributes( { filterSize: 'small' } ); - await userEvent.click( smallAttributeCheckbox ); + + await act( async () => { + await userEvent.click( smallAttributeCheckbox ); + } ); expect( applyButton ).not.toBeDisabled(); - await userEvent.click( smallAttributeCheckbox ); + await act( async () => { + await userEvent.click( smallAttributeCheckbox ); + } ); expect( applyButton ).toBeDisabled(); } ); } ); diff --git a/plugins/woocommerce-blocks/assets/js/blocks/checkout/inner-blocks/checkout-pickup-options-block/block.tsx b/plugins/woocommerce-blocks/assets/js/blocks/checkout/inner-blocks/checkout-pickup-options-block/block.tsx index 916662798cf..9d6984b6984 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/checkout/inner-blocks/checkout-pickup-options-block/block.tsx +++ b/plugins/woocommerce-blocks/assets/js/blocks/checkout/inner-blocks/checkout-pickup-options-block/block.tsx @@ -6,6 +6,7 @@ import { useState, useEffect, useCallback, + useMemo, createInterpolateElement, } from '@wordpress/element'; import { useShippingData, useStoreCart } from '@woocommerce/base-context/hooks'; @@ -138,10 +139,12 @@ const renderPickupLocation = ( const Block = (): JSX.Element | null => { const { shippingRates, selectShippingRate } = useShippingData(); - // Get pickup locations from the first shipping package. - const pickupLocations = ( shippingRates[ 0 ]?.shipping_rates || [] ).filter( - isPackageRateCollectable - ); + // Memoize pickup locations to prevent re-rendering when the shipping rates change. + const pickupLocations = useMemo( () => { + return ( shippingRates[ 0 ]?.shipping_rates || [] ).filter( + isPackageRateCollectable + ); + }, [ shippingRates ] ); const [ selectedOption, setSelectedOption ] = useState< string >( () => pickupLocations.find( ( rate ) => rate.selected )?.rate_id || '' @@ -168,13 +171,19 @@ const Block = (): JSX.Element | null => { renderPickupLocation, }; - // Update the selected option if there is no rate selected on mount. useEffect( () => { - if ( ! selectedOption && pickupLocations[ 0 ] ) { + if ( + ! selectedOption && + pickupLocations[ 0 ] && + selectedOption !== pickupLocations[ 0 ].rate_id + ) { setSelectedOption( 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 ); return ( <> diff --git a/plugins/woocommerce-blocks/assets/js/blocks/checkout/inner-blocks/checkout-shipping-methods-block/block.tsx b/plugins/woocommerce-blocks/assets/js/blocks/checkout/inner-blocks/checkout-shipping-methods-block/block.tsx index 13633478b0c..80362a4b009 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/checkout/inner-blocks/checkout-shipping-methods-block/block.tsx +++ b/plugins/woocommerce-blocks/assets/js/blocks/checkout/inner-blocks/checkout-shipping-methods-block/block.tsx @@ -26,6 +26,7 @@ import type { } from '@woocommerce/types'; import NoticeBanner from '@woocommerce/base-components/notice-banner'; import type { ReactElement } from 'react'; +import { useMemo } from '@wordpress/element'; /** * Renders a shipping rate control option. @@ -73,19 +74,22 @@ const Block = ( { noShippingPlaceholder = null } ): ReactElement | null => { const { shippingAddress } = useCustomerData(); - const filteredShippingRates = isCollectable - ? shippingRates.map( ( shippingRatesPackage ) => { - return { - ...shippingRatesPackage, - shipping_rates: shippingRatesPackage.shipping_rates.filter( - ( shippingRatesPackageRate ) => - ! hasCollectableRate( - shippingRatesPackageRate.method_id - ) - ), - }; - } ) - : shippingRates; + const filteredShippingRates = useMemo( () => { + return isCollectable + ? shippingRates.map( ( shippingRatesPackage ) => { + return { + ...shippingRatesPackage, + shipping_rates: + shippingRatesPackage.shipping_rates.filter( + ( shippingRatesPackageRate ) => + ! hasCollectableRate( + shippingRatesPackageRate.method_id + ) + ), + }; + } ) + : shippingRates; + }, [ shippingRates, isCollectable ] ); if ( ! needsShipping ) { return null; diff --git a/plugins/woocommerce-blocks/assets/js/blocks/mini-cart/block.tsx b/plugins/woocommerce-blocks/assets/js/blocks/mini-cart/block.tsx index 59e33ddcd74..77b4097c39c 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/mini-cart/block.tsx +++ b/plugins/woocommerce-blocks/assets/js/blocks/mini-cart/block.tsx @@ -20,15 +20,10 @@ import { isCartResponseTotals, isNumber, } from '@woocommerce/types'; -import { - unmountComponentAtNode, - useCallback, - useEffect, - useRef, - useState, -} from '@wordpress/element'; +import { useCallback, useEffect, useRef, useState } from '@wordpress/element'; import { sprintf, _n } from '@wordpress/i18n'; import clsx from 'clsx'; +import type { ReactRootWithContainer } from '@woocommerce/base-utils'; /** * Internal dependencies @@ -110,6 +105,8 @@ const MiniCartBlock = ( attributes: Props ): JSX.Element => { setContentsNode( node ); }, [] ); + const rootRef = useRef< ReactRootWithContainer[] | null >( null ); + useEffect( () => { const body = document.querySelector( 'body' ); if ( body ) { @@ -134,7 +131,7 @@ const MiniCartBlock = ( attributes: Props ): JSX.Element => { return; } if ( isOpen ) { - renderParentBlock( { + const renderedBlock = renderParentBlock( { Block: MiniCartContentsBlock, blockName, getProps: ( el: Element ) => { @@ -151,16 +148,25 @@ const MiniCartBlock = ( attributes: Props ): JSX.Element => { selector: '.wp-block-woocommerce-mini-cart-contents', blockMap: getRegisteredBlockComponents( blockName ), } ); + rootRef.current = renderedBlock; } } return () => { if ( contentsNode instanceof Element && isOpen ) { - const container = contentsNode.querySelector( + const unmountingContainer = contentsNode.querySelector( '.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(); + } ); + } } } }; diff --git a/plugins/woocommerce-blocks/assets/js/blocks/mini-cart/test/block.js b/plugins/woocommerce-blocks/assets/js/blocks/mini-cart/test/block.js index 313a7c3f3af..94a9644b75b 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/mini-cart/test/block.js +++ b/plugins/woocommerce-blocks/assets/js/blocks/mini-cart/test/block.js @@ -111,13 +111,6 @@ describe( 'Testing Mini-Cart', () => { 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 () => { @@ -132,9 +125,11 @@ describe( 'Testing Mini-Cart', () => { // Close drawer. let closeButton = null; + await waitFor( () => { closeButton = screen.getByLabelText( /close/i ); } ); + if ( closeButton ) { await act( async () => { await user.click( closeButton ); @@ -146,13 +141,6 @@ 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 () => { @@ -167,13 +155,6 @@ describe( 'Testing Mini-Cart', () => { } ); 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 () => { diff --git a/plugins/woocommerce-blocks/assets/js/blocks/rating-filter/test/block.tsx b/plugins/woocommerce-blocks/assets/js/blocks/rating-filter/test/block.tsx index 1cfbe910fb7..aad8468ae82 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/rating-filter/test/block.tsx +++ b/plugins/woocommerce-blocks/assets/js/blocks/rating-filter/test/block.tsx @@ -2,7 +2,14 @@ * External dependencies */ 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 userEvent from '@testing-library/user-event'; @@ -59,6 +66,7 @@ const selectors = { }; const setup = ( params: SetupParams ) => { + cleanup(); const url = `http://woo.local/${ params.filterRating ? '?rating_filter=' + params.filterRating : '' }`; @@ -78,14 +86,7 @@ const setup = ( params: SetupParams ) => { } ); const { container, ...utils } = render( - , - { 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 ); @@ -203,74 +204,81 @@ describe( 'Filter by Rating block', () => { describe( 'Single choice Dropdown', () => { test( 'renders dropdown', () => { const { getDropdown, getList } = setupSingleChoiceDropdown(); + expect( getDropdown() ).toBeInTheDocument(); expect( getList() ).toBeNull(); } ); - test( 'renders chips based on URL params', () => { - const ratingParam = '2'; - const { getRating2Chips, getRating4Chips, getRating5Chips } = - setupSingleChoiceDropdown( ratingParam ); + test( 'renders chips based on URL params', async () => { + await waitFor( async () => { + const ratingParam = '2'; + const { getRating2Chips, getRating4Chips, getRating5Chips } = + setupSingleChoiceDropdown( ratingParam ); - expect( getRating2Chips() ).toBeInTheDocument(); - expect( getRating4Chips() ).toBeNull(); - expect( getRating5Chips() ).toBeNull(); + expect( getRating2Chips() ).toBeInTheDocument(); + expect( getRating4Chips() ).toBeNull(); + expect( getRating5Chips() ).toBeNull(); + } ); } ); test( 'replaces chosen option when another one is clicked', async () => { - const ratingParam = '2'; - const { - getDropdown, - getRating2Chips, - getRating4Chips, - getRating4Suggestion, - } = setupSingleChoiceDropdown( ratingParam ); + await waitFor( async () => { + const ratingParam = '2'; + const { + getDropdown, + getRating2Chips, + getRating4Chips, + getRating4Suggestion, + } = setupSingleChoiceDropdown( ratingParam ); - expect( getRating2Chips() ).toBeInTheDocument(); - expect( getRating4Chips() ).toBeNull(); + expect( getRating2Chips() ).toBeInTheDocument(); + expect( getRating4Chips() ).toBeNull(); - const dropdown = getDropdown(); + const dropdown = getDropdown(); - if ( dropdown ) { - await userEvent.click( dropdown ); - acceptErrorWithDuplicatedKeys(); - } + if ( dropdown ) { + await userEvent.click( dropdown ); + acceptErrorWithDuplicatedKeys(); + } - const rating4Suggestion = getRating4Suggestion(); + const rating4Suggestion = getRating4Suggestion(); - if ( rating4Suggestion ) { - await userEvent.click( rating4Suggestion ); - } + if ( rating4Suggestion ) { + await userEvent.click( rating4Suggestion ); + } - expect( getRating2Chips() ).toBeNull(); - expect( getRating4Chips() ).toBeInTheDocument(); + expect( getRating2Chips() ).toBeNull(); + expect( getRating4Chips() ).toBeInTheDocument(); + } ); } ); test( 'removes the option when the X button is clicked', async () => { - const ratingParam = '4'; - const { - getRating2Chips, - getRating4Chips, - getRating5Chips, - getRemoveButtonFromChips, - } = setupMultipleChoiceDropdown( ratingParam ); + await waitFor( async () => { + const ratingParam = '4'; + const { + getRating2Chips, + getRating4Chips, + getRating5Chips, + getRemoveButtonFromChips, + } = setupMultipleChoiceDropdown( ratingParam ); - expect( getRating2Chips() ).toBeNull(); - expect( getRating4Chips() ).toBeInTheDocument(); - expect( getRating5Chips() ).toBeNull(); + expect( getRating2Chips() ).toBeNull(); + expect( getRating4Chips() ).toBeInTheDocument(); + expect( getRating5Chips() ).toBeNull(); - const removeRating4Button = getRemoveButtonFromChips( - getRating4Chips() - ); + const removeRating4Button = getRemoveButtonFromChips( + getRating4Chips() + ); - if ( removeRating4Button ) { - await userEvent.click( removeRating4Button ); - acceptErrorWithDuplicatedKeys(); - } + if ( removeRating4Button ) { + await userEvent.click( removeRating4Button ); + acceptErrorWithDuplicatedKeys(); + } - expect( getRating2Chips() ).toBeNull(); - expect( getRating4Chips() ).toBeNull(); - expect( getRating5Chips() ).toBeNull(); + expect( getRating2Chips() ).toBeNull(); + expect( getRating4Chips() ).toBeNull(); + expect( getRating5Chips() ).toBeNull(); + } ); } ); } ); @@ -281,83 +289,89 @@ describe( 'Filter by Rating block', () => { expect( getList() ).toBeNull(); } ); - test( 'renders chips based on URL params', () => { - const ratingParam = '2,4'; - const { getRating2Chips, getRating4Chips, getRating5Chips } = - setupMultipleChoiceDropdown( ratingParam ); + test( 'renders chips based on URL params', async () => { + await waitFor( async () => { + const ratingParam = '2,4'; + const { getRating2Chips, getRating4Chips, getRating5Chips } = + setupMultipleChoiceDropdown( ratingParam ); - expect( getRating2Chips() ).toBeInTheDocument(); - expect( getRating4Chips() ).toBeInTheDocument(); - expect( getRating5Chips() ).toBeNull(); + expect( getRating2Chips() ).toBeInTheDocument(); + expect( getRating4Chips() ).toBeInTheDocument(); + expect( getRating5Chips() ).toBeNull(); + } ); } ); test( 'adds chosen option to another one that is clicked', async () => { - const ratingParam = '2'; - const { - getDropdown, - getRating2Chips, - getRating4Chips, - getRating5Chips, - getRating4Suggestion, - getRating5Suggestion, - } = setupMultipleChoiceDropdown( ratingParam ); + await waitFor( async () => { + const ratingParam = '2'; + const { + getDropdown, + getRating2Chips, + getRating4Chips, + getRating5Chips, + getRating4Suggestion, + getRating5Suggestion, + } = setupMultipleChoiceDropdown( ratingParam ); - expect( getRating2Chips() ).toBeInTheDocument(); - expect( getRating4Chips() ).toBeNull(); - expect( getRating5Chips() ).toBeNull(); + expect( getRating2Chips() ).toBeInTheDocument(); + expect( getRating4Chips() ).toBeNull(); + expect( getRating5Chips() ).toBeNull(); - const dropdown = getDropdown(); + const dropdown = getDropdown(); - if ( dropdown ) { - await userEvent.click( dropdown ); - acceptErrorWithDuplicatedKeys(); - } + if ( dropdown ) { + await userEvent.click( dropdown ); + acceptErrorWithDuplicatedKeys(); + } - const rating4Suggestion = getRating4Suggestion(); + const rating4Suggestion = getRating4Suggestion(); - if ( rating4Suggestion ) { - await userEvent.click( rating4Suggestion ); - } + if ( rating4Suggestion ) { + await userEvent.click( rating4Suggestion ); + } - expect( getRating2Chips() ).toBeInTheDocument(); - expect( getRating4Chips() ).toBeInTheDocument(); - expect( getRating5Chips() ).toBeNull(); + expect( getRating2Chips() ).toBeInTheDocument(); + expect( getRating4Chips() ).toBeInTheDocument(); + expect( getRating5Chips() ).toBeNull(); - const rating5Suggestion = getRating5Suggestion(); + const rating5Suggestion = getRating5Suggestion(); - if ( rating5Suggestion ) { - await userEvent.click( rating5Suggestion ); - } + if ( rating5Suggestion ) { + await userEvent.click( rating5Suggestion ); + } - expect( getRating2Chips() ).toBeInTheDocument(); - expect( getRating4Chips() ).toBeInTheDocument(); - expect( getRating5Chips() ).toBeInTheDocument(); + expect( getRating2Chips() ).toBeInTheDocument(); + expect( getRating4Chips() ).toBeInTheDocument(); + expect( getRating5Chips() ).toBeInTheDocument(); + } ); } ); test( 'removes the option when the X button is clicked', async () => { - const ratingParam = '2,4,5'; - const { - getRating2Chips, - getRating4Chips, - getRating5Chips, - getRemoveButtonFromChips, - } = setupMultipleChoiceDropdown( ratingParam ); + await waitFor( async () => { + const ratingParam = '2,4,5'; + const { + getRating2Chips, + getRating4Chips, + getRating5Chips, + getRemoveButtonFromChips, + } = setupMultipleChoiceDropdown( ratingParam ); - expect( getRating2Chips() ).toBeInTheDocument(); - expect( getRating4Chips() ).toBeInTheDocument(); - expect( getRating5Chips() ).toBeInTheDocument(); + expect( getRating2Chips() ).toBeInTheDocument(); + expect( getRating4Chips() ).toBeInTheDocument(); + expect( getRating5Chips() ).toBeInTheDocument(); - const removeRating4Button = getRemoveButtonFromChips( - getRating4Chips() - ); + const removeRating4Button = getRemoveButtonFromChips( + getRating4Chips() + ); - if ( removeRating4Button ) { - await userEvent.click( removeRating4Button ); - } + if ( removeRating4Button ) { + await userEvent.click( removeRating4Button ); + } - expect( getRating2Chips() ).toBeInTheDocument(); - expect( getRating4Chips() ).toBeNull(); - expect( getRating5Chips() ).toBeInTheDocument(); + expect( getRating2Chips() ).toBeInTheDocument(); + expect( getRating4Chips() ).toBeNull(); + expect( getRating5Chips() ).toBeInTheDocument(); + } ); } ); } ); @@ -368,61 +382,67 @@ describe( 'Filter by Rating block', () => { expect( getList() ).toBeInTheDocument(); } ); - test( 'renders checked options based on URL params', () => { - const ratingParam = '4'; - const { - getRating2Checkbox, - getRating4Checkbox, - getRating5Checkbox, - } = setupSingleChoiceList( ratingParam ); + test( 'renders checked options based on URL params', async () => { + await waitFor( async () => { + const ratingParam = '4'; + const { + getRating2Checkbox, + getRating4Checkbox, + getRating5Checkbox, + } = setupSingleChoiceList( ratingParam ); - expect( getRating2Checkbox()?.checked ).toBeFalsy(); - expect( getRating4Checkbox()?.checked ).toBeTruthy(); - expect( getRating5Checkbox()?.checked ).toBeFalsy(); + expect( getRating2Checkbox()?.checked ).toBeFalsy(); + expect( getRating4Checkbox()?.checked ).toBeTruthy(); + expect( getRating5Checkbox()?.checked ).toBeFalsy(); + } ); } ); test( 'replaces chosen option when another one is clicked', async () => { - const ratingParam = '2'; - const { - getRating2Checkbox, - getRating4Checkbox, - getRating5Checkbox, - } = setupSingleChoiceList( ratingParam ); + await waitFor( async () => { + const ratingParam = '2'; + const { + getRating2Checkbox, + getRating4Checkbox, + getRating5Checkbox, + } = setupSingleChoiceList( ratingParam ); - expect( getRating2Checkbox()?.checked ).toBeTruthy(); - expect( getRating4Checkbox()?.checked ).toBeFalsy(); - expect( getRating5Checkbox()?.checked ).toBeFalsy(); + expect( getRating2Checkbox()?.checked ).toBeTruthy(); + expect( getRating4Checkbox()?.checked ).toBeFalsy(); + expect( getRating5Checkbox()?.checked ).toBeFalsy(); - const rating4checkbox = getRating4Checkbox(); + const rating4checkbox = getRating4Checkbox(); - if ( rating4checkbox ) { - await userEvent.click( rating4checkbox ); - } + if ( rating4checkbox ) { + await act( async () => { + await userEvent.click( rating4checkbox ); + } ); + } - expect( getRating2Checkbox()?.checked ).toBeFalsy(); - expect( getRating4Checkbox()?.checked ).toBeTruthy(); - expect( getRating5Checkbox()?.checked ).toBeFalsy(); + expect( getRating2Checkbox()?.checked ).toBeFalsy(); + expect( getRating4Checkbox()?.checked ).toBeTruthy(); + expect( getRating5Checkbox()?.checked ).toBeFalsy(); + } ); } ); test( 'removes the option when it is clicked again', async () => { - const ratingParam = '4'; - const { - getRating2Checkbox, - getRating4Checkbox, - getRating5Checkbox, - } = setupMultipleChoiceList( ratingParam ); + await waitFor( async () => { + const ratingParam = '4'; + const { + getRating2Checkbox, + getRating4Checkbox, + getRating5Checkbox, + } = setupMultipleChoiceList( ratingParam ); - expect( getRating2Checkbox()?.checked ).toBeFalsy(); - expect( getRating4Checkbox()?.checked ).toBeTruthy(); - expect( getRating5Checkbox()?.checked ).toBeFalsy(); + expect( getRating2Checkbox()?.checked ).toBeFalsy(); + expect( getRating4Checkbox()?.checked ).toBeTruthy(); + expect( getRating5Checkbox()?.checked ).toBeFalsy(); - const rating4checkbox = getRating4Checkbox(); + const rating4checkbox = getRating4Checkbox(); - if ( rating4checkbox ) { - await userEvent.click( rating4checkbox ); - } + if ( rating4checkbox ) { + await userEvent.click( rating4checkbox ); + } - await waitFor( () => { expect( getRating2Checkbox()?.checked ).toBeFalsy(); expect( getRating4Checkbox()?.checked ).toBeFalsy(); expect( getRating5Checkbox()?.checked ).toBeFalsy(); @@ -437,38 +457,40 @@ describe( 'Filter by Rating block', () => { expect( getList() ).toBeInTheDocument(); } ); - test( 'renders chips based on URL params', () => { - const ratingParam = '4,5'; - const { - getRating2Checkbox, - getRating4Checkbox, - getRating5Checkbox, - } = setupMultipleChoiceList( ratingParam ); + test( 'renders chips based on URL params', async () => { + await waitFor( async () => { + const ratingParam = '4,5'; + const { + getRating2Checkbox, + getRating4Checkbox, + getRating5Checkbox, + } = setupMultipleChoiceList( ratingParam ); - expect( getRating2Checkbox()?.checked ).toBeFalsy(); - expect( getRating4Checkbox()?.checked ).toBeTruthy(); - expect( getRating5Checkbox()?.checked ).toBeTruthy(); + expect( getRating2Checkbox()?.checked ).toBeFalsy(); + expect( getRating4Checkbox()?.checked ).toBeTruthy(); + expect( getRating5Checkbox()?.checked ).toBeTruthy(); + } ); } ); test( 'adds chosen option to another one that is clicked', async () => { - const ratingParam = '2,4'; - const { - getRating2Checkbox, - getRating4Checkbox, - getRating5Checkbox, - } = setupMultipleChoiceList( ratingParam ); + await waitFor( async () => { + const ratingParam = '2,4'; + const { + getRating2Checkbox, + getRating4Checkbox, + getRating5Checkbox, + } = setupMultipleChoiceList( ratingParam ); - expect( getRating2Checkbox()?.checked ).toBeTruthy(); - expect( getRating4Checkbox()?.checked ).toBeTruthy(); - expect( getRating5Checkbox()?.checked ).toBeFalsy(); + expect( getRating2Checkbox()?.checked ).toBeTruthy(); + expect( getRating4Checkbox()?.checked ).toBeTruthy(); + expect( getRating5Checkbox()?.checked ).toBeFalsy(); - const rating5checkbox = getRating5Checkbox(); + const rating5checkbox = getRating5Checkbox(); - if ( rating5checkbox ) { - await userEvent.click( rating5checkbox ); - } + if ( rating5checkbox ) { + await userEvent.click( rating5checkbox ); + } - await waitFor( () => { expect( getRating2Checkbox()?.checked ).toBeTruthy(); expect( getRating4Checkbox()?.checked ).toBeTruthy(); expect( getRating5Checkbox()?.checked ).toBeTruthy(); @@ -476,24 +498,24 @@ describe( 'Filter by Rating block', () => { } ); test( 'removes the option when it is clicked again', async () => { - const ratingParam = '2,4'; - const { - getRating2Checkbox, - getRating4Checkbox, - getRating5Checkbox, - } = setupMultipleChoiceList( ratingParam ); + await waitFor( async () => { + const ratingParam = '2,4'; + const { + getRating2Checkbox, + getRating4Checkbox, + getRating5Checkbox, + } = setupMultipleChoiceList( ratingParam ); - expect( getRating2Checkbox()?.checked ).toBeTruthy(); - expect( getRating4Checkbox()?.checked ).toBeTruthy(); - expect( getRating5Checkbox()?.checked ).toBeFalsy(); + expect( getRating2Checkbox()?.checked ).toBeTruthy(); + expect( getRating4Checkbox()?.checked ).toBeTruthy(); + expect( getRating5Checkbox()?.checked ).toBeFalsy(); - const rating2checkbox = getRating2Checkbox(); + const rating2checkbox = getRating2Checkbox(); - if ( rating2checkbox ) { - await userEvent.click( rating2checkbox ); - } + if ( rating2checkbox ) { + await userEvent.click( rating2checkbox ); + } - await waitFor( () => { expect( getRating2Checkbox()?.checked ).toBeFalsy(); expect( getRating4Checkbox()?.checked ).toBeTruthy(); expect( getRating5Checkbox()?.checked ).toBeFalsy(); diff --git a/plugins/woocommerce-blocks/assets/js/blocks/stock-filter/test/block.tsx b/plugins/woocommerce-blocks/assets/js/blocks/stock-filter/test/block.tsx index 358b619b96d..be9786cb8f5 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/stock-filter/test/block.tsx +++ b/plugins/woocommerce-blocks/assets/js/blocks/stock-filter/test/block.tsx @@ -2,7 +2,14 @@ * External dependencies */ 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 userEvent from '@testing-library/user-event'; @@ -68,6 +75,7 @@ const selectors = { }; const setup = ( params: SetupParams = {} ) => { + cleanup(); const url = `http://woo.local/${ params.filterStock ? '?filter_stock_status=' + params.filterStock : '' }`; @@ -87,14 +95,7 @@ const setup = ( params: SetupParams = {} ) => { }; const { container, ...utils } = render( - , - { 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 ); @@ -227,7 +228,7 @@ describe( 'Filter by Stock block', () => { fetchMock.resetMocks(); } ); - it( 'renders the stock filter block', async () => { + test( 'renders the stock filter block', async () => { const { container } = setup( { showFilterButton: false, showCounts: false, @@ -235,7 +236,7 @@ describe( 'Filter by Stock block', () => { 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( { showFilterButton: true, showCounts: false, @@ -243,7 +244,7 @@ describe( 'Filter by Stock block', () => { 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( { showFilterButton: false, showCounts: true, @@ -254,80 +255,86 @@ describe( 'Filter by Stock block', () => { describe( 'Single choice Dropdown', () => { test( 'renders dropdown', () => { const { getDropdown, getList } = setupSingleChoiceDropdown(); + expect( getDropdown() ).toBeInTheDocument(); expect( getList() ).toBeNull(); } ); - test( 'renders chips based on URL params', () => { - const ratingParam = 'instock'; - const { getInStockChips, getOutOfStockChips, getOnBackorderChips } = - setupSingleChoiceDropdown( ratingParam ); + test( 'renders chips based on URL params', async () => { + await waitFor( async () => { + const ratingParam = 'instock'; + const { + getInStockChips, + getOutOfStockChips, + getOnBackorderChips, + } = setupSingleChoiceDropdown( ratingParam ); - expect( getInStockChips() ).toBeInTheDocument(); - expect( getOutOfStockChips() ).toBeNull(); - expect( getOnBackorderChips() ).toBeNull(); + expect( getInStockChips() ).toBeInTheDocument(); + expect( getOutOfStockChips() ).toBeNull(); + expect( getOnBackorderChips() ).toBeNull(); + } ); } ); test( 'replaces chosen option when another one is clicked', async () => { - const user = userEvent.setup(); - const ratingParam = 'instock'; - const { - getDropdown, - getInStockChips, - getOutOfStockChips, - getOutOfStockSuggestion, - } = setupSingleChoiceDropdown( ratingParam ); + await waitFor( async () => { + const user = userEvent.setup(); + const ratingParam = 'instock'; + const { + getDropdown, + getInStockChips, + getOutOfStockChips, + getOutOfStockSuggestion, + } = setupSingleChoiceDropdown( ratingParam ); - expect( getInStockChips() ).toBeInTheDocument(); - expect( getOutOfStockChips() ).toBeNull(); + expect( getInStockChips() ).toBeInTheDocument(); + expect( getOutOfStockChips() ).toBeNull(); - const dropdown = getDropdown(); + const dropdown = getDropdown(); - if ( dropdown ) { - await act( async () => { - await user.click( dropdown ); - } ); - } + if ( dropdown ) { + await act( async () => { + await user.click( dropdown ); + } ); + } - const outOfStockSuggestion = getOutOfStockSuggestion(); + const outOfStockSuggestion = getOutOfStockSuggestion(); - if ( outOfStockSuggestion ) { - await act( async () => { - await user.click( outOfStockSuggestion ); - } ); - } + if ( outOfStockSuggestion ) { + await act( async () => { + await user.click( outOfStockSuggestion ); + } ); + } - expect( getInStockChips() ).toBeNull(); - expect( getOutOfStockChips() ).toBeInTheDocument(); + expect( getInStockChips() ).toBeNull(); + expect( getOutOfStockChips() ).toBeInTheDocument(); + } ); } ); test( 'removes the option when the X button is clicked', async () => { - const user = userEvent.setup(); - const ratingParam = 'outofstock'; - const { - getInStockChips, - getOutOfStockChips, - getOnBackorderChips, - getRemoveButtonFromChips, - } = setupMultipleChoiceDropdown( ratingParam ); + await waitFor( async () => { + const user = userEvent.setup(); + const ratingParam = 'outofstock'; + const { + getInStockChips, + getOutOfStockChips, + getOnBackorderChips, + getRemoveButtonFromChips, + } = setupMultipleChoiceDropdown( ratingParam ); - await waitFor( () => { expect( getInStockChips() ).toBeNull(); expect( getOutOfStockChips() ).toBeInTheDocument(); expect( getOnBackorderChips() ).toBeNull(); - } ); - const removeOutOfStockButton = getRemoveButtonFromChips( - getOutOfStockChips() - ); + const removeOutOfStockButton = getRemoveButtonFromChips( + getOutOfStockChips() + ); - if ( removeOutOfStockButton ) { - act( async () => { - await user.click( removeOutOfStockButton ); - } ); - } + if ( removeOutOfStockButton ) { + await act( async () => { + await user.click( removeOutOfStockButton ); + } ); + } - await waitFor( () => { expect( getInStockChips() ).toBeNull(); expect( getOutOfStockChips() ).toBeNull(); expect( getOnBackorderChips() ).toBeNull(); @@ -342,67 +349,65 @@ describe( 'Filter by Stock block', () => { expect( getList() ).toBeNull(); } ); - test( 'renders chips based on URL params', () => { - const ratingParam = 'instock,onbackorder'; - const { getInStockChips, getOutOfStockChips, getOnBackorderChips } = - setupMultipleChoiceDropdown( ratingParam ); + test( 'renders chips based on URL params', async () => { + await waitFor( async () => { + const ratingParam = 'instock,onbackorder'; + const { + getInStockChips, + getOutOfStockChips, + getOnBackorderChips, + } = setupMultipleChoiceDropdown( ratingParam ); - expect( getInStockChips() ).toBeInTheDocument(); - expect( getOutOfStockChips() ).toBeNull(); - expect( getOnBackorderChips() ).toBeInTheDocument(); - } ); - - test( 'adds chosen option to another one that is clicked', async () => { - const user = userEvent.setup(); - const ratingParam = 'onbackorder'; - const { - getDropdown, - getInStockChips, - getOutOfStockChips, - getOnBackorderChips, - getInStockSuggestion, - getOutOfStockSuggestion, - } = setupMultipleChoiceDropdown( ratingParam ); - - await waitFor( () => { - expect( getInStockChips() ).toBeNull(); + expect( getInStockChips() ).toBeInTheDocument(); expect( getOutOfStockChips() ).toBeNull(); expect( getOnBackorderChips() ).toBeInTheDocument(); } ); - const dropdown = getDropdown(); + } ); - if ( dropdown ) { - await act( async () => { + test( 'adds chosen option to another one that is clicked', async () => { + await waitFor( async () => { + const user = userEvent.setup(); + const ratingParam = 'onbackorder'; + const { + getDropdown, + getInStockChips, + getOutOfStockChips, + getOnBackorderChips, + getInStockSuggestion, + getOutOfStockSuggestion, + } = setupMultipleChoiceDropdown( ratingParam ); + + expect( getInStockChips() ).toBeNull(); + expect( getOutOfStockChips() ).toBeNull(); + expect( getOnBackorderChips() ).toBeInTheDocument(); + + const dropdown = getDropdown(); + + if ( dropdown ) { await user.click( dropdown ); - } ); - } + } - const inStockSuggestion = getInStockSuggestion(); + const inStockSuggestion = getInStockSuggestion(); - if ( inStockSuggestion ) { - await act( async () => { + if ( inStockSuggestion ) { await user.click( inStockSuggestion ); - } ); - } + } - expect( getInStockChips() ).toBeInTheDocument(); - expect( getOutOfStockChips() ).toBeNull(); - expect( getOnBackorderChips() ).toBeInTheDocument(); + expect( getInStockChips() ).toBeInTheDocument(); + expect( getOutOfStockChips() ).toBeNull(); + expect( getOnBackorderChips() ).toBeInTheDocument(); - const freshDropdown = getDropdown(); - if ( freshDropdown ) { - await act( async () => { + const freshDropdown = getDropdown(); + if ( freshDropdown ) { await user.click( freshDropdown ); - } ); - } + } - const outOfStockSuggestion = getOutOfStockSuggestion(); + const outOfStockSuggestion = getOutOfStockSuggestion(); - if ( outOfStockSuggestion ) { - userEvent.click( outOfStockSuggestion ); - } + if ( outOfStockSuggestion ) { + await userEvent.click( outOfStockSuggestion ); + } - await waitFor( () => { expect( getInStockChips() ).toBeInTheDocument(); expect( getOutOfStockChips() ).toBeInTheDocument(); expect( getOnBackorderChips() ).toBeInTheDocument(); @@ -410,32 +415,30 @@ describe( 'Filter by Stock block', () => { } ); test( 'removes the option when the X button is clicked', async () => { - const user = userEvent.setup(); - const ratingParam = 'instock,outofstock,onbackorder'; - const { - getInStockChips, - getOutOfStockChips, - getOnBackorderChips, - getRemoveButtonFromChips, - } = setupMultipleChoiceDropdown( ratingParam ); + await waitFor( async () => { + const user = userEvent.setup(); + const ratingParam = 'instock,outofstock,onbackorder'; + const { + getInStockChips, + getOutOfStockChips, + getOnBackorderChips, + getRemoveButtonFromChips, + } = setupMultipleChoiceDropdown( ratingParam ); - await waitFor( () => { expect( getInStockChips() ).toBeInTheDocument(); expect( getOutOfStockChips() ).toBeInTheDocument(); expect( getOnBackorderChips() ).toBeInTheDocument(); - } ); - const removeOutOfStockButton = getRemoveButtonFromChips( - getOutOfStockChips() - ); + const removeOutOfStockButton = getRemoveButtonFromChips( + getOutOfStockChips() + ); - if ( removeOutOfStockButton ) { - act( async () => { - await user.click( removeOutOfStockButton ); - } ); - } + if ( removeOutOfStockButton ) { + await act( async () => { + await user.click( removeOutOfStockButton ); + } ); + } - await waitFor( () => { expect( getInStockChips() ).toBeInTheDocument(); expect( getOutOfStockChips() ).toBeNull(); expect( getOnBackorderChips() ).toBeInTheDocument(); @@ -450,67 +453,73 @@ describe( 'Filter by Stock block', () => { expect( getList() ).toBeInTheDocument(); } ); - test( 'renders checked options based on URL params', () => { - const ratingParam = 'instock'; - const { - getInStockCheckbox, - getOutOfStockCheckbox, - getOnBackorderCheckbox, - } = setupSingleChoiceList( ratingParam ); + test( 'renders checked options based on URL params', async () => { + await waitFor( async () => { + const ratingParam = 'instock'; + const { + getInStockCheckbox, + getOutOfStockCheckbox, + getOnBackorderCheckbox, + } = setupSingleChoiceList( ratingParam ); - expect( getInStockCheckbox()?.checked ).toBeTruthy(); - expect( getOutOfStockCheckbox()?.checked ).toBeFalsy(); - expect( getOnBackorderCheckbox()?.checked ).toBeFalsy(); + expect( getInStockCheckbox()?.checked ).toBeTruthy(); + expect( getOutOfStockCheckbox()?.checked ).toBeFalsy(); + expect( getOnBackorderCheckbox()?.checked ).toBeFalsy(); + } ); } ); test( 'replaces chosen option when another one is clicked', async () => { - const user = userEvent.setup(); - const ratingParam = 'outofstock'; - const { - getInStockCheckbox, - getOutOfStockCheckbox, - getOnBackorderCheckbox, - } = setupSingleChoiceList( ratingParam ); + await waitFor( async () => { + const user = userEvent.setup(); + const ratingParam = 'outofstock'; + const { + getInStockCheckbox, + getOutOfStockCheckbox, + getOnBackorderCheckbox, + } = setupSingleChoiceList( ratingParam ); - expect( getInStockCheckbox()?.checked ).toBeFalsy(); - expect( getOutOfStockCheckbox()?.checked ).toBeTruthy(); - expect( getOnBackorderCheckbox()?.checked ).toBeFalsy(); + expect( getInStockCheckbox()?.checked ).toBeFalsy(); + expect( getOutOfStockCheckbox()?.checked ).toBeTruthy(); + expect( getOnBackorderCheckbox()?.checked ).toBeFalsy(); - const onBackorderCheckbox = getOnBackorderCheckbox(); + const onBackorderCheckbox = getOnBackorderCheckbox(); - if ( onBackorderCheckbox ) { - await act( async () => { - await user.click( onBackorderCheckbox ); - } ); - } + if ( onBackorderCheckbox ) { + await act( async () => { + await user.click( onBackorderCheckbox ); + } ); + } - expect( getInStockCheckbox()?.checked ).toBeFalsy(); - expect( getOutOfStockCheckbox()?.checked ).toBeFalsy(); - expect( getOnBackorderCheckbox()?.checked ).toBeTruthy(); + expect( getInStockCheckbox()?.checked ).toBeFalsy(); + expect( getOutOfStockCheckbox()?.checked ).toBeFalsy(); + expect( getOnBackorderCheckbox()?.checked ).toBeTruthy(); + } ); } ); test( 'removes the option when it is clicked again', async () => { - const ratingParam = 'onbackorder'; - const { - getInStockCheckbox, - getOutOfStockCheckbox, - getOnBackorderCheckbox, - } = setupMultipleChoiceList( ratingParam ); + await waitFor( async () => { + const ratingParam = 'onbackorder'; + const { + getInStockCheckbox, + getOutOfStockCheckbox, + getOnBackorderCheckbox, + } = setupMultipleChoiceList( ratingParam ); - expect( getInStockCheckbox()?.checked ).toBeFalsy(); - expect( getOutOfStockCheckbox()?.checked ).toBeFalsy(); - expect( getOnBackorderCheckbox()?.checked ).toBeTruthy(); - - const onBackorderCheckbox = getOnBackorderCheckbox(); - - if ( onBackorderCheckbox ) { - userEvent.click( onBackorderCheckbox ); - } - - await waitFor( () => { expect( getInStockCheckbox()?.checked ).toBeFalsy(); expect( getOutOfStockCheckbox()?.checked ).toBeFalsy(); - expect( getOnBackorderCheckbox()?.checked ).toBeFalsy(); + expect( getOnBackorderCheckbox()?.checked ).toBeTruthy(); + + const onBackorderCheckbox = getOnBackorderCheckbox(); + + if ( onBackorderCheckbox ) { + userEvent.click( onBackorderCheckbox ); + } + + await waitFor( () => { + expect( getInStockCheckbox()?.checked ).toBeFalsy(); + expect( getOutOfStockCheckbox()?.checked ).toBeFalsy(); + expect( getOnBackorderCheckbox()?.checked ).toBeFalsy(); + } ); } ); } ); } ); @@ -522,66 +531,72 @@ describe( 'Filter by Stock block', () => { expect( getList() ).toBeInTheDocument(); } ); - test( 'renders chips based on URL params', () => { - const ratingParam = 'instock,onbackorder'; - const { - getInStockCheckbox, - getOutOfStockCheckbox, - getOnBackorderCheckbox, - } = setupMultipleChoiceList( ratingParam ); + test( 'renders chips based on URL params', async () => { + await waitFor( async () => { + const ratingParam = 'instock,onbackorder'; + const { + getInStockCheckbox, + getOutOfStockCheckbox, + getOnBackorderCheckbox, + } = setupMultipleChoiceList( ratingParam ); - expect( getInStockCheckbox()?.checked ).toBeTruthy(); - expect( getOutOfStockCheckbox()?.checked ).toBeFalsy(); - expect( getOnBackorderCheckbox()?.checked ).toBeTruthy(); - } ); - - test( 'adds chosen option to another one that is clicked', async () => { - const ratingParam = 'outofstock,onbackorder'; - const { - getInStockCheckbox, - getOutOfStockCheckbox, - getOnBackorderCheckbox, - } = setupMultipleChoiceList( ratingParam ); - - expect( getInStockCheckbox()?.checked ).toBeFalsy(); - expect( getOutOfStockCheckbox()?.checked ).toBeTruthy(); - expect( getOnBackorderCheckbox()?.checked ).toBeTruthy(); - - const inStockCheckbox = getInStockCheckbox(); - - if ( inStockCheckbox ) { - userEvent.click( inStockCheckbox ); - } - - await waitFor( () => { expect( getInStockCheckbox()?.checked ).toBeTruthy(); - expect( getOutOfStockCheckbox()?.checked ).toBeTruthy(); + expect( getOutOfStockCheckbox()?.checked ).toBeFalsy(); expect( getOnBackorderCheckbox()?.checked ).toBeTruthy(); } ); } ); - test( 'removes the option when it is clicked again', async () => { - const ratingParam = 'instock,outofstock'; - const { - getInStockCheckbox, - getOutOfStockCheckbox, - getOnBackorderCheckbox, - } = setupMultipleChoiceList( ratingParam ); + test( 'adds chosen option to another one that is clicked', async () => { + await waitFor( async () => { + const ratingParam = 'outofstock,onbackorder'; + const { + getInStockCheckbox, + getOutOfStockCheckbox, + getOnBackorderCheckbox, + } = setupMultipleChoiceList( ratingParam ); - expect( getInStockCheckbox()?.checked ).toBeTruthy(); - expect( getOutOfStockCheckbox()?.checked ).toBeTruthy(); - expect( getOnBackorderCheckbox()?.checked ).toBeFalsy(); - - const inStockCheckbox = getInStockCheckbox(); - - if ( inStockCheckbox ) { - userEvent.click( inStockCheckbox ); - } - - await waitFor( () => { expect( getInStockCheckbox()?.checked ).toBeFalsy(); expect( getOutOfStockCheckbox()?.checked ).toBeTruthy(); + expect( getOnBackorderCheckbox()?.checked ).toBeTruthy(); + + const inStockCheckbox = getInStockCheckbox(); + + if ( inStockCheckbox ) { + userEvent.click( inStockCheckbox ); + } + + await waitFor( () => { + expect( getInStockCheckbox()?.checked ).toBeTruthy(); + expect( getOutOfStockCheckbox()?.checked ).toBeTruthy(); + expect( getOnBackorderCheckbox()?.checked ).toBeTruthy(); + } ); + } ); + } ); + + test( 'removes the option when it is clicked again', async () => { + await waitFor( async () => { + const ratingParam = 'instock,outofstock'; + const { + getInStockCheckbox, + getOutOfStockCheckbox, + getOnBackorderCheckbox, + } = setupMultipleChoiceList( ratingParam ); + + expect( getInStockCheckbox()?.checked ).toBeTruthy(); + expect( getOutOfStockCheckbox()?.checked ).toBeTruthy(); expect( getOnBackorderCheckbox()?.checked ).toBeFalsy(); + + const inStockCheckbox = getInStockCheckbox(); + + if ( inStockCheckbox ) { + userEvent.click( inStockCheckbox ); + } + + await waitFor( () => { + expect( getInStockCheckbox()?.checked ).toBeFalsy(); + expect( getOutOfStockCheckbox()?.checked ).toBeTruthy(); + expect( getOnBackorderCheckbox()?.checked ).toBeFalsy(); + } ); } ); } ); } ); diff --git a/plugins/woocommerce/changelog/48843-update-48796-use-createRoot-instead-of-render b/plugins/woocommerce/changelog/48843-update-48796-use-createRoot-instead-of-render new file mode 100644 index 00000000000..c6f497480f0 --- /dev/null +++ b/plugins/woocommerce/changelog/48843-update-48796-use-createRoot-instead-of-render @@ -0,0 +1,4 @@ +Significance: patch +Type: dev + +Switch `render()` to `createRoot().render()` to use React 18 features. \ No newline at end of file diff --git a/plugins/woocommerce/tests/e2e-pw/tests/shopper/cart-block-coupons.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/shopper/cart-block-coupons.spec.js index aeca8fbb71d..62d8d6f2cdc 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/shopper/cart-block-coupons.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/shopper/cart-block-coupons.spec.js @@ -137,9 +137,7 @@ test.describe( await page .getByRole( 'button', { name: 'Add a coupon' } ) .click(); - await page - .locator( '#wc-block-components-totals-coupon__input-0' ) - .fill( coupons[ i ].code ); + await page.getByLabel( 'Enter code' ).fill( coupons[ i ].code ); await page.getByText( 'Apply', { exact: true } ).click(); await expect( page @@ -182,9 +180,7 @@ test.describe( await page .getByRole( 'button', { name: 'Add a coupon' } ) .click(); - await page - .locator( '#wc-block-components-totals-coupon__input-0' ) - .fill( coupons[ i ].code ); + await page.getByLabel( 'Enter code' ).fill( coupons[ i ].code ); await page.getByText( 'Apply', { exact: true } ).click(); await expect( page @@ -224,9 +220,7 @@ test.describe( } ) => { // try to add two same coupons and verify the error message await page.getByRole( 'button', { name: 'Add a coupon' } ).click(); - await page - .locator( '#wc-block-components-totals-coupon__input-0' ) - .fill( coupons[ 0 ].code ); + await page.getByLabel( 'Enter code' ).fill( coupons[ 0 ].code ); await page.getByText( 'Apply', { exact: true } ).click(); await expect( page @@ -236,9 +230,7 @@ test.describe( ) ).toBeVisible(); await page.getByRole( 'button', { name: 'Add a coupon' } ).click(); - await page - .locator( '#wc-block-components-totals-coupon__input-0' ) - .fill( coupons[ 0 ].code ); + await page.getByLabel( 'Enter code' ).fill( coupons[ 0 ].code ); await page.getByText( 'Apply', { exact: true } ).click(); await expect( page @@ -254,9 +246,7 @@ test.describe( } ) => { // add coupon with usage limit await page.getByRole( 'button', { name: 'Add a coupon' } ).click(); - await page - .locator( '#wc-block-components-totals-coupon__input-0' ) - .fill( couponLimitedCode ); + await page.getByLabel( 'Enter code' ).fill( couponLimitedCode ); await page.getByText( 'Apply', { exact: true } ).click(); await expect( page diff --git a/plugins/woocommerce/tests/e2e-pw/tests/shopper/checkout-block-coupons.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/shopper/checkout-block-coupons.spec.js index 95bdb3f28f6..6db1b13ff81 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/shopper/checkout-block-coupons.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/shopper/checkout-block-coupons.spec.js @@ -138,9 +138,7 @@ test.describe( await page .getByRole( 'button', { name: 'Add a coupon' } ) .click(); - await page - .locator( '#wc-block-components-totals-coupon__input-0' ) - .fill( coupons[ i ].code ); + await page.getByLabel( 'Enter code' ).fill( coupons[ i ].code ); await page.getByText( 'Apply', { exact: true } ).click(); await expect( page @@ -183,9 +181,7 @@ test.describe( await page .getByRole( 'button', { name: 'Add a coupon' } ) .click(); - await page - .locator( '#wc-block-components-totals-coupon__input-0' ) - .fill( coupons[ i ].code ); + await page.getByLabel( 'Enter code' ).fill( coupons[ i ].code ); await page.getByText( 'Apply', { exact: true } ).click(); await expect( page @@ -225,9 +221,7 @@ test.describe( } ) => { // try to add two same coupons and verify the error message await page.getByRole( 'button', { name: 'Add a coupon' } ).click(); - await page - .locator( '#wc-block-components-totals-coupon__input-0' ) - .fill( coupons[ 0 ].code ); + await page.getByLabel( 'Enter code' ).fill( coupons[ 0 ].code ); await page.getByText( 'Apply', { exact: true } ).click(); await expect( page @@ -237,9 +231,7 @@ test.describe( ) ).toBeVisible(); await page.getByRole( 'button', { name: 'Add a coupon' } ).click(); - await page - .locator( '#wc-block-components-totals-coupon__input-0' ) - .fill( coupons[ 0 ].code ); + await page.getByLabel( 'Enter code' ).fill( coupons[ 0 ].code ); await page.getByText( 'Apply', { exact: true } ).click(); await expect( page @@ -255,9 +247,7 @@ test.describe( } ) => { // add coupon with usage limit await page.getByRole( 'button', { name: 'Add a coupon' } ).click(); - await page - .locator( '#wc-block-components-totals-coupon__input-0' ) - .fill( couponLimitedCode ); + await page.getByLabel( 'Enter code' ).fill( couponLimitedCode ); await page.getByText( 'Apply', { exact: true } ).click(); await expect( page