Merge branch 'trunk' into fix/43611-product-variation-clear-button
This commit is contained in:
commit
4a2c5bc1af
|
@ -21,15 +21,36 @@ import {
|
||||||
import type { ProductCollectionEditComponentProps } from '../types';
|
import type { ProductCollectionEditComponentProps } from '../types';
|
||||||
import { getCollectionByName } from '../collections';
|
import { getCollectionByName } from '../collections';
|
||||||
|
|
||||||
const ProductPicker = ( props: ProductCollectionEditComponentProps ) => {
|
const ProductPicker = (
|
||||||
|
props: ProductCollectionEditComponentProps & {
|
||||||
|
isDeletedProductReference: boolean;
|
||||||
|
}
|
||||||
|
) => {
|
||||||
const blockProps = useBlockProps();
|
const blockProps = useBlockProps();
|
||||||
const attributes = props.attributes;
|
const { attributes, isDeletedProductReference } = props;
|
||||||
|
|
||||||
const collection = getCollectionByName( attributes.collection );
|
const collection = getCollectionByName( attributes.collection );
|
||||||
if ( ! collection ) {
|
if ( ! collection ) {
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const infoText = isDeletedProductReference
|
||||||
|
? __(
|
||||||
|
'Previously selected product is no longer available.',
|
||||||
|
'woocommerce'
|
||||||
|
)
|
||||||
|
: createInterpolateElement(
|
||||||
|
sprintf(
|
||||||
|
/* translators: %s: collection title */
|
||||||
|
__(
|
||||||
|
'<strong>%s</strong> requires a product to be selected in order to display associated items.',
|
||||||
|
'woocommerce'
|
||||||
|
),
|
||||||
|
collection.title
|
||||||
|
),
|
||||||
|
{ strong: <strong /> }
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div { ...blockProps }>
|
<div { ...blockProps }>
|
||||||
<Placeholder className="wc-blocks-product-collection__editor-product-picker">
|
<Placeholder className="wc-blocks-product-collection__editor-product-picker">
|
||||||
|
@ -38,21 +59,7 @@ const ProductPicker = ( props: ProductCollectionEditComponentProps ) => {
|
||||||
icon={ info }
|
icon={ info }
|
||||||
className="wc-blocks-product-collection__info-icon"
|
className="wc-blocks-product-collection__info-icon"
|
||||||
/>
|
/>
|
||||||
<Text>
|
<Text>{ infoText }</Text>
|
||||||
{ createInterpolateElement(
|
|
||||||
sprintf(
|
|
||||||
/* translators: %s: collection title */
|
|
||||||
__(
|
|
||||||
'<strong>%s</strong> requires a product to be selected in order to display associated items.',
|
|
||||||
'woocommerce'
|
|
||||||
),
|
|
||||||
collection.title
|
|
||||||
),
|
|
||||||
{
|
|
||||||
strong: <strong />,
|
|
||||||
}
|
|
||||||
) }
|
|
||||||
</Text>
|
|
||||||
</HStack>
|
</HStack>
|
||||||
<ProductControl
|
<ProductControl
|
||||||
selected={
|
selected={
|
||||||
|
|
|
@ -174,6 +174,10 @@ $max-button-width: calc(100% / #{$max-button-columns});
|
||||||
.wc-blocks-product-collection__info-icon {
|
.wc-blocks-product-collection__info-icon {
|
||||||
fill: var(--wp--preset--color--luminous-vivid-orange, #e26f56);
|
fill: var(--wp--preset--color--luminous-vivid-orange, #e26f56);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.woocommerce-search-list__search {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Linked Product Control
|
// Linked Product Control
|
||||||
|
|
|
@ -5,11 +5,13 @@ import { store as blockEditorStore } from '@wordpress/block-editor';
|
||||||
import { useState } from '@wordpress/element';
|
import { useState } from '@wordpress/element';
|
||||||
import { useSelect } from '@wordpress/data';
|
import { useSelect } from '@wordpress/data';
|
||||||
import { useGetLocation } from '@woocommerce/blocks/product-template/utils';
|
import { useGetLocation } from '@woocommerce/blocks/product-template/utils';
|
||||||
|
import { Spinner, Flex } from '@wordpress/components';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
*/
|
*/
|
||||||
import {
|
import {
|
||||||
|
ProductCollectionContentProps,
|
||||||
ProductCollectionEditComponentProps,
|
ProductCollectionEditComponentProps,
|
||||||
ProductCollectionUIStatesInEditor,
|
ProductCollectionUIStatesInEditor,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
|
@ -17,7 +19,7 @@ import ProductCollectionPlaceholder from './product-collection-placeholder';
|
||||||
import ProductCollectionContent from './product-collection-content';
|
import ProductCollectionContent from './product-collection-content';
|
||||||
import CollectionSelectionModal from './collection-selection-modal';
|
import CollectionSelectionModal from './collection-selection-modal';
|
||||||
import './editor.scss';
|
import './editor.scss';
|
||||||
import { getProductCollectionUIStateInEditor } from '../utils';
|
import { useProductCollectionUIState } from '../utils';
|
||||||
import ProductPicker from './ProductPicker';
|
import ProductPicker from './ProductPicker';
|
||||||
|
|
||||||
const Edit = ( props: ProductCollectionEditComponentProps ) => {
|
const Edit = ( props: ProductCollectionEditComponentProps ) => {
|
||||||
|
@ -31,49 +33,65 @@ const Edit = ( props: ProductCollectionEditComponentProps ) => {
|
||||||
[ clientId ]
|
[ clientId ]
|
||||||
);
|
);
|
||||||
|
|
||||||
const productCollectionUIStateInEditor =
|
const { productCollectionUIStateInEditor, isLoading } =
|
||||||
getProductCollectionUIStateInEditor( {
|
useProductCollectionUIState( {
|
||||||
hasInnerBlocks,
|
|
||||||
location,
|
location,
|
||||||
attributes: props.attributes,
|
attributes,
|
||||||
|
hasInnerBlocks,
|
||||||
usesReference: props.usesReference,
|
usesReference: props.usesReference,
|
||||||
} );
|
} );
|
||||||
|
|
||||||
/**
|
// Show spinner while calculating Editor UI state.
|
||||||
* Component to render based on the UI state.
|
if ( isLoading ) {
|
||||||
*/
|
return (
|
||||||
let Component,
|
<Flex justify="center" align="center">
|
||||||
isUsingReferencePreviewMode = false;
|
<Spinner />
|
||||||
switch ( productCollectionUIStateInEditor ) {
|
</Flex>
|
||||||
case ProductCollectionUIStatesInEditor.COLLECTION_PICKER:
|
);
|
||||||
Component = ProductCollectionPlaceholder;
|
|
||||||
break;
|
|
||||||
case ProductCollectionUIStatesInEditor.PRODUCT_REFERENCE_PICKER:
|
|
||||||
Component = ProductPicker;
|
|
||||||
break;
|
|
||||||
case ProductCollectionUIStatesInEditor.VALID:
|
|
||||||
Component = ProductCollectionContent;
|
|
||||||
break;
|
|
||||||
case ProductCollectionUIStatesInEditor.VALID_WITH_PREVIEW:
|
|
||||||
Component = ProductCollectionContent;
|
|
||||||
isUsingReferencePreviewMode = true;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
// By default showing collection chooser.
|
|
||||||
Component = ProductCollectionPlaceholder;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const productCollectionContentProps: ProductCollectionContentProps = {
|
||||||
|
...props,
|
||||||
|
openCollectionSelectionModal: () => setIsSelectionModalOpen( true ),
|
||||||
|
location,
|
||||||
|
isUsingReferencePreviewMode:
|
||||||
|
productCollectionUIStateInEditor ===
|
||||||
|
ProductCollectionUIStatesInEditor.VALID_WITH_PREVIEW,
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderComponent = () => {
|
||||||
|
switch ( productCollectionUIStateInEditor ) {
|
||||||
|
case ProductCollectionUIStatesInEditor.COLLECTION_PICKER:
|
||||||
|
return <ProductCollectionPlaceholder { ...props } />;
|
||||||
|
case ProductCollectionUIStatesInEditor.PRODUCT_REFERENCE_PICKER:
|
||||||
|
return (
|
||||||
|
<ProductPicker
|
||||||
|
{ ...props }
|
||||||
|
isDeletedProductReference={ false }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
case ProductCollectionUIStatesInEditor.DELETED_PRODUCT_REFERENCE:
|
||||||
|
return (
|
||||||
|
<ProductPicker
|
||||||
|
{ ...props }
|
||||||
|
isDeletedProductReference={ true }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
case ProductCollectionUIStatesInEditor.VALID:
|
||||||
|
case ProductCollectionUIStatesInEditor.VALID_WITH_PREVIEW:
|
||||||
|
return (
|
||||||
|
<ProductCollectionContent
|
||||||
|
{ ...productCollectionContentProps }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
return <ProductCollectionPlaceholder { ...props } />;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Component
|
{ renderComponent() }
|
||||||
{ ...props }
|
|
||||||
openCollectionSelectionModal={ () =>
|
|
||||||
setIsSelectionModalOpen( true )
|
|
||||||
}
|
|
||||||
isUsingReferencePreviewMode={ isUsingReferencePreviewMode }
|
|
||||||
location={ location }
|
|
||||||
usesReference={ props.usesReference }
|
|
||||||
/>
|
|
||||||
{ isSelectionModalOpen && (
|
{ isSelectionModalOpen && (
|
||||||
<CollectionSelectionModal
|
<CollectionSelectionModal
|
||||||
clientId={ clientId }
|
clientId={ clientId }
|
||||||
|
|
|
@ -7,10 +7,10 @@ import { InspectorAdvancedControls } from '@wordpress/block-editor';
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
*/
|
*/
|
||||||
import ForcePageReloadControl from './force-page-reload-control';
|
import ForcePageReloadControl from './force-page-reload-control';
|
||||||
import type { ProductCollectionEditComponentProps } from '../../types';
|
import type { ProductCollectionContentProps } from '../../types';
|
||||||
|
|
||||||
export default function ProductCollectionAdvancedInspectorControls(
|
export default function ProductCollectionAdvancedInspectorControls(
|
||||||
props: Omit< ProductCollectionEditComponentProps, 'preview' >
|
props: ProductCollectionContentProps
|
||||||
) {
|
) {
|
||||||
const { clientId, attributes, setAttributes } = props;
|
const { clientId, attributes, setAttributes } = props;
|
||||||
const { forcePageReload } = attributes;
|
const { forcePageReload } = attributes;
|
||||||
|
|
|
@ -27,7 +27,7 @@ import {
|
||||||
import metadata from '../../block.json';
|
import metadata from '../../block.json';
|
||||||
import { useTracksLocation } from '../../tracks-utils';
|
import { useTracksLocation } from '../../tracks-utils';
|
||||||
import {
|
import {
|
||||||
ProductCollectionEditComponentProps,
|
ProductCollectionContentProps,
|
||||||
ProductCollectionAttributes,
|
ProductCollectionAttributes,
|
||||||
CoreFilterNames,
|
CoreFilterNames,
|
||||||
FilterName,
|
FilterName,
|
||||||
|
@ -58,7 +58,7 @@ const prepareShouldShowFilter =
|
||||||
};
|
};
|
||||||
|
|
||||||
const ProductCollectionInspectorControls = (
|
const ProductCollectionInspectorControls = (
|
||||||
props: ProductCollectionEditComponentProps
|
props: ProductCollectionContentProps
|
||||||
) => {
|
) => {
|
||||||
const { attributes, context, setAttributes } = props;
|
const { attributes, context, setAttributes } = props;
|
||||||
const { query, hideControls, displayLayout } = attributes;
|
const { query, hideControls, displayLayout } = attributes;
|
||||||
|
|
|
@ -18,7 +18,7 @@ import fastDeepEqual from 'fast-deep-equal/es6';
|
||||||
import type {
|
import type {
|
||||||
ProductCollectionAttributes,
|
ProductCollectionAttributes,
|
||||||
ProductCollectionQuery,
|
ProductCollectionQuery,
|
||||||
ProductCollectionEditComponentProps,
|
ProductCollectionContentProps,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
import { DEFAULT_ATTRIBUTES, INNER_BLOCKS_TEMPLATE } from '../constants';
|
import { DEFAULT_ATTRIBUTES, INNER_BLOCKS_TEMPLATE } from '../constants';
|
||||||
import {
|
import {
|
||||||
|
@ -68,7 +68,7 @@ const useQueryId = (
|
||||||
const ProductCollectionContent = ( {
|
const ProductCollectionContent = ( {
|
||||||
preview: { setPreviewState, initialPreviewState } = {},
|
preview: { setPreviewState, initialPreviewState } = {},
|
||||||
...props
|
...props
|
||||||
}: ProductCollectionEditComponentProps ) => {
|
}: ProductCollectionContentProps ) => {
|
||||||
const isInitialAttributesSet = useRef( false );
|
const isInitialAttributesSet = useRef( false );
|
||||||
const {
|
const {
|
||||||
clientId,
|
clientId,
|
||||||
|
|
|
@ -11,10 +11,10 @@ import { setQueryAttribute } from '../../utils';
|
||||||
import DisplaySettingsToolbar from './display-settings-toolbar';
|
import DisplaySettingsToolbar from './display-settings-toolbar';
|
||||||
import DisplayLayoutToolbar from './display-layout-toolbar';
|
import DisplayLayoutToolbar from './display-layout-toolbar';
|
||||||
import CollectionChooserToolbar from './collection-chooser-toolbar';
|
import CollectionChooserToolbar from './collection-chooser-toolbar';
|
||||||
import type { ProductCollectionEditComponentProps } from '../../types';
|
import type { ProductCollectionContentProps } from '../../types';
|
||||||
|
|
||||||
export default function ToolbarControls(
|
export default function ToolbarControls(
|
||||||
props: Omit< ProductCollectionEditComponentProps, 'preview' >
|
props: ProductCollectionContentProps
|
||||||
) {
|
) {
|
||||||
const { attributes, openCollectionSelectionModal, setAttributes } = props;
|
const { attributes, openCollectionSelectionModal, setAttributes } = props;
|
||||||
const { query, displayLayout } = attributes;
|
const { query, displayLayout } = attributes;
|
||||||
|
|
|
@ -14,9 +14,9 @@ export enum ProductCollectionUIStatesInEditor {
|
||||||
PRODUCT_REFERENCE_PICKER = 'product_context_picker',
|
PRODUCT_REFERENCE_PICKER = 'product_context_picker',
|
||||||
VALID_WITH_PREVIEW = 'uses_reference_preview_mode',
|
VALID_WITH_PREVIEW = 'uses_reference_preview_mode',
|
||||||
VALID = 'valid',
|
VALID = 'valid',
|
||||||
|
DELETED_PRODUCT_REFERENCE = 'deleted_product_reference',
|
||||||
// Future states
|
// Future states
|
||||||
// INVALID = 'invalid',
|
// INVALID = 'invalid',
|
||||||
// DELETED_PRODUCT_REFERENCE = 'deleted_product_reference',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ProductCollectionAttributes {
|
export interface ProductCollectionAttributes {
|
||||||
|
@ -110,7 +110,6 @@ export interface ProductCollectionQuery {
|
||||||
|
|
||||||
export type ProductCollectionEditComponentProps =
|
export type ProductCollectionEditComponentProps =
|
||||||
BlockEditProps< ProductCollectionAttributes > & {
|
BlockEditProps< ProductCollectionAttributes > & {
|
||||||
openCollectionSelectionModal: () => void;
|
|
||||||
preview?: {
|
preview?: {
|
||||||
initialPreviewState?: PreviewState;
|
initialPreviewState?: PreviewState;
|
||||||
setPreviewState?: SetPreviewState;
|
setPreviewState?: SetPreviewState;
|
||||||
|
@ -119,8 +118,13 @@ export type ProductCollectionEditComponentProps =
|
||||||
context: {
|
context: {
|
||||||
templateSlug: string;
|
templateSlug: string;
|
||||||
};
|
};
|
||||||
isUsingReferencePreviewMode: boolean;
|
};
|
||||||
|
|
||||||
|
export type ProductCollectionContentProps =
|
||||||
|
ProductCollectionEditComponentProps & {
|
||||||
location: WooCommerceBlockLocation;
|
location: WooCommerceBlockLocation;
|
||||||
|
isUsingReferencePreviewMode: boolean;
|
||||||
|
openCollectionSelectionModal: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TProductCollectionOrder = 'asc' | 'desc';
|
export type TProductCollectionOrder = 'asc' | 'desc';
|
||||||
|
|
|
@ -3,10 +3,16 @@
|
||||||
*/
|
*/
|
||||||
import { store as blockEditorStore } from '@wordpress/block-editor';
|
import { store as blockEditorStore } from '@wordpress/block-editor';
|
||||||
import { addFilter } from '@wordpress/hooks';
|
import { addFilter } from '@wordpress/hooks';
|
||||||
import { select } from '@wordpress/data';
|
import { select, useSelect } from '@wordpress/data';
|
||||||
|
import { store as coreDataStore } from '@wordpress/core-data';
|
||||||
import { isWpVersion } from '@woocommerce/settings';
|
import { isWpVersion } from '@woocommerce/settings';
|
||||||
import type { BlockEditProps, Block } from '@wordpress/blocks';
|
import type { BlockEditProps, Block } from '@wordpress/blocks';
|
||||||
import { useEffect, useLayoutEffect, useState } from '@wordpress/element';
|
import {
|
||||||
|
useEffect,
|
||||||
|
useLayoutEffect,
|
||||||
|
useState,
|
||||||
|
useMemo,
|
||||||
|
} from '@wordpress/element';
|
||||||
import { __ } from '@wordpress/i18n';
|
import { __ } from '@wordpress/i18n';
|
||||||
import type { ProductResponseItem } from '@woocommerce/types';
|
import type { ProductResponseItem } from '@woocommerce/types';
|
||||||
import { getProduct } from '@woocommerce/editor-components/utils';
|
import { getProduct } from '@woocommerce/editor-components/utils';
|
||||||
|
@ -193,7 +199,7 @@ export const getUsesReferencePreviewMessage = (
|
||||||
return '';
|
return '';
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getProductCollectionUIStateInEditor = ( {
|
export const useProductCollectionUIState = ( {
|
||||||
location,
|
location,
|
||||||
usesReference,
|
usesReference,
|
||||||
attributes,
|
attributes,
|
||||||
|
@ -203,59 +209,111 @@ export const getProductCollectionUIStateInEditor = ( {
|
||||||
usesReference?: string[] | undefined;
|
usesReference?: string[] | undefined;
|
||||||
attributes: ProductCollectionAttributes;
|
attributes: ProductCollectionAttributes;
|
||||||
hasInnerBlocks: boolean;
|
hasInnerBlocks: boolean;
|
||||||
} ): ProductCollectionUIStatesInEditor => {
|
} ) => {
|
||||||
const isInRequiredLocation = usesReference?.includes( location.type );
|
// Fetch product to check if it's deleted.
|
||||||
const isCollectionSelected = !! attributes.collection;
|
// `product` will be undefined if it doesn't exist.
|
||||||
|
const productId = attributes.query?.productReference;
|
||||||
|
const { product, hasResolved } = useSelect(
|
||||||
|
( selectFunc ) => {
|
||||||
|
if ( ! productId ) {
|
||||||
|
return { product: null, hasResolved: true };
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
const { getEntityRecord, hasFinishedResolution } =
|
||||||
* Case 1: Product context picker
|
selectFunc( coreDataStore );
|
||||||
*/
|
const selectorArgs = [ 'postType', 'product', productId ];
|
||||||
const isProductContextRequired = usesReference?.includes( 'product' );
|
return {
|
||||||
const isProductContextSelected =
|
product: getEntityRecord( ...selectorArgs ),
|
||||||
( attributes.query?.productReference ?? null ) !== null;
|
hasResolved: hasFinishedResolution(
|
||||||
if (
|
'getEntityRecord',
|
||||||
isCollectionSelected &&
|
selectorArgs
|
||||||
isProductContextRequired &&
|
),
|
||||||
! isInRequiredLocation &&
|
};
|
||||||
! isProductContextSelected
|
},
|
||||||
) {
|
[ productId ]
|
||||||
return ProductCollectionUIStatesInEditor.PRODUCT_REFERENCE_PICKER;
|
);
|
||||||
}
|
|
||||||
|
const productCollectionUIStateInEditor = useMemo( () => {
|
||||||
|
const isInRequiredLocation = usesReference?.includes( location.type );
|
||||||
|
const isCollectionSelected = !! attributes.collection;
|
||||||
|
|
||||||
/**
|
|
||||||
* Case 2: Preview mode - based on `usesReference` value
|
|
||||||
*/
|
|
||||||
if ( isInRequiredLocation ) {
|
|
||||||
/**
|
/**
|
||||||
* Block shouldn't be in preview mode when:
|
* Case 1: Product context picker
|
||||||
* 1. Current location is archive and termId is available.
|
|
||||||
* 2. Current location is product and productId is available.
|
|
||||||
*
|
|
||||||
* Because in these cases, we have required context on the editor side.
|
|
||||||
*/
|
*/
|
||||||
const isArchiveLocationWithTermId =
|
const isProductContextRequired = usesReference?.includes( 'product' );
|
||||||
location.type === LocationType.Archive &&
|
const isProductContextSelected =
|
||||||
( location.sourceData?.termId ?? null ) !== null;
|
( attributes.query?.productReference ?? null ) !== null;
|
||||||
const isProductLocationWithProductId =
|
|
||||||
location.type === LocationType.Product &&
|
|
||||||
( location.sourceData?.productId ?? null ) !== null;
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
! isArchiveLocationWithTermId &&
|
isCollectionSelected &&
|
||||||
! isProductLocationWithProductId
|
isProductContextRequired &&
|
||||||
|
! isInRequiredLocation &&
|
||||||
|
! isProductContextSelected
|
||||||
) {
|
) {
|
||||||
return ProductCollectionUIStatesInEditor.VALID_WITH_PREVIEW;
|
return ProductCollectionUIStatesInEditor.PRODUCT_REFERENCE_PICKER;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
// Case 2: Deleted product reference
|
||||||
* Case 3: Collection chooser
|
if (
|
||||||
*/
|
isCollectionSelected &&
|
||||||
if ( ! hasInnerBlocks && ! isCollectionSelected ) {
|
isProductContextRequired &&
|
||||||
return ProductCollectionUIStatesInEditor.COLLECTION_PICKER;
|
! isInRequiredLocation &&
|
||||||
}
|
isProductContextSelected
|
||||||
|
) {
|
||||||
|
const isProductDeleted =
|
||||||
|
productId &&
|
||||||
|
( product === undefined || product?.status === 'trash' );
|
||||||
|
if ( isProductDeleted ) {
|
||||||
|
return ProductCollectionUIStatesInEditor.DELETED_PRODUCT_REFERENCE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return ProductCollectionUIStatesInEditor.VALID;
|
/**
|
||||||
|
* Case 3: Preview mode - based on `usesReference` value
|
||||||
|
*/
|
||||||
|
if ( isInRequiredLocation ) {
|
||||||
|
/**
|
||||||
|
* Block shouldn't be in preview mode when:
|
||||||
|
* 1. Current location is archive and termId is available.
|
||||||
|
* 2. Current location is product and productId is available.
|
||||||
|
*
|
||||||
|
* Because in these cases, we have required context on the editor side.
|
||||||
|
*/
|
||||||
|
const isArchiveLocationWithTermId =
|
||||||
|
location.type === LocationType.Archive &&
|
||||||
|
( location.sourceData?.termId ?? null ) !== null;
|
||||||
|
const isProductLocationWithProductId =
|
||||||
|
location.type === LocationType.Product &&
|
||||||
|
( location.sourceData?.productId ?? null ) !== null;
|
||||||
|
|
||||||
|
if (
|
||||||
|
! isArchiveLocationWithTermId &&
|
||||||
|
! isProductLocationWithProductId
|
||||||
|
) {
|
||||||
|
return ProductCollectionUIStatesInEditor.VALID_WITH_PREVIEW;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Case 4: Collection chooser
|
||||||
|
*/
|
||||||
|
if ( ! hasInnerBlocks && ! isCollectionSelected ) {
|
||||||
|
return ProductCollectionUIStatesInEditor.COLLECTION_PICKER;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ProductCollectionUIStatesInEditor.VALID;
|
||||||
|
}, [
|
||||||
|
location.type,
|
||||||
|
location.sourceData?.termId,
|
||||||
|
location.sourceData?.productId,
|
||||||
|
usesReference,
|
||||||
|
attributes.collection,
|
||||||
|
productId,
|
||||||
|
product,
|
||||||
|
hasInnerBlocks,
|
||||||
|
attributes.query?.productReference,
|
||||||
|
] );
|
||||||
|
|
||||||
|
return { productCollectionUIStateInEditor, isLoading: ! hasResolved };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useSetPreviewState = ( {
|
export const useSetPreviewState = ( {
|
||||||
|
|
|
@ -10,6 +10,10 @@ import {
|
||||||
import { BlockEditProps, InnerBlockTemplate } from '@wordpress/blocks';
|
import { BlockEditProps, InnerBlockTemplate } from '@wordpress/blocks';
|
||||||
import { useEffect } from '@wordpress/element';
|
import { useEffect } from '@wordpress/element';
|
||||||
import { useSelect } from '@wordpress/data';
|
import { useSelect } from '@wordpress/data';
|
||||||
|
import ErrorPlaceholder, {
|
||||||
|
ErrorObject,
|
||||||
|
} from '@woocommerce/editor-components/error-placeholder';
|
||||||
|
import { __ } from '@wordpress/i18n';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
|
@ -132,14 +136,16 @@ export const Edit = ( {
|
||||||
|
|
||||||
useEffect( () => {
|
useEffect( () => {
|
||||||
const mode = getMode( currentTemplateId, templateType );
|
const mode = getMode( currentTemplateId, templateType );
|
||||||
|
const newProductGalleryClientId =
|
||||||
|
attributes.productGalleryClientId || clientId;
|
||||||
|
|
||||||
setAttributes( {
|
setAttributes( {
|
||||||
...attributes,
|
...attributes,
|
||||||
mode,
|
mode,
|
||||||
productGalleryClientId: clientId,
|
productGalleryClientId: newProductGalleryClientId,
|
||||||
} );
|
} );
|
||||||
// Move the Thumbnails block to the correct above or below the Large Image based on the thumbnailsPosition attribute.
|
// Move the Thumbnails block to the correct above or below the Large Image based on the thumbnailsPosition attribute.
|
||||||
moveInnerBlocksToPosition( attributes, clientId );
|
moveInnerBlocksToPosition( attributes, newProductGalleryClientId );
|
||||||
}, [
|
}, [
|
||||||
setAttributes,
|
setAttributes,
|
||||||
attributes,
|
attributes,
|
||||||
|
@ -148,6 +154,18 @@ export const Edit = ( {
|
||||||
templateType,
|
templateType,
|
||||||
] );
|
] );
|
||||||
|
|
||||||
|
if ( attributes.productGalleryClientId !== clientId ) {
|
||||||
|
const error = {
|
||||||
|
message: __(
|
||||||
|
'productGalleryClientId and clientId codes mismatch.',
|
||||||
|
'woocommerce'
|
||||||
|
),
|
||||||
|
type: 'general',
|
||||||
|
} as ErrorObject;
|
||||||
|
|
||||||
|
return <ErrorPlaceholder error={ error } isLoading={ false } />;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div { ...blockProps }>
|
<div { ...blockProps }>
|
||||||
<InspectorControls>
|
<InspectorControls>
|
||||||
|
|
|
@ -136,10 +136,10 @@ export const moveInnerBlocksToPosition = (
|
||||||
): void => {
|
): void => {
|
||||||
const { getBlock, getBlockRootClientId, getBlockIndex } =
|
const { getBlock, getBlockRootClientId, getBlockIndex } =
|
||||||
select( 'core/block-editor' );
|
select( 'core/block-editor' );
|
||||||
const { moveBlockToPosition } = dispatch( 'core/block-editor' );
|
|
||||||
const productGalleryBlock = getBlock( clientId );
|
const productGalleryBlock = getBlock( clientId );
|
||||||
|
|
||||||
if ( productGalleryBlock ) {
|
if ( productGalleryBlock?.name === 'woocommerce/product-gallery' ) {
|
||||||
|
const { moveBlockToPosition } = dispatch( 'core/block-editor' );
|
||||||
const previousLayout = productGalleryBlock.innerBlocks.length
|
const previousLayout = productGalleryBlock.innerBlocks.length
|
||||||
? productGalleryBlock.innerBlocks[ 0 ].attributes.layout
|
? productGalleryBlock.innerBlocks[ 0 ].attributes.layout
|
||||||
: null;
|
: null;
|
||||||
|
|
|
@ -207,7 +207,8 @@ class ProductCollectionPage {
|
||||||
}
|
}
|
||||||
|
|
||||||
async chooseProductInEditorProductPickerIfAvailable(
|
async chooseProductInEditorProductPickerIfAvailable(
|
||||||
pageReference: Page | FrameLocator
|
pageReference: Page | FrameLocator,
|
||||||
|
productName = 'Album'
|
||||||
) {
|
) {
|
||||||
const editorProductPicker = pageReference.locator(
|
const editorProductPicker = pageReference.locator(
|
||||||
SELECTORS.productPicker
|
SELECTORS.productPicker
|
||||||
|
@ -217,7 +218,7 @@ class ProductCollectionPage {
|
||||||
await editorProductPicker
|
await editorProductPicker
|
||||||
.locator( 'label' )
|
.locator( 'label' )
|
||||||
.filter( {
|
.filter( {
|
||||||
hasText: 'Album',
|
hasText: productName,
|
||||||
} )
|
} )
|
||||||
.click();
|
.click();
|
||||||
}
|
}
|
||||||
|
|
|
@ -356,4 +356,84 @@ test.describe( 'Product Collection registration', () => {
|
||||||
await expect( previewButtonLocator ).toBeHidden();
|
await expect( previewButtonLocator ).toBeHidden();
|
||||||
} );
|
} );
|
||||||
} );
|
} );
|
||||||
|
|
||||||
|
test( 'Product picker should be shown when selected product is deleted', async ( {
|
||||||
|
pageObject,
|
||||||
|
admin,
|
||||||
|
editor,
|
||||||
|
requestUtils,
|
||||||
|
page,
|
||||||
|
} ) => {
|
||||||
|
// Add a new test product to the database
|
||||||
|
let testProductId: number | null = null;
|
||||||
|
const newProduct = await requestUtils.rest( {
|
||||||
|
method: 'POST',
|
||||||
|
path: 'wc/v3/products',
|
||||||
|
data: {
|
||||||
|
name: 'A Test Product',
|
||||||
|
price: 10,
|
||||||
|
},
|
||||||
|
} );
|
||||||
|
testProductId = newProduct.id;
|
||||||
|
|
||||||
|
await admin.createNewPost();
|
||||||
|
await pageObject.insertProductCollection();
|
||||||
|
await pageObject.chooseCollectionInPost(
|
||||||
|
'myCustomCollectionWithProductContext'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Verify that product picker is shown in Editor
|
||||||
|
const editorProductPicker = editor.canvas.locator(
|
||||||
|
SELECTORS.productPicker
|
||||||
|
);
|
||||||
|
await expect( editorProductPicker ).toBeVisible();
|
||||||
|
|
||||||
|
// Once a product is selected, the product picker should be hidden
|
||||||
|
await pageObject.chooseProductInEditorProductPickerIfAvailable(
|
||||||
|
editor.canvas,
|
||||||
|
'A Test Product'
|
||||||
|
);
|
||||||
|
await expect( editorProductPicker ).toBeHidden();
|
||||||
|
|
||||||
|
await editor.saveDraft();
|
||||||
|
|
||||||
|
// Delete the product
|
||||||
|
await requestUtils.rest( {
|
||||||
|
method: 'DELETE',
|
||||||
|
path: `wc/v3/products/${ testProductId }`,
|
||||||
|
} );
|
||||||
|
|
||||||
|
// Product picker should be shown in Editor
|
||||||
|
await admin.page.reload();
|
||||||
|
const deletedProductPicker = editor.canvas.getByText(
|
||||||
|
'Previously selected product'
|
||||||
|
);
|
||||||
|
await expect( deletedProductPicker ).toBeVisible();
|
||||||
|
|
||||||
|
// Change status from "trash" to "publish"
|
||||||
|
await requestUtils.rest( {
|
||||||
|
method: 'PUT',
|
||||||
|
path: `wc/v3/products/${ testProductId }`,
|
||||||
|
data: {
|
||||||
|
status: 'publish',
|
||||||
|
},
|
||||||
|
} );
|
||||||
|
|
||||||
|
// Product Picker shouldn't be shown as product is available now
|
||||||
|
await page.reload();
|
||||||
|
await expect( editorProductPicker ).toBeHidden();
|
||||||
|
|
||||||
|
// Delete the product from database, instead of trashing it
|
||||||
|
await requestUtils.rest( {
|
||||||
|
method: 'DELETE',
|
||||||
|
path: `wc/v3/products/${ testProductId }`,
|
||||||
|
params: {
|
||||||
|
// Bypass trash and permanently delete the product
|
||||||
|
force: true,
|
||||||
|
},
|
||||||
|
} );
|
||||||
|
|
||||||
|
// Product picker should be shown in Editor
|
||||||
|
await expect( deletedProductPicker ).toBeVisible();
|
||||||
|
} );
|
||||||
} );
|
} );
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
Significance: minor
|
||||||
|
Type: add
|
||||||
|
|
||||||
|
Product Collection: Added Editor UI for missing product reference
|
|
@ -0,0 +1,4 @@
|
||||||
|
Significance: patch
|
||||||
|
Type: update
|
||||||
|
|
||||||
|
Expand the e2e suite we're running on WPCOM part #2.
|
|
@ -0,0 +1,4 @@
|
||||||
|
Significance: patch
|
||||||
|
Type: fix
|
||||||
|
|
||||||
|
Fix error when adding the Product Gallery (Beta) block into a pattern
|
|
@ -110,11 +110,14 @@ class ProductGallery extends AbstractBlock {
|
||||||
* @return string Rendered block type output.
|
* @return string Rendered block type output.
|
||||||
*/
|
*/
|
||||||
protected function render( $attributes, $content, $block ) {
|
protected function render( $attributes, $content, $block ) {
|
||||||
$post_id = $block->context['postId'] ?? '';
|
$post_id = $block->context['postId'] ?? '';
|
||||||
|
$product = wc_get_product( $post_id );
|
||||||
|
if ( ! $product instanceof \WC_Product ) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
$product_gallery_images = ProductGalleryUtils::get_product_gallery_images( $post_id, 'thumbnail', array() );
|
$product_gallery_images = ProductGalleryUtils::get_product_gallery_images( $post_id, 'thumbnail', array() );
|
||||||
$classname_single_image = '';
|
$classname_single_image = '';
|
||||||
// This is a temporary solution. We have to refactor this code when the block will have to be addable on every page/post https://github.com/woocommerce/woocommerce-blocks/issues/10882.
|
|
||||||
global $product;
|
|
||||||
|
|
||||||
if ( count( $product_gallery_images ) < 2 ) {
|
if ( count( $product_gallery_images ) < 2 ) {
|
||||||
// The gallery consists of a single image.
|
// The gallery consists of a single image.
|
||||||
|
@ -124,8 +127,6 @@ class ProductGallery extends AbstractBlock {
|
||||||
$number_of_thumbnails = $block->attributes['thumbnailsNumberOfThumbnails'] ?? 0;
|
$number_of_thumbnails = $block->attributes['thumbnailsNumberOfThumbnails'] ?? 0;
|
||||||
$classname = $attributes['className'] ?? '';
|
$classname = $attributes['className'] ?? '';
|
||||||
$dialog = isset( $attributes['mode'] ) && 'full' !== $attributes['mode'] ? $this->render_dialog() : '';
|
$dialog = isset( $attributes['mode'] ) && 'full' !== $attributes['mode'] ? $this->render_dialog() : '';
|
||||||
$post_id = $block->context['postId'] ?? '';
|
|
||||||
$product = wc_get_product( $post_id );
|
|
||||||
$product_gallery_first_image = ProductGalleryUtils::get_product_gallery_image_ids( $product, 1 );
|
$product_gallery_first_image = ProductGalleryUtils::get_product_gallery_image_ids( $product, 1 );
|
||||||
$product_gallery_first_image_id = reset( $product_gallery_first_image );
|
$product_gallery_first_image_id = reset( $product_gallery_first_image );
|
||||||
$product_id = strval( $product->get_id() );
|
$product_id = strval( $product->get_id() );
|
||||||
|
|
|
@ -26,7 +26,7 @@ class ProductGalleryUtils {
|
||||||
$product_gallery_images = array();
|
$product_gallery_images = array();
|
||||||
$product = wc_get_product( $post_id );
|
$product = wc_get_product( $post_id );
|
||||||
|
|
||||||
if ( $product ) {
|
if ( $product instanceof \WC_Product ) {
|
||||||
$all_product_gallery_image_ids = self::get_product_gallery_image_ids( $product );
|
$all_product_gallery_image_ids = self::get_product_gallery_image_ids( $product );
|
||||||
|
|
||||||
if ( 'full' === $size || 'full' !== $size && count( $all_product_gallery_image_ids ) > 1 ) {
|
if ( 'full' === $size || 'full' !== $size && count( $all_product_gallery_image_ids ) > 1 ) {
|
||||||
|
|
|
@ -15,6 +15,14 @@ config = {
|
||||||
'**/admin-tasks/**/*.spec.js',
|
'**/admin-tasks/**/*.spec.js',
|
||||||
'**/shopper/**/*.spec.js',
|
'**/shopper/**/*.spec.js',
|
||||||
'**/api-tests/**/*.test.js',
|
'**/api-tests/**/*.test.js',
|
||||||
|
'**/merchant/products/add-variable-product/**/*.spec.js',
|
||||||
|
'**/merchant/command-palette.spec.js',
|
||||||
|
'**/merchant/create-cart-block.spec.js',
|
||||||
|
'**/merchant/create-checkout-block.spec.js',
|
||||||
|
'**/merchant/create-coupon.spec.js',
|
||||||
|
'**/merchant/create-order.spec.js',
|
||||||
|
'**/merchant/create-page.spec.js',
|
||||||
|
'**/merchant/create-post.spec.js',
|
||||||
],
|
],
|
||||||
grepInvert: /@skip-on-default-wpcom/,
|
grepInvert: /@skip-on-default-wpcom/,
|
||||||
},
|
},
|
||||||
|
|
|
@ -149,9 +149,11 @@ test.describe(
|
||||||
.locator( 'legend' )
|
.locator( 'legend' )
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
await expect(
|
await expect(
|
||||||
page.locator(
|
page
|
||||||
'.wp-block-woocommerce-checkout-order-summary-block'
|
.locator(
|
||||||
)
|
'.wp-block-woocommerce-checkout-order-summary-block'
|
||||||
|
)
|
||||||
|
.first()
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
await expect(
|
await expect(
|
||||||
page.locator( '.wc-block-components-address-form' ).first()
|
page.locator( '.wc-block-components-address-form' ).first()
|
||||||
|
|
|
@ -39,102 +39,110 @@ const test = baseTest.extend( {
|
||||||
},
|
},
|
||||||
} );
|
} );
|
||||||
|
|
||||||
test.describe( 'Coupon management', { tag: '@services' }, () => {
|
test.describe(
|
||||||
for ( const couponType of Object.keys( couponData ) ) {
|
'Coupon management',
|
||||||
test( `can create new ${ couponType } coupon`, async ( {
|
{ tag: [ '@services', '@skip-on-default-wpcom' ] },
|
||||||
page,
|
() => {
|
||||||
coupon,
|
for ( const couponType of Object.keys( couponData ) ) {
|
||||||
} ) => {
|
test( `can create new ${ couponType } coupon`, async ( {
|
||||||
await test.step( 'add new coupon', async () => {
|
page,
|
||||||
await page.goto(
|
coupon,
|
||||||
'wp-admin/post-new.php?post_type=shop_coupon'
|
} ) => {
|
||||||
);
|
await test.step( 'add new coupon', async () => {
|
||||||
await page
|
await page.goto(
|
||||||
.getByLabel( 'Coupon code' )
|
'wp-admin/post-new.php?post_type=shop_coupon'
|
||||||
.fill( couponData[ couponType ].code );
|
);
|
||||||
await page
|
await page
|
||||||
.getByPlaceholder( 'Description (optional)' )
|
.getByLabel( 'Coupon code' )
|
||||||
.fill( couponData[ couponType ].description );
|
.fill( couponData[ couponType ].code );
|
||||||
await page
|
await page
|
||||||
.getByPlaceholder( '0' )
|
.getByPlaceholder( 'Description (optional)' )
|
||||||
.fill( couponData[ couponType ].amount );
|
.fill( couponData[ couponType ].description );
|
||||||
|
await page
|
||||||
|
.getByPlaceholder( '0' )
|
||||||
|
.fill( couponData[ couponType ].amount );
|
||||||
|
|
||||||
// set expiry date if it was provided
|
// set expiry date if it was provided
|
||||||
|
if ( couponData[ couponType ].expiryDate ) {
|
||||||
|
await page
|
||||||
|
.getByPlaceholder( 'yyyy-mm-dd' )
|
||||||
|
.fill( couponData[ couponType ].expiryDate );
|
||||||
|
}
|
||||||
|
|
||||||
|
// be explicit about whether free shipping is allowed
|
||||||
|
if ( couponData[ couponType ].freeShipping ) {
|
||||||
|
await page.getByLabel( 'Allow free shipping' ).check();
|
||||||
|
} else {
|
||||||
|
await page
|
||||||
|
.getByLabel( 'Allow free shipping' )
|
||||||
|
.uncheck();
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
|
||||||
|
// publish the coupon and retrieve the id
|
||||||
|
await test.step( 'publish the coupon', async () => {
|
||||||
|
await expect(
|
||||||
|
page.getByRole( 'link', { name: 'Move to Trash' } )
|
||||||
|
).toBeVisible();
|
||||||
|
await page
|
||||||
|
.getByRole( 'button', { name: 'Publish', exact: true } )
|
||||||
|
.click();
|
||||||
|
await expect(
|
||||||
|
page.getByText( 'Coupon updated.' )
|
||||||
|
).toBeVisible();
|
||||||
|
coupon.id = page.url().match( /(?<=post=)\d+/ )[ 0 ];
|
||||||
|
expect( coupon.id ).toBeDefined();
|
||||||
|
} );
|
||||||
|
|
||||||
|
// verify the creation of the coupon and details
|
||||||
|
await test.step( 'verify coupon creation', async () => {
|
||||||
|
await page.goto(
|
||||||
|
'wp-admin/edit.php?post_type=shop_coupon'
|
||||||
|
);
|
||||||
|
await expect(
|
||||||
|
page.getByRole( 'cell', {
|
||||||
|
name: couponData[ couponType ].code,
|
||||||
|
} )
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.getByRole( 'cell', {
|
||||||
|
name: couponData[ couponType ].description,
|
||||||
|
} )
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.getByRole( 'cell', {
|
||||||
|
name: couponData[ couponType ].amount,
|
||||||
|
exact: true,
|
||||||
|
} )
|
||||||
|
).toBeVisible();
|
||||||
|
} );
|
||||||
|
|
||||||
|
// check expiry date if it was set
|
||||||
if ( couponData[ couponType ].expiryDate ) {
|
if ( couponData[ couponType ].expiryDate ) {
|
||||||
await page
|
await test.step( 'verify coupon expiry date', async () => {
|
||||||
.getByPlaceholder( 'yyyy-mm-dd' )
|
await page
|
||||||
.fill( couponData[ couponType ].expiryDate );
|
.getByText( couponData[ couponType ].code )
|
||||||
|
.last()
|
||||||
|
.click();
|
||||||
|
await expect(
|
||||||
|
page.getByPlaceholder( 'yyyy-mm-dd' )
|
||||||
|
).toHaveValue( couponData[ couponType ].expiryDate );
|
||||||
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
// be explicit about whether free shipping is allowed
|
// if it was a free shipping coupon check that
|
||||||
if ( couponData[ couponType ].freeShipping ) {
|
if ( couponData[ couponType ].freeShipping ) {
|
||||||
await page.getByLabel( 'Allow free shipping' ).check();
|
await test.step( 'verify free shipping', async () => {
|
||||||
} else {
|
await page
|
||||||
await page.getByLabel( 'Allow free shipping' ).uncheck();
|
.getByText( couponData[ couponType ].code )
|
||||||
|
.last()
|
||||||
|
.click();
|
||||||
|
await expect(
|
||||||
|
page.getByLabel( 'Allow free shipping' )
|
||||||
|
).toBeChecked();
|
||||||
|
} );
|
||||||
}
|
}
|
||||||
} );
|
} );
|
||||||
|
}
|
||||||
// publish the coupon and retrieve the id
|
|
||||||
await test.step( 'publish the coupon', async () => {
|
|
||||||
await expect(
|
|
||||||
page.getByRole( 'link', { name: 'Move to Trash' } )
|
|
||||||
).toBeVisible();
|
|
||||||
await page
|
|
||||||
.getByRole( 'button', { name: 'Publish', exact: true } )
|
|
||||||
.click();
|
|
||||||
await expect(
|
|
||||||
page.getByText( 'Coupon updated.' )
|
|
||||||
).toBeVisible();
|
|
||||||
coupon.id = page.url().match( /(?<=post=)\d+/ )[ 0 ];
|
|
||||||
expect( coupon.id ).toBeDefined();
|
|
||||||
} );
|
|
||||||
|
|
||||||
// verify the creation of the coupon and details
|
|
||||||
await test.step( 'verify coupon creation', async () => {
|
|
||||||
await page.goto( 'wp-admin/edit.php?post_type=shop_coupon' );
|
|
||||||
await expect(
|
|
||||||
page.getByRole( 'cell', {
|
|
||||||
name: couponData[ couponType ].code,
|
|
||||||
} )
|
|
||||||
).toBeVisible();
|
|
||||||
await expect(
|
|
||||||
page.getByRole( 'cell', {
|
|
||||||
name: couponData[ couponType ].description,
|
|
||||||
} )
|
|
||||||
).toBeVisible();
|
|
||||||
await expect(
|
|
||||||
page.getByRole( 'cell', {
|
|
||||||
name: couponData[ couponType ].amount,
|
|
||||||
exact: true,
|
|
||||||
} )
|
|
||||||
).toBeVisible();
|
|
||||||
} );
|
|
||||||
|
|
||||||
// check expiry date if it was set
|
|
||||||
if ( couponData[ couponType ].expiryDate ) {
|
|
||||||
await test.step( 'verify coupon expiry date', async () => {
|
|
||||||
await page
|
|
||||||
.getByText( couponData[ couponType ].code )
|
|
||||||
.last()
|
|
||||||
.click();
|
|
||||||
await expect(
|
|
||||||
page.getByPlaceholder( 'yyyy-mm-dd' )
|
|
||||||
).toHaveValue( couponData[ couponType ].expiryDate );
|
|
||||||
} );
|
|
||||||
}
|
|
||||||
|
|
||||||
// if it was a free shipping coupon check that
|
|
||||||
if ( couponData[ couponType ].freeShipping ) {
|
|
||||||
await test.step( 'verify free shipping', async () => {
|
|
||||||
await page
|
|
||||||
.getByText( couponData[ couponType ].code )
|
|
||||||
.last()
|
|
||||||
.click();
|
|
||||||
await expect(
|
|
||||||
page.getByLabel( 'Allow free shipping' )
|
|
||||||
).toBeChecked();
|
|
||||||
} );
|
|
||||||
}
|
|
||||||
} );
|
|
||||||
}
|
}
|
||||||
} );
|
);
|
||||||
|
|
|
@ -84,105 +84,117 @@ test.describe( 'Add variations', { tag: '@gutenberg' }, () => {
|
||||||
}
|
}
|
||||||
} );
|
} );
|
||||||
|
|
||||||
test( 'can manually add a variation', async ( { page } ) => {
|
test(
|
||||||
await test.step( `Open "Edit product" page of product id ${ productId_addManually }`, async () => {
|
'can manually add a variation',
|
||||||
await page.goto(
|
{ tag: '@skip-on-default-wpcom' },
|
||||||
`/wp-admin/post.php?post=${ productId_addManually }&action=edit`
|
async ( { page } ) => {
|
||||||
);
|
await test.step( `Open "Edit product" page of product id ${ productId_addManually }`, async () => {
|
||||||
} );
|
await page.goto(
|
||||||
|
`/wp-admin/post.php?post=${ productId_addManually }&action=edit`
|
||||||
// hook up the woocommerce_variations_added jQuery trigger so we can check if it's fired
|
);
|
||||||
await test.step( 'Hook up the woocommerce_variations_added jQuery trigger', async () => {
|
|
||||||
await page.evaluate( () => {
|
|
||||||
window.woocommerceVariationsAddedFunctionCalls = [];
|
|
||||||
|
|
||||||
window
|
|
||||||
.jQuery( '#variable_product_options' )
|
|
||||||
.on( 'woocommerce_variations_added', ( event, data ) => {
|
|
||||||
window.woocommerceVariationsAddedFunctionCalls.push( [
|
|
||||||
event,
|
|
||||||
data,
|
|
||||||
] );
|
|
||||||
} );
|
|
||||||
} );
|
} );
|
||||||
} );
|
|
||||||
|
|
||||||
await test.step( 'Click on the "Variations" tab.', async () => {
|
// hook up the woocommerce_variations_added jQuery trigger so we can check if it's fired
|
||||||
await page.locator( '.variations_tab' ).click();
|
await test.step( 'Hook up the woocommerce_variations_added jQuery trigger', async () => {
|
||||||
} );
|
await page.evaluate( () => {
|
||||||
|
window.woocommerceVariationsAddedFunctionCalls = [];
|
||||||
|
|
||||||
await test.step( `Manually add ${ variationsToManuallyCreate.length } variations`, async () => {
|
window
|
||||||
const variationRows = page.locator( '.woocommerce_variation h3' );
|
.jQuery( '#variable_product_options' )
|
||||||
let variationRowsCount = await variationRows.count();
|
.on(
|
||||||
const originalVariationRowsCount = variationRowsCount;
|
'woocommerce_variations_added',
|
||||||
|
( event, data ) => {
|
||||||
for ( const variationToCreate of variationsToManuallyCreate ) {
|
window.woocommerceVariationsAddedFunctionCalls.push(
|
||||||
await test.step( 'Click "Add manually"', async () => {
|
[ event, data ]
|
||||||
const addManuallyButton = page.getByRole( 'button', {
|
);
|
||||||
name: 'Add manually',
|
}
|
||||||
} );
|
|
||||||
|
|
||||||
await addManuallyButton.click();
|
|
||||||
|
|
||||||
await expect( variationRows ).toHaveCount(
|
|
||||||
++variationRowsCount
|
|
||||||
);
|
|
||||||
|
|
||||||
// verify that the woocommerce_variations_added jQuery trigger was fired
|
|
||||||
const woocommerceVariationsAddedFunctionCalls =
|
|
||||||
await page.evaluate(
|
|
||||||
() => window.woocommerceVariationsAddedFunctionCalls
|
|
||||||
);
|
);
|
||||||
expect(
|
|
||||||
woocommerceVariationsAddedFunctionCalls.length
|
|
||||||
).toEqual(
|
|
||||||
variationRowsCount - originalVariationRowsCount
|
|
||||||
);
|
|
||||||
} );
|
} );
|
||||||
|
} );
|
||||||
|
|
||||||
for ( const attributeValue of variationToCreate ) {
|
await test.step( 'Click on the "Variations" tab.', async () => {
|
||||||
const attributeName = productAttributes.find(
|
await page.locator( '.variations_tab' ).click();
|
||||||
( { options } ) => options.includes( attributeValue )
|
} );
|
||||||
).name;
|
|
||||||
const addAttributeMenu = variationRows
|
await test.step( `Manually add ${ variationsToManuallyCreate.length } variations`, async () => {
|
||||||
.nth( 0 )
|
const variationRows = page.locator(
|
||||||
.locator( 'select', {
|
'.woocommerce_variation h3'
|
||||||
has: page.locator( 'option', {
|
);
|
||||||
hasText: attributeValue,
|
let variationRowsCount = await variationRows.count();
|
||||||
} ),
|
const originalVariationRowsCount = variationRowsCount;
|
||||||
|
|
||||||
|
for ( const variationToCreate of variationsToManuallyCreate ) {
|
||||||
|
await test.step( 'Click "Add manually"', async () => {
|
||||||
|
const addManuallyButton = page.getByRole( 'button', {
|
||||||
|
name: 'Add manually',
|
||||||
} );
|
} );
|
||||||
|
|
||||||
await test.step( `Select "${ attributeValue }" from the "${ attributeName }" attribute menu`, async () => {
|
await addManuallyButton.click();
|
||||||
await addAttributeMenu.selectOption( attributeValue );
|
|
||||||
|
await expect( variationRows ).toHaveCount(
|
||||||
|
++variationRowsCount
|
||||||
|
);
|
||||||
|
|
||||||
|
// verify that the woocommerce_variations_added jQuery trigger was fired
|
||||||
|
const woocommerceVariationsAddedFunctionCalls =
|
||||||
|
await page.evaluate(
|
||||||
|
() =>
|
||||||
|
window.woocommerceVariationsAddedFunctionCalls
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
woocommerceVariationsAddedFunctionCalls.length
|
||||||
|
).toEqual(
|
||||||
|
variationRowsCount - originalVariationRowsCount
|
||||||
|
);
|
||||||
} );
|
} );
|
||||||
}
|
|
||||||
|
|
||||||
await test.step( 'Click "Save changes"', async () => {
|
|
||||||
await page
|
|
||||||
.getByRole( 'button', {
|
|
||||||
name: 'Save changes',
|
|
||||||
} )
|
|
||||||
.click();
|
|
||||||
} );
|
|
||||||
|
|
||||||
await test.step( `Expect the variation ${ variationToCreate.join(
|
|
||||||
', '
|
|
||||||
) } to be successfully saved.`, async () => {
|
|
||||||
let newlyAddedVariationRow;
|
|
||||||
|
|
||||||
for ( const attributeValue of variationToCreate ) {
|
for ( const attributeValue of variationToCreate ) {
|
||||||
newlyAddedVariationRow = (
|
const attributeName = productAttributes.find(
|
||||||
newlyAddedVariationRow || variationRows
|
( { options } ) =>
|
||||||
).filter( {
|
options.includes( attributeValue )
|
||||||
has: page.locator( 'option[selected]', {
|
).name;
|
||||||
hasText: attributeValue,
|
const addAttributeMenu = variationRows
|
||||||
} ),
|
.nth( 0 )
|
||||||
|
.locator( 'select', {
|
||||||
|
has: page.locator( 'option', {
|
||||||
|
hasText: attributeValue,
|
||||||
|
} ),
|
||||||
|
} );
|
||||||
|
|
||||||
|
await test.step( `Select "${ attributeValue }" from the "${ attributeName }" attribute menu`, async () => {
|
||||||
|
await addAttributeMenu.selectOption(
|
||||||
|
attributeValue
|
||||||
|
);
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
await expect( newlyAddedVariationRow ).toBeVisible();
|
await test.step( 'Click "Save changes"', async () => {
|
||||||
} );
|
await page
|
||||||
}
|
.getByRole( 'button', {
|
||||||
} );
|
name: 'Save changes',
|
||||||
} );
|
} )
|
||||||
|
.click();
|
||||||
|
} );
|
||||||
|
|
||||||
|
await test.step( `Expect the variation ${ variationToCreate.join(
|
||||||
|
', '
|
||||||
|
) } to be successfully saved.`, async () => {
|
||||||
|
let newlyAddedVariationRow;
|
||||||
|
|
||||||
|
for ( const attributeValue of variationToCreate ) {
|
||||||
|
newlyAddedVariationRow = (
|
||||||
|
newlyAddedVariationRow || variationRows
|
||||||
|
).filter( {
|
||||||
|
has: page.locator( 'option[selected]', {
|
||||||
|
hasText: attributeValue,
|
||||||
|
} ),
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
await expect( newlyAddedVariationRow ).toBeVisible();
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
);
|
||||||
} );
|
} );
|
||||||
|
|
Loading…
Reference in New Issue