Merge branch 'trunk' into fix/49635-48841-refunded-taxes
This commit is contained in:
commit
728a57e71e
|
@ -1,3 +1,7 @@
|
|||
body.wc-modal--open {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
body.wc-block-product-gallery-modal-open {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
|
|
@ -15,7 +15,6 @@ 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
|
||||
|
@ -295,7 +294,7 @@ export const renderParentBlock = ( {
|
|||
selector: string;
|
||||
// Function to generate the props object for the block.
|
||||
getProps: ( el: Element, i: number ) => Record< string, unknown >;
|
||||
} ): ReactRootWithContainer[] => {
|
||||
} ): void => {
|
||||
/**
|
||||
* In addition to getProps, we need to render and return the children. This adds children to props.
|
||||
*/
|
||||
|
@ -311,7 +310,7 @@ export const renderParentBlock = ( {
|
|||
/**
|
||||
* The only difference between using renderParentBlock and renderFrontend is that here we provide children.
|
||||
*/
|
||||
return renderFrontend( {
|
||||
renderFrontend( {
|
||||
Block,
|
||||
selector,
|
||||
getProps: getPropsWithChildren,
|
||||
|
|
|
@ -21,11 +21,16 @@ jest.mock( '@wordpress/element', () => {
|
|||
};
|
||||
} );
|
||||
|
||||
const renderInCheckoutProvider = ( ui, options = {} ) => {
|
||||
const renderInCheckoutProvider = ( ui, options = { legacyRoot: true } ) => {
|
||||
const Wrapper = ( { children } ) => {
|
||||
return <CheckoutProvider>{ children }</CheckoutProvider>;
|
||||
};
|
||||
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;
|
||||
};
|
||||
|
@ -124,7 +129,7 @@ describe( 'Form Component', () => {
|
|||
);
|
||||
};
|
||||
|
||||
test( 'updates context value when interacting with form elements', async () => {
|
||||
it( 'updates context value when interacting with form elements', async () => {
|
||||
renderInCheckoutProvider(
|
||||
<>
|
||||
<WrappedAddressForm type="shipping" />
|
||||
|
@ -150,7 +155,7 @@ describe( 'Form Component', () => {
|
|||
);
|
||||
} );
|
||||
|
||||
test( 'input fields update when changing the country', async () => {
|
||||
it( 'input fields update when changing the country', async () => {
|
||||
renderInCheckoutProvider( <WrappedAddressForm type="shipping" /> );
|
||||
|
||||
await act( async () => {
|
||||
|
@ -177,7 +182,7 @@ describe( 'Form Component', () => {
|
|||
expect( screen.getByLabelText( /Postal code/ ) ).toBeInTheDocument();
|
||||
} );
|
||||
|
||||
test( 'input values are reset after changing the country', async () => {
|
||||
it( 'input values are reset after changing the country', async () => {
|
||||
renderInCheckoutProvider( <WrappedAddressForm type="shipping" /> );
|
||||
|
||||
// First enter an address with no state, but fill the city.
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { createRoot, useEffect, Suspense } from '@wordpress/element';
|
||||
import { render, 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
|
||||
|
@ -28,11 +27,6 @@ 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 >
|
||||
|
@ -61,32 +55,20 @@ export const renderBlock = <
|
|||
attributes = {} as TAttributes,
|
||||
props = {} as BlockProps< TProps, TAttributes >,
|
||||
errorBoundaryProps = {},
|
||||
}: RenderBlockParams< TProps, TAttributes > ): Root => {
|
||||
const BlockWrapper = () => {
|
||||
useEffect( () => {
|
||||
}: RenderBlockParams< TProps, TAttributes > ): void => {
|
||||
render(
|
||||
<BlockErrorBoundary { ...errorBoundaryProps }>
|
||||
<Suspense fallback={ <div className="wc-block-placeholder" /> }>
|
||||
{ Block && <Block { ...props } attributes={ attributes } /> }
|
||||
</Suspense>
|
||||
</BlockErrorBoundary>,
|
||||
container,
|
||||
() => {
|
||||
if ( container.classList ) {
|
||||
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<
|
||||
|
@ -117,14 +99,10 @@ const renderBlockInContainers = <
|
|||
containers,
|
||||
getProps = () => ( {} as BlockProps< TProps, TAttributes > ),
|
||||
getErrorBoundaryProps = () => ( {} ),
|
||||
}: RenderBlockInContainersParams<
|
||||
TProps,
|
||||
TAttributes
|
||||
> ): ReactRootWithContainer[] => {
|
||||
}: RenderBlockInContainersParams< TProps, TAttributes > ): void => {
|
||||
if ( containers.length === 0 ) {
|
||||
return [];
|
||||
return;
|
||||
}
|
||||
const roots: ReactRootWithContainer[] = [];
|
||||
|
||||
// Use Array.forEach for IE11 compatibility.
|
||||
Array.prototype.forEach.call( containers, ( el, i ) => {
|
||||
|
@ -136,19 +114,14 @@ const renderBlockInContainers = <
|
|||
...( props.attributes || {} ),
|
||||
};
|
||||
|
||||
roots.push( {
|
||||
renderBlock( {
|
||||
Block,
|
||||
container: el,
|
||||
root: renderBlock( {
|
||||
Block,
|
||||
container: el,
|
||||
props,
|
||||
attributes,
|
||||
errorBoundaryProps,
|
||||
} ),
|
||||
props,
|
||||
attributes,
|
||||
errorBoundaryProps,
|
||||
} );
|
||||
} );
|
||||
|
||||
return roots;
|
||||
};
|
||||
|
||||
// Given an element and a list of wrappers, check if the element is inside at
|
||||
|
@ -184,10 +157,7 @@ const renderBlockOutsideWrappers = <
|
|||
getErrorBoundaryProps,
|
||||
selector,
|
||||
wrappers,
|
||||
}: RenderBlockOutsideWrappersParams<
|
||||
TProps,
|
||||
TAttributes
|
||||
> ): ReactRootWithContainer[] => {
|
||||
}: RenderBlockOutsideWrappersParams< TProps, TAttributes > ): void => {
|
||||
const containers = document.body.querySelectorAll( selector );
|
||||
// Filter out blocks inside the wrappers.
|
||||
if ( wrappers && wrappers.length > 0 ) {
|
||||
|
@ -195,8 +165,7 @@ const renderBlockOutsideWrappers = <
|
|||
return ! isElementInsideWrappers( el, wrappers );
|
||||
} );
|
||||
}
|
||||
|
||||
return renderBlockInContainers( {
|
||||
renderBlockInContainers( {
|
||||
Block,
|
||||
containers,
|
||||
getProps,
|
||||
|
@ -265,21 +234,20 @@ export const renderFrontend = <
|
|||
props:
|
||||
| RenderBlockOutsideWrappersParams< TProps, TAttributes >
|
||||
| RenderBlockInsideWrapperParams< TProps, TAttributes >
|
||||
): ReactRootWithContainer[] => {
|
||||
): void => {
|
||||
const wrappersToSkipOnLoad = document.body.querySelectorAll(
|
||||
selectorsToSkipOnLoad.join( ',' )
|
||||
);
|
||||
|
||||
const { Block, getProps, getErrorBoundaryProps, selector } = props;
|
||||
|
||||
const roots = renderBlockOutsideWrappers( {
|
||||
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 ) => {
|
||||
|
@ -287,8 +255,6 @@ export const renderFrontend = <
|
|||
renderBlockInsideWrapper( { ...props, wrapper } );
|
||||
} );
|
||||
} );
|
||||
|
||||
return roots;
|
||||
};
|
||||
|
||||
export default renderFrontend;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { act, render, screen } from '@testing-library/react';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import * as hooks from '@woocommerce/base-context/hooks';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
|
@ -106,8 +106,14 @@ const setup = ( params: SetupParams ) => {
|
|||
results: stubCollectionData(),
|
||||
isLoading: false,
|
||||
} );
|
||||
const utils = render( <AttributeFilterBlock attributes={ attributes } /> );
|
||||
|
||||
const utils = render( <AttributeFilterBlock attributes={ attributes } />, {
|
||||
legacyRoot: true,
|
||||
} );
|
||||
// We need to switch to React 17 rendering to allow these tests to keep passing, but as a result the React
|
||||
// rendering error will be shown.
|
||||
expect( console ).toHaveErroredWith(
|
||||
`Warning: ReactDOM.render is no longer supported in React 18. Use createRoot instead. Until you switch to the new API, your app will behave as if it's running React 17. Learn more: https://reactjs.org/link/switch-to-createroot`
|
||||
);
|
||||
const applyButton = screen.getByRole( 'button', { name: /apply/i } );
|
||||
const smallAttributeCheckbox = screen.getByRole( 'checkbox', {
|
||||
name: /small/i,
|
||||
|
@ -158,10 +164,8 @@ 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();
|
||||
} );
|
||||
} );
|
||||
|
@ -176,25 +180,18 @@ 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 act( async () => {
|
||||
await userEvent.click( smallAttributeCheckbox );
|
||||
} );
|
||||
await userEvent.click( smallAttributeCheckbox );
|
||||
expect( applyButton ).not.toBeDisabled();
|
||||
|
||||
await act( async () => {
|
||||
await userEvent.click( smallAttributeCheckbox );
|
||||
} );
|
||||
await userEvent.click( smallAttributeCheckbox );
|
||||
expect( applyButton ).toBeDisabled();
|
||||
} );
|
||||
} );
|
||||
|
|
|
@ -6,7 +6,6 @@ import {
|
|||
useState,
|
||||
useEffect,
|
||||
useCallback,
|
||||
useMemo,
|
||||
createInterpolateElement,
|
||||
} from '@wordpress/element';
|
||||
import { useShippingData, useStoreCart } from '@woocommerce/base-context/hooks';
|
||||
|
@ -139,12 +138,10 @@ const renderPickupLocation = (
|
|||
const Block = (): JSX.Element | null => {
|
||||
const { shippingRates, selectShippingRate } = useShippingData();
|
||||
|
||||
// Memoize pickup locations to prevent re-rendering when the shipping rates change.
|
||||
const pickupLocations = useMemo( () => {
|
||||
return ( shippingRates[ 0 ]?.shipping_rates || [] ).filter(
|
||||
isPackageRateCollectable
|
||||
);
|
||||
}, [ shippingRates ] );
|
||||
// Get pickup locations from the first shipping package.
|
||||
const pickupLocations = ( shippingRates[ 0 ]?.shipping_rates || [] ).filter(
|
||||
isPackageRateCollectable
|
||||
);
|
||||
|
||||
const [ selectedOption, setSelectedOption ] = useState< string >(
|
||||
() => pickupLocations.find( ( rate ) => rate.selected )?.rate_id || ''
|
||||
|
@ -171,19 +168,13 @@ const Block = (): JSX.Element | null => {
|
|||
renderPickupLocation,
|
||||
};
|
||||
|
||||
// Update the selected option if there is no rate selected on mount.
|
||||
useEffect( () => {
|
||||
if (
|
||||
! selectedOption &&
|
||||
pickupLocations[ 0 ] &&
|
||||
selectedOption !== pickupLocations[ 0 ].rate_id
|
||||
) {
|
||||
if ( ! selectedOption && pickupLocations[ 0 ] ) {
|
||||
setSelectedOption( pickupLocations[ 0 ].rate_id );
|
||||
onSelectRate( pickupLocations[ 0 ].rate_id );
|
||||
}
|
||||
// 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 ] );
|
||||
|
||||
}, [ onSelectRate, pickupLocations, selectedOption ] );
|
||||
const packageCount = getShippingRatesPackageCount( shippingRates );
|
||||
return (
|
||||
<>
|
||||
|
|
|
@ -26,7 +26,6 @@ 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.
|
||||
|
@ -74,22 +73,19 @@ const Block = ( { noShippingPlaceholder = null } ): ReactElement | null => {
|
|||
|
||||
const { shippingAddress } = useCustomerData();
|
||||
|
||||
const filteredShippingRates = useMemo( () => {
|
||||
return isCollectable
|
||||
? shippingRates.map( ( shippingRatesPackage ) => {
|
||||
return {
|
||||
...shippingRatesPackage,
|
||||
shipping_rates:
|
||||
shippingRatesPackage.shipping_rates.filter(
|
||||
( shippingRatesPackageRate ) =>
|
||||
! hasCollectableRate(
|
||||
shippingRatesPackageRate.method_id
|
||||
)
|
||||
),
|
||||
};
|
||||
} )
|
||||
: shippingRates;
|
||||
}, [ shippingRates, isCollectable ] );
|
||||
const filteredShippingRates = isCollectable
|
||||
? shippingRates.map( ( shippingRatesPackage ) => {
|
||||
return {
|
||||
...shippingRatesPackage,
|
||||
shipping_rates: shippingRatesPackage.shipping_rates.filter(
|
||||
( shippingRatesPackageRate ) =>
|
||||
! hasCollectableRate(
|
||||
shippingRatesPackageRate.method_id
|
||||
)
|
||||
),
|
||||
};
|
||||
} )
|
||||
: shippingRates;
|
||||
|
||||
if ( ! needsShipping ) {
|
||||
return null;
|
||||
|
|
|
@ -73,22 +73,20 @@ body:has(.woocommerce-coming-soon-banner) {
|
|||
}
|
||||
|
||||
.wp-block-loginout {
|
||||
background-color: #000;
|
||||
border-radius: 6px;
|
||||
display: flex;
|
||||
height: 40px;
|
||||
width: 74px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
box-sizing: border-box;
|
||||
|
||||
a {
|
||||
box-sizing: border-box;
|
||||
background-color: #000;
|
||||
border-radius: 6px;
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
line-height: 17px;
|
||||
gap: 10px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
line-height: normal;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
padding: 17px 16px;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -20,10 +20,15 @@ import {
|
|||
isCartResponseTotals,
|
||||
isNumber,
|
||||
} from '@woocommerce/types';
|
||||
import { useCallback, useEffect, useRef, useState } from '@wordpress/element';
|
||||
import {
|
||||
unmountComponentAtNode,
|
||||
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
|
||||
|
@ -105,8 +110,6 @@ const MiniCartBlock = ( attributes: Props ): JSX.Element => {
|
|||
setContentsNode( node );
|
||||
}, [] );
|
||||
|
||||
const rootRef = useRef< ReactRootWithContainer[] | null >( null );
|
||||
|
||||
useEffect( () => {
|
||||
const body = document.querySelector( 'body' );
|
||||
if ( body ) {
|
||||
|
@ -131,7 +134,7 @@ const MiniCartBlock = ( attributes: Props ): JSX.Element => {
|
|||
return;
|
||||
}
|
||||
if ( isOpen ) {
|
||||
const renderedBlock = renderParentBlock( {
|
||||
renderParentBlock( {
|
||||
Block: MiniCartContentsBlock,
|
||||
blockName,
|
||||
getProps: ( el: Element ) => {
|
||||
|
@ -148,25 +151,16 @@ 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 unmountingContainer = contentsNode.querySelector(
|
||||
const container = contentsNode.querySelector(
|
||||
'.wp-block-woocommerce-mini-cart-contents'
|
||||
);
|
||||
|
||||
if ( unmountingContainer ) {
|
||||
const foundRoot = rootRef?.current?.find(
|
||||
( { container } ) => unmountingContainer === container
|
||||
);
|
||||
if ( typeof foundRoot?.root?.unmount === 'function' ) {
|
||||
setTimeout( () => {
|
||||
foundRoot.root.unmount();
|
||||
} );
|
||||
}
|
||||
if ( container ) {
|
||||
unmountComponentAtNode( container );
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -111,6 +111,13 @@ 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 () => {
|
||||
|
@ -125,11 +132,9 @@ 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 );
|
||||
|
@ -141,6 +146,13 @@ 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 () => {
|
||||
|
@ -155,6 +167,13 @@ 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 () => {
|
||||
|
|
|
@ -53,18 +53,6 @@
|
|||
"overlay": {
|
||||
"type": "string",
|
||||
"default": "never"
|
||||
},
|
||||
"overlayIcon": {
|
||||
"type": "string",
|
||||
"default": "filter-icon-1"
|
||||
},
|
||||
"overlayButtonStyle": {
|
||||
"type": "string",
|
||||
"default": "label-icon"
|
||||
},
|
||||
"overlayIconSize": {
|
||||
"type": "number",
|
||||
"default": "12"
|
||||
}
|
||||
},
|
||||
"viewScript": "wc-product-filters-frontend",
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { filter, filterThreeLines } from '@woocommerce/icons';
|
||||
import { getSetting } from '@woocommerce/settings';
|
||||
import { AttributeSetting } from '@woocommerce/types';
|
||||
import {
|
||||
|
@ -10,14 +9,19 @@ import {
|
|||
useBlockProps,
|
||||
useInnerBlocksProps,
|
||||
} from '@wordpress/block-editor';
|
||||
import { BlockEditProps, InnerBlockTemplate } from '@wordpress/blocks';
|
||||
import {
|
||||
BlockEditProps,
|
||||
BlockInstance,
|
||||
InnerBlockTemplate,
|
||||
createBlock,
|
||||
} from '@wordpress/blocks';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { Icon, menu, settings } from '@wordpress/icons';
|
||||
import { useEffect } from '@wordpress/element';
|
||||
import { select, dispatch } from '@wordpress/data';
|
||||
import { useLocalStorageState } from '@woocommerce/base-hooks';
|
||||
import {
|
||||
ExternalLink,
|
||||
PanelBody,
|
||||
RadioControl,
|
||||
RangeControl,
|
||||
// @ts-expect-error - no types.
|
||||
// eslint-disable-next-line @wordpress/no-unsafe-wp-apis
|
||||
__experimentalToggleGroupControl as ToggleGroupControl,
|
||||
|
@ -111,6 +115,7 @@ const TEMPLATE: InnerBlockTemplate[] = [
|
|||
export const Edit = ( {
|
||||
setAttributes,
|
||||
attributes,
|
||||
clientId,
|
||||
}: BlockEditProps< BlockAttributes > ) => {
|
||||
const blockProps = useBlockProps();
|
||||
|
||||
|
@ -119,6 +124,84 @@ export const Edit = ( {
|
|||
''
|
||||
);
|
||||
|
||||
const [
|
||||
productFiltersOverlayNavigationAttributes,
|
||||
setProductFiltersOverlayNavigationAttributes,
|
||||
] = useLocalStorageState< Record< string, unknown > >(
|
||||
'product-filters-overlay-navigation-attributes',
|
||||
{}
|
||||
);
|
||||
|
||||
useEffect( () => {
|
||||
const filtersClientIds = select( 'core/block-editor' ).getBlocksByName(
|
||||
'woocommerce/product-filters'
|
||||
);
|
||||
|
||||
let overlayBlock:
|
||||
| BlockInstance< { [ k: string ]: unknown } >
|
||||
| undefined;
|
||||
|
||||
for ( const filterClientId of filtersClientIds ) {
|
||||
const filterBlock =
|
||||
select( 'core/block-editor' ).getBlock( filterClientId );
|
||||
|
||||
if ( filterBlock ) {
|
||||
for ( const innerBlock of filterBlock.innerBlocks ) {
|
||||
if (
|
||||
innerBlock.name ===
|
||||
'woocommerce/product-filters-overlay-navigation' &&
|
||||
innerBlock.attributes.triggerType === 'open-overlay'
|
||||
) {
|
||||
overlayBlock = innerBlock;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( attributes.overlay === 'never' && overlayBlock ) {
|
||||
setProductFiltersOverlayNavigationAttributes(
|
||||
overlayBlock.attributes
|
||||
);
|
||||
|
||||
dispatch( 'core/block-editor' ).updateBlockAttributes(
|
||||
overlayBlock.clientId,
|
||||
{
|
||||
lock: {},
|
||||
}
|
||||
);
|
||||
|
||||
dispatch( 'core/block-editor' ).removeBlock(
|
||||
overlayBlock.clientId
|
||||
);
|
||||
} else if ( attributes.overlay !== 'never' && ! overlayBlock ) {
|
||||
if ( productFiltersOverlayNavigationAttributes ) {
|
||||
productFiltersOverlayNavigationAttributes.triggerType =
|
||||
'open-overlay';
|
||||
}
|
||||
|
||||
dispatch( 'core/block-editor' ).insertBlock(
|
||||
createBlock(
|
||||
'woocommerce/product-filters-overlay-navigation',
|
||||
productFiltersOverlayNavigationAttributes
|
||||
? productFiltersOverlayNavigationAttributes
|
||||
: {
|
||||
align: 'left',
|
||||
triggerType: 'open-overlay',
|
||||
lock: { move: true, remove: true },
|
||||
}
|
||||
),
|
||||
0,
|
||||
clientId,
|
||||
false
|
||||
);
|
||||
}
|
||||
}, [
|
||||
attributes.overlay,
|
||||
clientId,
|
||||
productFiltersOverlayNavigationAttributes,
|
||||
setProductFiltersOverlayNavigationAttributes,
|
||||
] );
|
||||
|
||||
return (
|
||||
<div { ...blockProps }>
|
||||
<InspectorControls>
|
||||
|
@ -144,126 +227,6 @@ export const Edit = ( {
|
|||
label={ __( 'Always', 'woocommerce' ) }
|
||||
/>
|
||||
</ToggleGroupControl>
|
||||
{ attributes.overlay === 'mobile' && (
|
||||
<>
|
||||
<RadioControl
|
||||
className="wc-block-editor-product-filters__overlay-button-style-toggle"
|
||||
label={ __( 'Button', 'woocommerce' ) }
|
||||
selected={ attributes.overlayButtonStyle }
|
||||
onChange={ (
|
||||
value: BlockAttributes[ 'overlayButtonStyle' ]
|
||||
) => {
|
||||
setAttributes( {
|
||||
overlayButtonStyle: value,
|
||||
} );
|
||||
} }
|
||||
options={ [
|
||||
{
|
||||
value: 'label-icon',
|
||||
label: __(
|
||||
'Label and icon',
|
||||
'woocommerce'
|
||||
),
|
||||
},
|
||||
{
|
||||
value: 'label',
|
||||
label: __(
|
||||
'Label only',
|
||||
'woocommerce'
|
||||
),
|
||||
},
|
||||
{
|
||||
value: 'icon',
|
||||
label: __( 'Icon only', 'woocommerce' ),
|
||||
},
|
||||
] }
|
||||
/>
|
||||
{ attributes.overlayButtonStyle !== 'label' && (
|
||||
<>
|
||||
<ToggleGroupControl
|
||||
className="wc-block-editor-product-filters__overlay-button-toggle"
|
||||
isBlock={ true }
|
||||
value={ attributes.overlayIcon }
|
||||
onChange={ (
|
||||
value: BlockAttributes[ 'overlayIcon' ]
|
||||
) => {
|
||||
setAttributes( {
|
||||
overlayIcon: value,
|
||||
} );
|
||||
} }
|
||||
>
|
||||
<ToggleGroupControlOption
|
||||
value={ 'filter-icon-1' }
|
||||
aria-label={ __(
|
||||
'Filter icon 1',
|
||||
'woocommerce'
|
||||
) }
|
||||
label={
|
||||
<Icon
|
||||
size={ 32 }
|
||||
icon={ filter }
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<ToggleGroupControlOption
|
||||
value={ 'filter-icon-2' }
|
||||
aria-label={ __(
|
||||
'Filter icon 2',
|
||||
'woocommerce'
|
||||
) }
|
||||
label={
|
||||
<Icon
|
||||
size={ 32 }
|
||||
icon={ filterThreeLines }
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<ToggleGroupControlOption
|
||||
value={ 'filter-icon-3' }
|
||||
aria-label={ __(
|
||||
'Filter icon 3',
|
||||
'woocommerce'
|
||||
) }
|
||||
label={
|
||||
<Icon
|
||||
size={ 32 }
|
||||
icon={ menu }
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<ToggleGroupControlOption
|
||||
value={ 'filter-icon-4' }
|
||||
aria-label={ __(
|
||||
'Filter icon 4',
|
||||
'woocommerce'
|
||||
) }
|
||||
label={
|
||||
<Icon
|
||||
size={ 32 }
|
||||
icon={ settings }
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</ToggleGroupControl>
|
||||
<RangeControl
|
||||
label={ __(
|
||||
'Icon size',
|
||||
'woocommerce'
|
||||
) }
|
||||
className="wc-block-editor-product-filters__overlay-button-size"
|
||||
value={ attributes.overlayIconSize }
|
||||
onChange={ ( value: number ) =>
|
||||
setAttributes( {
|
||||
overlayIconSize: value,
|
||||
} )
|
||||
}
|
||||
min={ 20 }
|
||||
max={ 80 }
|
||||
/>
|
||||
</>
|
||||
) }
|
||||
</>
|
||||
) }
|
||||
{ attributes.overlay !== 'never' && (
|
||||
<ExternalLink
|
||||
href={ templatePartEditUri }
|
||||
|
|
|
@ -1,15 +1,40 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { store } from '@woocommerce/interactivity';
|
||||
import { getContext as getContextFn, store } from '@woocommerce/interactivity';
|
||||
|
||||
export interface ProductFiltersContext {
|
||||
productId: string;
|
||||
isDialogOpen: boolean;
|
||||
hasPageWithWordPressAdminBar: boolean;
|
||||
}
|
||||
|
||||
const getContext = ( ns?: string ) =>
|
||||
getContextFn< ProductFiltersContext >( ns );
|
||||
|
||||
const productFilters = {
|
||||
state: {},
|
||||
actions: {},
|
||||
state: {
|
||||
isDialogOpen: () => {
|
||||
const context = getContext();
|
||||
return context.isDialogOpen;
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
openDialog: () => {
|
||||
const context = getContext();
|
||||
document.body.classList.add( 'wc-modal--open' );
|
||||
context.hasPageWithWordPressAdminBar = Boolean(
|
||||
document.getElementById( 'wpadminbar' )
|
||||
);
|
||||
|
||||
context.isDialogOpen = true;
|
||||
},
|
||||
closeDialog: () => {
|
||||
const context = getContext();
|
||||
document.body.classList.remove( 'wc-modal--open' );
|
||||
|
||||
context.isDialogOpen = false;
|
||||
},
|
||||
},
|
||||
callbacks: {},
|
||||
};
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ import { isExperimentalBlocksEnabled } from '@woocommerce/block-settings';
|
|||
*/
|
||||
import metadata from './block.json';
|
||||
import { ProductFiltersBlockSettings } from './settings';
|
||||
import './style.scss';
|
||||
|
||||
if ( isExperimentalBlocksEnabled() ) {
|
||||
registerBlockType( metadata, ProductFiltersBlockSettings );
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { BlockVariation } from '@wordpress/blocks';
|
||||
import { Icon, button } from '@wordpress/icons';
|
||||
|
||||
const variations: BlockVariation[] = [
|
||||
{
|
||||
|
@ -12,6 +13,8 @@ const variations: BlockVariation[] = [
|
|||
triggerType: 'open-overlay',
|
||||
},
|
||||
isDefault: false,
|
||||
icon: <Icon icon={ button } />,
|
||||
isActive: [ 'triggerType' ],
|
||||
},
|
||||
];
|
||||
|
||||
|
|
|
@ -20,12 +20,16 @@
|
|||
"default": "link"
|
||||
},
|
||||
"iconSize": {
|
||||
"type": "string"
|
||||
"type": "number"
|
||||
},
|
||||
"overlayMode": {
|
||||
"type": "string",
|
||||
"default": "never"
|
||||
},
|
||||
"overlayIcon": {
|
||||
"type": "string",
|
||||
"default": "filter-icon-1"
|
||||
},
|
||||
"style": {
|
||||
"type": "object",
|
||||
"default": {
|
||||
|
@ -40,6 +44,7 @@
|
|||
}
|
||||
},
|
||||
"supports": {
|
||||
"interactivity": true,
|
||||
"align": [ "left", "right", "center"],
|
||||
"inserter": false,
|
||||
"color": {
|
||||
|
|
|
@ -6,7 +6,8 @@ import { useBlockProps, useInnerBlocksProps } from '@wordpress/block-editor';
|
|||
import { BlockEditProps, store as blocksStore } from '@wordpress/blocks';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import clsx from 'clsx';
|
||||
import { Icon, close } from '@wordpress/icons';
|
||||
import { Icon, close, menu, settings } from '@wordpress/icons';
|
||||
import { filter, filterThreeLines } from '@woocommerce/icons';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
|
@ -16,7 +17,6 @@ import type {
|
|||
BlockContext,
|
||||
BlockVariationTriggerType,
|
||||
} from './types';
|
||||
import { default as productFiltersIcon } from '../../icon';
|
||||
import { BlockOverlayAttribute as ProductFiltersBlockOverlayAttribute } from '../../constants';
|
||||
import './editor.scss';
|
||||
import { Inspector } from './inspector-controls';
|
||||
|
@ -37,16 +37,33 @@ const OverlayNavigationLabel = ( {
|
|||
const OverlayNavigationIcon = ( {
|
||||
variation,
|
||||
iconSize,
|
||||
overlayIcon,
|
||||
style,
|
||||
}: {
|
||||
variation: BlockVariationTriggerType;
|
||||
iconSize: number | undefined;
|
||||
overlayIcon: string;
|
||||
style: BlockAttributes[ 'style' ];
|
||||
} ) => {
|
||||
let icon = close;
|
||||
|
||||
if ( variation === 'open-overlay' ) {
|
||||
icon = productFiltersIcon();
|
||||
switch ( overlayIcon ) {
|
||||
case 'filter-icon-4':
|
||||
icon = settings;
|
||||
break;
|
||||
case 'filter-icon-3':
|
||||
icon = menu;
|
||||
break;
|
||||
case 'filter-icon-2':
|
||||
icon = filterThreeLines;
|
||||
break;
|
||||
case 'filter-icon-1':
|
||||
icon = filter;
|
||||
break;
|
||||
default:
|
||||
icon = filter;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -65,11 +82,13 @@ const OverlayNavigationContent = ( {
|
|||
variation,
|
||||
iconSize,
|
||||
style,
|
||||
overlayIcon,
|
||||
navigationStyle,
|
||||
}: {
|
||||
variation: BlockVariationTriggerType;
|
||||
iconSize: BlockAttributes[ 'iconSize' ];
|
||||
style: BlockAttributes[ 'style' ];
|
||||
overlayIcon: BlockAttributes[ 'overlayIcon' ];
|
||||
navigationStyle: BlockAttributes[ 'navigationStyle' ];
|
||||
} ) => {
|
||||
const overlayNavigationLabel = (
|
||||
|
@ -79,6 +98,7 @@ const OverlayNavigationContent = ( {
|
|||
<OverlayNavigationIcon
|
||||
variation={ variation }
|
||||
iconSize={ iconSize }
|
||||
overlayIcon={ overlayIcon }
|
||||
style={ style }
|
||||
/>
|
||||
);
|
||||
|
@ -111,8 +131,14 @@ const OverlayNavigationContent = ( {
|
|||
type BlockProps = BlockEditProps< BlockAttributes > & { context: BlockContext };
|
||||
|
||||
export const Edit = ( { attributes, setAttributes, context }: BlockProps ) => {
|
||||
const { navigationStyle, buttonStyle, iconSize, style, triggerType } =
|
||||
attributes;
|
||||
const {
|
||||
navigationStyle,
|
||||
buttonStyle,
|
||||
iconSize,
|
||||
overlayIcon,
|
||||
style,
|
||||
triggerType,
|
||||
} = attributes;
|
||||
const { 'woocommerce/product-filters/overlay': productFiltersOverlayMode } =
|
||||
context;
|
||||
const blockProps = useBlockProps( {
|
||||
|
@ -214,6 +240,7 @@ export const Edit = ( { attributes, setAttributes, context }: BlockProps ) => {
|
|||
variation={ triggerType }
|
||||
iconSize={ iconSize }
|
||||
navigationStyle={ navigationStyle }
|
||||
overlayIcon={ overlayIcon }
|
||||
style={ style }
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { store } from '@woocommerce/interactivity';
|
||||
|
||||
export interface ProductFiltersContext {
|
||||
isDialogOpen: boolean;
|
||||
}
|
||||
|
||||
const productFiltersOverlayNavigation = {
|
||||
state: {},
|
||||
actions: {},
|
||||
callbacks: {},
|
||||
};
|
||||
|
||||
store( 'woocommerce/product-filters', productFiltersOverlayNavigation );
|
||||
|
||||
export type ProductFiltersOverlayNavigation =
|
||||
typeof productFiltersOverlayNavigation;
|
|
@ -2,8 +2,8 @@
|
|||
* External dependencies
|
||||
*/
|
||||
import { registerBlockType } from '@wordpress/blocks';
|
||||
import { Icon } from '@wordpress/icons';
|
||||
import { closeSquareShadow } from '@woocommerce/icons';
|
||||
import { Icon } from '@wordpress/icons';
|
||||
import { isExperimentalBlocksEnabled } from '@woocommerce/block-settings';
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
/* eslint-disable @wordpress/no-unsafe-wp-apis */
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { InspectorControls } from '@wordpress/block-editor';
|
||||
import { BlockEditProps } from '@wordpress/blocks';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { filter, filterThreeLines } from '@woocommerce/icons';
|
||||
import { Icon, menu, settings } from '@wordpress/icons';
|
||||
import {
|
||||
PanelBody,
|
||||
RadioControl,
|
||||
|
@ -35,7 +36,8 @@ export const Inspector = ( {
|
|||
setAttributes,
|
||||
buttonStyles,
|
||||
}: InspectorProps ) => {
|
||||
const { navigationStyle, buttonStyle, iconSize } = attributes;
|
||||
const { navigationStyle, buttonStyle, iconSize, overlayIcon, triggerType } =
|
||||
attributes;
|
||||
return (
|
||||
<InspectorControls group="styles">
|
||||
<PanelBody title={ __( 'Style', 'woocommerce' ) }>
|
||||
|
@ -101,6 +103,61 @@ export const Inspector = ( {
|
|||
/>
|
||||
) }
|
||||
|
||||
{ triggerType === 'open-overlay' &&
|
||||
navigationStyle !== 'label-only' && (
|
||||
<ToggleGroupControl
|
||||
label={ __( 'Icon', 'woocommerce' ) }
|
||||
className="wc-block-editor-product-filters__overlay-button-toggle"
|
||||
isBlock={ true }
|
||||
value={ overlayIcon }
|
||||
onChange={ (
|
||||
value: BlockAttributes[ 'overlayIcon' ]
|
||||
) => {
|
||||
setAttributes( {
|
||||
overlayIcon: value,
|
||||
} );
|
||||
} }
|
||||
>
|
||||
<ToggleGroupControlOption
|
||||
value={ 'filter-icon-1' }
|
||||
aria-label={ __(
|
||||
'Filter icon 1',
|
||||
'woocommerce'
|
||||
) }
|
||||
label={ <Icon size={ 32 } icon={ filter } /> }
|
||||
/>
|
||||
<ToggleGroupControlOption
|
||||
value={ 'filter-icon-2' }
|
||||
aria-label={ __(
|
||||
'Filter icon 2',
|
||||
'woocommerce'
|
||||
) }
|
||||
label={
|
||||
<Icon
|
||||
size={ 32 }
|
||||
icon={ filterThreeLines }
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<ToggleGroupControlOption
|
||||
value={ 'filter-icon-3' }
|
||||
aria-label={ __(
|
||||
'Filter icon 3',
|
||||
'woocommerce'
|
||||
) }
|
||||
label={ <Icon size={ 32 } icon={ menu } /> }
|
||||
/>
|
||||
<ToggleGroupControlOption
|
||||
value={ 'filter-icon-4' }
|
||||
aria-label={ __(
|
||||
'Filter icon 4',
|
||||
'woocommerce'
|
||||
) }
|
||||
label={ <Icon size={ 32 } icon={ settings } /> }
|
||||
/>
|
||||
</ToggleGroupControl>
|
||||
) }
|
||||
|
||||
{ navigationStyle !== 'label-only' && (
|
||||
<RangeControl
|
||||
className="wc-block-product-filters-overlay-navigation__icon-size-control"
|
||||
|
|
|
@ -8,15 +8,18 @@
|
|||
cursor: pointer;
|
||||
|
||||
&.alignright {
|
||||
margin-left: auto;
|
||||
justify-content: right;
|
||||
}
|
||||
|
||||
&.alignleft {
|
||||
margin-left: unset;
|
||||
justify-content: unset;
|
||||
}
|
||||
|
||||
&.aligncenter {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
&.hidden {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ export type BlockAttributes = {
|
|||
iconSize?: number;
|
||||
overlayMode: ProductFiltersBlockOverlayAttributeOptions;
|
||||
triggerType: BlockVariationTriggerType;
|
||||
overlayIcon: string;
|
||||
style: {
|
||||
border?: {
|
||||
radius?: string | BorderRadius;
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
.wc-block-product-filters {
|
||||
dialog {
|
||||
flex-direction: column;
|
||||
position: fixed;
|
||||
border: none;
|
||||
top: 0;
|
||||
z-index: 9999;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
|
||||
&.wc-block-product-filters--dialog-open {
|
||||
display: flex;
|
||||
padding-left: 0;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
&.wc-block-product-filters--with-admin-bar {
|
||||
margin-top: $gap;
|
||||
height: calc(100vh - 2 * $gap);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,14 +2,7 @@
|
|||
* External dependencies
|
||||
*/
|
||||
import React from '@wordpress/element';
|
||||
import {
|
||||
act,
|
||||
cleanup,
|
||||
render,
|
||||
screen,
|
||||
waitFor,
|
||||
within,
|
||||
} from '@testing-library/react';
|
||||
import { render, screen, waitFor, within } from '@testing-library/react';
|
||||
import * as hooks from '@woocommerce/base-context/hooks';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
|
@ -66,7 +59,6 @@ const selectors = {
|
|||
};
|
||||
|
||||
const setup = ( params: SetupParams ) => {
|
||||
cleanup();
|
||||
const url = `http://woo.local/${
|
||||
params.filterRating ? '?rating_filter=' + params.filterRating : ''
|
||||
}`;
|
||||
|
@ -86,7 +78,14 @@ const setup = ( params: SetupParams ) => {
|
|||
} );
|
||||
|
||||
const { container, ...utils } = render(
|
||||
<RatingFilterBlock attributes={ attributes } />
|
||||
<RatingFilterBlock attributes={ attributes } />,
|
||||
{ legacyRoot: true }
|
||||
);
|
||||
|
||||
// We need to switch to React 17 rendering to allow these tests to keep passing, but as a result the React
|
||||
// rendering error will be shown.
|
||||
expect( console ).toHaveErroredWith(
|
||||
`Warning: ReactDOM.render is no longer supported in React 18. Use createRoot instead. Until you switch to the new API, your app will behave as if it's running React 17. Learn more: https://reactjs.org/link/switch-to-createroot`
|
||||
);
|
||||
|
||||
const getList = () => container.querySelector( selectors.list );
|
||||
|
@ -204,81 +203,74 @@ 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', async () => {
|
||||
await waitFor( async () => {
|
||||
const ratingParam = '2';
|
||||
const { getRating2Chips, getRating4Chips, getRating5Chips } =
|
||||
setupSingleChoiceDropdown( ratingParam );
|
||||
test( 'renders chips based on URL params', () => {
|
||||
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 () => {
|
||||
await waitFor( async () => {
|
||||
const ratingParam = '2';
|
||||
const {
|
||||
getDropdown,
|
||||
getRating2Chips,
|
||||
getRating4Chips,
|
||||
getRating4Suggestion,
|
||||
} = setupSingleChoiceDropdown( ratingParam );
|
||||
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 () => {
|
||||
await waitFor( async () => {
|
||||
const ratingParam = '4';
|
||||
const {
|
||||
getRating2Chips,
|
||||
getRating4Chips,
|
||||
getRating5Chips,
|
||||
getRemoveButtonFromChips,
|
||||
} = setupMultipleChoiceDropdown( ratingParam );
|
||||
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();
|
||||
} );
|
||||
} );
|
||||
|
||||
|
@ -289,89 +281,83 @@ describe( 'Filter by Rating block', () => {
|
|||
expect( getList() ).toBeNull();
|
||||
} );
|
||||
|
||||
test( 'renders chips based on URL params', async () => {
|
||||
await waitFor( async () => {
|
||||
const ratingParam = '2,4';
|
||||
const { getRating2Chips, getRating4Chips, getRating5Chips } =
|
||||
setupMultipleChoiceDropdown( ratingParam );
|
||||
test( 'renders chips based on URL params', () => {
|
||||
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 () => {
|
||||
await waitFor( async () => {
|
||||
const ratingParam = '2';
|
||||
const {
|
||||
getDropdown,
|
||||
getRating2Chips,
|
||||
getRating4Chips,
|
||||
getRating5Chips,
|
||||
getRating4Suggestion,
|
||||
getRating5Suggestion,
|
||||
} = setupMultipleChoiceDropdown( ratingParam );
|
||||
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 () => {
|
||||
await waitFor( async () => {
|
||||
const ratingParam = '2,4,5';
|
||||
const {
|
||||
getRating2Chips,
|
||||
getRating4Chips,
|
||||
getRating5Chips,
|
||||
getRemoveButtonFromChips,
|
||||
} = setupMultipleChoiceDropdown( ratingParam );
|
||||
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();
|
||||
} );
|
||||
} );
|
||||
|
||||
|
@ -382,67 +368,61 @@ describe( 'Filter by Rating block', () => {
|
|||
expect( getList() ).toBeInTheDocument();
|
||||
} );
|
||||
|
||||
test( 'renders checked options based on URL params', async () => {
|
||||
await waitFor( async () => {
|
||||
const ratingParam = '4';
|
||||
const {
|
||||
getRating2Checkbox,
|
||||
getRating4Checkbox,
|
||||
getRating5Checkbox,
|
||||
} = setupSingleChoiceList( ratingParam );
|
||||
test( 'renders checked options based on URL params', () => {
|
||||
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 () => {
|
||||
await waitFor( async () => {
|
||||
const ratingParam = '2';
|
||||
const {
|
||||
getRating2Checkbox,
|
||||
getRating4Checkbox,
|
||||
getRating5Checkbox,
|
||||
} = setupSingleChoiceList( ratingParam );
|
||||
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 act( async () => {
|
||||
await userEvent.click( rating4checkbox );
|
||||
} );
|
||||
}
|
||||
if ( rating4checkbox ) {
|
||||
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 () => {
|
||||
await waitFor( async () => {
|
||||
const ratingParam = '4';
|
||||
const {
|
||||
getRating2Checkbox,
|
||||
getRating4Checkbox,
|
||||
getRating5Checkbox,
|
||||
} = setupMultipleChoiceList( ratingParam );
|
||||
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();
|
||||
|
@ -457,40 +437,38 @@ describe( 'Filter by Rating block', () => {
|
|||
expect( getList() ).toBeInTheDocument();
|
||||
} );
|
||||
|
||||
test( 'renders chips based on URL params', async () => {
|
||||
await waitFor( async () => {
|
||||
const ratingParam = '4,5';
|
||||
const {
|
||||
getRating2Checkbox,
|
||||
getRating4Checkbox,
|
||||
getRating5Checkbox,
|
||||
} = setupMultipleChoiceList( ratingParam );
|
||||
test( 'renders chips based on URL params', () => {
|
||||
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 () => {
|
||||
await waitFor( async () => {
|
||||
const ratingParam = '2,4';
|
||||
const {
|
||||
getRating2Checkbox,
|
||||
getRating4Checkbox,
|
||||
getRating5Checkbox,
|
||||
} = setupMultipleChoiceList( ratingParam );
|
||||
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();
|
||||
|
@ -498,24 +476,24 @@ describe( 'Filter by Rating block', () => {
|
|||
} );
|
||||
|
||||
test( 'removes the option when it is clicked again', async () => {
|
||||
await waitFor( async () => {
|
||||
const ratingParam = '2,4';
|
||||
const {
|
||||
getRating2Checkbox,
|
||||
getRating4Checkbox,
|
||||
getRating5Checkbox,
|
||||
} = setupMultipleChoiceList( ratingParam );
|
||||
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();
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { InnerBlocks, useBlockProps } from '@wordpress/block-editor';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import metadata from './block.json';
|
||||
|
||||
const v1 = {
|
||||
attributes: metadata.attributes,
|
||||
supports: metadata.supports,
|
||||
save: () => {
|
||||
const blockProps = useBlockProps.save();
|
||||
|
||||
return (
|
||||
<div { ...blockProps }>
|
||||
{ /* @ts-expect-error: `InnerBlocks.Content` is a component that is typed in WordPress core*/ }
|
||||
<InnerBlocks.Content />
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
const deprecated = [ v1 ];
|
||||
|
||||
export default deprecated;
|
|
@ -10,10 +10,12 @@ import { BLOCK_ICON } from './constants';
|
|||
import metadata from './block.json';
|
||||
import edit from './edit';
|
||||
import save from './save';
|
||||
import deprecated from './deprecated';
|
||||
|
||||
// @ts-expect-error: `registerBlockType` is a function that is typed in WordPress core.
|
||||
registerBlockType( metadata, {
|
||||
icon: BLOCK_ICON,
|
||||
edit,
|
||||
save,
|
||||
deprecated,
|
||||
} );
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
import { InnerBlocks, useBlockProps } from '@wordpress/block-editor';
|
||||
|
||||
const Save = () => {
|
||||
// We add the `woocommerce` class to the wrapper to apply WooCommerce styles to the block.
|
||||
const blockProps = useBlockProps.save( {
|
||||
className: 'woocommerce',
|
||||
} );
|
||||
|
|
|
@ -2,14 +2,7 @@
|
|||
* External dependencies
|
||||
*/
|
||||
import React from '@wordpress/element';
|
||||
import {
|
||||
act,
|
||||
cleanup,
|
||||
render,
|
||||
screen,
|
||||
within,
|
||||
waitFor,
|
||||
} from '@testing-library/react';
|
||||
import { act, render, screen, within, waitFor } from '@testing-library/react';
|
||||
import { default as fetchMock } from 'jest-fetch-mock';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
|
@ -75,7 +68,6 @@ const selectors = {
|
|||
};
|
||||
|
||||
const setup = ( params: SetupParams = {} ) => {
|
||||
cleanup();
|
||||
const url = `http://woo.local/${
|
||||
params.filterStock ? '?filter_stock_status=' + params.filterStock : ''
|
||||
}`;
|
||||
|
@ -95,7 +87,14 @@ const setup = ( params: SetupParams = {} ) => {
|
|||
};
|
||||
|
||||
const { container, ...utils } = render(
|
||||
<Block attributes={ attributes } />
|
||||
<Block attributes={ attributes } />,
|
||||
{ legacyRoot: true }
|
||||
);
|
||||
|
||||
// We need to switch to React 17 rendering to allow these tests to keep passing, but as a result the React
|
||||
// rendering error will be shown.
|
||||
expect( console ).toHaveErroredWith(
|
||||
`Warning: ReactDOM.render is no longer supported in React 18. Use createRoot instead. Until you switch to the new API, your app will behave as if it's running React 17. Learn more: https://reactjs.org/link/switch-to-createroot`
|
||||
);
|
||||
|
||||
const getList = () => container.querySelector( selectors.list );
|
||||
|
@ -228,7 +227,7 @@ describe( 'Filter by Stock block', () => {
|
|||
fetchMock.resetMocks();
|
||||
} );
|
||||
|
||||
test( 'renders the stock filter block', async () => {
|
||||
it( 'renders the stock filter block', async () => {
|
||||
const { container } = setup( {
|
||||
showFilterButton: false,
|
||||
showCounts: false,
|
||||
|
@ -236,7 +235,7 @@ describe( 'Filter by Stock block', () => {
|
|||
expect( container ).toMatchSnapshot();
|
||||
} );
|
||||
|
||||
test( 'renders the stock filter block with the filter button', async () => {
|
||||
it( 'renders the stock filter block with the filter button', async () => {
|
||||
const { container } = setup( {
|
||||
showFilterButton: true,
|
||||
showCounts: false,
|
||||
|
@ -244,7 +243,7 @@ describe( 'Filter by Stock block', () => {
|
|||
expect( container ).toMatchSnapshot();
|
||||
} );
|
||||
|
||||
test( 'renders the stock filter block with the product counts', async () => {
|
||||
it( 'renders the stock filter block with the product counts', async () => {
|
||||
const { container } = setup( {
|
||||
showFilterButton: false,
|
||||
showCounts: true,
|
||||
|
@ -255,86 +254,80 @@ 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', async () => {
|
||||
await waitFor( async () => {
|
||||
const ratingParam = 'instock';
|
||||
const {
|
||||
getInStockChips,
|
||||
getOutOfStockChips,
|
||||
getOnBackorderChips,
|
||||
} = setupSingleChoiceDropdown( ratingParam );
|
||||
test( 'renders chips based on URL params', () => {
|
||||
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 () => {
|
||||
await waitFor( async () => {
|
||||
const user = userEvent.setup();
|
||||
const ratingParam = 'instock';
|
||||
const {
|
||||
getDropdown,
|
||||
getInStockChips,
|
||||
getOutOfStockChips,
|
||||
getOutOfStockSuggestion,
|
||||
} = setupSingleChoiceDropdown( ratingParam );
|
||||
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 () => {
|
||||
await waitFor( async () => {
|
||||
const user = userEvent.setup();
|
||||
const ratingParam = 'outofstock';
|
||||
const {
|
||||
getInStockChips,
|
||||
getOutOfStockChips,
|
||||
getOnBackorderChips,
|
||||
getRemoveButtonFromChips,
|
||||
} = setupMultipleChoiceDropdown( ratingParam );
|
||||
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 ) {
|
||||
await act( async () => {
|
||||
await user.click( removeOutOfStockButton );
|
||||
} );
|
||||
}
|
||||
if ( removeOutOfStockButton ) {
|
||||
act( async () => {
|
||||
await user.click( removeOutOfStockButton );
|
||||
} );
|
||||
}
|
||||
|
||||
await waitFor( () => {
|
||||
expect( getInStockChips() ).toBeNull();
|
||||
expect( getOutOfStockChips() ).toBeNull();
|
||||
expect( getOnBackorderChips() ).toBeNull();
|
||||
|
@ -349,65 +342,67 @@ describe( 'Filter by Stock block', () => {
|
|||
expect( getList() ).toBeNull();
|
||||
} );
|
||||
|
||||
test( 'renders chips based on URL params', async () => {
|
||||
await waitFor( async () => {
|
||||
const ratingParam = 'instock,onbackorder';
|
||||
const {
|
||||
getInStockChips,
|
||||
getOutOfStockChips,
|
||||
getOnBackorderChips,
|
||||
} = setupMultipleChoiceDropdown( ratingParam );
|
||||
test( 'renders chips based on URL params', () => {
|
||||
const ratingParam = 'instock,onbackorder';
|
||||
const { getInStockChips, getOutOfStockChips, getOnBackorderChips } =
|
||||
setupMultipleChoiceDropdown( ratingParam );
|
||||
|
||||
expect( getInStockChips() ).toBeInTheDocument();
|
||||
expect( getOutOfStockChips() ).toBeNull();
|
||||
expect( getOnBackorderChips() ).toBeInTheDocument();
|
||||
} );
|
||||
expect( getInStockChips() ).toBeInTheDocument();
|
||||
expect( getOutOfStockChips() ).toBeNull();
|
||||
expect( getOnBackorderChips() ).toBeInTheDocument();
|
||||
} );
|
||||
|
||||
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 );
|
||||
const user = userEvent.setup();
|
||||
const ratingParam = 'onbackorder';
|
||||
const {
|
||||
getDropdown,
|
||||
getInStockChips,
|
||||
getOutOfStockChips,
|
||||
getOnBackorderChips,
|
||||
getInStockSuggestion,
|
||||
getOutOfStockSuggestion,
|
||||
} = setupMultipleChoiceDropdown( ratingParam );
|
||||
|
||||
await waitFor( () => {
|
||||
expect( getInStockChips() ).toBeNull();
|
||||
expect( getOutOfStockChips() ).toBeNull();
|
||||
expect( getOnBackorderChips() ).toBeInTheDocument();
|
||||
} );
|
||||
const dropdown = getDropdown();
|
||||
|
||||
const dropdown = getDropdown();
|
||||
|
||||
if ( dropdown ) {
|
||||
if ( dropdown ) {
|
||||
await act( async () => {
|
||||
await user.click( dropdown );
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
const inStockSuggestion = getInStockSuggestion();
|
||||
const inStockSuggestion = getInStockSuggestion();
|
||||
|
||||
if ( inStockSuggestion ) {
|
||||
if ( inStockSuggestion ) {
|
||||
await act( async () => {
|
||||
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 ) {
|
||||
const freshDropdown = getDropdown();
|
||||
if ( freshDropdown ) {
|
||||
await act( async () => {
|
||||
await user.click( freshDropdown );
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
const outOfStockSuggestion = getOutOfStockSuggestion();
|
||||
const outOfStockSuggestion = getOutOfStockSuggestion();
|
||||
|
||||
if ( outOfStockSuggestion ) {
|
||||
await userEvent.click( outOfStockSuggestion );
|
||||
}
|
||||
if ( outOfStockSuggestion ) {
|
||||
userEvent.click( outOfStockSuggestion );
|
||||
}
|
||||
|
||||
await waitFor( () => {
|
||||
expect( getInStockChips() ).toBeInTheDocument();
|
||||
expect( getOutOfStockChips() ).toBeInTheDocument();
|
||||
expect( getOnBackorderChips() ).toBeInTheDocument();
|
||||
|
@ -415,30 +410,32 @@ describe( 'Filter by Stock block', () => {
|
|||
} );
|
||||
|
||||
test( 'removes the option when the X button is clicked', async () => {
|
||||
await waitFor( async () => {
|
||||
const user = userEvent.setup();
|
||||
const ratingParam = 'instock,outofstock,onbackorder';
|
||||
const {
|
||||
getInStockChips,
|
||||
getOutOfStockChips,
|
||||
getOnBackorderChips,
|
||||
getRemoveButtonFromChips,
|
||||
} = setupMultipleChoiceDropdown( ratingParam );
|
||||
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 ) {
|
||||
await act( async () => {
|
||||
await user.click( removeOutOfStockButton );
|
||||
} );
|
||||
}
|
||||
if ( removeOutOfStockButton ) {
|
||||
act( async () => {
|
||||
await user.click( removeOutOfStockButton );
|
||||
} );
|
||||
}
|
||||
|
||||
await waitFor( () => {
|
||||
expect( getInStockChips() ).toBeInTheDocument();
|
||||
expect( getOutOfStockChips() ).toBeNull();
|
||||
expect( getOnBackorderChips() ).toBeInTheDocument();
|
||||
|
@ -453,73 +450,67 @@ describe( 'Filter by Stock block', () => {
|
|||
expect( getList() ).toBeInTheDocument();
|
||||
} );
|
||||
|
||||
test( 'renders checked options based on URL params', async () => {
|
||||
await waitFor( async () => {
|
||||
const ratingParam = 'instock';
|
||||
const {
|
||||
getInStockCheckbox,
|
||||
getOutOfStockCheckbox,
|
||||
getOnBackorderCheckbox,
|
||||
} = setupSingleChoiceList( ratingParam );
|
||||
test( 'renders checked options based on URL params', () => {
|
||||
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 () => {
|
||||
await waitFor( async () => {
|
||||
const user = userEvent.setup();
|
||||
const ratingParam = 'outofstock';
|
||||
const {
|
||||
getInStockCheckbox,
|
||||
getOutOfStockCheckbox,
|
||||
getOnBackorderCheckbox,
|
||||
} = setupSingleChoiceList( ratingParam );
|
||||
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 () => {
|
||||
await waitFor( async () => {
|
||||
const ratingParam = 'onbackorder';
|
||||
const {
|
||||
getInStockCheckbox,
|
||||
getOutOfStockCheckbox,
|
||||
getOnBackorderCheckbox,
|
||||
} = setupMultipleChoiceList( ratingParam );
|
||||
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 ).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 ).toBeFalsy();
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
|
@ -531,72 +522,66 @@ describe( 'Filter by Stock block', () => {
|
|||
expect( getList() ).toBeInTheDocument();
|
||||
} );
|
||||
|
||||
test( 'renders chips based on URL params', async () => {
|
||||
await waitFor( async () => {
|
||||
const ratingParam = 'instock,onbackorder';
|
||||
const {
|
||||
getInStockCheckbox,
|
||||
getOutOfStockCheckbox,
|
||||
getOnBackorderCheckbox,
|
||||
} = setupMultipleChoiceList( ratingParam );
|
||||
test( 'renders chips based on URL params', () => {
|
||||
const ratingParam = 'instock,onbackorder';
|
||||
const {
|
||||
getInStockCheckbox,
|
||||
getOutOfStockCheckbox,
|
||||
getOnBackorderCheckbox,
|
||||
} = setupMultipleChoiceList( ratingParam );
|
||||
|
||||
expect( getInStockCheckbox()?.checked ).toBeTruthy();
|
||||
expect( getOutOfStockCheckbox()?.checked ).toBeFalsy();
|
||||
expect( getOnBackorderCheckbox()?.checked ).toBeTruthy();
|
||||
} );
|
||||
expect( getInStockCheckbox()?.checked ).toBeTruthy();
|
||||
expect( getOutOfStockCheckbox()?.checked ).toBeFalsy();
|
||||
expect( getOnBackorderCheckbox()?.checked ).toBeTruthy();
|
||||
} );
|
||||
|
||||
test( 'adds chosen option to another one that is clicked', async () => {
|
||||
await waitFor( async () => {
|
||||
const ratingParam = 'outofstock,onbackorder';
|
||||
const {
|
||||
getInStockCheckbox,
|
||||
getOutOfStockCheckbox,
|
||||
getOnBackorderCheckbox,
|
||||
} = setupMultipleChoiceList( ratingParam );
|
||||
const ratingParam = 'outofstock,onbackorder';
|
||||
const {
|
||||
getInStockCheckbox,
|
||||
getOutOfStockCheckbox,
|
||||
getOnBackorderCheckbox,
|
||||
} = setupMultipleChoiceList( ratingParam );
|
||||
|
||||
expect( getInStockCheckbox()?.checked ).toBeFalsy();
|
||||
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();
|
||||
|
||||
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 );
|
||||
const ratingParam = 'instock,outofstock';
|
||||
const {
|
||||
getInStockCheckbox,
|
||||
getOutOfStockCheckbox,
|
||||
getOnBackorderCheckbox,
|
||||
} = setupMultipleChoiceList( ratingParam );
|
||||
|
||||
expect( getInStockCheckbox()?.checked ).toBeTruthy();
|
||||
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();
|
||||
|
||||
const inStockCheckbox = getInStockCheckbox();
|
||||
|
||||
if ( inStockCheckbox ) {
|
||||
userEvent.click( inStockCheckbox );
|
||||
}
|
||||
|
||||
await waitFor( () => {
|
||||
expect( getInStockCheckbox()?.checked ).toBeFalsy();
|
||||
expect( getOutOfStockCheckbox()?.checked ).toBeTruthy();
|
||||
expect( getOnBackorderCheckbox()?.checked ).toBeFalsy();
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
|
|
|
@ -28,14 +28,20 @@ test.describe( `Filters Overlay Navigation`, () => {
|
|||
} );
|
||||
} );
|
||||
|
||||
test( 'should be included in the Filters Overlay template part', async ( {
|
||||
// Since we need to overhaul the overlay area, we can skip this test for now.
|
||||
// eslint-disable-next-line playwright/no-skipped-test
|
||||
test.skip( 'should be included in the Filters Overlay template part', async ( {
|
||||
editor,
|
||||
} ) => {
|
||||
const block = editor.canvas.getByLabel( `Block: ${ blockData.title }` );
|
||||
await expect( block ).toBeVisible();
|
||||
} );
|
||||
|
||||
test( 'should have settings and styles controls', async ( { editor } ) => {
|
||||
// Since we need to overhaul the overlay area, we can skip this test for now.
|
||||
// eslint-disable-next-line playwright/no-skipped-test
|
||||
test.skip( 'should have settings and styles controls', async ( {
|
||||
editor,
|
||||
} ) => {
|
||||
const block = editor.canvas.getByLabel( `Block: ${ blockData.title }` );
|
||||
await block.click();
|
||||
|
||||
|
|
|
@ -3,6 +3,35 @@
|
|||
*/
|
||||
import { test, expect } from '@woocommerce/e2e-utils';
|
||||
|
||||
const templatePartData = {
|
||||
selectors: {
|
||||
frontend: {},
|
||||
editor: {
|
||||
blocks: {
|
||||
activeFilters: {
|
||||
title: 'Active (Experimental)',
|
||||
blockLabel: 'Block: Active (Experimental)',
|
||||
},
|
||||
productFilters: {
|
||||
title: 'Product Filters (Experimental)',
|
||||
blockLabel: 'Block: Product Filters (Experimental)',
|
||||
},
|
||||
filterOptions: {
|
||||
title: 'Filter Options',
|
||||
blockLabel: 'Block: Filter Options',
|
||||
},
|
||||
productFiltersOverlayNavigation: {
|
||||
title: 'Overlay Navigation (Experimental)',
|
||||
name: 'woocommerce/product-filters-overlay-navigation',
|
||||
blockLabel: 'Block: Overlay Navigation (Experimental)',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
slug: 'product-filters',
|
||||
productPage: '/product/hoodie/',
|
||||
};
|
||||
|
||||
test.describe( 'Filters Overlay Template Part', () => {
|
||||
test.beforeEach( async ( { admin, requestUtils } ) => {
|
||||
await requestUtils.activatePlugin(
|
||||
|
@ -34,10 +63,255 @@ test.describe( 'Filters Overlay Template Part', () => {
|
|||
.locator( '[data-type="core/template-part"]' )
|
||||
.filter( {
|
||||
has: editor.canvas.getByLabel(
|
||||
'Block: Product Filters (Experimental)'
|
||||
templatePartData.selectors.editor.blocks.productFilters
|
||||
.blockLabel
|
||||
),
|
||||
} );
|
||||
|
||||
await expect( productFiltersTemplatePart ).toBeVisible();
|
||||
} );
|
||||
|
||||
test.describe( 'frontend', () => {
|
||||
test.beforeEach( async ( { admin } ) => {
|
||||
await admin.visitSiteEditor( {
|
||||
postId: `woocommerce/woocommerce//archive-product`,
|
||||
postType: 'wp_template',
|
||||
canvas: 'edit',
|
||||
} );
|
||||
} );
|
||||
|
||||
test( 'should open and close the dialog when clicking on the Product Filters Overlay Navigation block', async ( {
|
||||
editor,
|
||||
page,
|
||||
frontendUtils,
|
||||
} ) => {
|
||||
await editor.setContent( '' );
|
||||
await editor.openGlobalBlockInserter();
|
||||
await page
|
||||
.getByText(
|
||||
templatePartData.selectors.editor.blocks.productFilters
|
||||
.title
|
||||
)
|
||||
.click();
|
||||
const block = editor.canvas.getByLabel(
|
||||
templatePartData.selectors.editor.blocks.productFilters
|
||||
.blockLabel
|
||||
);
|
||||
await expect( block ).toBeVisible();
|
||||
|
||||
// This forces the list view to show the inner blocks of the Product Filters template part.
|
||||
await editor.canvas
|
||||
.getByLabel(
|
||||
templatePartData.selectors.editor.blocks.activeFilters
|
||||
.blockLabel
|
||||
)
|
||||
.getByLabel(
|
||||
templatePartData.selectors.editor.blocks.filterOptions
|
||||
.blockLabel
|
||||
)
|
||||
.click();
|
||||
|
||||
await editor.openDocumentSettingsSidebar();
|
||||
await page.getByLabel( 'Document Overview' ).click();
|
||||
await page
|
||||
.getByRole( 'link', {
|
||||
name: templatePartData.selectors.editor.blocks
|
||||
.productFilters.title,
|
||||
} )
|
||||
.nth( 1 )
|
||||
.click();
|
||||
|
||||
const layoutSettings = editor.page.getByText(
|
||||
'OverlayNeverMobileAlways'
|
||||
);
|
||||
await layoutSettings.getByLabel( 'Always' ).click();
|
||||
await editor.page
|
||||
.getByRole( 'link', {
|
||||
name: templatePartData.selectors.editor.blocks
|
||||
.productFiltersOverlayNavigation.title,
|
||||
} )
|
||||
.click();
|
||||
|
||||
await editor.saveSiteEditorEntities( {
|
||||
isOnlyCurrentEntityDirty: false,
|
||||
} );
|
||||
|
||||
await page.goto( '/shop/' );
|
||||
|
||||
const productFiltersOverlayNavigation = (
|
||||
await frontendUtils.getBlockByName(
|
||||
templatePartData.selectors.editor.blocks
|
||||
.productFiltersOverlayNavigation.name
|
||||
)
|
||||
).filter( {
|
||||
has: page.locator( ':visible' ),
|
||||
} );
|
||||
|
||||
await expect( productFiltersOverlayNavigation ).toBeVisible();
|
||||
|
||||
await page
|
||||
.locator( '.wc-block-product-filters-overlay-navigation' )
|
||||
.first()
|
||||
.click();
|
||||
|
||||
const productFiltersDialog = page.locator(
|
||||
'.wc-block-product-filters--dialog-open'
|
||||
);
|
||||
|
||||
await expect( productFiltersDialog ).toBeVisible();
|
||||
|
||||
const productFiltersDialogCloseButton = (
|
||||
await frontendUtils.getBlockByName(
|
||||
templatePartData.selectors.editor.blocks
|
||||
.productFiltersOverlayNavigation.name
|
||||
)
|
||||
).filter( { hasText: 'Close' } );
|
||||
|
||||
await expect( productFiltersDialogCloseButton ).toBeVisible();
|
||||
|
||||
await productFiltersDialogCloseButton.click();
|
||||
|
||||
await expect( productFiltersDialog ).toBeHidden();
|
||||
} );
|
||||
|
||||
test( 'should hide Product Filters Overlay Navigation block when the Overlay mode is set to `Never`', async ( {
|
||||
editor,
|
||||
page,
|
||||
frontendUtils,
|
||||
} ) => {
|
||||
await editor.setContent( '' );
|
||||
await editor.openGlobalBlockInserter();
|
||||
await page
|
||||
.getByText(
|
||||
templatePartData.selectors.editor.blocks.productFilters
|
||||
.title
|
||||
)
|
||||
.click();
|
||||
const block = editor.canvas.getByLabel(
|
||||
templatePartData.selectors.editor.blocks.productFilters
|
||||
.blockLabel
|
||||
);
|
||||
await expect( block ).toBeVisible();
|
||||
|
||||
// This forces the list view to show the inner blocks of the Product Filters template part.
|
||||
await editor.canvas
|
||||
.getByLabel(
|
||||
templatePartData.selectors.editor.blocks.activeFilters
|
||||
.blockLabel
|
||||
)
|
||||
.getByLabel(
|
||||
templatePartData.selectors.editor.blocks.filterOptions
|
||||
.blockLabel
|
||||
)
|
||||
.click();
|
||||
|
||||
await editor.openDocumentSettingsSidebar();
|
||||
await page.getByLabel( 'Document Overview' ).click();
|
||||
await page
|
||||
.getByRole( 'link', {
|
||||
name: templatePartData.selectors.editor.blocks
|
||||
.productFilters.title,
|
||||
} )
|
||||
.nth( 1 )
|
||||
.click();
|
||||
|
||||
const layoutSettings = editor.page.getByText(
|
||||
'OverlayNeverMobileAlways'
|
||||
);
|
||||
await layoutSettings.getByLabel( 'Never' ).click();
|
||||
await editor.page
|
||||
.getByRole( 'link', {
|
||||
name: templatePartData.selectors.editor.blocks
|
||||
.productFiltersOverlayNavigation.title,
|
||||
} )
|
||||
.click();
|
||||
|
||||
await editor.saveSiteEditorEntities( {
|
||||
isOnlyCurrentEntityDirty: true,
|
||||
} );
|
||||
|
||||
await page.goto( '/shop/' );
|
||||
|
||||
const productFiltersOverlayNavigation = (
|
||||
await frontendUtils.getBlockByName(
|
||||
templatePartData.selectors.editor.blocks
|
||||
.productFiltersOverlayNavigation.name
|
||||
)
|
||||
).filter( {
|
||||
has: page.locator( ':visible' ),
|
||||
} );
|
||||
|
||||
await expect( productFiltersOverlayNavigation ).toBeHidden();
|
||||
} );
|
||||
|
||||
test( 'should hide Product Filters Overlay Navigation block when the Overlay mode is set to `Mobile` and user is on desktop', async ( {
|
||||
editor,
|
||||
page,
|
||||
frontendUtils,
|
||||
} ) => {
|
||||
await editor.setContent( '' );
|
||||
await editor.openGlobalBlockInserter();
|
||||
await page
|
||||
.getByText(
|
||||
templatePartData.selectors.editor.blocks.productFilters
|
||||
.title
|
||||
)
|
||||
.click();
|
||||
const block = editor.canvas.getByLabel(
|
||||
templatePartData.selectors.editor.blocks.productFilters
|
||||
.blockLabel
|
||||
);
|
||||
await expect( block ).toBeVisible();
|
||||
|
||||
// This forces the list view to show the inner blocks of the Product Filters template part.
|
||||
await editor.canvas
|
||||
.getByLabel(
|
||||
templatePartData.selectors.editor.blocks.activeFilters
|
||||
.blockLabel
|
||||
)
|
||||
.getByLabel(
|
||||
templatePartData.selectors.editor.blocks.filterOptions
|
||||
.blockLabel
|
||||
)
|
||||
.click();
|
||||
|
||||
await editor.openDocumentSettingsSidebar();
|
||||
await page.getByLabel( 'Document Overview' ).click();
|
||||
await page
|
||||
.getByRole( 'link', {
|
||||
name: templatePartData.selectors.editor.blocks
|
||||
.productFilters.title,
|
||||
} )
|
||||
.nth( 1 )
|
||||
.click();
|
||||
|
||||
const layoutSettings = editor.page.getByText(
|
||||
'OverlayNeverMobileAlways'
|
||||
);
|
||||
await layoutSettings.getByLabel( 'Mobile' ).click();
|
||||
await editor.page
|
||||
.getByRole( 'link', {
|
||||
name: templatePartData.selectors.editor.blocks
|
||||
.productFiltersOverlayNavigation.title,
|
||||
} )
|
||||
.click();
|
||||
|
||||
await editor.saveSiteEditorEntities( {
|
||||
isOnlyCurrentEntityDirty: false,
|
||||
} );
|
||||
|
||||
await page.goto( '/shop/' );
|
||||
|
||||
const productFiltersOverlayNavigation = (
|
||||
await frontendUtils.getBlockByName(
|
||||
templatePartData.selectors.editor.blocks
|
||||
.productFiltersOverlayNavigation.name
|
||||
)
|
||||
).filter( {
|
||||
has: page.locator( ':visible' ),
|
||||
} );
|
||||
|
||||
await expect( productFiltersOverlayNavigation ).toBeHidden();
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
|
|
|
@ -17,10 +17,21 @@ const blockData = {
|
|||
settings: {},
|
||||
layoutWrapper:
|
||||
'.wp-block-woocommerce-product-filters-is-layout-flex',
|
||||
blocks: {
|
||||
filters: {
|
||||
title: 'Product Filters (Experimental)',
|
||||
label: 'Block: Product Filters (Experimental)',
|
||||
},
|
||||
overlay: {
|
||||
title: 'Overlay Navigation (Experimental)',
|
||||
label: 'Block: Overlay Navigation (Experimental)',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
slug: 'archive-product',
|
||||
productPage: '/product/hoodie/',
|
||||
shopPage: '/shop/',
|
||||
};
|
||||
|
||||
const test = base.extend< { pageObject: ProductFiltersPage } >( {
|
||||
|
@ -53,7 +64,7 @@ test.describe( `${ blockData.name }`, () => {
|
|||
await pageObject.addProductFiltersBlock( { cleanContent: true } );
|
||||
|
||||
const block = editor.canvas.getByLabel(
|
||||
'Block: Product Filters (Experimental)'
|
||||
blockData.selectors.editor.blocks.filters.label
|
||||
);
|
||||
await expect( block ).toBeVisible();
|
||||
|
||||
|
@ -141,7 +152,7 @@ test.describe( `${ blockData.name }`, () => {
|
|||
await pageObject.addProductFiltersBlock( { cleanContent: true } );
|
||||
|
||||
const block = editor.canvas.getByLabel(
|
||||
'Block: Product Filters (Experimental)'
|
||||
blockData.selectors.editor.blocks.filters.label
|
||||
);
|
||||
await expect( block ).toBeVisible();
|
||||
|
||||
|
@ -151,7 +162,7 @@ test.describe( `${ blockData.name }`, () => {
|
|||
await expect( listView ).toBeVisible();
|
||||
|
||||
const productFiltersBlockListItem = listView.getByRole( 'link', {
|
||||
name: 'Product Filters (Experimental)',
|
||||
name: blockData.selectors.editor.blocks.filters.title,
|
||||
} );
|
||||
await expect( productFiltersBlockListItem ).toBeVisible();
|
||||
const listViewExpander =
|
||||
|
@ -198,7 +209,7 @@ test.describe( `${ blockData.name }`, () => {
|
|||
await pageObject.addProductFiltersBlock( { cleanContent: true } );
|
||||
|
||||
const block = editor.canvas.getByLabel(
|
||||
'Block: Product Filters (Experimental)'
|
||||
blockData.selectors.editor.blocks.filters.label
|
||||
);
|
||||
await expect( block ).toBeVisible();
|
||||
|
||||
|
@ -245,10 +256,17 @@ test.describe( `${ blockData.name }`, () => {
|
|||
} ) => {
|
||||
await pageObject.addProductFiltersBlock( { cleanContent: true } );
|
||||
|
||||
const block = editor.canvas.getByLabel(
|
||||
'Block: Product Filters (Experimental)'
|
||||
const filtersBlock = editor.canvas.getByLabel(
|
||||
blockData.selectors.editor.blocks.filters.label
|
||||
);
|
||||
await expect( block ).toBeVisible();
|
||||
await expect( filtersBlock ).toBeVisible();
|
||||
|
||||
const overlayBlock = editor.canvas.getByLabel(
|
||||
blockData.selectors.editor.blocks.overlay.label
|
||||
);
|
||||
|
||||
// Overlay mode is set to 'Never' by default so the block should be hidden
|
||||
await expect( overlayBlock ).toBeHidden();
|
||||
|
||||
await editor.openDocumentSettingsSidebar();
|
||||
|
||||
|
@ -259,17 +277,6 @@ test.describe( `${ blockData.name }`, () => {
|
|||
|
||||
// Overlay settings
|
||||
const overlayModeSettings = [ 'Never', 'Mobile', 'Always' ];
|
||||
const overlayButtonSettings = [
|
||||
'Label and icon',
|
||||
'Label only',
|
||||
'Icon only',
|
||||
];
|
||||
const overlayIconsSettings = [
|
||||
'Filter icon 1',
|
||||
'Filter icon 2',
|
||||
'Filter icon 3',
|
||||
'Filter icon 4',
|
||||
];
|
||||
|
||||
await expect( editor.page.getByText( 'Overlay' ) ).toBeVisible();
|
||||
|
||||
|
@ -277,43 +284,27 @@ test.describe( `${ blockData.name }`, () => {
|
|||
await expect( editor.page.getByText( mode ) ).toBeVisible();
|
||||
}
|
||||
|
||||
await editor.page.getByLabel( 'Never' ).click();
|
||||
|
||||
await expect( editor.page.getByText( 'Edit overlay' ) ).toBeHidden();
|
||||
|
||||
await expect( overlayBlock ).toBeHidden();
|
||||
|
||||
await editor.page.getByLabel( 'Mobile' ).click();
|
||||
await expect( editor.page.getByText( 'BUTTON' ) ).toBeVisible();
|
||||
|
||||
for ( const mode of overlayButtonSettings ) {
|
||||
await expect( editor.page.getByText( mode ) ).toBeVisible();
|
||||
}
|
||||
|
||||
for ( const mode of overlayIconsSettings ) {
|
||||
await expect( editor.page.getByLabel( mode ) ).toBeVisible();
|
||||
}
|
||||
|
||||
await expect( editor.page.getByText( 'ICON SIZE' ) ).toBeVisible();
|
||||
await expect( editor.page.getByText( 'Edit overlay' ) ).toBeVisible();
|
||||
|
||||
await expect( overlayBlock ).toBeVisible();
|
||||
|
||||
await editor.page.getByLabel( 'Always' ).click();
|
||||
|
||||
await expect( editor.page.getByText( 'BUTTON' ) ).toBeHidden();
|
||||
|
||||
for ( const mode of overlayButtonSettings ) {
|
||||
await expect( editor.page.getByText( mode ) ).toBeHidden();
|
||||
}
|
||||
|
||||
for ( const mode of overlayIconsSettings ) {
|
||||
await expect( editor.page.getByLabel( mode ) ).toBeHidden();
|
||||
}
|
||||
|
||||
await expect( editor.page.getByText( 'Edit overlay' ) ).toBeVisible();
|
||||
|
||||
await editor.page.getByLabel( 'Mobile' ).click();
|
||||
await expect( overlayBlock ).toBeVisible();
|
||||
|
||||
await editor.page.locator( 'input[value="label"]' ).click();
|
||||
await editor.page.getByLabel( 'Never' ).click();
|
||||
|
||||
for ( const mode of overlayIconsSettings ) {
|
||||
await expect( editor.page.getByLabel( mode ) ).toBeHidden();
|
||||
}
|
||||
|
||||
await expect( editor.page.getByText( 'Edit overlay' ) ).toBeVisible();
|
||||
await expect( overlayBlock ).toBeHidden();
|
||||
} );
|
||||
|
||||
test( 'Layout > default to vertical stretch', async ( {
|
||||
|
|
|
@ -43,4 +43,40 @@ export class ProductFiltersPage {
|
|||
}
|
||||
return this.editor.getBlockByName( blockName );
|
||||
}
|
||||
|
||||
async getProductFiltersOverlayNavigationBlock( {
|
||||
page,
|
||||
}: {
|
||||
page: 'frontend' | 'editor';
|
||||
} ) {
|
||||
const blockName = 'woocommerce/product-filters-overlay-navigation';
|
||||
if ( page === 'frontend' ) {
|
||||
return (
|
||||
await this.frontendUtils.getBlockByName( blockName )
|
||||
).filter( {
|
||||
has: this.page.locator( ':visible' ),
|
||||
} );
|
||||
}
|
||||
return this.editor.canvas.getByLabel(
|
||||
'Block: Overlay Navigation (Experimental)'
|
||||
);
|
||||
}
|
||||
|
||||
async selectOverlayMode( {
|
||||
mode,
|
||||
}: {
|
||||
mode: 'mobile' | 'always' | 'never';
|
||||
} ) {
|
||||
switch ( mode ) {
|
||||
case 'always':
|
||||
await this.page.getByLabel( 'Always' ).click();
|
||||
break;
|
||||
case 'mobile':
|
||||
await this.page.getByLabel( 'Mobile' ).click();
|
||||
break;
|
||||
case 'never':
|
||||
await this.page.getByLabel( 'Never' ).click();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
Significance: patch
|
||||
Type: tweak
|
||||
Comment: Add the Fullscreen view to the Product Filters
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
Significance: patch
|
||||
Type: tweak
|
||||
Comment: Product Filters: update overlay navigation UX
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
Significance: patch
|
||||
Type: dev
|
||||
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
Significance: patch
|
||||
Type: dev
|
||||
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
Significance: patch
|
||||
Type: fix
|
||||
Comment: Fix size for coming soon banner button
|
||||
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: fix
|
||||
|
||||
Deprecate single product block save #51153
|
|
@ -0,0 +1,4 @@
|
|||
Significance: patch
|
||||
Type: fix
|
||||
|
||||
Fix duplicate spec evaluation in evaluate_specs()
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: performance
|
||||
|
||||
Only load local pickup methods on cart/checkout pages
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: update
|
||||
|
||||
Clean up Purchase task
|
|
@ -1699,7 +1699,10 @@ p.demo_store,
|
|||
* Buttons
|
||||
*/
|
||||
.woocommerce:where(body:not(.woocommerce-block-theme-has-button-styles)),
|
||||
:where(body:not(.woocommerce-block-theme-has-button-styles)) .woocommerce {
|
||||
:where(body:not(.woocommerce-block-theme-has-button-styles)):where(
|
||||
:not(.edit-post-visual-editor *)
|
||||
)
|
||||
.woocommerce {
|
||||
a.button,
|
||||
button.button,
|
||||
input.button,
|
||||
|
|
|
@ -1,203 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks;
|
||||
|
||||
use Automattic\WooCommerce\Internal\Admin\Onboarding\OnboardingProducts;
|
||||
use Automattic\WooCommerce\Internal\Admin\Onboarding\OnboardingThemes;
|
||||
use Automattic\WooCommerce\Internal\Admin\Onboarding\OnboardingProfile;
|
||||
use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Task;
|
||||
|
||||
/**
|
||||
* Purchase Task
|
||||
*/
|
||||
class Purchase extends Task {
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param TaskList $task_list Parent task list.
|
||||
*/
|
||||
public function __construct( $task_list ) {
|
||||
parent::__construct( $task_list );
|
||||
add_action( 'update_option_woocommerce_onboarding_profile', array( $this, 'clear_dismissal' ), 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear dismissal on onboarding product type changes.
|
||||
*
|
||||
* @param array $old_value Old value.
|
||||
* @param array $new_value New value.
|
||||
*/
|
||||
public function clear_dismissal( $old_value, $new_value ) {
|
||||
$product_types = isset( $new_value['product_types'] ) ? (array) $new_value['product_types'] : array();
|
||||
$previous_product_types = isset( $old_value['product_types'] ) ? (array) $old_value['product_types'] : array();
|
||||
|
||||
if ( empty( array_diff( $product_types, $previous_product_types ) ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->undo_dismiss();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the task arguments.
|
||||
* ID.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_id() {
|
||||
return 'purchase';
|
||||
}
|
||||
|
||||
/**
|
||||
* Title.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_title() {
|
||||
$products = $this->get_paid_products_and_themes();
|
||||
$first_product = count( $products['purchaseable'] ) >= 1 ? $products['purchaseable'][0] : false;
|
||||
|
||||
if ( ! $first_product ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$product_label = isset( $first_product['label'] ) ? $first_product['label'] : $first_product['title'];
|
||||
$additional_count = count( $products['purchaseable'] ) - 1;
|
||||
|
||||
if ( $this->get_parent_option( 'use_completed_title' ) && $this->is_complete() ) {
|
||||
return count( $products['purchaseable'] ) === 1
|
||||
? sprintf(
|
||||
/* translators: %1$s: a purchased product name */
|
||||
__(
|
||||
'You added %1$s',
|
||||
'woocommerce'
|
||||
),
|
||||
$product_label
|
||||
)
|
||||
: sprintf(
|
||||
/* translators: %1$s: a purchased product name, %2$d the number of other products purchased */
|
||||
_n(
|
||||
'You added %1$s and %2$d other product',
|
||||
'You added %1$s and %2$d other products',
|
||||
$additional_count,
|
||||
'woocommerce'
|
||||
),
|
||||
$product_label,
|
||||
$additional_count
|
||||
);
|
||||
}
|
||||
|
||||
return count( $products['purchaseable'] ) === 1
|
||||
? sprintf(
|
||||
/* translators: %1$s: a purchaseable product name */
|
||||
__(
|
||||
'Add %s to my store',
|
||||
'woocommerce'
|
||||
),
|
||||
$product_label
|
||||
)
|
||||
: sprintf(
|
||||
/* translators: %1$s: a purchaseable product name, %2$d the number of other products to purchase */
|
||||
_n(
|
||||
'Add %1$s and %2$d more product to my store',
|
||||
'Add %1$s and %2$d more products to my store',
|
||||
$additional_count,
|
||||
'woocommerce'
|
||||
),
|
||||
$product_label,
|
||||
$additional_count
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Content.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_content() {
|
||||
$products = $this->get_paid_products_and_themes();
|
||||
|
||||
if ( count( $products['remaining'] ) === 1 ) {
|
||||
return isset( $products['purchaseable'][0]['description'] ) ? $products['purchaseable'][0]['description'] : $products['purchaseable'][0]['excerpt'];
|
||||
}
|
||||
return sprintf(
|
||||
/* translators: %1$s: list of product names comma separated, %2%s the last product name */
|
||||
__(
|
||||
'Good choice! You chose to add %1$s and %2$s to your store.',
|
||||
'woocommerce'
|
||||
),
|
||||
implode( ', ', array_slice( $products['remaining'], 0, -1 ) ) . ( count( $products['remaining'] ) > 2 ? ',' : '' ),
|
||||
end( $products['remaining'] )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Action label.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_action_label() {
|
||||
return __( 'Purchase & install now', 'woocommerce' );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Time.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_time() {
|
||||
return __( '2 minutes', 'woocommerce' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Task completion.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_complete() {
|
||||
$products = $this->get_paid_products_and_themes();
|
||||
return count( $products['remaining'] ) === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dismissable.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_dismissable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Task visibility.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function can_view() {
|
||||
$products = $this->get_paid_products_and_themes();
|
||||
return count( $products['purchaseable'] ) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get purchaseable and remaining products.
|
||||
*
|
||||
* @return array purchaseable and remaining products and themes.
|
||||
*/
|
||||
public static function get_paid_products_and_themes() {
|
||||
$relevant_products = OnboardingProducts::get_relevant_products();
|
||||
|
||||
$profiler_data = get_option( OnboardingProfile::DATA_OPTION, array() );
|
||||
$theme = isset( $profiler_data['theme'] ) ? $profiler_data['theme'] : null;
|
||||
$paid_theme = $theme ? OnboardingThemes::get_paid_theme_by_slug( $theme ) : null;
|
||||
if ( $paid_theme ) {
|
||||
|
||||
$relevant_products['purchaseable'][] = $paid_theme;
|
||||
|
||||
if ( isset( $paid_theme['is_installed'] ) && false === $paid_theme['is_installed'] ) {
|
||||
$relevant_products['remaining'][] = $paid_theme['title'];
|
||||
}
|
||||
}
|
||||
return $relevant_products;
|
||||
}
|
||||
}
|
|
@ -13,6 +13,13 @@ use Automattic\WooCommerce\Admin\RemoteSpecs\RuleProcessors\RuleEvaluator;
|
|||
* Evaluates the spec and returns the evaluated suggestion.
|
||||
*/
|
||||
class EvaluateSuggestion {
|
||||
/**
|
||||
* Stores memoized results of evaluate_specs.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $memo = array();
|
||||
|
||||
/**
|
||||
* Evaluates the spec and returns the suggestion.
|
||||
*
|
||||
|
@ -58,6 +65,12 @@ class EvaluateSuggestion {
|
|||
* @return array The visible suggestions and errors.
|
||||
*/
|
||||
public static function evaluate_specs( $specs, $logger_args = array() ) {
|
||||
$specs_key = self::get_memo_key( $specs );
|
||||
|
||||
if ( isset( self::$memo[ $specs_key ] ) ) {
|
||||
return self::$memo[ $specs_key ];
|
||||
}
|
||||
|
||||
$suggestions = array();
|
||||
$errors = array();
|
||||
|
||||
|
@ -72,9 +85,43 @@ class EvaluateSuggestion {
|
|||
}
|
||||
}
|
||||
|
||||
return array(
|
||||
$result = array(
|
||||
'suggestions' => $suggestions,
|
||||
'errors' => $errors,
|
||||
);
|
||||
|
||||
// Memoize results, with a fail safe to prevent unbounded memory growth.
|
||||
// This limit is unlikely to be reached under normal circumstances.
|
||||
if ( count( self::$memo ) > 50 ) {
|
||||
self::reset_memo();
|
||||
}
|
||||
self::$memo[ $specs_key ] = $result;
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the memoized results. Useful for testing.
|
||||
*/
|
||||
public static function reset_memo() {
|
||||
self::$memo = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a memoization key for the given specs.
|
||||
*
|
||||
* @param array $specs The specs to generate a key for.
|
||||
*
|
||||
* @return string The memoization key.
|
||||
*/
|
||||
private static function get_memo_key( $specs ) {
|
||||
$data = wp_json_encode( $specs );
|
||||
|
||||
if ( function_exists( 'hash' ) && in_array( 'xxh3', hash_algos(), true ) ) {
|
||||
// Use xxHash (xxh3) if available.
|
||||
return hash( 'xxh3', $data );
|
||||
}
|
||||
// Fall back to CRC32.
|
||||
return (string) crc32( $data );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -245,8 +245,12 @@ class Cart extends AbstractBlock {
|
|||
$this->asset_data_registry->add( 'hasDarkEditorStyleSupport', current_theme_supports( 'dark-editor-style' ) );
|
||||
$this->asset_data_registry->register_page_id( isset( $attributes['checkoutPageId'] ) ? $attributes['checkoutPageId'] : 0 );
|
||||
$this->asset_data_registry->add( 'isBlockTheme', wc_current_theme_is_fse_theme() );
|
||||
|
||||
$pickup_location_settings = LocalPickupUtils::get_local_pickup_settings();
|
||||
$local_pickup_method_ids = LocalPickupUtils::get_local_pickup_method_ids();
|
||||
|
||||
$this->asset_data_registry->add( 'localPickupEnabled', $pickup_location_settings['enabled'] );
|
||||
$this->asset_data_registry->add( 'collectableMethodIds', $local_pickup_method_ids );
|
||||
|
||||
// Hydrate the following data depending on admin or frontend context.
|
||||
if ( ! is_admin() && ! WC()->is_rest_api_request() ) {
|
||||
|
|
|
@ -370,8 +370,11 @@ class Checkout extends AbstractBlock {
|
|||
$this->asset_data_registry->add( 'isBlockTheme', wc_current_theme_is_fse_theme() );
|
||||
|
||||
$pickup_location_settings = LocalPickupUtils::get_local_pickup_settings();
|
||||
$local_pickup_method_ids = LocalPickupUtils::get_local_pickup_method_ids();
|
||||
|
||||
$this->asset_data_registry->add( 'localPickupEnabled', $pickup_location_settings['enabled'] );
|
||||
$this->asset_data_registry->add( 'localPickupText', $pickup_location_settings['title'] );
|
||||
$this->asset_data_registry->add( 'collectableMethodIds', $local_pickup_method_ids );
|
||||
|
||||
$is_block_editor = $this->is_block_editor();
|
||||
|
||||
|
@ -385,8 +388,8 @@ class Checkout extends AbstractBlock {
|
|||
$shipping_methods = WC()->shipping()->get_shipping_methods();
|
||||
$formatted_shipping_methods = array_reduce(
|
||||
$shipping_methods,
|
||||
function ( $acc, $method ) {
|
||||
if ( in_array( $method->id, LocalPickupUtils::get_local_pickup_method_ids(), true ) ) {
|
||||
function ( $acc, $method ) use ( $local_pickup_method_ids ) {
|
||||
if ( in_array( $method->id, $local_pickup_method_ids, true ) ) {
|
||||
return $acc;
|
||||
}
|
||||
if ( $method->supports( 'settings' ) ) {
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Utils\BlockTemplateUtils;
|
||||
|
||||
/**
|
||||
* ProductFilters class.
|
||||
*/
|
||||
|
@ -18,16 +20,91 @@ class ProductFilters extends AbstractBlock {
|
|||
* @return string[]
|
||||
*/
|
||||
protected function get_block_type_uses_context() {
|
||||
return [ 'postId' ];
|
||||
return array( 'postId' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the frontend style handle for this block type.
|
||||
* Return the dialog content.
|
||||
*
|
||||
* @return null
|
||||
* @return string
|
||||
*/
|
||||
protected function get_block_type_style() {
|
||||
return null;
|
||||
protected function render_dialog() {
|
||||
$template_part = BlockTemplateUtils::get_template_part( 'product-filters-overlay' );
|
||||
|
||||
$html = $this->render_template_part( $template_part );
|
||||
|
||||
$html = strtr(
|
||||
'<dialog hidden role="dialog" aria-modal="true">
|
||||
{{html}}
|
||||
</dialog>',
|
||||
array(
|
||||
'{{html}}' => $html,
|
||||
)
|
||||
);
|
||||
|
||||
$p = new \WP_HTML_Tag_Processor( $html );
|
||||
if ( $p->next_tag() ) {
|
||||
$p->set_attribute( 'data-wc-interactive', wp_json_encode( array( 'namespace' => 'woocommerce/product-filters' ), JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP ) );
|
||||
$p->set_attribute( 'data-wc-bind--hidden', '!state.isDialogOpen' );
|
||||
$p->set_attribute( 'data-wc-class--wc-block-product-filters--dialog-open', 'state.isDialogOpen' );
|
||||
$p->set_attribute( 'data-wc-class--wc-block-product-filters--with-admin-bar', 'context.hasPageWithWordPressAdminBar' );
|
||||
$html = $p->get_updated_html();
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is used to render the template part. For each template part, we parse the blocks and render them.
|
||||
*
|
||||
* @param string $template_part The template part to render.
|
||||
* @return string The rendered template part.
|
||||
*/
|
||||
protected function render_template_part( $template_part ) {
|
||||
$parsed_blocks = parse_blocks( $template_part );
|
||||
$wrapper_template_part_block = $parsed_blocks[0];
|
||||
$html = $wrapper_template_part_block['innerHTML'];
|
||||
$target_div = '</div>';
|
||||
|
||||
$template_part_content_html = array_reduce(
|
||||
$wrapper_template_part_block['innerBlocks'],
|
||||
function ( $carry, $item ) {
|
||||
if ( 'core/template-part' === $item['blockName'] ) {
|
||||
$inner_template_part = BlockTemplateUtils::get_template_part( $item['attrs']['slug'] );
|
||||
$inner_template_part_content_html = $this->render_template_part( $inner_template_part );
|
||||
|
||||
return $carry . $inner_template_part_content_html;
|
||||
}
|
||||
return $carry . render_block( $item );
|
||||
},
|
||||
''
|
||||
);
|
||||
|
||||
$html = str_replace( $target_div, $template_part_content_html . $target_div, $html );
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inject dialog into the product filters HTML.
|
||||
*
|
||||
* @param string $product_filters_html The Product Filters HTML.
|
||||
* @param string $dialog_html The dialog HTML.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function inject_dialog( $product_filters_html, $dialog_html ) {
|
||||
// Find the position of the last </div>.
|
||||
$pos = strrpos( $product_filters_html, '</div>' );
|
||||
|
||||
if ( $pos ) {
|
||||
// Inject the dialog_html at the correct position.
|
||||
$html = substr_replace( $product_filters_html, $dialog_html, $pos, 0 );
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
return $product_filters_html;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -39,6 +116,28 @@ class ProductFilters extends AbstractBlock {
|
|||
* @return string Rendered block type output.
|
||||
*/
|
||||
protected function render( $attributes, $content, $block ) {
|
||||
return $content;
|
||||
$html = $content;
|
||||
$p = new \WP_HTML_Tag_Processor( $html );
|
||||
|
||||
if ( $p->next_tag() ) {
|
||||
$p->set_attribute( 'data-wc-interactive', wp_json_encode( array( 'namespace' => 'woocommerce/product-filters' ), JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP ) );
|
||||
$p->set_attribute(
|
||||
'data-wc-context',
|
||||
wp_json_encode(
|
||||
array(
|
||||
'isDialogOpen' => false,
|
||||
'hasPageWithWordPressAdminBar' => false,
|
||||
),
|
||||
JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP
|
||||
)
|
||||
);
|
||||
$html = $p->get_updated_html();
|
||||
}
|
||||
|
||||
$dialog_html = $this->render_dialog();
|
||||
|
||||
$html = $this->inject_dialog( $html, $dialog_html );
|
||||
|
||||
return $html;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,17 +21,6 @@ class ProductFiltersOverlayNavigation extends AbstractBlock {
|
|||
return [ 'woocommerce/product-filters/overlay' ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the frontend script handle for this block type.
|
||||
*
|
||||
* @see $this->register_block_type()
|
||||
* @param string $key Data to get, or default to everything.
|
||||
* @return array|string|null
|
||||
*/
|
||||
protected function get_block_type_script( $key = null ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Include and render the block.
|
||||
*
|
||||
|
@ -46,13 +35,13 @@ class ProductFiltersOverlayNavigation extends AbstractBlock {
|
|||
'class' => 'wc-block-product-filters-overlay-navigation',
|
||||
)
|
||||
);
|
||||
$overlay_mode = $block->context['woocommerce/product-filters/overlay'];
|
||||
$overlay_mode = isset( $block->context['woocommerce/product-filters/overlay'] ) ? $block->context['woocommerce/product-filters/overlay'] : 'never';
|
||||
|
||||
if ( 'never' === $overlay_mode || ( ! wp_is_mobile() && 'mobile' === $overlay_mode ) ) {
|
||||
if ( 'open-overlay' === $attributes['triggerType'] && ( 'never' === $overlay_mode || ( ! wp_is_mobile() && 'mobile' === $overlay_mode ) ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$html_content = strtr(
|
||||
$html = strtr(
|
||||
'<div {{wrapper_attributes}}>
|
||||
{{primary_content}}
|
||||
{{secondary_content}}
|
||||
|
@ -63,7 +52,20 @@ class ProductFiltersOverlayNavigation extends AbstractBlock {
|
|||
'{{secondary_content}}' => 'open-overlay' === $attributes['triggerType'] ? $this->render_label( $attributes ) : $this->render_icon( $attributes ),
|
||||
)
|
||||
);
|
||||
return $html_content;
|
||||
|
||||
$p = new \WP_HTML_Tag_Processor( $html );
|
||||
|
||||
if ( $p->next_tag() ) {
|
||||
$p->set_attribute( 'data-wc-interactive', wp_json_encode( array( 'namespace' => 'woocommerce/product-filters' ), JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP ) );
|
||||
$p->set_attribute(
|
||||
'data-wc-on--click',
|
||||
'open-overlay' === $attributes['triggerType'] ? 'actions.openDialog' : 'actions.closeDialog'
|
||||
);
|
||||
$p->set_attribute( 'data-wc-class--hidden', 'open-overlay' === $attributes['triggerType'] ? 'state.isDialogOpen' : '!state.isDialogOpen' );
|
||||
$html = $p->get_updated_html();
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -60,8 +60,6 @@ class ShippingController {
|
|||
}
|
||||
);
|
||||
}
|
||||
|
||||
$this->asset_data_registry->add( 'collectableMethodIds', array( 'Automattic\WooCommerce\StoreApi\Utilities\LocalPickupUtils', 'get_local_pickup_method_ids' ) );
|
||||
$this->asset_data_registry->add( 'shippingCostRequiresAddress', get_option( 'woocommerce_shipping_cost_requires_address', false ) === 'yes' );
|
||||
add_action( 'rest_api_init', array( $this, 'register_settings' ) );
|
||||
add_action( 'admin_enqueue_scripts', array( $this, 'admin_scripts' ) );
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<!-- wp:woocommerce/product-filters-overlay {"lock":{"move":true,"remove":true}} -->
|
||||
<div class="wp-block-woocommerce-product-filters-overlay wc-block-product-filters-overlay" style="padding-top:1rem;padding-right:1rem;padding-bottom:1rem;padding-left:1rem">
|
||||
<!-- wp:woocommerce/product-filters-overlay-navigation {"lock":{"move":true,"remove":true}} -->
|
||||
<!-- wp:woocommerce/product-filters-overlay-navigation {"triggerType":"close-overlay","lock":{"move":true,"remove":true}} -->
|
||||
<div class="wp-block-woocommerce-product-filters-overlay-navigation alignright wc-block-product-filters-overlay-navigation"></div>
|
||||
<!-- /wp:woocommerce/product-filters-overlay-navigation -->
|
||||
|
||||
|
|
|
@ -1,45 +1,12 @@
|
|||
const { test, expect } = require( '@playwright/test' );
|
||||
const wcApi = require( '@woocommerce/woocommerce-rest-api' ).default;
|
||||
const { test: baseTest, expect } = require( '../../fixtures/fixtures' );
|
||||
|
||||
test.describe( 'Payment setup task', () => {
|
||||
test.use( { storageState: process.env.ADMINSTATE } );
|
||||
|
||||
test.beforeEach( async ( { baseURL } ) => {
|
||||
await new wcApi( {
|
||||
url: baseURL,
|
||||
consumerKey: process.env.CONSUMER_KEY,
|
||||
consumerSecret: process.env.CONSUMER_SECRET,
|
||||
version: 'wc-admin',
|
||||
} ).post( 'onboarding/profile', {
|
||||
const test = baseTest.extend( {
|
||||
storageState: process.env.ADMINSTATE,
|
||||
page: async ( { api, page, wpApi, wcAdminApi }, use ) => {
|
||||
await wcAdminApi.post( 'onboarding/profile', {
|
||||
skipped: true,
|
||||
} );
|
||||
} );
|
||||
|
||||
test.afterAll( async ( { baseURL } ) => {
|
||||
const api = new wcApi( {
|
||||
url: baseURL,
|
||||
consumerKey: process.env.CONSUMER_KEY,
|
||||
consumerSecret: process.env.CONSUMER_SECRET,
|
||||
version: 'wc/v3',
|
||||
} );
|
||||
await api.put( 'payment_gateways/bacs', {
|
||||
enabled: false,
|
||||
} );
|
||||
await api.put( 'payment_gateways/cod', {
|
||||
enabled: false,
|
||||
} );
|
||||
} );
|
||||
|
||||
test( 'Saving valid bank account transfer details enables the payment method', async ( {
|
||||
baseURL,
|
||||
page,
|
||||
} ) => {
|
||||
const api = new wcApi( {
|
||||
url: baseURL,
|
||||
consumerKey: process.env.CONSUMER_KEY,
|
||||
consumerSecret: process.env.CONSUMER_SECRET,
|
||||
version: 'wc/v3',
|
||||
} );
|
||||
// Ensure store's base country location is a WooPayments non-supported country (AF).
|
||||
// Otherwise, the WooPayments task page logic or WooPayments redirects will kick in.
|
||||
await api.post( 'settings/general/batch', {
|
||||
|
@ -50,14 +17,44 @@ test.describe( 'Payment setup task', () => {
|
|||
},
|
||||
],
|
||||
} );
|
||||
|
||||
const bacsInitialState = await api.get( 'payment_gateways/bacs' );
|
||||
const codInitialState = await api.get( 'payment_gateways/cod' );
|
||||
|
||||
// Disable the help popover.
|
||||
await wpApi.post( '/wp-json/wp/v2/users/1?_locale=user', {
|
||||
data: {
|
||||
woocommerce_meta: {
|
||||
help_panel_highlight_shown: '"yes"',
|
||||
},
|
||||
},
|
||||
} );
|
||||
|
||||
await use( page );
|
||||
|
||||
// Reset the payment gateways to their initial state.
|
||||
await api.put( 'payment_gateways/bacs', {
|
||||
enabled: bacsInitialState.data.enabled,
|
||||
} );
|
||||
await api.put( 'payment_gateways/cod', {
|
||||
enabled: codInitialState.data.enabled,
|
||||
} );
|
||||
},
|
||||
} );
|
||||
|
||||
test.describe( 'Payment setup task', () => {
|
||||
test( 'Saving valid bank account transfer details enables the payment method', async ( {
|
||||
page,
|
||||
api,
|
||||
} ) => {
|
||||
await api.put( 'payment_gateways/bacs', {
|
||||
enabled: false,
|
||||
} );
|
||||
|
||||
// Load the bank transfer page.
|
||||
await page.goto(
|
||||
'wp-admin/admin.php?page=wc-admin&task=payments&id=bacs'
|
||||
);
|
||||
// purposely no await -- close the help dialog if/when it appears
|
||||
page.locator( '.components-button.is-small.has-icon' )
|
||||
.click()
|
||||
.catch( () => {} );
|
||||
|
||||
// Fill in bank transfer form.
|
||||
await page
|
||||
|
@ -93,25 +90,8 @@ test.describe( 'Payment setup task', () => {
|
|||
} );
|
||||
|
||||
test( 'Can visit the payment setup task from the homescreen if the setup wizard has been skipped', async ( {
|
||||
baseURL,
|
||||
page,
|
||||
} ) => {
|
||||
const api = new wcApi( {
|
||||
url: baseURL,
|
||||
consumerKey: process.env.CONSUMER_KEY,
|
||||
consumerSecret: process.env.CONSUMER_SECRET,
|
||||
version: 'wc/v3',
|
||||
} );
|
||||
// Ensure store's base country location is a WooPayments non-supported country (AF).
|
||||
// Otherwise, the WooPayments task page logic or WooPayments redirects will kick in.
|
||||
await api.post( 'settings/general/batch', {
|
||||
update: [
|
||||
{
|
||||
id: 'woocommerce_default_country',
|
||||
value: 'AF',
|
||||
},
|
||||
],
|
||||
} );
|
||||
await page.goto( 'wp-admin/admin.php?page=wc-admin' );
|
||||
await page.locator( 'text=Get paid' ).click();
|
||||
await expect(
|
||||
|
@ -121,43 +101,31 @@ test.describe( 'Payment setup task', () => {
|
|||
|
||||
test( 'Enabling cash on delivery enables the payment method', async ( {
|
||||
page,
|
||||
baseURL,
|
||||
api,
|
||||
} ) => {
|
||||
const api = new wcApi( {
|
||||
url: baseURL,
|
||||
consumerKey: process.env.CONSUMER_KEY,
|
||||
consumerSecret: process.env.CONSUMER_SECRET,
|
||||
version: 'wc/v3',
|
||||
await api.put( 'payment_gateways/cod', {
|
||||
enabled: false,
|
||||
} );
|
||||
// Ensure store's base country location is a WooPayments non-supported country (AF).
|
||||
// Otherwise, the WooPayments task page logic or WooPayments redirects will kick in.
|
||||
await api.post( 'settings/general/batch', {
|
||||
update: [
|
||||
{
|
||||
id: 'woocommerce_default_country',
|
||||
value: 'AF',
|
||||
},
|
||||
],
|
||||
} );
|
||||
await page.goto( 'wp-admin/admin.php?page=wc-admin&task=payments' );
|
||||
|
||||
// purposely no await -- close the help dialog if/when it appears
|
||||
page.locator( '.components-button.is-small.has-icon' )
|
||||
.click()
|
||||
.catch( () => {} );
|
||||
const paymentGatewaysResponse = page.waitForResponse(
|
||||
( response ) =>
|
||||
response.url().includes( 'wp-json/wc/v3/payment_gateways' ) &&
|
||||
response.ok()
|
||||
);
|
||||
await page.goto( 'wp-admin/admin.php?page=wc-admin&task=payments' );
|
||||
await paymentGatewaysResponse;
|
||||
|
||||
// Enable COD payment option.
|
||||
await page
|
||||
.locator(
|
||||
'div.woocommerce-task-payment-cod > div.woocommerce-task-payment__footer > .woocommerce-task-payment__action'
|
||||
)
|
||||
.locator( 'div.woocommerce-task-payment-cod' )
|
||||
.getByRole( 'button', { name: 'Enable' } )
|
||||
.click();
|
||||
// Check that COD was set up.
|
||||
await expect(
|
||||
page.locator(
|
||||
'div.woocommerce-task-payment-cod > div.woocommerce-task-payment__footer > .woocommerce-task-payment__action'
|
||||
)
|
||||
).toContainText( 'Manage' );
|
||||
page
|
||||
.locator( 'div.woocommerce-task-payment-cod' )
|
||||
.getByRole( 'button', { name: 'Manage' } )
|
||||
).toBeVisible();
|
||||
|
||||
await page.goto( 'wp-admin/admin.php?page=wc-settings&tab=checkout' );
|
||||
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
const { test, expect } = require( '@playwright/test' );
|
||||
const wcApi = require( '@woocommerce/woocommerce-rest-api' ).default;
|
||||
const { test: baseTest, expect } = require( '../../fixtures/fixtures' );
|
||||
const { random } = require( '../../utils/helpers' );
|
||||
|
||||
const simpleProductName = 'Add new order simple product';
|
||||
const variableProductName = 'Add new order variable product';
|
||||
const externalProductName = 'Add new order external product';
|
||||
const groupedProductName = 'Add new order grouped product';
|
||||
const taxClasses = [
|
||||
{
|
||||
name: 'Tax Class Simple',
|
||||
|
@ -37,28 +33,230 @@ const taxRates = [
|
|||
},
|
||||
];
|
||||
const taxTotals = [ '10.00', '20.00', '240.00' ];
|
||||
let simpleProductId,
|
||||
variableProductId,
|
||||
externalProductId,
|
||||
subProductAId,
|
||||
subProductBId,
|
||||
groupedProductId,
|
||||
customerId,
|
||||
orderId;
|
||||
|
||||
async function getOrderIdFromPage( page ) {
|
||||
// get order ID from the page
|
||||
const orderText = await page
|
||||
.locator( 'h2.woocommerce-order-data__heading' )
|
||||
.textContent();
|
||||
const parts = orderText.match( /([0-9])\w+/ );
|
||||
return parts[ 0 ];
|
||||
}
|
||||
|
||||
async function addProductToOrder( page, product, quantity ) {
|
||||
await page.getByRole( 'button', { name: 'Add item(s)' } ).click();
|
||||
await page.getByRole( 'button', { name: 'Add product(s)' } ).click();
|
||||
await page.getByText( 'Search for a product…' ).click();
|
||||
await page.locator( 'span > .select2-search__field' ).fill( product.name );
|
||||
await page.getByRole( 'option', { name: product.name } ).first().click();
|
||||
await page
|
||||
.locator( 'tr' )
|
||||
.filter( { hasText: product.name } )
|
||||
.getByPlaceholder( '1' )
|
||||
.fill( quantity.toString() );
|
||||
await page.locator( '#btn-ok' ).click();
|
||||
}
|
||||
|
||||
const test = baseTest.extend( {
|
||||
storageState: process.env.ADMINSTATE,
|
||||
order: async ( { api }, use ) => {
|
||||
const order = {};
|
||||
|
||||
await use( order );
|
||||
|
||||
if ( order.id ) {
|
||||
await api.delete( `orders/${ order.id }`, { force: true } );
|
||||
}
|
||||
},
|
||||
|
||||
customer: async ( { api }, use ) => {
|
||||
let customer = {};
|
||||
const username = `sideshowbob_${ random() }`;
|
||||
|
||||
await api
|
||||
.post( 'customers', {
|
||||
email: `${ username }@example.com`,
|
||||
first_name: 'Sideshow',
|
||||
last_name: 'Bob',
|
||||
username,
|
||||
billing: {
|
||||
first_name: 'Sideshow',
|
||||
last_name: 'Bob',
|
||||
company: 'Die Bart Die',
|
||||
address_1: '123 Fake St',
|
||||
address_2: '',
|
||||
city: 'Springfield',
|
||||
state: 'FL',
|
||||
postcode: '12345',
|
||||
country: 'US',
|
||||
email: `${ username }@example.com`,
|
||||
phone: '555-555-5556',
|
||||
},
|
||||
shipping: {
|
||||
first_name: 'Sideshow',
|
||||
last_name: 'Bob',
|
||||
company: 'Die Bart Die',
|
||||
address_1: '321 Fake St',
|
||||
address_2: '',
|
||||
city: 'Springfield',
|
||||
state: 'FL',
|
||||
postcode: '12345',
|
||||
country: 'US',
|
||||
},
|
||||
} )
|
||||
.then( ( response ) => {
|
||||
customer = response.data;
|
||||
} );
|
||||
|
||||
await use( customer );
|
||||
|
||||
// Cleanup
|
||||
await api.delete( `customers/${ customer.id }`, { force: true } );
|
||||
},
|
||||
|
||||
simpleProduct: async ( { api }, use ) => {
|
||||
let product = {};
|
||||
|
||||
await api
|
||||
.post( 'products', {
|
||||
name: `Product simple ${ random() }`,
|
||||
type: 'simple',
|
||||
regular_price: '100',
|
||||
tax_class: 'Tax Class Simple',
|
||||
} )
|
||||
.then( ( response ) => {
|
||||
product = response.data;
|
||||
} );
|
||||
|
||||
await use( product );
|
||||
|
||||
// Cleanup
|
||||
await api.delete( `products/${ product.id }`, { force: true } );
|
||||
},
|
||||
|
||||
variableProduct: async ( { api }, use ) => {
|
||||
let product = {};
|
||||
|
||||
const variations = [
|
||||
{
|
||||
regular_price: '100',
|
||||
attributes: [
|
||||
{
|
||||
name: 'Size',
|
||||
option: 'Small',
|
||||
},
|
||||
{
|
||||
name: 'Colour',
|
||||
option: 'Yellow',
|
||||
},
|
||||
],
|
||||
tax_class: 'Tax Class Variable',
|
||||
},
|
||||
{
|
||||
regular_price: '100',
|
||||
attributes: [
|
||||
{
|
||||
name: 'Size',
|
||||
option: 'Medium',
|
||||
},
|
||||
{
|
||||
name: 'Colour',
|
||||
option: 'Magenta',
|
||||
},
|
||||
],
|
||||
tax_class: 'Tax Class Variable',
|
||||
},
|
||||
];
|
||||
|
||||
await api
|
||||
.post( 'products', {
|
||||
name: `Product variable ${ random() }`,
|
||||
type: 'variable',
|
||||
tax_class: 'Tax Class Variable',
|
||||
} )
|
||||
.then( ( response ) => {
|
||||
product = response.data;
|
||||
} );
|
||||
|
||||
for ( const key in variations ) {
|
||||
api.post(
|
||||
`products/${ product.id }/variations`,
|
||||
variations[ key ]
|
||||
);
|
||||
}
|
||||
|
||||
await use( product );
|
||||
|
||||
// Cleanup
|
||||
await api.delete( `products/${ product.id }`, { force: true } );
|
||||
},
|
||||
|
||||
externalProduct: async ( { api }, use ) => {
|
||||
let product = {};
|
||||
|
||||
await api
|
||||
.post( 'products', {
|
||||
name: `Product external ${ random() }`,
|
||||
regular_price: '800',
|
||||
tax_class: 'Tax Class External',
|
||||
external_url: 'https://wordpress.org/plugins/woocommerce',
|
||||
type: 'external',
|
||||
button_text: 'Buy now',
|
||||
} )
|
||||
.then( ( response ) => {
|
||||
product = response.data;
|
||||
} );
|
||||
|
||||
await use( product );
|
||||
|
||||
// Cleanup
|
||||
await api.delete( `products/${ product.id }`, { force: true } );
|
||||
},
|
||||
|
||||
groupedProduct: async ( { api }, use ) => {
|
||||
let product = {};
|
||||
let subProductAId;
|
||||
let subProductBId;
|
||||
|
||||
await api
|
||||
.post( 'products', {
|
||||
name: 'Add-on A',
|
||||
regular_price: '11.95',
|
||||
} )
|
||||
.then( ( response ) => {
|
||||
subProductAId = response.data.id;
|
||||
} );
|
||||
await api
|
||||
.post( 'products', {
|
||||
name: 'Add-on B',
|
||||
regular_price: '18.97',
|
||||
} )
|
||||
.then( ( response ) => {
|
||||
subProductBId = response.data.id;
|
||||
} );
|
||||
await api
|
||||
.post( 'products', {
|
||||
name: `Product grouped ${ random() }`,
|
||||
regular_price: '29.99',
|
||||
grouped_products: [ subProductAId, subProductBId ],
|
||||
type: 'grouped',
|
||||
} )
|
||||
.then( ( response ) => {
|
||||
product = response.data;
|
||||
} );
|
||||
|
||||
await use( product );
|
||||
|
||||
// Cleanup
|
||||
await api.delete( `products/${ product.id }`, { force: true } );
|
||||
},
|
||||
} );
|
||||
|
||||
test.describe(
|
||||
'WooCommerce Orders > Add new order',
|
||||
{ tag: [ '@services', '@hpos' ] },
|
||||
() => {
|
||||
test.use( { storageState: process.env.ADMINSTATE } );
|
||||
|
||||
test.beforeAll( async ( { baseURL } ) => {
|
||||
const api = new wcApi( {
|
||||
url: baseURL,
|
||||
consumerKey: process.env.CONSUMER_KEY,
|
||||
consumerSecret: process.env.CONSUMER_SECRET,
|
||||
version: 'wc/v3',
|
||||
} );
|
||||
test.beforeAll( async ( { api } ) => {
|
||||
// enable taxes on the account
|
||||
await api.put( 'settings/general/woocommerce_calc_taxes', {
|
||||
value: 'yes',
|
||||
|
@ -71,171 +269,9 @@ test.describe(
|
|||
for ( let i = 0; i < taxRates.length; i++ ) {
|
||||
await api.post( 'taxes', taxRates[ i ] );
|
||||
}
|
||||
// create simple product
|
||||
await api
|
||||
.post( 'products', {
|
||||
name: simpleProductName,
|
||||
type: 'simple',
|
||||
regular_price: '100',
|
||||
tax_class: 'Tax Class Simple',
|
||||
} )
|
||||
.then( ( resp ) => {
|
||||
simpleProductId = resp.data.id;
|
||||
} );
|
||||
// create variable product
|
||||
const variations = [
|
||||
{
|
||||
regular_price: '100',
|
||||
attributes: [
|
||||
{
|
||||
name: 'Size',
|
||||
option: 'Small',
|
||||
},
|
||||
{
|
||||
name: 'Colour',
|
||||
option: 'Yellow',
|
||||
},
|
||||
],
|
||||
tax_class: 'Tax Class Variable',
|
||||
},
|
||||
{
|
||||
regular_price: '100',
|
||||
attributes: [
|
||||
{
|
||||
name: 'Size',
|
||||
option: 'Medium',
|
||||
},
|
||||
{
|
||||
name: 'Colour',
|
||||
option: 'Magenta',
|
||||
},
|
||||
],
|
||||
tax_class: 'Tax Class Variable',
|
||||
},
|
||||
];
|
||||
await api
|
||||
.post( 'products', {
|
||||
name: variableProductName,
|
||||
type: 'variable',
|
||||
tax_class: 'Tax Class Variable',
|
||||
} )
|
||||
.then( ( response ) => {
|
||||
variableProductId = response.data.id;
|
||||
for ( const key in variations ) {
|
||||
api.post(
|
||||
`products/${ variableProductId }/variations`,
|
||||
variations[ key ]
|
||||
);
|
||||
}
|
||||
} );
|
||||
// create external product
|
||||
await api
|
||||
.post( 'products', {
|
||||
name: externalProductName,
|
||||
regular_price: '800',
|
||||
tax_class: 'Tax Class External',
|
||||
external_url: 'https://wordpress.org/plugins/woocommerce',
|
||||
type: 'external',
|
||||
button_text: 'Buy now',
|
||||
} )
|
||||
.then( ( response ) => {
|
||||
externalProductId = response.data.id;
|
||||
} );
|
||||
// create grouped product
|
||||
await api
|
||||
.post( 'products', {
|
||||
name: 'Add-on A',
|
||||
regular_price: '11.95',
|
||||
} )
|
||||
.then( ( response ) => {
|
||||
subProductAId = response.data.id;
|
||||
} );
|
||||
await api
|
||||
.post( 'products', {
|
||||
name: 'Add-on B',
|
||||
regular_price: '18.97',
|
||||
} )
|
||||
.then( ( response ) => {
|
||||
subProductBId = response.data.id;
|
||||
} );
|
||||
await api
|
||||
.post( 'products', {
|
||||
name: groupedProductName,
|
||||
regular_price: '29.99',
|
||||
grouped_products: [ subProductAId, subProductBId ],
|
||||
type: 'grouped',
|
||||
} )
|
||||
.then( ( response ) => {
|
||||
groupedProductId = response.data.id;
|
||||
} );
|
||||
// create a customer
|
||||
await api
|
||||
.post( 'customers', {
|
||||
email: 'sideshowbob@example.com',
|
||||
first_name: 'Sideshow',
|
||||
last_name: 'Bob',
|
||||
username: 'sideshowbob',
|
||||
billing: {
|
||||
first_name: 'Sideshow',
|
||||
last_name: 'Bob',
|
||||
company: 'Die Bart Die',
|
||||
address_1: '123 Fake St',
|
||||
address_2: '',
|
||||
city: 'Springfield',
|
||||
state: 'FL',
|
||||
postcode: '12345',
|
||||
country: 'US',
|
||||
email: 'sideshowbob@example.com',
|
||||
phone: '555-555-5556',
|
||||
},
|
||||
shipping: {
|
||||
first_name: 'Sideshow',
|
||||
last_name: 'Bob',
|
||||
company: 'Die Bart Die',
|
||||
address_1: '321 Fake St',
|
||||
address_2: '',
|
||||
city: 'Springfield',
|
||||
state: 'FL',
|
||||
postcode: '12345',
|
||||
country: 'US',
|
||||
},
|
||||
} )
|
||||
.then( ( response ) => {
|
||||
customerId = response.data.id;
|
||||
} );
|
||||
} );
|
||||
|
||||
test.afterEach( async ( { baseURL } ) => {
|
||||
const api = new wcApi( {
|
||||
url: baseURL,
|
||||
consumerKey: process.env.CONSUMER_KEY,
|
||||
consumerSecret: process.env.CONSUMER_SECRET,
|
||||
version: 'wc/v3',
|
||||
} );
|
||||
// clean up order after each test
|
||||
if ( orderId && orderId !== '' ) {
|
||||
await api.delete( `orders/${ orderId }`, { force: true } );
|
||||
}
|
||||
} );
|
||||
|
||||
test.afterAll( async ( { baseURL } ) => {
|
||||
const api = new wcApi( {
|
||||
url: baseURL,
|
||||
consumerKey: process.env.CONSUMER_KEY,
|
||||
consumerSecret: process.env.CONSUMER_SECRET,
|
||||
version: 'wc/v3',
|
||||
} );
|
||||
// cleans up all products after run
|
||||
await api.post( 'products/batch', {
|
||||
delete: [
|
||||
simpleProductId,
|
||||
variableProductId,
|
||||
externalProductId,
|
||||
subProductAId,
|
||||
subProductBId,
|
||||
groupedProductId,
|
||||
],
|
||||
} );
|
||||
test.afterAll( async ( { api } ) => {
|
||||
// clean up tax classes and rates
|
||||
for ( const { slug } of taxClasses ) {
|
||||
await api
|
||||
|
@ -258,19 +294,15 @@ test.describe(
|
|||
await api.put( 'settings/general/woocommerce_calc_taxes', {
|
||||
value: 'no',
|
||||
} );
|
||||
// clean up customer
|
||||
await api.delete( `customers/${ customerId }`, { force: true } );
|
||||
} );
|
||||
|
||||
test( 'can create a simple guest order', async ( { page } ) => {
|
||||
test( 'can create a simple guest order', async ( {
|
||||
page,
|
||||
simpleProduct,
|
||||
order,
|
||||
} ) => {
|
||||
await page.goto( 'wp-admin/admin.php?page=wc-orders&action=new' );
|
||||
|
||||
// get order ID from the page
|
||||
const orderText = await page
|
||||
.locator( 'h2.woocommerce-order-data__heading' )
|
||||
.textContent();
|
||||
orderId = orderText.match( /([0-9])\w+/ );
|
||||
orderId = orderId[ 0 ].toString();
|
||||
order.id = await getOrderIdFromPage( page );
|
||||
|
||||
await page
|
||||
.locator( '#order_status' )
|
||||
|
@ -334,22 +366,7 @@ test.describe(
|
|||
.fill( 'Only asked for a slushie' );
|
||||
|
||||
// Add a product
|
||||
await page.getByRole( 'button', { name: 'Add item(s)' } ).click();
|
||||
await page
|
||||
.getByRole( 'button', { name: 'Add product(s)' } )
|
||||
.click();
|
||||
await page.getByText( 'Search for a product…' ).click();
|
||||
await page
|
||||
.locator( 'span > .select2-search__field' )
|
||||
.fill( 'Simple' );
|
||||
await page
|
||||
.getByRole( 'option', { name: simpleProductName } )
|
||||
.click();
|
||||
await page
|
||||
.getByRole( 'row', { name: '×Add new order simple product' } )
|
||||
.getByPlaceholder( '1' )
|
||||
.fill( '2' );
|
||||
await page.locator( '#btn-ok' ).click();
|
||||
await addProductToOrder( page, simpleProduct, 2 );
|
||||
|
||||
// Create the order
|
||||
await page.getByRole( 'button', { name: 'Create' } ).click();
|
||||
|
@ -375,40 +392,26 @@ test.describe(
|
|||
|
||||
test( 'can create an order for an existing customer', async ( {
|
||||
page,
|
||||
simpleProduct,
|
||||
customer,
|
||||
order,
|
||||
} ) => {
|
||||
await page.goto( 'wp-admin/admin.php?page=wc-orders&action=new' );
|
||||
|
||||
// get order ID from the page
|
||||
const orderText = await page
|
||||
.locator( 'h2.woocommerce-order-data__heading' )
|
||||
.textContent();
|
||||
orderId = orderText.match( /([0-9])\w+/ );
|
||||
orderId = orderId[ 0 ].toString();
|
||||
order.id = await getOrderIdFromPage( page );
|
||||
|
||||
// Select customer
|
||||
await page.getByText( 'Guest' ).click();
|
||||
await page
|
||||
.locator( 'input[aria-owns="select2-customer_user-results"]' )
|
||||
.fill( 'sideshowbob@' );
|
||||
await page.getByRole( 'option', { name: 'Sideshow Bob' } ).click();
|
||||
.fill( customer.username );
|
||||
await page
|
||||
.getByRole( 'option', {
|
||||
name: `${ customer.first_name } ${ customer.last_name }`,
|
||||
} )
|
||||
.click();
|
||||
|
||||
// Add a product
|
||||
await page.getByRole( 'button', { name: 'Add item(s)' } ).click();
|
||||
await page
|
||||
.getByRole( 'button', { name: 'Add product(s)' } )
|
||||
.click();
|
||||
await page.getByText( 'Search for a product…' ).click();
|
||||
await page
|
||||
.locator( 'span > .select2-search__field' )
|
||||
.fill( 'Simple' );
|
||||
await page
|
||||
.getByRole( 'option', { name: simpleProductName } )
|
||||
.click();
|
||||
await page
|
||||
.getByRole( 'row', { name: '×Add new order simple product' } )
|
||||
.getByPlaceholder( '1' )
|
||||
.fill( '2' );
|
||||
await page.locator( '#btn-ok' ).click();
|
||||
await addProductToOrder( page, simpleProduct, 2 );
|
||||
|
||||
// Create the order
|
||||
await page.getByRole( 'button', { name: 'Create' } ).click();
|
||||
|
@ -431,12 +434,14 @@ test.describe(
|
|||
// View customer profile
|
||||
await page.getByRole( 'link', { name: 'Profile →' } ).click();
|
||||
await expect(
|
||||
page.getByRole( 'heading', { name: 'Edit User sideshowbob' } )
|
||||
page.getByRole( 'heading', {
|
||||
name: `Edit User ${ customer.username }`,
|
||||
} )
|
||||
).toBeVisible();
|
||||
|
||||
// Go back to the order
|
||||
await page.goto(
|
||||
`wp-admin/admin.php?page=wc-orders&action=edit&id=${ orderId }`
|
||||
`wp-admin/admin.php?page=wc-orders&action=edit&id=${ order.id }`
|
||||
);
|
||||
await page
|
||||
.getByRole( 'link', {
|
||||
|
@ -449,17 +454,12 @@ test.describe(
|
|||
await expect( page.getByRole( 'row' ) ).toHaveCount( 3 ); // 1 order and header and footer rows
|
||||
} );
|
||||
|
||||
test( 'can create new order', async ( { page } ) => {
|
||||
test( 'can create new order', async ( { page, order } ) => {
|
||||
await page.goto( 'wp-admin/admin.php?page=wc-orders&action=new' );
|
||||
await expect(
|
||||
page.locator( 'h1.wp-heading-inline' )
|
||||
).toContainText( 'Add new order' );
|
||||
// get order ID from the page
|
||||
const orderText = await page
|
||||
.locator( 'h2.woocommerce-order-data__heading' )
|
||||
.textContent();
|
||||
orderId = orderText.match( /([0-9])\w+/ );
|
||||
orderId = orderId[ 0 ].toString();
|
||||
order.id = await getOrderIdFromPage( page );
|
||||
|
||||
await page
|
||||
.locator( '#order_status' )
|
||||
|
@ -488,74 +488,51 @@ test.describe(
|
|||
|
||||
test( 'can create new complex order with multiple product types & tax classes', async ( {
|
||||
page,
|
||||
simpleProduct,
|
||||
variableProduct,
|
||||
externalProduct,
|
||||
groupedProduct,
|
||||
order,
|
||||
} ) => {
|
||||
orderId = '';
|
||||
await page.goto( 'wp-admin/admin.php?page=wc-orders&action=new' );
|
||||
order.id = await getOrderIdFromPage( page );
|
||||
|
||||
// open modal for adding line items
|
||||
await page.locator( 'button.add-line-item' ).click();
|
||||
await page.locator( 'button.add-order-item' ).click();
|
||||
|
||||
// search for each product to add
|
||||
await page.locator( 'text=Search for a product…' ).click();
|
||||
await page
|
||||
.locator( '.select2-search--dropdown' )
|
||||
.getByRole( 'combobox' )
|
||||
.pressSequentially( simpleProductName );
|
||||
await page
|
||||
.locator(
|
||||
'li.select2-results__option.select2-results__option--highlighted'
|
||||
)
|
||||
.click();
|
||||
|
||||
await page.locator( 'text=Search for a product…' ).click();
|
||||
await page
|
||||
.locator( '.select2-search--dropdown' )
|
||||
.getByRole( 'combobox' )
|
||||
.pressSequentially( variableProductName );
|
||||
await page
|
||||
.locator(
|
||||
'li.select2-results__option.select2-results__option--highlighted'
|
||||
)
|
||||
.click();
|
||||
|
||||
await page.locator( 'text=Search for a product…' ).click();
|
||||
await page
|
||||
.locator( '.select2-search--dropdown' )
|
||||
.getByRole( 'combobox' )
|
||||
.type( groupedProductName );
|
||||
await page
|
||||
.locator(
|
||||
'li.select2-results__option.select2-results__option--highlighted'
|
||||
)
|
||||
.click();
|
||||
|
||||
await page.locator( 'text=Search for a product…' ).click();
|
||||
await page
|
||||
.locator( '.select2-search--dropdown' )
|
||||
.getByRole( 'combobox' )
|
||||
.type( externalProductName );
|
||||
await page
|
||||
.locator(
|
||||
'li.select2-results__option.select2-results__option--highlighted'
|
||||
)
|
||||
.click();
|
||||
for ( const product of [
|
||||
simpleProduct,
|
||||
variableProduct,
|
||||
groupedProduct,
|
||||
externalProduct,
|
||||
] ) {
|
||||
await page.getByText( 'Search for a product…' ).click();
|
||||
await page
|
||||
.locator( 'span > .select2-search__field' )
|
||||
.fill( product.name );
|
||||
await page
|
||||
.getByRole( 'option', { name: product.name } )
|
||||
.first()
|
||||
.click();
|
||||
}
|
||||
|
||||
await page.locator( 'button#btn-ok' ).click();
|
||||
|
||||
// assert that products added
|
||||
await expect(
|
||||
page.locator( 'td.name > a >> nth=0' )
|
||||
).toContainText( simpleProductName );
|
||||
).toContainText( simpleProduct.name );
|
||||
await expect(
|
||||
page.locator( 'td.name > a >> nth=1' )
|
||||
).toContainText( variableProductName );
|
||||
).toContainText( variableProduct.name );
|
||||
await expect(
|
||||
page.locator( 'td.name > a >> nth=2' )
|
||||
).toContainText( groupedProductName );
|
||||
).toContainText( groupedProduct.name );
|
||||
await expect(
|
||||
page.locator( 'td.name > a >> nth=3' )
|
||||
).toContainText( externalProductName );
|
||||
).toContainText( externalProduct.name );
|
||||
|
||||
// Recalculate taxes
|
||||
page.on( 'dialog', ( dialog ) => dialog.accept() );
|
||||
|
|
|
@ -138,7 +138,9 @@ test.describe(
|
|||
await page
|
||||
.getByRole( 'button', { name: 'Add a coupon' } )
|
||||
.click();
|
||||
await page.getByLabel( 'Enter code' ).fill( coupons[ i ].code );
|
||||
await page
|
||||
.locator( '#wc-block-components-totals-coupon__input-0' )
|
||||
.fill( coupons[ i ].code );
|
||||
await page.getByText( 'Apply', { exact: true } ).click();
|
||||
await expect(
|
||||
page
|
||||
|
@ -181,7 +183,9 @@ test.describe(
|
|||
await page
|
||||
.getByRole( 'button', { name: 'Add a coupon' } )
|
||||
.click();
|
||||
await page.getByLabel( 'Enter code' ).fill( coupons[ i ].code );
|
||||
await page
|
||||
.locator( '#wc-block-components-totals-coupon__input-0' )
|
||||
.fill( coupons[ i ].code );
|
||||
await page.getByText( 'Apply', { exact: true } ).click();
|
||||
await expect(
|
||||
page
|
||||
|
@ -221,7 +225,9 @@ test.describe(
|
|||
} ) => {
|
||||
// try to add two same coupons and verify the error message
|
||||
await page.getByRole( 'button', { name: 'Add a coupon' } ).click();
|
||||
await page.getByLabel( 'Enter code' ).fill( coupons[ 0 ].code );
|
||||
await page
|
||||
.locator( '#wc-block-components-totals-coupon__input-0' )
|
||||
.fill( coupons[ 0 ].code );
|
||||
await page.getByText( 'Apply', { exact: true } ).click();
|
||||
await expect(
|
||||
page
|
||||
|
@ -231,7 +237,9 @@ test.describe(
|
|||
)
|
||||
).toBeVisible();
|
||||
await page.getByRole( 'button', { name: 'Add a coupon' } ).click();
|
||||
await page.getByLabel( 'Enter code' ).fill( coupons[ 0 ].code );
|
||||
await page
|
||||
.locator( '#wc-block-components-totals-coupon__input-0' )
|
||||
.fill( coupons[ 0 ].code );
|
||||
await page.getByText( 'Apply', { exact: true } ).click();
|
||||
await expect(
|
||||
page
|
||||
|
@ -247,7 +255,9 @@ test.describe(
|
|||
} ) => {
|
||||
// add coupon with usage limit
|
||||
await page.getByRole( 'button', { name: 'Add a coupon' } ).click();
|
||||
await page.getByLabel( 'Enter code' ).fill( couponLimitedCode );
|
||||
await page
|
||||
.locator( '#wc-block-components-totals-coupon__input-0' )
|
||||
.fill( couponLimitedCode );
|
||||
await page.getByText( 'Apply', { exact: true } ).click();
|
||||
await expect(
|
||||
page
|
||||
|
|
|
@ -1,186 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Test the TaskList class.
|
||||
*
|
||||
* @package WooCommerce\Admin\Tests\OnboardingTasks
|
||||
*/
|
||||
|
||||
use Automattic\WooCommerce\Internal\Admin\Onboarding\OnboardingThemes;
|
||||
use Automattic\WooCommerce\Internal\Admin\Onboarding\OnboardingProfile;
|
||||
use Automattic\WooCommerce\Admin\Features\OnboardingTasks\TaskList;
|
||||
use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks\Purchase;
|
||||
|
||||
/**
|
||||
* class WC_Admin_Tests_OnboardingTasks_TaskList
|
||||
*/
|
||||
class WC_Admin_Tests_OnboardingTasks_Task_Purchase extends WC_Unit_Test_Case {
|
||||
|
||||
/**
|
||||
* Task list.
|
||||
*
|
||||
* @var Task|null
|
||||
*/
|
||||
protected $task = null;
|
||||
|
||||
/**
|
||||
* Setup test data. Called before every test.
|
||||
*/
|
||||
public function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->task = new Purchase( new TaskList() );
|
||||
set_transient(
|
||||
OnboardingThemes::THEMES_TRANSIENT,
|
||||
array(
|
||||
'free' => array(
|
||||
'slug' => 'free',
|
||||
'is_installed' => false,
|
||||
),
|
||||
'paid' => array(
|
||||
'slug' => 'paid',
|
||||
'id' => 12312,
|
||||
'price' => '$79.00',
|
||||
'title' => 'theme title',
|
||||
'is_installed' => false,
|
||||
),
|
||||
'paid_installed' => array(
|
||||
'slug' => 'paid_installed',
|
||||
'id' => 12312,
|
||||
'price' => '$79.00',
|
||||
'title' => 'theme title',
|
||||
'is_installed' => true,
|
||||
),
|
||||
'free_with_price' => array(
|
||||
'slug' => 'free_with_price',
|
||||
'id' => 12312,
|
||||
'price' => '$0.00',
|
||||
'title' => 'theme title',
|
||||
'is_installed' => false,
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tear down.
|
||||
*/
|
||||
public function tearDown(): void {
|
||||
parent::tearDown();
|
||||
delete_transient( OnboardingThemes::THEMES_TRANSIENT );
|
||||
delete_option( OnboardingProfile::DATA_OPTION );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test is_complete function of Purchase task.
|
||||
*/
|
||||
public function test_is_complete_if_no_remaining_products() {
|
||||
update_option( OnboardingProfile::DATA_OPTION, array( 'product_types' => array( 'physical' ) ) );
|
||||
$this->assertEquals( true, $this->task->is_complete() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test is_complete function of Purchase task.
|
||||
*/
|
||||
public function test_is_not_complete_if_remaining_paid_products() {
|
||||
update_option( OnboardingProfile::DATA_OPTION, array( 'product_types' => array( 'memberships' ) ) );
|
||||
$this->assertEquals( false, $this->task->is_complete() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test is_complete function of Purchase task.
|
||||
*/
|
||||
public function test_is_complete_if_no_paid_themes() {
|
||||
update_option(
|
||||
OnboardingProfile::DATA_OPTION,
|
||||
array(
|
||||
'product_types' => array(),
|
||||
'theme' => 'free',
|
||||
)
|
||||
);
|
||||
$this->assertEquals( true, $this->task->is_complete() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test is_complete function of Purchase task.
|
||||
*/
|
||||
public function test_is_not_complete_if_paid_theme_that_is_not_installed() {
|
||||
update_option(
|
||||
OnboardingProfile::DATA_OPTION,
|
||||
array(
|
||||
'product_types' => array(),
|
||||
'theme' => 'paid',
|
||||
)
|
||||
);
|
||||
$this->assertEquals( false, $this->task->is_complete() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test is_complete function of Purchase task.
|
||||
*/
|
||||
public function test_is_complete_if_paid_theme_that_is_installed() {
|
||||
update_option(
|
||||
OnboardingProfile::DATA_OPTION,
|
||||
array(
|
||||
'product_types' => array(),
|
||||
'theme' => 'paid_installed',
|
||||
)
|
||||
);
|
||||
$this->assertEquals( true, $this->task->is_complete() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test is_complete function of Purchase task.
|
||||
*/
|
||||
public function test_is_complete_if_free_theme_with_set_price() {
|
||||
update_option(
|
||||
OnboardingProfile::DATA_OPTION,
|
||||
array(
|
||||
'product_types' => array(),
|
||||
'theme' => 'free_with_price',
|
||||
)
|
||||
);
|
||||
$this->assertEquals( true, $this->task->is_complete() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the task title for a single paid item.
|
||||
*/
|
||||
public function test_get_title_if_single_paid_item() {
|
||||
update_option(
|
||||
OnboardingProfile::DATA_OPTION,
|
||||
array(
|
||||
'product_types' => array(),
|
||||
'theme' => 'paid',
|
||||
)
|
||||
);
|
||||
$this->assertEquals( 'Add theme title to my store', $this->task->get_title() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the task title if 2 paid items exist.
|
||||
*/
|
||||
public function test_get_title_if_multiple_paid_themes() {
|
||||
update_option(
|
||||
OnboardingProfile::DATA_OPTION,
|
||||
array(
|
||||
'product_types' => array( 'memberships' ),
|
||||
'theme' => 'paid',
|
||||
)
|
||||
);
|
||||
$this->assertEquals( 'Add Memberships and 1 more product to my store', $this->task->get_title() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the task title if multiple additional paid items exist.
|
||||
*/
|
||||
public function test_get_title_if_multiple_paid_products() {
|
||||
update_option(
|
||||
OnboardingProfile::DATA_OPTION,
|
||||
array(
|
||||
'product_types' => array( 'memberships', 'bookings' ),
|
||||
'theme' => 'paid',
|
||||
)
|
||||
);
|
||||
$this->assertEquals( 'Add Memberships and 2 more products to my store', $this->task->get_title() );
|
||||
}
|
||||
}
|
|
@ -323,6 +323,31 @@ class WC_Admin_Tests_PaymentGatewaySuggestions_EvaluateSuggestion extends WC_Uni
|
|||
remove_filter( 'woocommerce_admin_remote_specs_evaluator_should_log', '__return_true' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the memo is set correctly.
|
||||
*/
|
||||
public function test_memo_set_correctly() {
|
||||
$specs = array(
|
||||
array(
|
||||
'id' => 'test-gateway-1',
|
||||
'is_visible' => true,
|
||||
),
|
||||
array(
|
||||
'id' => 'test-gateway-2',
|
||||
'is_visible' => false,
|
||||
),
|
||||
);
|
||||
|
||||
$result = TestableEvaluateSuggestion::evaluate_specs( $specs );
|
||||
$memo = TestableEvaluateSuggestion::get_memo_for_tests();
|
||||
|
||||
$this->assertCount( 1, $memo );
|
||||
$memo_key = array_keys( $memo )[0];
|
||||
$this->assertEquals( $result, $memo[ $memo_key ] );
|
||||
$this->assertCount( 1, $result['suggestions'] );
|
||||
$this->assertEquals( 'test-gateway-1', $result['suggestions'][0]->id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides the WC logger.
|
||||
*
|
||||
|
@ -359,3 +384,19 @@ class WC_Admin_Tests_PaymentGatewaySuggestions_EvaluateSuggestion extends WC_Uni
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
//phpcs:disable Generic.Files.OneObjectStructurePerFile.MultipleFound, Squiz.Classes.ClassFileName.NoMatch, Suin.Classes.PSR4.IncorrectClassName
|
||||
/**
|
||||
* TestableEvaluateSuggestion class.
|
||||
*/
|
||||
class TestableEvaluateSuggestion extends EvaluateSuggestion {
|
||||
/**
|
||||
* Get the memo for testing.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_memo_for_tests() {
|
||||
return self::$memo;
|
||||
}
|
||||
}
|
||||
//phpcs:enable Generic.Files.OneObjectStructurePerFile.MultipleFound, Squiz.Classes.ClassFileName.NoMatch, Suin.Classes.PSR4.IncorrectClassName
|
||||
|
|
|
@ -25,7 +25,7 @@ class WC_Admin_Tests_PaymentGatewaySuggestions_Init extends WC_Unit_Test_Case {
|
|||
delete_option( 'woocommerce_show_marketplace_suggestions' );
|
||||
add_filter(
|
||||
'transient_woocommerce_admin_' . PaymentGatewaySuggestionsDataSourcePoller::ID . '_specs',
|
||||
function( $value ) {
|
||||
function ( $value ) {
|
||||
if ( $value ) {
|
||||
return $value;
|
||||
}
|
||||
|
@ -37,6 +37,8 @@ class WC_Admin_Tests_PaymentGatewaySuggestions_Init extends WC_Unit_Test_Case {
|
|||
);
|
||||
}
|
||||
);
|
||||
|
||||
EvaluateSuggestion::reset_memo();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -57,7 +59,7 @@ class WC_Admin_Tests_PaymentGatewaySuggestions_Init extends WC_Unit_Test_Case {
|
|||
remove_all_filters( 'transient_woocommerce_admin_' . PaymentGatewaySuggestionsDataSourcePoller::ID . '_specs' );
|
||||
add_filter(
|
||||
DataSourcePoller::FILTER_NAME,
|
||||
function() {
|
||||
function () {
|
||||
return array();
|
||||
}
|
||||
);
|
||||
|
@ -242,7 +244,7 @@ class WC_Admin_Tests_PaymentGatewaySuggestions_Init extends WC_Unit_Test_Case {
|
|||
|
||||
add_filter(
|
||||
'locale',
|
||||
function( $_locale ) {
|
||||
function () {
|
||||
return 'zh_TW';
|
||||
}
|
||||
);
|
||||
|
@ -364,5 +366,4 @@ class WC_Admin_Tests_PaymentGatewaySuggestions_Init extends WC_Unit_Test_Case {
|
|||
// Clean up.
|
||||
delete_option( PaymentGatewaySuggestions::RECOMMENDED_PAYMENT_PLUGINS_DISMISS_OPTION );
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<?php
|
||||
declare( strict_types = 1 );
|
||||
|
||||
namespace Automattic\WooCommerce\Tests\Admin\ShippingPartnerSuggestions;
|
||||
namespace Automattic\WooCommerce\Tests\Admin\Features\ShippingPartnerSuggestions;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Features\PaymentGatewaySuggestions\EvaluateSuggestion;
|
||||
use Automattic\WooCommerce\Admin\Features\ShippingPartnerSuggestions\DefaultShippingPartners;
|
||||
|
@ -31,6 +31,8 @@ class DefaultShippingPartnersTest extends WC_Unit_Test_Case {
|
|||
update_option( 'woocommerce_store_address', 'foo' );
|
||||
|
||||
update_option( 'active_plugins', array( 'foo/foo.php' ) );
|
||||
|
||||
EvaluateSuggestion::reset_memo();
|
||||
}
|
||||
|
||||
/**
|
|
@ -7,6 +7,7 @@ use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks\Shipping;
|
|||
use Automattic\WooCommerce\Admin\Features\ShippingPartnerSuggestions\DefaultShippingPartners;
|
||||
use Automattic\WooCommerce\Admin\Features\ShippingPartnerSuggestions\ShippingPartnerSuggestions;
|
||||
use Automattic\WooCommerce\Admin\Features\ShippingPartnerSuggestions\ShippingPartnerSuggestionsDataSourcePoller;
|
||||
use Automattic\WooCommerce\Admin\Features\PaymentGatewaySuggestions\EvaluateSuggestion;
|
||||
use WC_Unit_Test_Case;
|
||||
|
||||
/**
|
||||
|
@ -37,7 +38,7 @@ class ShippingPartnerSuggestionsTest extends WC_Unit_Test_Case {
|
|||
delete_option( 'woocommerce_show_marketplace_suggestions' );
|
||||
add_filter(
|
||||
'transient_woocommerce_admin_' . ShippingPartnerSuggestionsDataSourcePoller::ID . '_specs',
|
||||
function( $value ) {
|
||||
function ( $value ) {
|
||||
if ( $value ) {
|
||||
return $value;
|
||||
}
|
||||
|
@ -70,6 +71,8 @@ class ShippingPartnerSuggestionsTest extends WC_Unit_Test_Case {
|
|||
// Have a mock logger used by the suggestions rule evaluator.
|
||||
$this->mock_logger = $this->getMockBuilder( 'WC_Logger_Interface' )->getMock();
|
||||
add_filter( 'woocommerce_logging_class', array( $this, 'override_wc_logger' ) );
|
||||
|
||||
EvaluateSuggestion::reset_memo();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -91,7 +94,7 @@ class ShippingPartnerSuggestionsTest extends WC_Unit_Test_Case {
|
|||
remove_all_filters( 'transient_woocommerce_admin_' . ShippingPartnerSuggestionsDataSourcePoller::ID . '_specs' );
|
||||
add_filter(
|
||||
DataSourcePoller::FILTER_NAME,
|
||||
function() {
|
||||
function () {
|
||||
return array();
|
||||
}
|
||||
);
|
||||
|
|
|
@ -29,6 +29,8 @@ class DefaultPromotionsTest extends WC_Unit_Test_Case {
|
|||
update_option( 'woocommerce_store_address', 'foo' );
|
||||
|
||||
update_option( 'active_plugins', array( 'foo/foo.php' ) );
|
||||
|
||||
EvaluateSuggestion::reset_memo();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -26,7 +26,7 @@ class InitTest extends WC_Unit_Test_Case {
|
|||
delete_option( 'woocommerce_show_marketplace_suggestions' );
|
||||
add_filter(
|
||||
'transient_woocommerce_admin_' . WCPayPromotionDataSourcePoller::ID . '_specs',
|
||||
function( $value ) {
|
||||
function ( $value ) {
|
||||
if ( $value ) {
|
||||
return $value;
|
||||
}
|
||||
|
@ -38,6 +38,8 @@ class InitTest extends WC_Unit_Test_Case {
|
|||
);
|
||||
}
|
||||
);
|
||||
|
||||
EvaluateSuggestion::reset_memo();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -59,7 +61,7 @@ class InitTest extends WC_Unit_Test_Case {
|
|||
remove_all_filters( 'transient_woocommerce_admin_' . WCPayPromotionDataSourcePoller::ID . '_specs' );
|
||||
add_filter(
|
||||
DataSourcePoller::FILTER_NAME,
|
||||
function() {
|
||||
function () {
|
||||
return array();
|
||||
}
|
||||
);
|
||||
|
|
Loading…
Reference in New Issue