Enhance checkout filter system. (https://github.com/woocommerce/woocommerce-blocks/pull/3835)
* Add filter to extend product price * Remove code targeting WC Subscriptions * Rename filter * Use extendibility API instead of filters * Remove __EXPERIMENTAL_CART_ITEM_PRICE_FILTER from docs * throw errors on validation * Don't catch filter errors for admins * Add tests * wrap filter calls in memo * pass extensions as top level prop * abstract errors * add jsdoc * update tests * review * turn __experimentalApplyCheckoutFilter into a hook and move useMemo inside it * revert name * wrap getCheckoutFilters in useMemo * refactor filter function so memozation is done inside components * unify true instance * fix rebase Co-authored-by: Albert Juhé Lluveras <contact@albertjuhe.com>
This commit is contained in:
parent
eac22ca2f7
commit
30c2079af4
|
@ -6,10 +6,15 @@ import Label from '@woocommerce/base-components/label';
|
||||||
import ProductPrice from '@woocommerce/base-components/product-price';
|
import ProductPrice from '@woocommerce/base-components/product-price';
|
||||||
import ProductName from '@woocommerce/base-components/product-name';
|
import ProductName from '@woocommerce/base-components/product-name';
|
||||||
import { getCurrency } from '@woocommerce/price-format';
|
import { getCurrency } from '@woocommerce/price-format';
|
||||||
import { __experimentalApplyCheckoutFilter } from '@woocommerce/blocks-checkout';
|
import {
|
||||||
|
__experimentalApplyCheckoutFilter,
|
||||||
|
mustBeString,
|
||||||
|
mustContain,
|
||||||
|
} from '@woocommerce/blocks-checkout';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import Dinero from 'dinero.js';
|
import Dinero from 'dinero.js';
|
||||||
import { DISPLAY_CART_PRICES_INCLUDING_TAX } from '@woocommerce/block-settings';
|
import { DISPLAY_CART_PRICES_INCLUDING_TAX } from '@woocommerce/block-settings';
|
||||||
|
import { useCallback, useMemo } from '@wordpress/element';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
|
@ -36,16 +41,27 @@ const OrderSummaryItem = ( { cartItem } ) => {
|
||||||
extensions,
|
extensions,
|
||||||
} = cartItem;
|
} = cartItem;
|
||||||
|
|
||||||
|
const productPriceValidation = useCallback(
|
||||||
|
( value ) => mustBeString( value ) && mustContain( value, '<price/>' ),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
const arg = useMemo(
|
||||||
|
() => ( {
|
||||||
|
context: 'summary',
|
||||||
|
cartItem,
|
||||||
|
} ),
|
||||||
|
[ cartItem ]
|
||||||
|
);
|
||||||
|
|
||||||
const priceCurrency = getCurrency( prices );
|
const priceCurrency = getCurrency( prices );
|
||||||
|
|
||||||
const name = __experimentalApplyCheckoutFilter( {
|
const name = __experimentalApplyCheckoutFilter( {
|
||||||
filterName: 'itemName',
|
filterName: 'itemName',
|
||||||
defaultValue: initialName,
|
defaultValue: initialName,
|
||||||
arg: {
|
extensions,
|
||||||
extensions,
|
arg,
|
||||||
context: 'summary',
|
validation: mustBeString,
|
||||||
},
|
|
||||||
validation: ( value ) => typeof value === 'string',
|
|
||||||
} );
|
} );
|
||||||
|
|
||||||
const regularPriceSingle = Dinero( {
|
const regularPriceSingle = Dinero( {
|
||||||
|
@ -74,24 +90,18 @@ const OrderSummaryItem = ( { cartItem } ) => {
|
||||||
const subtotalPriceFormat = __experimentalApplyCheckoutFilter( {
|
const subtotalPriceFormat = __experimentalApplyCheckoutFilter( {
|
||||||
filterName: 'subtotalPriceFormat',
|
filterName: 'subtotalPriceFormat',
|
||||||
defaultValue: '<price/>',
|
defaultValue: '<price/>',
|
||||||
arg: {
|
extensions,
|
||||||
lineItem: cartItem,
|
arg,
|
||||||
},
|
validation: productPriceValidation,
|
||||||
// Only accept strings.
|
|
||||||
validation: ( value ) =>
|
|
||||||
typeof value === 'string' && value.includes( '<price/>' ),
|
|
||||||
} );
|
} );
|
||||||
|
|
||||||
// Allow extensions to filter how the price is displayed. Ie: prepending or appending some values.
|
// Allow extensions to filter how the price is displayed. Ie: prepending or appending some values.
|
||||||
const productPriceFormat = __experimentalApplyCheckoutFilter( {
|
const productPriceFormat = __experimentalApplyCheckoutFilter( {
|
||||||
filterName: 'cartItemPrice',
|
filterName: 'cartItemPrice',
|
||||||
defaultValue: '<price/>',
|
defaultValue: '<price/>',
|
||||||
arg: {
|
extensions,
|
||||||
cartItem,
|
arg,
|
||||||
block: 'checkout',
|
validation: productPriceValidation,
|
||||||
},
|
|
||||||
validation: ( value ) =>
|
|
||||||
typeof value === 'string' && value.includes( '<price/>' ),
|
|
||||||
} );
|
} );
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -11,6 +11,7 @@ import FormattedMonetaryAmount from '@woocommerce/base-components/formatted-mone
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import {
|
import {
|
||||||
__experimentalApplyCheckoutFilter,
|
__experimentalApplyCheckoutFilter,
|
||||||
|
mustBeString,
|
||||||
TotalsItem,
|
TotalsItem,
|
||||||
} from '@woocommerce/blocks-checkout';
|
} from '@woocommerce/blocks-checkout';
|
||||||
import { useStoreCart } from '@woocommerce/base-hooks';
|
import { useStoreCart } from '@woocommerce/base-hooks';
|
||||||
|
@ -28,11 +29,9 @@ const TotalsFooterItem = ( { currency, values } ) => {
|
||||||
const label = __experimentalApplyCheckoutFilter( {
|
const label = __experimentalApplyCheckoutFilter( {
|
||||||
filterName: 'totalLabel',
|
filterName: 'totalLabel',
|
||||||
defaultValue: __( 'Total', 'woo-gutenberg-products-block' ),
|
defaultValue: __( 'Total', 'woo-gutenberg-products-block' ),
|
||||||
arg: {
|
extensions,
|
||||||
extensions,
|
|
||||||
},
|
|
||||||
// Only accept strings.
|
// Only accept strings.
|
||||||
validation: ( value ) => typeof value === 'string',
|
validation: mustBeString,
|
||||||
} );
|
} );
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -16,9 +16,14 @@ import {
|
||||||
ProductSaleBadge,
|
ProductSaleBadge,
|
||||||
} from '@woocommerce/base-components/cart-checkout';
|
} from '@woocommerce/base-components/cart-checkout';
|
||||||
import { getCurrency } from '@woocommerce/price-format';
|
import { getCurrency } from '@woocommerce/price-format';
|
||||||
import { __experimentalApplyCheckoutFilter } from '@woocommerce/blocks-checkout';
|
import {
|
||||||
|
__experimentalApplyCheckoutFilter,
|
||||||
|
mustBeString,
|
||||||
|
mustContain,
|
||||||
|
} from '@woocommerce/blocks-checkout';
|
||||||
import Dinero from 'dinero.js';
|
import Dinero from 'dinero.js';
|
||||||
import { DISPLAY_CART_PRICES_INCLUDING_TAX } from '@woocommerce/block-settings';
|
import { DISPLAY_CART_PRICES_INCLUDING_TAX } from '@woocommerce/block-settings';
|
||||||
|
import { useCallback, useMemo } from '@wordpress/element';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {import('@woocommerce/type-defs/cart').CartItem} CartItem
|
* @typedef {import('@woocommerce/type-defs/cart').CartItem} CartItem
|
||||||
|
@ -94,16 +99,24 @@ const CartLineItemRow = ( { lineItem = {} } ) => {
|
||||||
isPendingDelete,
|
isPendingDelete,
|
||||||
} = useStoreCartItemQuantity( lineItem );
|
} = useStoreCartItemQuantity( lineItem );
|
||||||
|
|
||||||
|
const productPriceValidation = useCallback(
|
||||||
|
( value ) => mustBeString( value ) && mustContain( value, '<price/>' ),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
const arg = useMemo(
|
||||||
|
() => ( {
|
||||||
|
context: 'cart',
|
||||||
|
cartItem: lineItem,
|
||||||
|
} ),
|
||||||
|
[ lineItem ]
|
||||||
|
);
|
||||||
const priceCurrency = getCurrency( prices );
|
const priceCurrency = getCurrency( prices );
|
||||||
|
|
||||||
const name = __experimentalApplyCheckoutFilter( {
|
const name = __experimentalApplyCheckoutFilter( {
|
||||||
filterName: 'itemName',
|
filterName: 'itemName',
|
||||||
defaultValue: initialName,
|
defaultValue: initialName,
|
||||||
arg: {
|
extensions,
|
||||||
extensions,
|
arg,
|
||||||
context: 'cart',
|
validation: mustBeString,
|
||||||
},
|
|
||||||
validation: ( value ) => typeof value === 'string',
|
|
||||||
} );
|
} );
|
||||||
|
|
||||||
const regularAmountSingle = Dinero( {
|
const regularAmountSingle = Dinero( {
|
||||||
|
@ -132,37 +145,29 @@ const CartLineItemRow = ( { lineItem = {} } ) => {
|
||||||
catalogVisibility === 'hidden' || catalogVisibility === 'search';
|
catalogVisibility === 'hidden' || catalogVisibility === 'search';
|
||||||
|
|
||||||
// Allow extensions to filter how the price is displayed. Ie: prepending or appending some values.
|
// Allow extensions to filter how the price is displayed. Ie: prepending or appending some values.
|
||||||
|
|
||||||
const productPriceFormat = __experimentalApplyCheckoutFilter( {
|
const productPriceFormat = __experimentalApplyCheckoutFilter( {
|
||||||
filterName: 'cartItemPrice',
|
filterName: 'cartItemPrice',
|
||||||
defaultValue: '<price/>',
|
defaultValue: '<price/>',
|
||||||
arg: {
|
extensions,
|
||||||
cartItem: lineItem,
|
arg,
|
||||||
block: 'cart',
|
validation: productPriceValidation,
|
||||||
},
|
|
||||||
validation: ( value ) =>
|
|
||||||
typeof value === 'string' && value.includes( '<price/>' ),
|
|
||||||
} );
|
} );
|
||||||
|
|
||||||
const subtotalPriceFormat = __experimentalApplyCheckoutFilter( {
|
const subtotalPriceFormat = __experimentalApplyCheckoutFilter( {
|
||||||
filterName: 'subtotalPriceFormat',
|
filterName: 'subtotalPriceFormat',
|
||||||
defaultValue: '<price/>',
|
defaultValue: '<price/>',
|
||||||
arg: {
|
extensions,
|
||||||
lineItem,
|
arg,
|
||||||
},
|
validation: productPriceValidation,
|
||||||
// Only accept strings.
|
|
||||||
validation: ( value ) =>
|
|
||||||
typeof value === 'string' && value.includes( '<price/>' ),
|
|
||||||
} );
|
} );
|
||||||
|
|
||||||
const saleBadgePriceFormat = __experimentalApplyCheckoutFilter( {
|
const saleBadgePriceFormat = __experimentalApplyCheckoutFilter( {
|
||||||
filterName: 'saleBadgePriceFormat',
|
filterName: 'saleBadgePriceFormat',
|
||||||
defaultValue: '<price/>',
|
defaultValue: '<price/>',
|
||||||
arg: {
|
extensions,
|
||||||
lineItem,
|
arg,
|
||||||
},
|
validation: productPriceValidation,
|
||||||
// Only accept strings.
|
|
||||||
validation: ( value ) =>
|
|
||||||
typeof value === 'string' && value.includes( '<price/>' ),
|
|
||||||
} );
|
} );
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -3545,6 +3545,20 @@
|
||||||
"@testing-library/dom": "^7.28.1"
|
"@testing-library/dom": "^7.28.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@testing-library/react-hooks": {
|
||||||
|
"version": "5.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@testing-library/react-hooks/-/react-hooks-5.0.3.tgz",
|
||||||
|
"integrity": "sha512-UrnnRc5II7LMH14xsYNm/WRch/67cBafmrSQcyFh0v+UUmSf1uzfB7zn5jQXSettGwOSxJwdQUN7PgkT0w22Lg==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.12.5",
|
||||||
|
"@types/react": ">=16.9.0",
|
||||||
|
"@types/react-dom": ">=16.9.0",
|
||||||
|
"@types/react-test-renderer": ">=16.9.0",
|
||||||
|
"filter-console": "^0.1.1",
|
||||||
|
"react-error-boundary": "^3.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@testing-library/user-event": {
|
"@testing-library/user-event": {
|
||||||
"version": "12.6.3",
|
"version": "12.6.3",
|
||||||
"resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-12.6.3.tgz",
|
"resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-12.6.3.tgz",
|
||||||
|
@ -3948,6 +3962,15 @@
|
||||||
"@types/react": "*"
|
"@types/react": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@types/react-test-renderer": {
|
||||||
|
"version": "17.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/react-test-renderer/-/react-test-renderer-17.0.1.tgz",
|
||||||
|
"integrity": "sha512-3Fi2O6Zzq/f3QR9dRnlnHso9bMl7weKCviFmfF6B4LS1Uat6Hkm15k0ZAQuDz+UBq6B3+g+NM6IT2nr5QgPzCw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@types/react": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@types/reactcss": {
|
"@types/reactcss": {
|
||||||
"version": "1.2.3",
|
"version": "1.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/@types/reactcss/-/reactcss-1.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/@types/reactcss/-/reactcss-1.2.3.tgz",
|
||||||
|
@ -15160,6 +15183,12 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"filter-console": {
|
||||||
|
"version": "0.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/filter-console/-/filter-console-0.1.1.tgz",
|
||||||
|
"integrity": "sha512-zrXoV1Uaz52DqPs+qEwNJWJFAWZpYJ47UNmpN9q4j+/EYsz85uV0DC9k8tRND5kYmoVzL0W+Y75q4Rg8sRJCdg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"finalhandler": {
|
"finalhandler": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
|
||||||
|
@ -27306,6 +27335,15 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"react-error-boundary": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-lmPrdi5SLRJR+AeJkqdkGlW/CRkAUvZnETahK58J4xb5wpbfDngasEGu+w0T1iXEhVrYBJZeW+c4V1hILCnMWQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.12.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
"react-error-overlay": {
|
"react-error-overlay": {
|
||||||
"version": "6.0.9",
|
"version": "6.0.9",
|
||||||
"resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.9.tgz",
|
"resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.9.tgz",
|
||||||
|
|
|
@ -83,6 +83,7 @@
|
||||||
"@storybook/react": "6.0.28",
|
"@storybook/react": "6.0.28",
|
||||||
"@testing-library/jest-dom": "5.11.9",
|
"@testing-library/jest-dom": "5.11.9",
|
||||||
"@testing-library/react": "11.2.5",
|
"@testing-library/react": "11.2.5",
|
||||||
|
"@testing-library/react-hooks": "^5.0.3",
|
||||||
"@testing-library/user-event": "12.6.3",
|
"@testing-library/user-event": "12.6.3",
|
||||||
"@types/jest": "26.0.20",
|
"@types/jest": "26.0.20",
|
||||||
"@types/react": "16.14.3",
|
"@types/react": "16.14.3",
|
||||||
|
@ -103,7 +104,7 @@
|
||||||
"@wordpress/env": "3.0.0",
|
"@wordpress/env": "3.0.0",
|
||||||
"@wordpress/html-entities": "2.8.0",
|
"@wordpress/html-entities": "2.8.0",
|
||||||
"@wordpress/i18n": "3.15.0",
|
"@wordpress/i18n": "3.15.0",
|
||||||
"@wordpress/is-shallow-equal": "1.8.0",
|
"@wordpress/is-shallow-equal": "^1.8.0",
|
||||||
"@wordpress/scripts": "13.0.1",
|
"@wordpress/scripts": "13.0.1",
|
||||||
"autoprefixer": "10.2.3",
|
"autoprefixer": "10.2.3",
|
||||||
"axios": "0.21.1",
|
"axios": "0.21.1",
|
||||||
|
@ -139,7 +140,7 @@
|
||||||
"progress-bar-webpack-plugin": "2.1.0",
|
"progress-bar-webpack-plugin": "2.1.0",
|
||||||
"promptly": "3.2.0",
|
"promptly": "3.2.0",
|
||||||
"puppeteer": "npm:puppeteer-core@5.5.0",
|
"puppeteer": "npm:puppeteer-core@5.5.0",
|
||||||
"react-test-renderer": "17.0.1",
|
"react-test-renderer": "^17.0.1",
|
||||||
"request-promise": "4.2.6",
|
"request-promise": "4.2.6",
|
||||||
"rimraf": "3.0.2",
|
"rimraf": "3.0.2",
|
||||||
"sass-loader": "10.1.0",
|
"sass-loader": "10.1.0",
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
export * from './totals';
|
export * from './totals';
|
||||||
export * from './shipping';
|
export * from './shipping';
|
||||||
|
export * from './utils';
|
||||||
export * from './slot';
|
export * from './slot';
|
||||||
export * from './registry';
|
export * from './registry';
|
||||||
export { default as ExperimentalOrderMeta } from './order-meta';
|
export { default as ExperimentalOrderMeta } from './order-meta';
|
||||||
|
|
|
@ -1,3 +1,14 @@
|
||||||
|
/**
|
||||||
|
* External dependencies
|
||||||
|
*/
|
||||||
|
import { useMemo } from '@wordpress/element';
|
||||||
|
import { CURRENT_USER_IS_ADMIN } from '@woocommerce/block-settings';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal dependencies
|
||||||
|
*/
|
||||||
|
import { returnTrue } from '../';
|
||||||
|
|
||||||
let checkoutFilters = {};
|
let checkoutFilters = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -32,33 +43,42 @@ const getCheckoutFilters = ( filterName ) => {
|
||||||
/**
|
/**
|
||||||
* Apply a filter.
|
* Apply a filter.
|
||||||
*
|
*
|
||||||
* @param {Object} o Object of arguments.
|
* @param {Object} o Object of arguments.
|
||||||
* @param {string} o.filterName Name of the filter to apply.
|
* @param {string} o.filterName Name of the filter to apply.
|
||||||
* @param {any} o.defaultValue Default value to filter.
|
* @param {any} o.defaultValue Default value to filter.
|
||||||
* @param {any} [o.arg] Argument to pass to registered functions. If
|
* @param {Object} [o.extensions] Values extend to REST API response.
|
||||||
* several arguments need to be passed, use an
|
* @param {any} [o.arg] Argument to pass to registered functions.
|
||||||
* object.
|
* If several arguments need to be passed, use
|
||||||
* @param {Function} [o.validation] Function that needs to return true when the
|
* an object.
|
||||||
* filtered value is passed in order for the
|
* @param {Function} [o.validation] Function that needs to return true when
|
||||||
* filter to be applied.
|
* the filtered value is passed in order for
|
||||||
|
* the filter to be applied.
|
||||||
* @return {any} Filtered value.
|
* @return {any} Filtered value.
|
||||||
*/
|
*/
|
||||||
export const __experimentalApplyCheckoutFilter = ( {
|
export const __experimentalApplyCheckoutFilter = ( {
|
||||||
filterName,
|
filterName,
|
||||||
defaultValue,
|
defaultValue,
|
||||||
|
extensions,
|
||||||
arg = null,
|
arg = null,
|
||||||
validation = () => true,
|
validation = returnTrue,
|
||||||
} ) => {
|
} ) => {
|
||||||
const filters = getCheckoutFilters( filterName );
|
return useMemo( () => {
|
||||||
let value = defaultValue;
|
const filters = getCheckoutFilters( filterName );
|
||||||
filters.forEach( ( filter ) => {
|
|
||||||
try {
|
let value = defaultValue;
|
||||||
const newValue = filter( value, arg );
|
filters.forEach( ( filter ) => {
|
||||||
value = validation( newValue ) ? newValue : value;
|
try {
|
||||||
} catch ( e ) {
|
const newValue = filter( value, extensions, arg );
|
||||||
// eslint-disable-next-line no-console
|
value = validation( newValue ) ? newValue : value;
|
||||||
console.log( e );
|
} catch ( e ) {
|
||||||
}
|
if ( CURRENT_USER_IS_ADMIN ) {
|
||||||
} );
|
throw e;
|
||||||
return value;
|
} else {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error( e );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
return value;
|
||||||
|
}, [ filterName, defaultValue, extensions, arg, validation ] );
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
/**
|
||||||
|
* External dependencies
|
||||||
|
*/
|
||||||
|
import { renderHook } from '@testing-library/react-hooks';
|
||||||
|
/**
|
||||||
|
* Internal dependencies
|
||||||
|
*/
|
||||||
|
import {
|
||||||
|
__experimentalRegisterCheckoutFilters,
|
||||||
|
__experimentalApplyCheckoutFilter,
|
||||||
|
} from '../';
|
||||||
|
|
||||||
|
jest.mock( '@woocommerce/block-settings', () => {
|
||||||
|
const originalModule = jest.requireActual( '@woocommerce/settings' );
|
||||||
|
return {
|
||||||
|
// @ts-ignore We know @woocommerce/settings is an object.
|
||||||
|
...originalModule,
|
||||||
|
CURRENT_USER_IS_ADMIN: true,
|
||||||
|
};
|
||||||
|
} );
|
||||||
|
|
||||||
|
describe( 'Checkout registry (as admin user)', () => {
|
||||||
|
test( 'should throw if the filter throws and user is an admin', () => {
|
||||||
|
const filterName = 'ErrorTestFilter';
|
||||||
|
const value = 'Hello World';
|
||||||
|
__experimentalRegisterCheckoutFilters( filterName, {
|
||||||
|
[ filterName ]: () => {
|
||||||
|
throw new Error( 'test error' );
|
||||||
|
},
|
||||||
|
} );
|
||||||
|
|
||||||
|
const { result } = renderHook( () =>
|
||||||
|
__experimentalApplyCheckoutFilter( {
|
||||||
|
filterName,
|
||||||
|
defaultValue: value,
|
||||||
|
} )
|
||||||
|
);
|
||||||
|
expect( result.error ).toEqual( Error( 'test error' ) );
|
||||||
|
} );
|
||||||
|
|
||||||
|
test( 'should throw if validation throws and user is an admin', () => {
|
||||||
|
const filterName = 'ValidationTestFilter';
|
||||||
|
const value = 'Hello World';
|
||||||
|
__experimentalRegisterCheckoutFilters( filterName, {
|
||||||
|
[ filterName ]: ( val ) => val,
|
||||||
|
} );
|
||||||
|
const { result } = renderHook( () =>
|
||||||
|
__experimentalApplyCheckoutFilter( {
|
||||||
|
filterName,
|
||||||
|
defaultValue: value,
|
||||||
|
validation: () => {
|
||||||
|
throw Error( 'validation error' );
|
||||||
|
},
|
||||||
|
} )
|
||||||
|
);
|
||||||
|
expect( result.error ).toEqual( Error( 'validation error' ) );
|
||||||
|
} );
|
||||||
|
} );
|
|
@ -1,3 +1,7 @@
|
||||||
|
/**
|
||||||
|
* External dependencies
|
||||||
|
*/
|
||||||
|
import { renderHook } from '@testing-library/react-hooks';
|
||||||
/**
|
/**
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
*/
|
*/
|
||||||
|
@ -11,29 +15,32 @@ describe( 'Checkout registry', () => {
|
||||||
|
|
||||||
test( 'should return default value if there are no filters', () => {
|
test( 'should return default value if there are no filters', () => {
|
||||||
const value = 'Hello World';
|
const value = 'Hello World';
|
||||||
const newValue = __experimentalApplyCheckoutFilter( {
|
const { result: newValue } = renderHook( () =>
|
||||||
filterName,
|
__experimentalApplyCheckoutFilter( {
|
||||||
defaultValue: value,
|
filterName,
|
||||||
} );
|
defaultValue: value,
|
||||||
|
} )
|
||||||
expect( newValue ).toBe( value );
|
);
|
||||||
|
expect( newValue.current ).toBe( value );
|
||||||
} );
|
} );
|
||||||
|
|
||||||
test( 'should return filtered value when a filter is registered', () => {
|
test( 'should return filtered value when a filter is registered', () => {
|
||||||
const value = 'Hello World';
|
const value = 'Hello World';
|
||||||
__experimentalRegisterCheckoutFilters( filterName, {
|
__experimentalRegisterCheckoutFilters( filterName, {
|
||||||
[ filterName ]: ( val, args ) =>
|
[ filterName ]: ( val, extensions, args ) =>
|
||||||
val.toUpperCase() + args.punctuationSign,
|
val.toUpperCase() + args.punctuationSign,
|
||||||
} );
|
} );
|
||||||
const newValue = __experimentalApplyCheckoutFilter( {
|
const { result: newValue } = renderHook( () =>
|
||||||
filterName,
|
__experimentalApplyCheckoutFilter( {
|
||||||
defaultValue: value,
|
filterName,
|
||||||
arg: {
|
defaultValue: value,
|
||||||
punctuationSign: '!',
|
arg: {
|
||||||
},
|
punctuationSign: '!',
|
||||||
} );
|
},
|
||||||
|
} )
|
||||||
|
);
|
||||||
|
|
||||||
expect( newValue ).toBe( 'HELLO WORLD!' );
|
expect( newValue.current ).toBe( 'HELLO WORLD!' );
|
||||||
} );
|
} );
|
||||||
|
|
||||||
test( 'should not return filtered value if validation failed', () => {
|
test( 'should not return filtered value if validation failed', () => {
|
||||||
|
@ -41,12 +48,39 @@ describe( 'Checkout registry', () => {
|
||||||
__experimentalRegisterCheckoutFilters( filterName, {
|
__experimentalRegisterCheckoutFilters( filterName, {
|
||||||
[ filterName ]: ( val ) => val.toUpperCase(),
|
[ filterName ]: ( val ) => val.toUpperCase(),
|
||||||
} );
|
} );
|
||||||
const newValue = __experimentalApplyCheckoutFilter( {
|
const { result: newValue } = renderHook( () =>
|
||||||
filterName,
|
__experimentalApplyCheckoutFilter( {
|
||||||
defaultValue: value,
|
filterName,
|
||||||
validation: ( val ) => ! val.includes( 'HELLO' ),
|
defaultValue: value,
|
||||||
} );
|
validation: ( val ) => ! val.includes( 'HELLO' ),
|
||||||
|
} )
|
||||||
|
);
|
||||||
|
|
||||||
expect( newValue ).toBe( value );
|
expect( newValue.current ).toBe( value );
|
||||||
|
} );
|
||||||
|
|
||||||
|
test( 'should catch filter errors if user is not an admin', () => {
|
||||||
|
const spy = {};
|
||||||
|
spy.console = jest
|
||||||
|
.spyOn( console, 'error' )
|
||||||
|
.mockImplementation( () => {} );
|
||||||
|
|
||||||
|
const error = new Error( 'test error' );
|
||||||
|
const value = 'Hello World';
|
||||||
|
__experimentalRegisterCheckoutFilters( filterName, {
|
||||||
|
[ filterName ]: () => {
|
||||||
|
throw error;
|
||||||
|
},
|
||||||
|
} );
|
||||||
|
const { result: newValue } = renderHook( () =>
|
||||||
|
__experimentalApplyCheckoutFilter( {
|
||||||
|
filterName,
|
||||||
|
defaultValue: value,
|
||||||
|
} )
|
||||||
|
);
|
||||||
|
|
||||||
|
expect( spy.console ).toHaveBeenCalledWith( error );
|
||||||
|
expect( newValue.current ).toBe( value );
|
||||||
|
spy.console.mockRestore();
|
||||||
} );
|
} );
|
||||||
} );
|
} );
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
export * from './validation';
|
|
@ -0,0 +1,62 @@
|
||||||
|
/**
|
||||||
|
* External dependencies
|
||||||
|
*/
|
||||||
|
import { __, sprintf } from '@wordpress/i18n';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if value passed is a string, throws an error if not.
|
||||||
|
*
|
||||||
|
* @param {string} value Value to be validated.
|
||||||
|
*
|
||||||
|
* @return {Error|true} Error if value is not string, true otherwise.
|
||||||
|
*/
|
||||||
|
export const mustBeString = ( value ) => {
|
||||||
|
if ( typeof value !== 'string' ) {
|
||||||
|
throw Error(
|
||||||
|
sprintf(
|
||||||
|
// translators: %s is type of value passed
|
||||||
|
__(
|
||||||
|
'Returned value must be a string, you passed "%s"',
|
||||||
|
'woo-gutenberg-products-block'
|
||||||
|
),
|
||||||
|
typeof value
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if value passed contain passed label
|
||||||
|
*
|
||||||
|
* @param {string} value Value to be validated.
|
||||||
|
* @param {string} label Label to be searched for.
|
||||||
|
*
|
||||||
|
* @return {Error|true} Error if value contains label, true otherwise.
|
||||||
|
*/
|
||||||
|
export const mustContain = ( value, label ) => {
|
||||||
|
if ( ! value.includes( label ) ) {
|
||||||
|
throw Error(
|
||||||
|
sprintf(
|
||||||
|
// translators: %1$s value passed to filter, %2$s : value that must be included.
|
||||||
|
__(
|
||||||
|
'Returned value must include %1$s, you passed "%2$s"',
|
||||||
|
'woo-gutenberg-products-block'
|
||||||
|
),
|
||||||
|
value,
|
||||||
|
label
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A function that always return true.
|
||||||
|
* We need to have a single instance of this function so it doesn't
|
||||||
|
* invalidate our memo comparison.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @return {true} Returns true.
|
||||||
|
*/
|
||||||
|
export const returnTrue = () => true;
|
|
@ -11,6 +11,7 @@ global.wcSettings = {
|
||||||
precision: 2,
|
precision: 2,
|
||||||
symbol: '$',
|
symbol: '$',
|
||||||
},
|
},
|
||||||
|
currentUserIsAdmin: false,
|
||||||
date: {
|
date: {
|
||||||
dow: 0,
|
dow: 0,
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in New Issue