Remove WC Core shipping settings if Cart/Checkout blocks are in use (https://github.com/woocommerce/woocommerce-blocks/pull/8679)
* Add CartCheckoutUtils class This class will store reusable methods relating to Cart/Checkout Blocks, i.e. whether they are used on the Cart/Checkout page. * Update ShippingController to use the new CartCheckoutUtils function This will reduce code duplication when checking if the Cart/Checkout blocks are in use on the Cart/Checkout page. * Add filter to remove shipping settings when Cart/Checkout are default * Ensure setting displays correctly if cart is default but not checkout * Add tests to ensure core shipping settings update correctly * Add setCartCheckoutPages function to update set the cart/checkout page * Force shipping to be enabled if the Checkout block is in use. * Add filter to override cost requires address option * Add shippingCostRequiresAddress option * Check if the address is required before showing rates * Show shipping rates in editor * Add shippingCostRequiresAddress attribute to shipping methods block * Update frontend type to show shippingCostRequiresAddress is a prop * Add control to toggle shippingCostRequiresAddress option * Show address notice in the correct scenario * Send shippingCostRequiresAddress to Block in front end context * Add e2e test for editor control * Add e2e tests for shipping options on the front end * Add updateAttributeInSiblingBlock function * Add shippingCostRequiresAddress to shipping method block * Ensure attribute is updated in both blocks when editing * In Shipping Methods Block, show correct component based on block setting * Show correct block in editor * Remove broken test from PR * Clean up updateAttributeInSiblingBlock * Add setCartCheckoutPages function to update set the cart/checkout page * Add tests to ensure core shipping settings update correctly * Add isAddressComplete function Borrowed from woocommerce/woocommerce-blocks#8141 * Check if the address is required before showing rates * Show shipping rates in editor * Show address notice in the correct scenario * Add e2e tests for shipping options on the front end * Ensure errorId is passed to StateInput * Add fullShippingAddressPushed action to wc/store/cart * Add fullShippingAddressPushed case to reducer * Ensure fullShippingAddressPushed is set when initialising cart store * Add fullShippingAddressPushed selector and default state entry * Add shippingAddressHasValidationErrors util function * Do not overwrite addresses when selecting a rate * Set whether full address has been pushed when saving address changes * In Shipping Methods Block, show correct component based on block setting * Don't show from price if rates should be hidden until address entered * Check city validation errors to assert if shipping address is valid * Rename merchant.js to merchant.ts * Move local pickup functions to common merchant util * Update local pickup tests to use common merchant utils * Add test to ensure setting toggles in both blocks * Add navigating to settings and saving in merchant util * Create addPickupLocation merchant util * Add test for local pickup and require full address * Make sure correct conditions are met to show shipping options * Ensure checkbox is checked during local pickup tests * Unset the checkbox when tests are finished running * Update checkout block fixture * Prevent error in unit tests * Import validation store key from constants Required because importing from the index causes the validation data store to register twice * Update checkout terms test to wait for button not to be disabled * Revert "Add isAddressComplete function" This reverts commit 9967dc0d4f10cf638859ae085e6f4cc2901dd299.
This commit is contained in:
parent
95efc38d1f
commit
991e407fa9
|
@ -8,6 +8,7 @@ export interface StateInputProps {
|
|||
onChange: ( value: string ) => void;
|
||||
required?: boolean;
|
||||
errorMessage?: string;
|
||||
errorId?: string;
|
||||
}
|
||||
|
||||
export type StateInputWithStatesProps = StateInputProps & {
|
||||
|
|
|
@ -36,6 +36,7 @@ const StateInput = ( {
|
|||
autoComplete = 'off',
|
||||
value = '',
|
||||
required = false,
|
||||
errorId = '',
|
||||
}: StateInputWithStatesProps ): JSX.Element => {
|
||||
const countryStates = states[ country ];
|
||||
const options = useMemo(
|
||||
|
@ -102,6 +103,7 @@ const StateInput = ( {
|
|||
'Please select a state.',
|
||||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
errorId={ errorId }
|
||||
required={ required }
|
||||
autoComplete={ autoComplete }
|
||||
/>
|
||||
|
|
|
@ -44,4 +44,8 @@ export default {
|
|||
remove: true,
|
||||
},
|
||||
},
|
||||
shippingCostRequiresAddress: {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
},
|
||||
};
|
||||
|
|
|
@ -19,6 +19,10 @@
|
|||
"remove": true,
|
||||
"move": true
|
||||
}
|
||||
},
|
||||
"shippingCostRequiresAddress": {
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
}
|
||||
},
|
||||
"parent": [ "woocommerce/checkout-fields-block" ],
|
||||
|
|
|
@ -17,6 +17,7 @@ import './style.scss';
|
|||
import { RatePrice, getLocalPickupPrices, getShippingPrices } from './shared';
|
||||
import type { minMaxPrices } from './shared';
|
||||
import { defaultLocalPickupText, defaultShippingText } from './constants';
|
||||
import { shippingAddressHasValidationErrors } from '../../../../data/cart/utils';
|
||||
|
||||
const LocalPickupSelector = ( {
|
||||
checked,
|
||||
|
@ -71,15 +72,19 @@ const ShippingSelector = ( {
|
|||
showPrice,
|
||||
showIcon,
|
||||
toggleText,
|
||||
shippingCostRequiresAddress = false,
|
||||
}: {
|
||||
checked: string;
|
||||
rate: minMaxPrices;
|
||||
showPrice: boolean;
|
||||
showIcon: boolean;
|
||||
shippingCostRequiresAddress: boolean;
|
||||
toggleText: string;
|
||||
} ) => {
|
||||
const rateShouldBeHidden =
|
||||
shippingCostRequiresAddress && shippingAddressHasValidationErrors();
|
||||
const Price =
|
||||
rate.min === undefined ? (
|
||||
rate.min === undefined || rateShouldBeHidden ? (
|
||||
<span className="wc-block-checkout__shipping-method-option-price">
|
||||
{ __(
|
||||
'calculated with an address',
|
||||
|
@ -122,11 +127,13 @@ const Block = ( {
|
|||
showIcon,
|
||||
localPickupText,
|
||||
shippingText,
|
||||
shippingCostRequiresAddress = false,
|
||||
}: {
|
||||
checked: string;
|
||||
onChange: ( value: string ) => void;
|
||||
showPrice: boolean;
|
||||
showIcon: boolean;
|
||||
shippingCostRequiresAddress: boolean;
|
||||
localPickupText: string;
|
||||
shippingText: string;
|
||||
} ): JSX.Element | null => {
|
||||
|
@ -145,6 +152,7 @@ const Block = ( {
|
|||
rate={ getShippingPrices( shippingRates[ 0 ]?.shipping_rates ) }
|
||||
showPrice={ showPrice }
|
||||
showIcon={ showIcon }
|
||||
shippingCostRequiresAddress={ shippingCostRequiresAddress }
|
||||
toggleText={ shippingText || defaultShippingText }
|
||||
/>
|
||||
<LocalPickupSelector
|
||||
|
|
|
@ -23,6 +23,8 @@ import { innerBlockAreas } from '@woocommerce/blocks-checkout';
|
|||
import { useDispatch, useSelect } from '@wordpress/data';
|
||||
import { CHECKOUT_STORE_KEY } from '@woocommerce/block-data';
|
||||
import ExternalLinkCard from '@woocommerce/editor-components/external-link-card';
|
||||
import { Attributes } from '@woocommerce/blocks/checkout/types';
|
||||
import { updateAttributeInSiblingBlock } from '@woocommerce/utils';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
|
@ -152,7 +154,9 @@ const ShippingSelector = ( {
|
|||
export const Edit = ( {
|
||||
attributes,
|
||||
setAttributes,
|
||||
clientId,
|
||||
}: {
|
||||
clientId: string;
|
||||
attributes: {
|
||||
title: string;
|
||||
description: string;
|
||||
|
@ -163,9 +167,16 @@ export const Edit = ( {
|
|||
showPrice: boolean;
|
||||
showIcon: boolean;
|
||||
className: string;
|
||||
shippingCostRequiresAddress: boolean;
|
||||
};
|
||||
setAttributes: ( attributes: Record< string, unknown > ) => void;
|
||||
} ): JSX.Element | null => {
|
||||
const toggleAttribute = ( key: keyof Attributes ): void => {
|
||||
const newAttributes = {} as Partial< Attributes >;
|
||||
newAttributes[ key ] = ! ( attributes[ key ] as boolean );
|
||||
setAttributes( newAttributes );
|
||||
};
|
||||
|
||||
const { setPrefersCollection } = useDispatch( CHECKOUT_STORE_KEY );
|
||||
const { prefersCollection } = useSelect( ( select ) => {
|
||||
const checkoutStore = select( CHECKOUT_STORE_KEY );
|
||||
|
@ -210,6 +221,30 @@ export const Edit = ( {
|
|||
) }
|
||||
>
|
||||
<InspectorControls>
|
||||
<PanelBody
|
||||
title={ __(
|
||||
'Calculations',
|
||||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
>
|
||||
<ToggleControl
|
||||
label={ __(
|
||||
'Hide shipping costs until an address is entered',
|
||||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
checked={ attributes.shippingCostRequiresAddress }
|
||||
onChange={ ( selected ) => {
|
||||
updateAttributeInSiblingBlock(
|
||||
clientId,
|
||||
'shippingCostRequiresAddress',
|
||||
selected,
|
||||
'woocommerce/checkout-shipping-methods-block'
|
||||
);
|
||||
|
||||
toggleAttribute( 'shippingCostRequiresAddress' );
|
||||
} }
|
||||
/>
|
||||
</PanelBody>
|
||||
<PanelBody
|
||||
title={ __( 'Appearance', 'woo-gutenberg-products-block' ) }
|
||||
>
|
||||
|
|
|
@ -25,10 +25,12 @@ const FrontendBlock = ( {
|
|||
showIcon,
|
||||
shippingText,
|
||||
localPickupText,
|
||||
shippingCostRequiresAddress,
|
||||
}: {
|
||||
title: string;
|
||||
description: string;
|
||||
showStepNumber: boolean;
|
||||
shippingCostRequiresAddress: boolean;
|
||||
children: JSX.Element;
|
||||
className?: string;
|
||||
showPrice: boolean;
|
||||
|
@ -90,6 +92,7 @@ const FrontendBlock = ( {
|
|||
showIcon={ showIcon }
|
||||
localPickupText={ localPickupText }
|
||||
shippingText={ shippingText }
|
||||
shippingCostRequiresAddress={ shippingCostRequiresAddress }
|
||||
/>
|
||||
{ children }
|
||||
</FormStep>
|
||||
|
|
|
@ -24,4 +24,8 @@ export default {
|
|||
remove: true,
|
||||
},
|
||||
},
|
||||
shippingCostRequiresAddress: {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
},
|
||||
};
|
||||
|
|
|
@ -19,6 +19,10 @@
|
|||
"remove": true,
|
||||
"move": true
|
||||
}
|
||||
},
|
||||
"shippingCostRequiresAddress": {
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
}
|
||||
},
|
||||
"parent": [ "woocommerce/checkout-fields-block" ],
|
||||
|
|
|
@ -21,11 +21,14 @@ import type {
|
|||
CartShippingPackageShippingRate,
|
||||
} from '@woocommerce/types';
|
||||
import type { ReactElement } from 'react';
|
||||
import { useSelect } from '@wordpress/data';
|
||||
import { CART_STORE_KEY } from '@woocommerce/block-data';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import './style.scss';
|
||||
import { shippingAddressHasValidationErrors } from '../../../../data/cart/utils';
|
||||
|
||||
/**
|
||||
* Renders a shipping rate control option.
|
||||
|
@ -52,7 +55,10 @@ const renderShippingRatesControlOption = (
|
|||
};
|
||||
};
|
||||
|
||||
const Block = ( { noShippingPlaceholder = null } ): ReactElement | null => {
|
||||
const Block = ( {
|
||||
noShippingPlaceholder = null,
|
||||
shippingCostRequiresAddress = false,
|
||||
} ): ReactElement | null => {
|
||||
const { isEditor } = useEditorContext();
|
||||
|
||||
const {
|
||||
|
@ -63,6 +69,10 @@ const Block = ( { noShippingPlaceholder = null } ): ReactElement | null => {
|
|||
isCollectable,
|
||||
} = useShippingData();
|
||||
|
||||
const shippingAddressPushed = useSelect( ( select ) => {
|
||||
return select( CART_STORE_KEY ).getFullShippingAddressPushed();
|
||||
} );
|
||||
|
||||
const filteredShippingRates = isCollectable
|
||||
? shippingRates.map( ( shippingRatesPackage ) => {
|
||||
return {
|
||||
|
@ -81,13 +91,15 @@ const Block = ( { noShippingPlaceholder = null } ): ReactElement | null => {
|
|||
return null;
|
||||
}
|
||||
|
||||
const shippingAddressIsComplete = ! shippingAddressHasValidationErrors();
|
||||
|
||||
const shippingRatesPackageCount =
|
||||
getShippingRatesPackageCount( shippingRates );
|
||||
|
||||
if (
|
||||
! isEditor &&
|
||||
! hasCalculatedShipping &&
|
||||
! shippingRatesPackageCount
|
||||
( ! hasCalculatedShipping && ! shippingRatesPackageCount ) ||
|
||||
( shippingCostRequiresAddress &&
|
||||
( ! shippingAddressPushed || ! shippingAddressIsComplete ) )
|
||||
) {
|
||||
return (
|
||||
<p>
|
||||
|
|
|
@ -4,12 +4,14 @@
|
|||
import classnames from 'classnames';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { InspectorControls, useBlockProps } from '@wordpress/block-editor';
|
||||
import { PanelBody, ExternalLink } from '@wordpress/components';
|
||||
import { PanelBody, ExternalLink, ToggleControl } from '@wordpress/components';
|
||||
import { ADMIN_URL, getSetting } from '@woocommerce/settings';
|
||||
import ExternalLinkCard from '@woocommerce/editor-components/external-link-card';
|
||||
import { innerBlockAreas } from '@woocommerce/blocks-checkout';
|
||||
import { useCheckoutAddress } from '@woocommerce/base-context/hooks';
|
||||
import Noninteractive from '@woocommerce/base-components/noninteractive';
|
||||
import { Attributes } from '@woocommerce/blocks/checkout/types';
|
||||
import { updateAttributeInSiblingBlock } from '@woocommerce/utils';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
|
@ -32,12 +34,15 @@ type shippingAdminLink = {
|
|||
export const Edit = ( {
|
||||
attributes,
|
||||
setAttributes,
|
||||
clientId,
|
||||
}: {
|
||||
clientId: string;
|
||||
attributes: {
|
||||
title: string;
|
||||
description: string;
|
||||
showStepNumber: boolean;
|
||||
className: string;
|
||||
shippingCostRequiresAddress: boolean;
|
||||
};
|
||||
setAttributes: ( attributes: Record< string, unknown > ) => void;
|
||||
} ): JSX.Element | null => {
|
||||
|
@ -54,6 +59,12 @@ export const Edit = ( {
|
|||
return null;
|
||||
}
|
||||
|
||||
const toggleAttribute = ( key: keyof Attributes ): void => {
|
||||
const newAttributes = {} as Partial< Attributes >;
|
||||
newAttributes[ key ] = ! ( attributes[ key ] as boolean );
|
||||
setAttributes( newAttributes );
|
||||
};
|
||||
|
||||
return (
|
||||
<FormStepBlock
|
||||
attributes={ attributes }
|
||||
|
@ -64,6 +75,29 @@ export const Edit = ( {
|
|||
) }
|
||||
>
|
||||
<InspectorControls>
|
||||
<PanelBody
|
||||
title={ __(
|
||||
'Calculations',
|
||||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
>
|
||||
<ToggleControl
|
||||
label={ __(
|
||||
'Hide shipping costs until an address is entered',
|
||||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
checked={ attributes.shippingCostRequiresAddress }
|
||||
onChange={ ( selected ) => {
|
||||
updateAttributeInSiblingBlock(
|
||||
clientId,
|
||||
'shippingCostRequiresAddress',
|
||||
selected,
|
||||
'woocommerce/checkout-shipping-method-block'
|
||||
);
|
||||
toggleAttribute( 'shippingCostRequiresAddress' );
|
||||
} }
|
||||
/>
|
||||
</PanelBody>
|
||||
{ globalShippingMethods.length > 0 && (
|
||||
<PanelBody
|
||||
title={ __(
|
||||
|
@ -129,7 +163,12 @@ export const Edit = ( {
|
|||
) }
|
||||
</InspectorControls>
|
||||
<Noninteractive>
|
||||
<Block noShippingPlaceholder={ <NoShippingPlaceholder /> } />
|
||||
<Block
|
||||
noShippingPlaceholder={ <NoShippingPlaceholder /> }
|
||||
shippingCostRequiresAddress={
|
||||
attributes.shippingCostRequiresAddress
|
||||
}
|
||||
/>
|
||||
</Noninteractive>
|
||||
<AdditionalFields block={ innerBlockAreas.SHIPPING_METHODS } />
|
||||
</FormStepBlock>
|
||||
|
|
|
@ -20,6 +20,7 @@ const FrontendBlock = ( {
|
|||
showStepNumber,
|
||||
children,
|
||||
className,
|
||||
shippingCostRequiresAddress = false,
|
||||
}: {
|
||||
title: string;
|
||||
description: string;
|
||||
|
@ -31,6 +32,7 @@ const FrontendBlock = ( {
|
|||
showStepNumber: boolean;
|
||||
children: JSX.Element;
|
||||
className?: string;
|
||||
shippingCostRequiresAddress: boolean;
|
||||
} ) => {
|
||||
const checkoutIsProcessing = useSelect( ( select ) =>
|
||||
select( CHECKOUT_STORE_KEY ).isProcessing()
|
||||
|
@ -53,7 +55,9 @@ const FrontendBlock = ( {
|
|||
description={ description }
|
||||
showStepNumber={ showStepNumber }
|
||||
>
|
||||
<Block />
|
||||
<Block
|
||||
shippingCostRequiresAddress={ shippingCostRequiresAddress }
|
||||
/>
|
||||
{ children }
|
||||
</FormStep>
|
||||
);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
export const ACTION_TYPES = {
|
||||
SET_CART_DATA: 'SET_CART_DATA',
|
||||
SET_FULL_SHIPPING_ADDRESS_PUSHED: 'SET_FULL_SHIPPING_ADDRESS_PUSHED',
|
||||
SET_ERROR_DATA: 'SET_ERROR_DATA',
|
||||
APPLYING_COUPON: 'APPLYING_COUPON',
|
||||
REMOVING_COUPON: 'REMOVING_COUPON',
|
||||
|
|
|
@ -417,7 +417,14 @@ export const selectShippingRate =
|
|||
},
|
||||
cache: 'no-store',
|
||||
} );
|
||||
dispatch.receiveCart( response );
|
||||
// Remove shipping and billing address from the response, so we don't overwrite what the shopper is
|
||||
// entering in the form if rates suddenly appear mid-edit.
|
||||
const {
|
||||
shipping_address: shippingAddress,
|
||||
billing_address: billingAddress,
|
||||
...rest
|
||||
} = response;
|
||||
dispatch.receiveCart( rest );
|
||||
return response as CartResponse;
|
||||
} catch ( error ) {
|
||||
dispatch.receiveError( error );
|
||||
|
@ -474,6 +481,13 @@ export const updateCustomerData =
|
|||
}
|
||||
};
|
||||
|
||||
export const setFullShippingAddressPushed = (
|
||||
fullShippingAddressPushed: boolean
|
||||
) => ( {
|
||||
type: types.SET_FULL_SHIPPING_ADDRESS_PUSHED,
|
||||
fullShippingAddressPushed,
|
||||
} );
|
||||
|
||||
type Actions =
|
||||
| typeof addItemToCart
|
||||
| typeof applyCoupon
|
||||
|
@ -494,6 +508,7 @@ type Actions =
|
|||
| typeof setShippingAddress
|
||||
| typeof shippingRatesBeingSelected
|
||||
| typeof updateCustomerData
|
||||
| typeof setFullShippingAddressPushed
|
||||
| typeof updatingCustomerData;
|
||||
|
||||
export type CartAction = ReturnOrGeneratorYieldUnion< Actions | Thunks >;
|
||||
|
|
|
@ -100,6 +100,7 @@ export const defaultCartState: CartState = {
|
|||
applyingCoupon: '',
|
||||
removingCoupon: '',
|
||||
isCartDataStale: false,
|
||||
fullShippingAddressPushed: false,
|
||||
},
|
||||
errors: EMPTY_CART_ERRORS,
|
||||
};
|
||||
|
|
|
@ -17,6 +17,7 @@ import isShallowEqual from '@wordpress/is-shallow-equal';
|
|||
import { STORE_KEY } from './constants';
|
||||
import { VALIDATION_STORE_KEY } from '../validation';
|
||||
import { processErrorResponse } from '../utils';
|
||||
import { shippingAddressHasValidationErrors } from './utils';
|
||||
|
||||
type CustomerData = {
|
||||
billingAddress: CartBillingAddress;
|
||||
|
@ -192,6 +193,11 @@ const updateCustomerData = debounce( (): void => {
|
|||
) as BaseAddressKey[] ),
|
||||
];
|
||||
}
|
||||
} )
|
||||
.finally( () => {
|
||||
if ( ! shippingAddressHasValidationErrors() ) {
|
||||
dispatch( STORE_KEY ).setFullShippingAddressPushed( true );
|
||||
}
|
||||
} );
|
||||
}
|
||||
}, 1000 );
|
||||
|
|
|
@ -48,6 +48,15 @@ const reducer: Reducer< CartState > = (
|
|||
action: Partial< CartAction >
|
||||
) => {
|
||||
switch ( action.type ) {
|
||||
case types.SET_FULL_SHIPPING_ADDRESS_PUSHED:
|
||||
state = {
|
||||
...state,
|
||||
metaData: {
|
||||
...state.metaData,
|
||||
fullShippingAddressPushed: action.fullShippingAddressPushed,
|
||||
},
|
||||
};
|
||||
break;
|
||||
case types.SET_ERROR_DATA:
|
||||
if ( action.error ) {
|
||||
state = {
|
||||
|
|
|
@ -9,6 +9,7 @@ import { CartResponse } from '@woocommerce/types';
|
|||
*/
|
||||
import { CART_API_ERROR } from './constants';
|
||||
import type { CartDispatchFromMap, CartResolveSelectFromMap } from './index';
|
||||
import { shippingAddressHasValidationErrors } from './utils';
|
||||
|
||||
/**
|
||||
* Resolver for retrieving all cart data.
|
||||
|
@ -27,6 +28,10 @@ export const getCartData =
|
|||
receiveError( CART_API_ERROR );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! shippingAddressHasValidationErrors() ) {
|
||||
dispatch.setFullShippingAddressPushed( true );
|
||||
}
|
||||
receiveCart( cartData );
|
||||
};
|
||||
|
||||
|
|
|
@ -222,3 +222,10 @@ export const getItemsPendingQuantityUpdate = ( state: CartState ): string[] => {
|
|||
export const getItemsPendingDelete = ( state: CartState ): string[] => {
|
||||
return state.cartItemsPendingDelete;
|
||||
};
|
||||
|
||||
/**
|
||||
* Whether the address has changes that have not been synced with the server.
|
||||
*/
|
||||
export const getFullShippingAddressPushed = ( state: CartState ): boolean => {
|
||||
return state.metaData.fullShippingAddressPushed;
|
||||
};
|
||||
|
|
|
@ -64,6 +64,7 @@ jest.mock( '../utils', () => ( {
|
|||
// need to update payment methods, they are not relevant to the tests in this file.
|
||||
jest.mock( '../update-payment-methods', () => ( {
|
||||
debouncedUpdatePaymentMethods: jest.fn(),
|
||||
updatePaymentMethods: jest.fn(),
|
||||
} ) );
|
||||
|
||||
describe( 'pushChanges', () => {
|
||||
|
|
|
@ -3,9 +3,38 @@
|
|||
*/
|
||||
import { camelCase, mapKeys } from 'lodash';
|
||||
import { Cart, CartResponse } from '@woocommerce/types';
|
||||
import { select } from '@wordpress/data';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { STORE_KEY as VALIDATION_STORE_KEY } from '../validation/constants';
|
||||
|
||||
export const mapCartResponseToCart = ( responseCart: CartResponse ): Cart => {
|
||||
return mapKeys( responseCart, ( _, key ) =>
|
||||
camelCase( key )
|
||||
) as unknown as Cart;
|
||||
};
|
||||
|
||||
export const shippingAddressHasValidationErrors = () => {
|
||||
const validationStore = select( VALIDATION_STORE_KEY );
|
||||
// Check if the shipping address form has validation errors - if not then we know the full required
|
||||
// address has been pushed to the server.
|
||||
const stateValidationErrors =
|
||||
validationStore.getValidationError( 'shipping_state' );
|
||||
const address1ValidationErrors =
|
||||
validationStore.getValidationError( 'shipping_address_1' );
|
||||
const countryValidationErrors =
|
||||
validationStore.getValidationError( 'shipping_country' );
|
||||
const postcodeValidationErrors =
|
||||
validationStore.getValidationError( 'shipping_postcode' );
|
||||
const cityValidationErrors =
|
||||
validationStore.getValidationError( 'shipping_city' );
|
||||
return [
|
||||
cityValidationErrors,
|
||||
stateValidationErrors,
|
||||
address1ValidationErrors,
|
||||
countryValidationErrors,
|
||||
postcodeValidationErrors,
|
||||
].some( ( entry ) => typeof entry !== 'undefined' );
|
||||
};
|
||||
|
|
|
@ -210,6 +210,8 @@ export interface CartMeta {
|
|||
isCartDataStale: boolean;
|
||||
applyingCoupon: string;
|
||||
removingCoupon: string;
|
||||
/* Whether the full address has been previously pushed to the server */
|
||||
fullShippingAddressPushed: boolean;
|
||||
}
|
||||
export interface ExtensionCartUpdateArgs {
|
||||
data: Record< string, unknown >;
|
||||
|
|
|
@ -10,6 +10,7 @@ import {
|
|||
AttributeWithTerms,
|
||||
isAttributeTerm,
|
||||
} from '@woocommerce/types';
|
||||
import { dispatch, select } from '@wordpress/data';
|
||||
|
||||
const ATTRIBUTES = getSetting< AttributeSetting[] >( 'attributes', [] );
|
||||
|
||||
|
@ -108,3 +109,35 @@ export const getTaxonomyFromAttributeId = ( attributeId: number ) => {
|
|||
const attribute = getAttributeFromID( attributeId );
|
||||
return attribute ? attribute.taxonomy : null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates an attribute in a sibling block. Useful if two settings control the same attribute, but you don't want to
|
||||
* have this attribute exist on a parent block.
|
||||
*/
|
||||
export const updateAttributeInSiblingBlock = (
|
||||
clientId: string,
|
||||
attribute: string,
|
||||
newValue: unknown,
|
||||
siblingBlockName: string
|
||||
) => {
|
||||
const store = select( 'core/block-editor' );
|
||||
const actions = dispatch( 'core/block-editor' );
|
||||
const parentBlocks = store.getBlockParents( clientId );
|
||||
|
||||
let shippingMethodsBlockClientId = '';
|
||||
|
||||
// Loop through parent block's children until we find woocommerce/checkout-shipping-methods-block.
|
||||
// Also set this attribute in the woocommerce/checkout-shipping-methods-block.
|
||||
parentBlocks.forEach( ( parent ) => {
|
||||
const childBlock = store
|
||||
.getBlock( parent )
|
||||
.innerBlocks.find( ( child ) => child.name === siblingBlockName );
|
||||
if ( ! childBlock ) {
|
||||
return;
|
||||
}
|
||||
shippingMethodsBlockClientId = childBlock.clientId;
|
||||
} );
|
||||
actions.updateBlockAttributes( shippingMethodsBlockClientId, {
|
||||
[ attribute ]: newValue,
|
||||
} );
|
||||
};
|
||||
|
|
|
@ -3,6 +3,8 @@ namespace Automattic\WooCommerce\Blocks\Shipping;
|
|||
|
||||
use Automattic\WooCommerce\Blocks\Assets\Api as AssetApi;
|
||||
use Automattic\WooCommerce\Blocks\Assets\AssetDataRegistry;
|
||||
use Automattic\WooCommerce\Blocks\Tests\BlockTypes\Cart;
|
||||
use Automattic\WooCommerce\Blocks\Utils\CartCheckoutUtils;
|
||||
use Automattic\WooCommerce\StoreApi\Utilities\LocalPickupUtils;
|
||||
use Automattic\WooCommerce\Utilities\ArrayUtil;
|
||||
|
||||
|
@ -51,6 +53,7 @@ class ShippingController {
|
|||
);
|
||||
}
|
||||
$this->asset_data_registry->add( 'collectableMethodIds', array( 'Automattic\WooCommerce\StoreApi\Utilities\LocalPickupUtils', 'get_local_pickup_method_ids' ), true );
|
||||
$this->asset_data_registry->add( 'shippingCostRequiresAddress', get_option( 'woocommerce_shipping_cost_requires_address', false ) === 'yes' );
|
||||
add_action( 'rest_api_init', [ $this, 'register_settings' ] );
|
||||
add_action( 'admin_enqueue_scripts', [ $this, 'admin_scripts' ] );
|
||||
add_action( 'admin_enqueue_scripts', [ $this, 'hydrate_client_settings' ] );
|
||||
|
@ -60,6 +63,105 @@ class ShippingController {
|
|||
add_filter( 'woocommerce_shipping_packages', array( $this, 'filter_shipping_packages' ) );
|
||||
add_filter( 'pre_update_option_woocommerce_pickup_location_settings', array( $this, 'flush_cache' ) );
|
||||
add_filter( 'pre_update_option_pickup_location_pickup_locations', array( $this, 'flush_cache' ) );
|
||||
add_filter( 'woocommerce_shipping_settings', array( $this, 'remove_shipping_settings' ) );
|
||||
add_filter( 'wc_shipping_enabled', array( $this, 'force_shipping_enabled' ), 100, 1 );
|
||||
|
||||
// This is required to short circuit `show_shipping` from class-wc-cart.php - without it, that function
|
||||
// returns based on the option's value in the DB and we can't override it any other way.
|
||||
add_filter( 'option_woocommerce_shipping_cost_requires_address', array( $this, 'override_cost_requires_address_option' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides the option to force shipping calculations NOT to wait until an address is entered, but only if the
|
||||
* Checkout page contains the Checkout Block.
|
||||
*
|
||||
* @param boolean $value Whether shipping cost calculation requires address to be entered.
|
||||
* @return boolean Whether shipping cost calculation should require an address to be entered before calculating.
|
||||
*/
|
||||
public function override_cost_requires_address_option( $value ) {
|
||||
if ( CartCheckoutUtils::is_checkout_block_default() ) {
|
||||
return 'no';
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Force shipping to be enabled if the Checkout block is in use on the Checkout page.
|
||||
*
|
||||
* @param boolean $enabled Whether shipping is currently enabled.
|
||||
* @return boolean Whether shipping should continue to be enabled/disabled.
|
||||
*/
|
||||
public function force_shipping_enabled( $enabled ) {
|
||||
if ( CartCheckoutUtils::is_checkout_block_default() ) {
|
||||
return true;
|
||||
}
|
||||
return $enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the Checkout block Remove shipping settings from WC Core's admin panels that are now block settings.
|
||||
*
|
||||
* @param array $settings The default WC shipping settings.
|
||||
* @return array|mixed The filtered settings with relevant items removed.
|
||||
*/
|
||||
public function remove_shipping_settings( $settings ) {
|
||||
|
||||
// Do not add the "Hide shipping costs until an address is entered" setting if the Checkout block is not used on the WC checkout page.
|
||||
if ( CartCheckoutUtils::is_checkout_block_default() ) {
|
||||
$settings = array_filter(
|
||||
$settings,
|
||||
function( $setting ) {
|
||||
return ! in_array(
|
||||
$setting['id'],
|
||||
array(
|
||||
'woocommerce_shipping_cost_requires_address',
|
||||
),
|
||||
true
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Do not add the shipping calculator setting if the Cart block is not used on the WC cart page.
|
||||
if ( CartCheckoutUtils::is_cart_block_default() ) {
|
||||
|
||||
// If the Cart is default, but not the checkout, we should ensure the 'Calculations' title is added to the
|
||||
// `woocommerce_shipping_cost_requires_address` options group, since it is attached to the
|
||||
// `woocommerce_enable_shipping_calc` option that we're going to remove later.
|
||||
if ( ! CartCheckoutUtils::is_checkout_block_default() ) {
|
||||
$calculations_title = '';
|
||||
|
||||
// Get Calculations title so we can add it to 'Hide shipping costs until an address is entered' option.
|
||||
foreach ( $settings as $setting ) {
|
||||
if ( 'woocommerce_enable_shipping_calc' === $setting['id'] ) {
|
||||
$calculations_title = $setting['title'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Add Calculations title to 'Hide shipping costs until an address is entered' option.
|
||||
foreach ( $settings as $index => $setting ) {
|
||||
if ( 'woocommerce_shipping_cost_requires_address' === $setting['id'] ) {
|
||||
$settings[ $index ]['title'] = $calculations_title;
|
||||
$settings[ $index ]['checkboxgroup'] = 'start';
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
$settings = array_filter(
|
||||
$settings,
|
||||
function( $setting ) {
|
||||
return ! in_array(
|
||||
$setting['id'],
|
||||
array(
|
||||
'woocommerce_enable_shipping_calc',
|
||||
),
|
||||
true
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
return $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -223,8 +325,7 @@ class ShippingController {
|
|||
* Registers the Local Pickup shipping method used by the Checkout Block.
|
||||
*/
|
||||
public function register_local_pickup() {
|
||||
$checkout_page_id = wc_get_page_id( 'checkout' );
|
||||
if ( $checkout_page_id && has_block( 'woocommerce/checkout', $checkout_page_id ) ) {
|
||||
if ( CartCheckoutUtils::is_checkout_block_default() ) {
|
||||
wc()->shipping->register_shipping_method( new PickupLocation() );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\Utils;
|
||||
|
||||
/**
|
||||
* Class containing utility methods for dealing with the Cart and Checkout blocks.
|
||||
*/
|
||||
class CartCheckoutUtils {
|
||||
|
||||
/**
|
||||
* Checks if the default cart page is using the Cart block.
|
||||
*
|
||||
* @return bool true if the WC cart page is using the Cart block.
|
||||
*/
|
||||
public static function is_cart_block_default() {
|
||||
$cart_page_id = wc_get_page_id( 'cart' );
|
||||
return $cart_page_id && has_block( 'woocommerce/cart', $cart_page_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the default checkout page is using the Checkout block.
|
||||
*
|
||||
* @return bool true if the WC checkout page is using the Checkout block.
|
||||
*/
|
||||
public static function is_checkout_block_default() {
|
||||
$checkout_page_id = wc_get_page_id( 'checkout' );
|
||||
return $checkout_page_id && has_block( 'woocommerce/checkout', $checkout_page_id );
|
||||
}
|
||||
}
|
|
@ -1 +1 @@
|
|||
{"title":"Checkout Block","pageContent":"<!-- wp:woocommerce/checkout -->\n<div class=\"wp-block-woocommerce-checkout wc-block-checkout is-loading\"><!-- wp:woocommerce/checkout-fields-block -->\n<div class=\"wp-block-woocommerce-checkout-fields-block\"><!-- wp:woocommerce/checkout-express-payment-block -->\n<div class=\"wp-block-woocommerce-checkout-express-payment-block\"></div>\n<!-- /wp:woocommerce/checkout-express-payment-block -->\n\n<!-- wp:woocommerce/checkout-contact-information-block -->\n<div class=\"wp-block-woocommerce-checkout-contact-information-block\"></div>\n<!-- /wp:woocommerce/checkout-contact-information-block -->\n\n<!-- wp:woocommerce/checkout-shipping-address-block -->\n<div class=\"wp-block-woocommerce-checkout-shipping-address-block\"></div>\n<!-- /wp:woocommerce/checkout-shipping-address-block -->\n\n<!-- wp:woocommerce/checkout-billing-address-block -->\n<div class=\"wp-block-woocommerce-checkout-billing-address-block\"></div>\n<!-- /wp:woocommerce/checkout-billing-address-block -->\n\n<!-- wp:woocommerce/checkout-shipping-methods-block -->\n<div class=\"wp-block-woocommerce-checkout-shipping-methods-block\"></div>\n<!-- /wp:woocommerce/checkout-shipping-methods-block -->\n\n<!-- wp:woocommerce/checkout-payment-block -->\n<div class=\"wp-block-woocommerce-checkout-payment-block\"></div>\n<!-- /wp:woocommerce/checkout-payment-block -->\n\n<!-- wp:woocommerce/checkout-order-note-block -->\n<div class=\"wp-block-woocommerce-checkout-order-note-block\"></div>\n<!-- /wp:woocommerce/checkout-order-note-block -->\n\n<!-- wp:woocommerce/checkout-terms-block -->\n<div class=\"wp-block-woocommerce-checkout-terms-block\"></div>\n<!-- /wp:woocommerce/checkout-terms-block -->\n\n<!-- wp:woocommerce/checkout-actions-block -->\n<div class=\"wp-block-woocommerce-checkout-actions-block\"></div>\n<!-- /wp:woocommerce/checkout-actions-block --></div>\n<!-- /wp:woocommerce/checkout-fields-block -->\n\n<!-- wp:woocommerce/checkout-totals-block -->\n<div class=\"wp-block-woocommerce-checkout-totals-block\"><!-- wp:woocommerce/checkout-order-summary-block -->\n<div class=\"wp-block-woocommerce-checkout-order-summary-block\"></div>\n<!-- /wp:woocommerce/checkout-order-summary-block --></div>\n<!-- /wp:woocommerce/checkout-totals-block --></div>\n<!-- /wp:woocommerce/checkout -->"}
|
||||
{"title":"Checkout Block","pageContent":"<!-- wp:woocommerce/checkout -->\n<div class=\"wp-block-woocommerce-checkout wc-block-checkout is-loading\"><!-- wp:woocommerce/checkout-fields-block -->\n<div class=\"wp-block-woocommerce-checkout-fields-block\"><!-- wp:woocommerce/checkout-express-payment-block -->\n<div class=\"wp-block-woocommerce-checkout-express-payment-block\"></div>\n<!-- /wp:woocommerce/checkout-express-payment-block -->\n\n<!-- wp:woocommerce/checkout-contact-information-block -->\n<div class=\"wp-block-woocommerce-checkout-contact-information-block\"></div>\n<!-- /wp:woocommerce/checkout-contact-information-block -->\n\n<!-- wp:woocommerce/checkout-shipping-address-block -->\n<div class=\"wp-block-woocommerce-checkout-shipping-address-block\"></div>\n<!-- /wp:woocommerce/checkout-shipping-address-block -->\n\n<!-- wp:woocommerce/checkout-billing-address-block -->\n<div class=\"wp-block-woocommerce-checkout-billing-address-block\"></div>\n<!-- /wp:woocommerce/checkout-billing-address-block -->\n\n<!-- wp:woocommerce/checkout-shipping-methods-block {\"shippingCostRequiresAddress\":false} -->\n<div class=\"wp-block-woocommerce-checkout-shipping-methods-block\"></div>\n<!-- /wp:woocommerce/checkout-shipping-methods-block -->\n\n<!-- wp:woocommerce/checkout-payment-block -->\n<div class=\"wp-block-woocommerce-checkout-payment-block\"></div>\n<!-- /wp:woocommerce/checkout-payment-block -->\n\n<!-- wp:woocommerce/checkout-order-note-block -->\n<div class=\"wp-block-woocommerce-checkout-order-note-block\"></div>\n<!-- /wp:woocommerce/checkout-order-note-block -->\n\n<!-- wp:woocommerce/checkout-terms-block -->\n<div class=\"wp-block-woocommerce-checkout-terms-block\"></div>\n<!-- /wp:woocommerce/checkout-terms-block -->\n\n<!-- wp:woocommerce/checkout-actions-block -->\n<div class=\"wp-block-woocommerce-checkout-actions-block\"></div>\n<!-- /wp:woocommerce/checkout-actions-block --></div>\n<!-- /wp:woocommerce/checkout-fields-block -->\n\n<!-- wp:woocommerce/checkout-totals-block -->\n<div class=\"wp-block-woocommerce-checkout-totals-block\"><!-- wp:woocommerce/checkout-order-summary-block -->\n<div class=\"wp-block-woocommerce-checkout-order-summary-block\"></div>\n<!-- /wp:woocommerce/checkout-order-summary-block --></div>\n<!-- /wp:woocommerce/checkout-totals-block --></div>\n<!-- /wp:woocommerce/checkout -->"}
|
||||
|
|
|
@ -22,6 +22,7 @@ import {
|
|||
openWidgetEditor,
|
||||
closeModalIfExists,
|
||||
} from '../../utils.js';
|
||||
import { merchant as merchantUtils } from '../../../utils/merchant';
|
||||
|
||||
const block = {
|
||||
name: 'Checkout',
|
||||
|
@ -64,6 +65,79 @@ describe( `${ block.name } Block`, () => {
|
|||
await selectBlockByName( block.slug );
|
||||
} );
|
||||
|
||||
it( 'can toggle "hide shipping costs until an address is entered"', async () => {
|
||||
await selectBlockByName(
|
||||
'woocommerce/checkout-shipping-methods-block'
|
||||
);
|
||||
const toggleLabel = await findLabelWithText(
|
||||
'Hide shipping costs until an address is entered'
|
||||
);
|
||||
await toggleLabel.click();
|
||||
const shippingOptionsRequireAddressText = await page.$x(
|
||||
'//p[contains(text(), "Shipping options will be displayed here after entering your full shipping address.")]'
|
||||
);
|
||||
await expect( shippingOptionsRequireAddressText ).toHaveLength(
|
||||
1
|
||||
);
|
||||
|
||||
await toggleLabel.click();
|
||||
await expect( page ).toMatchElement(
|
||||
'.wc-block-components-shipping-rates-control'
|
||||
);
|
||||
} );
|
||||
|
||||
it( 'toggles the same setting in shipping method and shipping methods blocks', async () => {
|
||||
await merchantUtils.goToLocalPickupSettingsPage();
|
||||
await merchantUtils.enableLocalPickup();
|
||||
await merchantUtils.saveLocalPickupSettingsPageWithRefresh();
|
||||
|
||||
await visitBlockPage( `${ block.name } Block` );
|
||||
await expect( page ).toClick(
|
||||
'.wc-block-checkout__shipping-method button',
|
||||
{ text: 'Shipping' }
|
||||
);
|
||||
await openDocumentSettingsSidebar();
|
||||
const toggleLabel = await findLabelWithText(
|
||||
'Hide shipping costs until an address is entered'
|
||||
);
|
||||
await toggleLabel.click();
|
||||
const [ label ] = await page.$x(
|
||||
'//label[contains(., "Hide shipping costs until an address is entered")]'
|
||||
);
|
||||
const shippingMethodForValue = await page.evaluate(
|
||||
( passedLabel ) => passedLabel.getAttribute( 'for' ),
|
||||
label
|
||||
);
|
||||
const shippingMethodSettingIsChecked = await page.evaluate(
|
||||
( passedShippingMethodForValue ) =>
|
||||
document.getElementById( passedShippingMethodForValue )
|
||||
.checked,
|
||||
shippingMethodForValue
|
||||
);
|
||||
await expect( shippingMethodSettingIsChecked ).toBe( true );
|
||||
await selectBlockByName(
|
||||
'woocommerce/checkout-shipping-methods-block'
|
||||
);
|
||||
const [ shippingMethodsLabel ] = await page.$x(
|
||||
'//label[contains(., "Hide shipping costs until an address is entered")]'
|
||||
);
|
||||
const shippingMethodsLabelForValue = await page.evaluate(
|
||||
( passedShippingMethodsLabel ) =>
|
||||
passedShippingMethodsLabel.getAttribute( 'for' ),
|
||||
shippingMethodsLabel
|
||||
);
|
||||
const shippingMethodLabelIsChecked = await page.evaluate(
|
||||
( passedShippingMethodsLabelForValue ) =>
|
||||
document.getElementById(
|
||||
passedShippingMethodsLabelForValue
|
||||
).checked,
|
||||
shippingMethodsLabelForValue
|
||||
);
|
||||
expect( shippingMethodSettingIsChecked ).toBe(
|
||||
shippingMethodLabelIsChecked
|
||||
);
|
||||
} );
|
||||
|
||||
it( 'can enable dark mode inputs', async () => {
|
||||
const toggleLabel = await findLabelWithText(
|
||||
'Dark mode inputs'
|
||||
|
|
|
@ -83,6 +83,11 @@ describe( 'Merchant → Checkout → Can adjust T&S and Privacy Policy options',
|
|||
await shopper.block.goToCheckout();
|
||||
await shopper.block.fillBillingDetails( BILLING_DETAILS );
|
||||
|
||||
// Wait for the "Place Order" button to avoid flakey tests.
|
||||
await page.waitForSelector(
|
||||
'.wc-block-components-checkout-place-order-button:not([disabled])'
|
||||
);
|
||||
|
||||
// Placing an order now, must lead to an error.
|
||||
await page.click( '.wc-block-components-checkout-place-order-button' );
|
||||
|
||||
|
|
|
@ -3,52 +3,17 @@
|
|||
*/
|
||||
import { switchUserToAdmin, visitAdminPage } from '@wordpress/e2e-test-utils';
|
||||
import { findLabelWithText } from '@woocommerce/blocks-test-utils';
|
||||
import WooCommerceRestApi from '@woocommerce/woocommerce-rest-api';
|
||||
import { default as axios } from 'axios';
|
||||
|
||||
const goToSettingsPage = async () => {
|
||||
await visitAdminPage(
|
||||
'admin.php',
|
||||
'page=wc-settings&tab=shipping§ion=pickup_location'
|
||||
);
|
||||
await page.waitForSelector(
|
||||
'#wc-shipping-method-pickup-location-settings-container'
|
||||
);
|
||||
};
|
||||
|
||||
const saveSettingsPageWithRefresh = async () => {
|
||||
await expect( page ).toClick( 'button', {
|
||||
text: 'Save changes',
|
||||
} );
|
||||
await expect( page ).toMatchElement( '.components-snackbar__content', {
|
||||
text: 'Local Pickup settings have been saved.',
|
||||
} );
|
||||
await goToSettingsPage();
|
||||
};
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { merchant } from '../../../utils';
|
||||
|
||||
const setDefaults = async () => {
|
||||
const enabledLabel = await findLabelWithText( 'Enable local pickup' );
|
||||
const enabledChecked = await page.$eval(
|
||||
'#inspector-checkbox-control-1',
|
||||
( el ) => ( el as HTMLInputElement ).checked
|
||||
);
|
||||
if ( enabledChecked ) {
|
||||
await enabledLabel.click();
|
||||
}
|
||||
|
||||
await expect( page ).toFill(
|
||||
'input[name="local_pickup_title"]',
|
||||
'Local Pickup'
|
||||
);
|
||||
|
||||
const costLabel = await findLabelWithText(
|
||||
'Add a price for customers who choose local pickup'
|
||||
);
|
||||
const costChecked = await page.$eval(
|
||||
'#inspector-checkbox-control-1',
|
||||
( el ) => ( el as HTMLInputElement ).checked
|
||||
);
|
||||
if ( costChecked ) {
|
||||
await costLabel.click();
|
||||
}
|
||||
await merchant.enableLocalPickup();
|
||||
await merchant.removeCostForLocalPickup();
|
||||
};
|
||||
|
||||
const clearLocations = async () => {
|
||||
|
@ -66,32 +31,123 @@ const clearLocations = async () => {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the WC Cart and Checkout page IDs to the IDs of the pages with the given slugs.
|
||||
*/
|
||||
const setCartCheckoutPages = async ( {
|
||||
cartSlug,
|
||||
checkoutSlug,
|
||||
}: {
|
||||
cartSlug: string;
|
||||
checkoutSlug: string;
|
||||
} ) => {
|
||||
const WPAPI = `${ process.env.WORDPRESS_BASE_URL }/wp-json/wp/v2/pages`;
|
||||
const response = await axios.get( `${ WPAPI }?per_page=100` );
|
||||
const pages = response.data;
|
||||
const cartBlock = pages.find( ( page ) => page.slug === cartSlug );
|
||||
const checkoutBlock = pages.find( ( page ) => page.slug === checkoutSlug );
|
||||
const WooCommerce = new WooCommerceRestApi( {
|
||||
url: `${ process.env.WORDPRESS_BASE_URL }/`,
|
||||
consumerKey: 'consumer_key', // Your consumer key
|
||||
consumerSecret: 'consumer_secret', // Your consumer secret
|
||||
version: 'wc/v3',
|
||||
axiosConfig: {
|
||||
auth: {
|
||||
username: process.env.WORDPRESS_LOGIN,
|
||||
password: process.env.WORDPRESS_PASSWORD,
|
||||
},
|
||||
},
|
||||
} );
|
||||
const fixture = [
|
||||
{
|
||||
id: 'woocommerce_cart_page_id',
|
||||
value: cartBlock.id.toString() || '',
|
||||
},
|
||||
{
|
||||
id: 'woocommerce_checkout_page_id',
|
||||
value: checkoutBlock.id.toString() || '',
|
||||
},
|
||||
];
|
||||
|
||||
await WooCommerce.post( 'settings/advanced/batch', {
|
||||
update: fixture,
|
||||
} );
|
||||
};
|
||||
describe( `Local Pickup Settings`, () => {
|
||||
beforeAll( async () => {
|
||||
await switchUserToAdmin();
|
||||
await goToSettingsPage();
|
||||
await merchant.goToLocalPickupSettingsPage();
|
||||
await setDefaults();
|
||||
await clearLocations();
|
||||
await saveSettingsPageWithRefresh();
|
||||
await merchant.saveLocalPickupSettingsPageWithRefresh();
|
||||
} );
|
||||
|
||||
afterAll( async () => {
|
||||
await switchUserToAdmin();
|
||||
await goToSettingsPage();
|
||||
await merchant.goToLocalPickupSettingsPage();
|
||||
await setDefaults();
|
||||
await clearLocations();
|
||||
await saveSettingsPageWithRefresh();
|
||||
await merchant.saveLocalPickupSettingsPageWithRefresh();
|
||||
} );
|
||||
|
||||
beforeEach( async () => {
|
||||
await switchUserToAdmin();
|
||||
await goToSettingsPage();
|
||||
await merchant.goToLocalPickupSettingsPage();
|
||||
} );
|
||||
|
||||
it( 'renders without crashing', async () => {
|
||||
await expect( page ).toMatchElement( '#local-pickup-settings' );
|
||||
} );
|
||||
|
||||
describe( 'Core Settings', () => {
|
||||
afterAll( async () => {
|
||||
await setCartCheckoutPages( {
|
||||
cartSlug: 'cart-block',
|
||||
checkoutSlug: 'checkout-block',
|
||||
} );
|
||||
} );
|
||||
it( 'hides the correct shipping options if Checkout block is the default', async () => {
|
||||
await visitAdminPage(
|
||||
'admin.php',
|
||||
'page=wc-settings&tab=shipping§ion=options'
|
||||
);
|
||||
const hideShippingLabel = await findLabelWithText(
|
||||
'Hide shipping costs until an address is entered'
|
||||
);
|
||||
expect( hideShippingLabel ).toBeUndefined();
|
||||
|
||||
const shippingCalculatorLabel = await findLabelWithText(
|
||||
'Enable the shipping calculator on the cart page'
|
||||
);
|
||||
expect( shippingCalculatorLabel ).toBeUndefined();
|
||||
} );
|
||||
|
||||
it( 'does not hide the relevant setting if Cart or Checkout block is not the default', async () => {
|
||||
await setCartCheckoutPages( {
|
||||
cartSlug: 'cart',
|
||||
checkoutSlug: 'checkout',
|
||||
} );
|
||||
|
||||
await visitAdminPage(
|
||||
'admin.php',
|
||||
'page=wc-settings&tab=advanced'
|
||||
);
|
||||
await visitAdminPage(
|
||||
'admin.php',
|
||||
'page=wc-settings&tab=shipping§ion=options'
|
||||
);
|
||||
const hideShippingLabel = await page.$x(
|
||||
'//label[contains(., "Hide shipping costs until an address is entered")]'
|
||||
);
|
||||
await expect( hideShippingLabel ).toHaveLength( 1 );
|
||||
|
||||
const shippingCalculatorLabel = await page.$x(
|
||||
'//label[contains(., "Enable the shipping calculator on the cart page")]'
|
||||
);
|
||||
await expect( shippingCalculatorLabel ).toHaveLength( 1 );
|
||||
} );
|
||||
} );
|
||||
|
||||
describe( 'Global Settings', () => {
|
||||
it( 'allows toggling of enabled on', async () => {
|
||||
const initialChecked = await page.$eval(
|
||||
|
@ -102,7 +158,7 @@ describe( `Local Pickup Settings`, () => {
|
|||
'Enable local pickup'
|
||||
);
|
||||
await toggleLabel.click();
|
||||
await saveSettingsPageWithRefresh();
|
||||
await merchant.saveLocalPickupSettingsPageWithRefresh();
|
||||
|
||||
expect(
|
||||
await page.$eval(
|
||||
|
@ -118,7 +174,7 @@ describe( `Local Pickup Settings`, () => {
|
|||
'Local Pickup Test'
|
||||
);
|
||||
|
||||
await saveSettingsPageWithRefresh();
|
||||
await merchant.saveLocalPickupSettingsPageWithRefresh();
|
||||
|
||||
expect(
|
||||
await page.$eval(
|
||||
|
@ -158,7 +214,7 @@ describe( `Local Pickup Settings`, () => {
|
|||
'none'
|
||||
);
|
||||
|
||||
await saveSettingsPageWithRefresh();
|
||||
await merchant.saveLocalPickupSettingsPageWithRefresh();
|
||||
|
||||
const refreshChecked = await page.$eval(
|
||||
'#inspector-checkbox-control-1',
|
||||
|
@ -218,7 +274,7 @@ describe( `Local Pickup Settings`, () => {
|
|||
text: 'Done',
|
||||
} );
|
||||
|
||||
await saveSettingsPageWithRefresh();
|
||||
await merchant.saveLocalPickupSettingsPageWithRefresh();
|
||||
|
||||
await expect( page ).toMatchElement(
|
||||
'.pickup-locations tbody tr td',
|
||||
|
@ -247,7 +303,7 @@ describe( `Local Pickup Settings`, () => {
|
|||
text: 'Delete location',
|
||||
} );
|
||||
|
||||
await saveSettingsPageWithRefresh();
|
||||
await merchant.saveLocalPickupSettingsPageWithRefresh();
|
||||
await expect( page ).not.toMatchElement(
|
||||
'.pickup-locations tbody tr td',
|
||||
{
|
||||
|
|
|
@ -29,7 +29,7 @@ import {
|
|||
SIMPLE_VIRTUAL_PRODUCT_NAME,
|
||||
BASE_URL,
|
||||
} from '../../../../utils';
|
||||
|
||||
import { merchant as merchantUtils } from '../../../../utils/merchant';
|
||||
import { createCoupon } from '../../../utils';
|
||||
|
||||
let coupon;
|
||||
|
@ -297,6 +297,44 @@ describe( 'Shopper → Checkout', () => {
|
|||
const NORMAL_SHIPPING_NAME = 'Normal Shipping';
|
||||
const NORMAL_SHIPPING_PRICE = '$20.00';
|
||||
|
||||
afterAll( async () => {
|
||||
await merchant.login();
|
||||
await visitBlockPage( 'Checkout Block' );
|
||||
await openDocumentSettingsSidebar();
|
||||
await switchBlockInspectorTabWhenGutenbergIsInstalled( 'Settings' );
|
||||
await selectBlockByName(
|
||||
'woocommerce/checkout-shipping-methods-block'
|
||||
);
|
||||
const [ label ] = await page.$x(
|
||||
'//label[contains(., "Hide shipping costs until an address is entered")]'
|
||||
);
|
||||
const shippingMethodForValue = await page.evaluate(
|
||||
( passedLabel ) => passedLabel.getAttribute( 'for' ),
|
||||
label
|
||||
);
|
||||
let shippingMethodSettingIsChecked = await page.evaluate(
|
||||
( passedShippingMethodForValue ) =>
|
||||
document.getElementById( passedShippingMethodForValue )
|
||||
.checked,
|
||||
shippingMethodForValue
|
||||
);
|
||||
if ( ! shippingMethodSettingIsChecked ) {
|
||||
await setCheckbox(
|
||||
await getToggleIdByLabel(
|
||||
'Hide shipping costs until an address is entered'
|
||||
)
|
||||
);
|
||||
}
|
||||
shippingMethodSettingIsChecked = await page.evaluate(
|
||||
( passedShippingMethodForValue ) =>
|
||||
document.getElementById( passedShippingMethodForValue )
|
||||
.checked,
|
||||
shippingMethodForValue
|
||||
);
|
||||
|
||||
await merchantUtils.disableLocalPickup();
|
||||
} );
|
||||
|
||||
it( 'User can choose free shipping', async () => {
|
||||
await shopper.block.goToShop();
|
||||
await shopper.addToCartFromShopPage( SIMPLE_PHYSICAL_PRODUCT_NAME );
|
||||
|
@ -326,11 +364,175 @@ describe( 'Shopper → Checkout', () => {
|
|||
await expect( page ).toMatch( 'Order received' );
|
||||
await expect( page ).toMatch( NORMAL_SHIPPING_NAME );
|
||||
} );
|
||||
|
||||
it( 'User sees the correct shipping options based on block settings', async () => {
|
||||
await preventCompatibilityNotice();
|
||||
await merchant.login();
|
||||
await visitBlockPage( 'Checkout Block' );
|
||||
await openDocumentSettingsSidebar();
|
||||
await switchBlockInspectorTabWhenGutenbergIsInstalled( 'Settings' );
|
||||
await selectBlockByName(
|
||||
'woocommerce/checkout-shipping-methods-block'
|
||||
);
|
||||
|
||||
const [ label ] = await page.$x(
|
||||
'//label[contains(., "Hide shipping costs until an address is entered")]'
|
||||
);
|
||||
const shippingMethodForValue = await page.evaluate(
|
||||
( passedLabel ) => passedLabel.getAttribute( 'for' ),
|
||||
label
|
||||
);
|
||||
let shippingMethodSettingIsChecked = await page.evaluate(
|
||||
( passedShippingMethodForValue ) =>
|
||||
document.getElementById( passedShippingMethodForValue )
|
||||
.checked,
|
||||
shippingMethodForValue
|
||||
);
|
||||
if ( ! shippingMethodSettingIsChecked ) {
|
||||
await setCheckbox(
|
||||
await getToggleIdByLabel(
|
||||
'Hide shipping costs until an address is entered'
|
||||
)
|
||||
);
|
||||
}
|
||||
shippingMethodSettingIsChecked = await page.evaluate(
|
||||
( passedShippingMethodForValue ) =>
|
||||
document.getElementById( passedShippingMethodForValue )
|
||||
.checked,
|
||||
shippingMethodForValue
|
||||
);
|
||||
await expect( shippingMethodSettingIsChecked ).toBe( true );
|
||||
await saveOrPublish();
|
||||
await shopper.block.emptyCart();
|
||||
// Log out to have a fresh empty cart.
|
||||
await shopper.logout();
|
||||
await shopper.block.goToShop();
|
||||
await shopper.addToCartFromShopPage( SIMPLE_PHYSICAL_PRODUCT_NAME );
|
||||
await shopper.block.goToCheckout();
|
||||
// Expect no shipping options to be shown, but with a friendly message.
|
||||
const shippingOptionsRequireAddressText = await page.$x(
|
||||
'//p[contains(text(), "Shipping options will be displayed here after entering your full shipping address.")]'
|
||||
);
|
||||
expect( shippingOptionsRequireAddressText ).toHaveLength( 1 );
|
||||
|
||||
// Enter the address and expect shipping options to be shown.
|
||||
await shopper.block.fillInCheckoutWithTestData();
|
||||
await expect( page ).toMatchElement(
|
||||
'.wc-block-components-shipping-rates-control'
|
||||
);
|
||||
|
||||
// This sequence will reset the checkout form.
|
||||
await shopper.login();
|
||||
await shopper.logout();
|
||||
|
||||
await preventCompatibilityNotice();
|
||||
await merchant.login();
|
||||
await visitBlockPage( 'Checkout Block' );
|
||||
await openDocumentSettingsSidebar();
|
||||
await switchBlockInspectorTabWhenGutenbergIsInstalled( 'Settings' );
|
||||
await selectBlockByName(
|
||||
'woocommerce/checkout-shipping-methods-block'
|
||||
);
|
||||
|
||||
await unsetCheckbox(
|
||||
await getToggleIdByLabel(
|
||||
'Hide shipping costs until an address is entered'
|
||||
)
|
||||
);
|
||||
await saveOrPublish();
|
||||
await shopper.block.emptyCart();
|
||||
|
||||
await shopper.block.goToShop();
|
||||
await shopper.addToCartFromShopPage( SIMPLE_PHYSICAL_PRODUCT_NAME );
|
||||
await shopper.block.goToCheckout();
|
||||
|
||||
// Expect the shipping options to be displayed without entering an address.
|
||||
await expect( page ).toMatchElement(
|
||||
'.wc-block-components-shipping-rates-control'
|
||||
);
|
||||
} );
|
||||
|
||||
it( 'User does not see shipping rates until full address is entered', async () => {
|
||||
await preventCompatibilityNotice();
|
||||
await merchant.login();
|
||||
|
||||
await merchantUtils.enableLocalPickup();
|
||||
await merchantUtils.addLocalPickupLocation();
|
||||
await visitBlockPage( 'Checkout Block' );
|
||||
await openDocumentSettingsSidebar();
|
||||
await switchBlockInspectorTabWhenGutenbergIsInstalled( 'Settings' );
|
||||
await selectBlockByName(
|
||||
'woocommerce/checkout-shipping-methods-block'
|
||||
);
|
||||
|
||||
await setCheckbox(
|
||||
await getToggleIdByLabel(
|
||||
'Hide shipping costs until an address is entered'
|
||||
)
|
||||
);
|
||||
await saveOrPublish();
|
||||
await shopper.block.emptyCart();
|
||||
// Log out to have a fresh empty cart.
|
||||
await shopper.logout();
|
||||
await shopper.block.goToShop();
|
||||
await shopper.addToCartFromShopPage( SIMPLE_PHYSICAL_PRODUCT_NAME );
|
||||
await shopper.block.goToCheckout();
|
||||
|
||||
// Expect no shipping options to be shown, but with a friendly message.
|
||||
const shippingOptionsRequireAddressText = await page.$x(
|
||||
'//p[contains(text(), "Shipping options will be displayed here after entering your full shipping address.")]'
|
||||
);
|
||||
|
||||
expect( shippingOptionsRequireAddressText ).toHaveLength( 1 );
|
||||
|
||||
await expect( page ).toClick(
|
||||
'.wc-block-checkout__shipping-method button',
|
||||
{ text: 'Shipping' }
|
||||
);
|
||||
|
||||
// Enter the address but not city and expect shipping options not to be shown.
|
||||
await shopper.block.fillInCheckoutWithTestData( { city: '' } );
|
||||
|
||||
await expect( page ).not.toMatchElement(
|
||||
'.wc-block-components-shipping-rates-control'
|
||||
);
|
||||
|
||||
// This sequence will reset the checkout form.
|
||||
await shopper.login();
|
||||
await shopper.logout();
|
||||
|
||||
await preventCompatibilityNotice();
|
||||
await merchant.login();
|
||||
await visitBlockPage( 'Checkout Block' );
|
||||
await openDocumentSettingsSidebar();
|
||||
await switchBlockInspectorTabWhenGutenbergIsInstalled( 'Settings' );
|
||||
await selectBlockByName(
|
||||
'woocommerce/checkout-shipping-methods-block'
|
||||
);
|
||||
|
||||
await unsetCheckbox(
|
||||
await getToggleIdByLabel(
|
||||
'Hide shipping costs until an address is entered'
|
||||
)
|
||||
);
|
||||
await saveOrPublish();
|
||||
await shopper.block.emptyCart();
|
||||
|
||||
await shopper.block.goToShop();
|
||||
await shopper.addToCartFromShopPage( SIMPLE_PHYSICAL_PRODUCT_NAME );
|
||||
await shopper.block.goToCheckout();
|
||||
|
||||
// Expect the shipping options to be displayed without entering an address.
|
||||
await expect( page ).toMatchElement(
|
||||
'.wc-block-components-shipping-rates-control'
|
||||
);
|
||||
} );
|
||||
} );
|
||||
|
||||
describe( 'Coupons', () => {
|
||||
beforeAll( async () => {
|
||||
coupon = await createCoupon( { usageLimit: 1 } );
|
||||
await shopper.logout();
|
||||
await shopper.login();
|
||||
} );
|
||||
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { merchant as wcMerchant } from '@woocommerce/e2e-utils';
|
||||
import { visitAdminPage } from '@wordpress/e2e-test-utils';
|
||||
|
||||
export const merchant = {
|
||||
...wcMerchant,
|
||||
changeLanguage: async ( language ) => {
|
||||
await visitAdminPage( 'options-general.php' );
|
||||
await page.select( 'select#WPLANG', language );
|
||||
await page.click( 'input[type="submit"]' );
|
||||
await page.waitForSelector( '#setting-error-settings_updated', {
|
||||
visible: true,
|
||||
} );
|
||||
},
|
||||
};
|
|
@ -0,0 +1,110 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { merchant as wcMerchant } from '@woocommerce/e2e-utils';
|
||||
import { visitAdminPage } from '@wordpress/e2e-test-utils';
|
||||
import { findLabelWithText } from '@woocommerce/blocks-test-utils';
|
||||
|
||||
export const merchant = {
|
||||
...wcMerchant,
|
||||
changeLanguage: async ( language ) => {
|
||||
await visitAdminPage( 'options-general.php' );
|
||||
await page.select( 'select#WPLANG', language );
|
||||
await page.click( 'input[type="submit"]' );
|
||||
await page.waitForSelector( '#setting-error-settings_updated', {
|
||||
visible: true,
|
||||
} );
|
||||
},
|
||||
goToLocalPickupSettingsPage: async () => {
|
||||
await visitAdminPage(
|
||||
'admin.php',
|
||||
'page=wc-settings&tab=shipping§ion=pickup_location'
|
||||
);
|
||||
await page.waitForSelector(
|
||||
'#wc-shipping-method-pickup-location-settings-container'
|
||||
);
|
||||
},
|
||||
saveLocalPickupSettingsPageWithRefresh: async () => {
|
||||
await expect( page ).toClick( 'button', {
|
||||
text: 'Save changes',
|
||||
} );
|
||||
await expect( page ).toMatchElement( '.components-snackbar__content', {
|
||||
text: 'Local Pickup settings have been saved.',
|
||||
} );
|
||||
await merchant.goToLocalPickupSettingsPage();
|
||||
},
|
||||
enableLocalPickup: async () => {
|
||||
await merchant.goToLocalPickupSettingsPage();
|
||||
const enabledLabel = await findLabelWithText( 'Enable local pickup' );
|
||||
const enabledChecked = await page.$eval(
|
||||
'#inspector-checkbox-control-1',
|
||||
( el ) => ( el as HTMLInputElement ).checked
|
||||
);
|
||||
if ( ! enabledChecked ) {
|
||||
await enabledLabel.click();
|
||||
}
|
||||
|
||||
await expect( page ).toFill(
|
||||
'input[name="local_pickup_title"]',
|
||||
'Local Pickup'
|
||||
);
|
||||
await merchant.saveLocalPickupSettingsPageWithRefresh();
|
||||
},
|
||||
disableLocalPickup: async () => {
|
||||
await merchant.goToLocalPickupSettingsPage();
|
||||
const enabledLabel = await findLabelWithText( 'Enable local pickup' );
|
||||
const enabledChecked = await page.$eval(
|
||||
'#inspector-checkbox-control-1',
|
||||
( el ) => ( el as HTMLInputElement ).checked
|
||||
);
|
||||
if ( enabledChecked ) {
|
||||
await enabledLabel.click();
|
||||
}
|
||||
await merchant.saveLocalPickupSettingsPageWithRefresh();
|
||||
},
|
||||
removeCostForLocalPickup: async () => {
|
||||
const costLabel = await findLabelWithText(
|
||||
'Add a price for customers who choose local pickup'
|
||||
);
|
||||
const costChecked = await page.$eval(
|
||||
'#inspector-checkbox-control-1',
|
||||
( el ) => ( el as HTMLInputElement ).checked
|
||||
);
|
||||
if ( costChecked ) {
|
||||
await costLabel.click();
|
||||
}
|
||||
},
|
||||
addLocalPickupLocation: async () => {
|
||||
await merchant.goToLocalPickupSettingsPage();
|
||||
await expect( page ).toClick( 'button', {
|
||||
text: 'Add pickup location',
|
||||
} );
|
||||
await expect( page ).toFill(
|
||||
'input[name="location_name"]',
|
||||
'Test Location'
|
||||
);
|
||||
await expect( page ).toFill(
|
||||
'input[name="location_address"]',
|
||||
'Test Address 1'
|
||||
);
|
||||
await expect( page ).toFill(
|
||||
'input[name="location_city"]',
|
||||
'Test City'
|
||||
);
|
||||
await expect( page ).toFill(
|
||||
'input[name="location_postcode"]',
|
||||
'90210'
|
||||
);
|
||||
await expect( page ).toFill(
|
||||
'input[name="pickup_details"]',
|
||||
'Collect from store'
|
||||
);
|
||||
await expect( page ).toSelect(
|
||||
'select[name="location_country"]',
|
||||
'US'
|
||||
);
|
||||
await expect( page ).toSelect( 'select[name="location_state"]', 'CA' );
|
||||
await expect( page ).toClick( 'button', { text: 'Done' } );
|
||||
await merchant.saveLocalPickupSettingsPageWithRefresh();
|
||||
},
|
||||
};
|
Loading…
Reference in New Issue