* Move type defs

* Move type guards

* Fix imports

* Extract prepareTotalItems to TS file

* usePaymentMethodInterface as TS file

* Fix TS props

* Fix currency type defs

* Add return type to usePaymentMethodInterface

* Add key prop to CartTotalItem

* Fixed up js tests

* Move SymbolPosition into type-defs package

Co-authored-by: Thomas Roberts <thomas.roberts@automattic.com>
This commit is contained in:
Mike Jolley 2021-05-25 12:49:13 +01:00 committed by GitHub
parent 3b69179cbe
commit cf8ab9fa93
40 changed files with 209 additions and 169 deletions

View File

@ -12,7 +12,7 @@ import {
import PropTypes from 'prop-types';
import classnames from 'classnames';
import FormattedMonetaryAmount from '@woocommerce/base-components/formatted-monetary-amount';
import { isObject } from '@woocommerce/base-utils';
import { isObject } from '@woocommerce/types';
/**
* Internal dependencies

View File

@ -6,12 +6,12 @@ import { speak } from '@wordpress/a11y';
import classNames from 'classnames';
import { useCallback } from '@wordpress/element';
import { DOWN, UP } from '@wordpress/keycodes';
import { isNumber } from '@woocommerce/types';
/**
* Internal dependencies
*/
import './style.scss';
import { isNumber } from '../../utils/type-guards';
interface QuantitySelectorProps {
className?: string;

View File

@ -7,19 +7,20 @@ import { CART_STORE_KEY as storeKey } from '@woocommerce/block-data';
import { useDebounce } from 'use-debounce';
import { usePrevious } from '@woocommerce/base-hooks';
import { triggerFragmentRefresh } from '@woocommerce/base-utils';
import type { CartItem, StoreCartItemQuantity } from '@woocommerce/types';
import {
CartItem,
StoreCartItemQuantity,
isNumber,
isObject,
isString,
objectHasProp,
} from '@woocommerce/types';
/**
* Internal dependencies
*/
import { useStoreCart } from './use-store-cart';
import { useCheckoutContext } from '../../providers/cart-checkout';
import {
isNumber,
isObject,
isString,
objectHasProp,
} from '../../../utils/type-guards';
/**
* Ensures the object passed has props key: string and quantity: number

View File

@ -1,7 +1,7 @@
/**
* Internal dependencies
*/
import { prepareTotalItems } from '../use-payment-method-interface';
import { prepareTotalItems } from '../utils';
describe( 'prepareTotalItems', () => {
const fixture = {
@ -17,21 +17,25 @@ describe( 'prepareTotalItems', () => {
};
const expected = [
{
key: 'total_items',
label: 'Subtotal:',
value: 200,
valueWithTax: 220,
},
{
key: 'total_fees',
label: 'Fees:',
value: 100,
valueWithTax: 110,
},
{
key: 'total_discount',
label: 'Discount:',
value: 350,
valueWithTax: 400,
},
{
key: 'total_tax',
label: 'Taxes:',
value: 30,
valueWithTax: 30,
@ -40,6 +44,7 @@ describe( 'prepareTotalItems', () => {
const expectedWithShipping = [
...expected,
{
key: 'total_shipping',
label: 'Shipping:',
value: 50,
valueWithTax: 55,

View File

@ -19,67 +19,12 @@ import { useCheckoutContext } from '../../providers/cart-checkout/checkout-state
import { usePaymentMethodDataContext } from '../../providers/cart-checkout/payment-methods';
import { useShippingDataContext } from '../../providers/cart-checkout/shipping';
import { useCustomerDataContext } from '../../providers/cart-checkout/customer';
import { prepareTotalItems } from './utils';
/**
* @typedef {import('@woocommerce/type-defs/registered-payment-method-props').RegisteredPaymentMethodProps} RegisteredPaymentMethodProps
* @typedef {import('@woocommerce/type-defs/cart').CartTotalItem} CartTotalItem
* Returns am interface to use as payment method props.
*/
/**
* Prepares the total items into a shape usable for display as passed on to
* registered payment methods.
*
* @param {Object} totals Current cart total items
* @param {boolean} needsShipping Whether or not shipping is needed.
*
* @return {CartTotalItem[]} Array for cart total items prepared for use.
*/
export const prepareTotalItems = ( totals, needsShipping ) => {
const newTotals = [];
const factory = ( label, property ) => {
const value = parseInt( totals[ property ], 10 );
const tax = parseInt( totals[ property + '_tax' ], 10 );
return {
label,
value,
valueWithTax: value + tax,
};
};
newTotals.push(
factory(
__( 'Subtotal:', 'woo-gutenberg-products-block' ),
'total_items'
)
);
newTotals.push(
factory( __( 'Fees:', 'woo-gutenberg-products-block' ), 'total_fees' )
);
newTotals.push(
factory(
__( 'Discount:', 'woo-gutenberg-products-block' ),
'total_discount'
)
);
newTotals.push( {
label: __( 'Taxes:', 'woo-gutenberg-products-block' ),
value: parseInt( totals.total_tax, 10 ),
valueWithTax: parseInt( totals.total_tax, 10 ),
} );
if ( needsShipping ) {
newTotals.push(
factory(
__( 'Shipping:', 'woo-gutenberg-products-block' ),
'total_shipping'
)
);
}
return newTotals;
};
/**
* @return {RegisteredPaymentMethodProps} Interface to use as payment method props.
*/
export const usePaymentMethodInterface = () => {
export const usePaymentMethodInterface = (): Record< string, unknown > => {
const {
isCalculating,
isComplete,
@ -107,7 +52,6 @@ export const usePaymentMethodInterface = () => {
selectedRates,
setSelectedRates,
isSelectingRate,
onShippingRateSuccess,
onShippingRateFail,
onShippingRateSelectSuccess,
@ -142,27 +86,7 @@ export const usePaymentMethodInterface = () => {
}, [ cartTotals, needsShipping ] );
return {
checkoutStatus: {
isCalculating,
isComplete,
isIdle,
isProcessing,
},
paymentStatus: currentStatus,
shippingStatus: {
shippingErrorStatus,
shippingErrorTypes,
},
shippingData: {
shippingRates,
shippingRatesLoading,
selectedRates,
setSelectedRates,
isSelectingRate,
shippingAddress,
setShippingAddress,
needsShipping,
},
activePaymentMethod,
billing: {
billingData,
cartTotal: currentCartTotal.current,
@ -171,10 +95,25 @@ export const usePaymentMethodInterface = () => {
displayPricesIncludingTax: getSetting(
'displayCartPricesIncludingTax',
false
),
) as boolean,
appliedCoupons,
customerId,
},
checkoutStatus: {
isCalculating,
isComplete,
isIdle,
isProcessing,
},
components: {
ValidationInputError,
PaymentMethodIcons,
PaymentMethodLabel,
},
emitResponse: {
noticeContexts,
responseTypes,
},
eventRegistration: {
onCheckoutBeforeProcessing,
onCheckoutValidationBeforeProcessing,
@ -186,18 +125,23 @@ export const usePaymentMethodInterface = () => {
onShippingRateSelectFail,
onPaymentProcessing,
},
components: {
ValidationInputError,
PaymentMethodIcons,
PaymentMethodLabel,
},
emitResponse: {
noticeContexts,
responseTypes,
},
onSubmit,
activePaymentMethod,
paymentStatus: currentStatus,
setExpressPaymentError,
shippingData: {
shippingRates,
shippingRatesLoading,
selectedRates,
setSelectedRates,
isSelectingRate,
shippingAddress,
setShippingAddress,
needsShipping,
},
shippingStatus: {
shippingErrorStatus,
shippingErrorTypes,
},
shouldSavePayment,
};
};

View File

@ -0,0 +1,85 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import {
CartResponseTotals,
objectHasProp,
isString,
} from '@woocommerce/types';
export interface CartTotalItem {
key: string;
label: string;
value: number;
valueWithTax: number;
}
/**
* Prepares the total items into a shape usable for display as passed on to
* registered payment methods.
*
* @param {Object} totals Current cart total items
* @param {boolean} needsShipping Whether or not shipping is needed.
*/
export const prepareTotalItems = (
totals: CartResponseTotals,
needsShipping: boolean
): CartTotalItem[] => {
const newTotals = [];
const factory = ( label: string, property: string ): CartTotalItem => {
const taxProperty = property + '_tax';
const value =
objectHasProp( totals, property ) && isString( totals[ property ] )
? parseInt( totals[ property ] as string, 10 )
: 0;
const tax =
objectHasProp( totals, taxProperty ) &&
isString( totals[ taxProperty ] )
? parseInt( totals[ taxProperty ] as string, 10 )
: 0;
return {
key: property,
label,
value,
valueWithTax: value + tax,
};
};
newTotals.push(
factory(
__( 'Subtotal:', 'woo-gutenberg-products-block' ),
'total_items'
)
);
newTotals.push(
factory( __( 'Fees:', 'woo-gutenberg-products-block' ), 'total_fees' )
);
newTotals.push(
factory(
__( 'Discount:', 'woo-gutenberg-products-block' ),
'total_discount'
)
);
newTotals.push( {
key: 'total_tax',
label: __( 'Taxes:', 'woo-gutenberg-products-block' ),
value: parseInt( totals.total_tax, 10 ),
valueWithTax: parseInt( totals.total_tax, 10 ),
} );
if ( needsShipping ) {
newTotals.push(
factory(
__( 'Shipping:', 'woo-gutenberg-products-block' ),
'total_shipping'
)
);
}
return newTotals;
};

View File

@ -73,7 +73,7 @@ export const useCustomerData = (): {
billingData: CartResponseBillingAddress;
shippingAddress: CartResponseShippingAddress;
setBillingData: ( data: CartResponseBillingAddress ) => void;
setShippingAddress: ( data: CartResponseBillingAddress ) => void;
setShippingAddress: ( data: CartResponseShippingAddress ) => void;
} => {
const { updateCustomerData } = useDispatch( storeKey );
const { addErrorNotice, removeNotice } = useStoreNotices();

View File

@ -1,7 +1,7 @@
/**
* Internal dependencies
* External dependencies
*/
import { isObject } from '../../utils/type-guards';
import { isObject } from '@woocommerce/types';
export enum responseTypes {
SUCCESS = 'success',

View File

@ -13,6 +13,7 @@ import {
import { __ } from '@wordpress/i18n';
import { usePrevious } from '@woocommerce/base-hooks';
import deprecated from '@wordpress/deprecated';
import { isObject } from '@woocommerce/types';
/**
* Internal dependencies
@ -41,7 +42,6 @@ import { useStoreNotices } from '../../../hooks/use-store-notices';
import { useStoreEvents } from '../../../hooks/use-store-events';
import { useCheckoutNotices } from '../../../hooks/use-checkout-notices';
import { useEmitResponse } from '../../../hooks/use-emit-response';
import { isObject } from '../../../../utils/type-guards';
/**
* @typedef {import('@woocommerce/type-defs/contexts').CheckoutDataContext} CheckoutDataContext

View File

@ -7,10 +7,9 @@
"../../settings/shared/index.ts",
"../../settings/blocks/index.ts",
"../../base/hooks/index.js",
"../utils/type-guards.ts",
"../../base/utils/",
"../../data/",
"../../type-defs",
"../../types/",
"../components"
],
"exclude": [ "**/test/**" ]

View File

@ -8,4 +8,3 @@ export * from './get-valid-block-attributes';
export * from './product-data';
export * from './derive-selected-shipping-rates';
export * from './from-entries-polyfill';
export * from './type-guards';

View File

@ -30,7 +30,7 @@ import {
import Dinero from 'dinero.js';
import { useCallback, useMemo } from '@wordpress/element';
import type { CartItem } from '@woocommerce/type-defs/cart';
import { objectHasProp } from '@woocommerce/base-utils';
import { objectHasProp } from '@woocommerce/types';
import { getSetting } from '@woocommerce/settings';
/**

View File

@ -1,3 +1,8 @@
/**
* External dependencies
*/
import { SymbolPosition } from '@woocommerce/type-defs/currency';
declare global {
interface Window {
wcSettings: Record< string, unknown >;
@ -12,7 +17,7 @@ export interface WooCommerceSiteCurrency {
// The symbol for the currency (eg '$')
symbol: string;
// The position for the symbol ('left', or 'right')
symbolPosition: 'left' | 'right' | 'left_space' | 'right_space';
symbolPosition: SymbolPosition;
// The string used for the decimal separator.
decimalSeparator: string;
// The string used for the thousands separator.

View File

@ -3,6 +3,7 @@
"include": [
".",
"../../type-defs",
"../../types/",
"../../mapped-types.ts",
"../../filters/exclude-draft-status-from-analytics.js"
]

View File

@ -0,0 +1,2 @@
export * from './type-defs';
export * from './type-guards';

View File

@ -1,26 +1,17 @@
/* eslint-disable camelcase -- API responses have camelcase properties */
/**
* External dependencies
* Internal dependencies
*/
import { CurrencyResponse } from './currency';
import {
CartImageItem,
CartItemPrices,
CartItemTotals,
CartVariationItem,
CatalogVisibility,
} from '@woocommerce/type-defs/cart';
} from './cart';
export interface CurrencyResponseInfo {
currency_code: string;
currency_symbol: string;
currency_minor_unit: number;
currency_decimal_separator: string;
currency_thousand_separator: string;
currency_prefix: string;
currency_suffix: string;
}
export interface CartResponseTotalsItem extends CurrencyResponseInfo {
export interface CartResponseTotalsItem extends CurrencyResponse {
total_discount: string;
total_discount_tax: string;
}
@ -61,7 +52,7 @@ export type ExtensionsData =
| Record< string, never >;
export interface CartResponseShippingPackageShippingRate
extends CurrencyResponseInfo {
extends CurrencyResponse {
rate_id: string;
name: string;
description: string;
@ -110,7 +101,7 @@ export interface CartResponseVariationItem {
value: string;
}
export interface CartResponseItemPrices extends CurrencyResponseInfo {
export interface CartResponseItemPrices extends CurrencyResponse {
price: string;
regular_price: string;
sale_price: string;
@ -123,7 +114,7 @@ export interface CartResponseItemPrices extends CurrencyResponseInfo {
};
}
export interface CartResponseItemTotals extends CurrencyResponseInfo {
export interface CartResponseItemTotals extends CurrencyResponse {
line_subtotal: string;
line_subtotal_tax: string;
line_total: string;
@ -160,7 +151,7 @@ export interface CartResponseTotalsTaxLineItem {
rate: string;
}
export interface CartResponseFeeItemTotals extends CurrencyResponseInfo {
export interface CartResponseFeeItemTotals extends CurrencyResponse {
total: string;
total_tax: string;
}
@ -171,7 +162,7 @@ export type CartResponseFeeItem = {
totals: CartResponseFeeItemTotals;
};
export interface CartResponseTotals extends CurrencyResponseInfo {
export interface CartResponseTotals extends CurrencyResponse {
total_items: string;
total_items_tax: string;
total_fees: string;

View File

@ -13,8 +13,8 @@
*
* @property {BillingData} billingData The current billing data, including address and email.
* @property {CartShippingAddress} shippingAddress The current set address for shipping.
* @property {Function} setBillingData A function for setting billing data.
* @property {Function} setShippingAddress A function for setting shipping address.
* @property {function()} setBillingData A function for setting billing data.
* @property {function()} setShippingAddress A function for setting shipping address.
*/
/**
@ -27,10 +27,10 @@
* @property {CartShippingOption[]} shippingRates An array of available shipping rates.
* @property {boolean} shippingRatesLoading Whether or not the shipping rates are being loaded.
* @property {string[]} selectedRates The ids of the rates that are selected.
* @property {Function} setSelectedRates Function for setting the selected rates.
* @property {function()} setSelectedRates Function for setting the selected rates.
* @property {boolean} isSelectingRate True when rate is being selected.
* @property {CartShippingAddress} shippingAddress The current set address for shipping.
* @property {Function} setShippingAddress Function for setting the shipping address.
* @property {function()} setShippingAddress Function for setting the shipping address.
* @property {function()} onShippingRateSuccess Used to register a callback to be invoked when shipping
* rates are retrieved.
* @property {function()} onShippingRateSelectSuccess Used to register a callback to be invoked when shipping

View File

@ -0,0 +1,22 @@
export interface Currency {
code: string;
decimalSeparator: string;
minorUnit: number;
prefix: string;
suffix: string;
symbol: string;
thousandSeparator: string;
}
/* eslint-disable camelcase -- API responses have camelcase properties */
export interface CurrencyResponse {
currency_code: string;
currency_symbol: string;
currency_minor_unit: number;
currency_decimal_separator: string;
currency_thousand_separator: string;
currency_prefix: string;
currency_suffix: string;
}
export type SymbolPosition = 'left' | 'left_space' | 'right' | 'right_space';

View File

@ -12,7 +12,7 @@ import type {
CartResponseShippingRate,
CartResponse,
} from './cart-response';
import type { ResponseError } from '../data/types';
import type { ResponseError } from '../../data/types';
export interface StoreCartItemQuantity {
isPendingDelete: boolean;
quantity: number;

View File

@ -2,3 +2,4 @@ export * from './cart-response';
export * from './product-response';
export * from './cart';
export * from './hooks';
export * from './currency';

View File

@ -1,15 +1,11 @@
/* eslint-disable camelcase -- API responses have camelcase properties */
export interface CurrencyResponseInfo {
currency_code: string;
currency_symbol: string;
currency_minor_unit: number;
currency_decimal_separator: string;
currency_thousand_separator: string;
currency_prefix: string;
currency_suffix: string;
}
export interface ProductResponseItemPrices extends CurrencyResponseInfo {
/**
* Internal dependencies
*/
import { CurrencyResponse } from './currency';
export interface ProductResponseItemPrices extends CurrencyResponse {
price: string;
regular_price: string;
sale_price: string;

View File

@ -17,11 +17,11 @@ export const isObject = < T extends Record< string, unknown >, U >(
};
export function objectHasProp< P extends PropertyKey >(
target: Record< string, unknown >,
target: unknown,
property: P
): target is { [ K in P ]: unknown } {
// The `in` operator throws a `TypeError` for non-object values.
return property in target;
return isObject( target ) && property in target;
}
// eslint-disable-next-line @typescript-eslint/ban-types

View File

@ -85,6 +85,7 @@ const getAlias = ( options = {} ) => {
__dirname,
`../assets/js/${ pathPart }previews/`
),
'@woocommerce/types': path.resolve( __dirname, `../assets/js/types/` ),
};
};

View File

@ -1,2 +1 @@
export * from './utils';
export * from './types';

View File

@ -1,9 +0,0 @@
export interface Currency {
code: string;
decimalSeparator: string;
minorUnit: number;
prefix: string;
suffix: string;
symbol: string;
thousandSeparator: string;
}

View File

@ -2,15 +2,12 @@
* External dependencies
*/
import { CURRENCY } from '@woocommerce/settings';
import type { CurrencyResponseInfo } from '@woocommerce/type-defs/cart-response';
import type { CartShippingPackageShippingRate } from '@woocommerce/type-defs/cart';
/**
* Internal dependencies
*/
import type { Currency } from '../types';
type SymbolPosition = 'left' | 'left_space' | 'right' | 'right_space';
import type {
Currency,
CurrencyResponse,
CartShippingPackageShippingRate,
SymbolPosition,
} from '@woocommerce/types';
/**
* Get currency prefix.
@ -73,7 +70,7 @@ const siteCurrencySettings: Currency = {
export const getCurrencyFromPriceResponse = (
// Currency data object, for example an API response containing currency formatting data.
currencyData:
| CurrencyResponseInfo
| CurrencyResponse
| Record< string, never >
| CartShippingPackageShippingRate
): Currency => {

View File

@ -5,7 +5,7 @@
".",
"../assets/js/icons",
"../assets/js/settings",
"../assets/js/type-defs",
"../assets/js/types",
"../assets/js/base/components",
"../assets/js/base/hooks",
"../settings/shared/index.ts",

View File

@ -27,7 +27,8 @@
"@woocommerce/resource-previews": "assets/js/previews",
"@woocommerce/shared-context": "assets/js/shared/context",
"@woocommerce/shared-hocs": "assets/js/shared/hocs",
"@woocommerce/blocks-test-utils": "tests/utils"
"@woocommerce/blocks-test-utils": "tests/utils",
"@woocommerce/types": "assets/js/types"
},
"setupFiles": [
"@wordpress/jest-preset-default/scripts/setup-globals.js",

View File

@ -52,8 +52,8 @@
"@woocommerce/knobs": [ "storybook/knobs" ],
"@woocommerce/settings": [ "assets/js/settings/shared" ],
"@woocommerce/shared-context": [ "assets/js/shared/context" ],
"@woocommerce/type-defs/*": [ "assets/js/type-defs/*" ],
"@woocommerce/types": [ "assets/js/type-defs" ]
"@woocommerce/type-defs/*": [ "assets/js/types/type-defs/*" ],
"@woocommerce/types": [ "assets/js/types" ]
}
}
}