From 17cbf3a3adf346b1b3cd95baf86fe1e56f00fcd0 Mon Sep 17 00:00:00 2001 From: Kelly Dwan Date: Wed, 20 Jun 2018 13:43:53 -0400 Subject: [PATCH 1/4] Add `formatCurrency`, which relies on a global currency object and local set on wcSettings --- .../client/lib/currency/index.js | 36 ++++-- .../client/lib/currency/test/index.js | 112 ++++++++++++------ .../woocommerce-admin/lib/client-assets.php | 2 + plugins/woocommerce-admin/lib/common.php | 23 ++++ .../tests/js/setup-globals.js | 8 ++ 5 files changed, 134 insertions(+), 47 deletions(-) diff --git a/plugins/woocommerce-admin/client/lib/currency/index.js b/plugins/woocommerce-admin/client/lib/currency/index.js index e5218526583..06fcb93241f 100644 --- a/plugins/woocommerce-admin/client/lib/currency/index.js +++ b/plugins/woocommerce-admin/client/lib/currency/index.js @@ -1,7 +1,27 @@ /** @format */ /** - * NOTE This is a placeholder library until we figure out currency formatting for real. + * External dependencies */ +import { isNaN } from 'lodash'; + +/** + * Formats money with a given currency code. Uses site's current locale for symbol formatting + * + * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/NumberFormat + * @param {Number|String} number number to format + * @param {String} currency currency code e.g. 'USD' + * @returns {?String} A formatted string. + */ +export function formatCurrency( number, currency ) { + const locale = wcSettings.locale || 'en-US'; // Default so we don't break. + if ( 'number' !== typeof number ) { + number = parseFloat( number ); + } + if ( isNaN( number ) ) { + return ''; + } + return new Intl.NumberFormat( locale, { style: 'currency', currency } ).format( number ); +} /** * Get the rounded decimal value of a number at the precision used for a given currency. @@ -10,8 +30,8 @@ * @param {Number|String} number A floating point number (or integer), or string that converts to a number * @return {Number} The original number rounded to a decimal point */ -export function getCurrencyFormatDecimal( number, /* currency = 'USD' */ ) { - const precision = 2; // this would depend on currency +export function getCurrencyFormatDecimal( number ) { + const { precision = 2 } = wcSettings.currency; if ( 'number' !== typeof number ) { number = parseFloat( number ); } @@ -22,14 +42,14 @@ export function getCurrencyFormatDecimal( number, /* currency = 'USD' */ ) { } /** - * Get the string representation of a floating point number to the precision used by a given currency. + * Get the string representation of a floating point number to the precision used by the current currency. * This is different from `formatCurrency` by not returning the currency symbol. * - * @param {Number|String} number A floating point number (or integer), or string that converts to a number - * @return {String} The original number rounded to a decimal point + * @param {Number|String} number A floating point number (or integer), or string that converts to a number + * @return {String} The original number rounded to a decimal point */ -export function getCurrencyFormatString( number, /* currency = 'USD' */ ) { - const precision = 2; // this would depend on currency +export function getCurrencyFormatString( number ) { + const { precision = 2 } = wcSettings.currency; if ( 'number' !== typeof number ) { number = parseFloat( number ); } diff --git a/plugins/woocommerce-admin/client/lib/currency/test/index.js b/plugins/woocommerce-admin/client/lib/currency/test/index.js index 3c5c2dd9a2b..65556a8ea7f 100644 --- a/plugins/woocommerce-admin/client/lib/currency/test/index.js +++ b/plugins/woocommerce-admin/client/lib/currency/test/index.js @@ -2,66 +2,100 @@ /** * Internal dependencies */ -import { getCurrencyFormatDecimal, getCurrencyFormatString } from '../index'; +import { formatCurrency, getCurrencyFormatDecimal, getCurrencyFormatString } from '../index'; + +describe( 'formatCurrency', () => { + it( 'should round a number to 2 decimal places in USD', () => { + expect( formatCurrency( 9.49258, 'USD' ) ).toBe( '$9.49' ); + expect( formatCurrency( 30, 'USD' ) ).toBe( '$30.00' ); + expect( formatCurrency( 3.0002, 'USD' ) ).toBe( '$3.00' ); + } ); + + it( 'should round a number to 2 decimal places in GBP', () => { + expect( formatCurrency( 8.9272, 'GBP' ) ).toBe( '£8.93' ); + expect( formatCurrency( 11, 'GBP' ) ).toBe( '£11.00' ); + expect( formatCurrency( 7.0002, 'GBP' ) ).toBe( '£7.00' ); + } ); + + it( 'should round a number to 0 decimal places in JPY', () => { + expect( formatCurrency( 1239.88, 'JPY' ) ).toBe( '¥1,240' ); + expect( formatCurrency( 1500, 'JPY' ) ).toBe( '¥1,500' ); + expect( formatCurrency( 33715.02, 'JPY' ) ).toBe( '¥33,715' ); + } ); + + it( 'should correctly convert and round a string', () => { + expect( formatCurrency( '19.80', 'USD' ) ).toBe( '$19.80' ); + } ); + + it( "should return empty string when given an input that isn't a number", () => { + expect( formatCurrency( 'abc', 'USD' ) ).toBe( '' ); + expect( formatCurrency( false, 'USD' ) ).toBe( '' ); + expect( formatCurrency( null, 'USD' ) ).toBe( '' ); + } ); +} ); describe( 'getCurrencyFormatDecimal', () => { it( 'should round a number to 2 decimal places in USD', () => { - expect( getCurrencyFormatDecimal( 9.49258, 'USD' ) ).toBe( 9.49 ); - expect( getCurrencyFormatDecimal( 30, 'USD' ) ).toBe( 30 ); - expect( getCurrencyFormatDecimal( 3.0002, 'USD' ) ).toBe( 3 ); + global.wcSettings.currency.precision = 2; + expect( getCurrencyFormatDecimal( 9.49258 ) ).toBe( 9.49 ); + expect( getCurrencyFormatDecimal( 30 ) ).toBe( 30 ); + expect( getCurrencyFormatDecimal( 3.0002 ) ).toBe( 3 ); } ); - // @TODO: Add these tests back once we support multiple currencies - // it( 'should round a number to 2 decimal places in GBP', () => { - // expect( getCurrencyFormatDecimal( 8.9272, 'GBP' ) ).toBe( 8.93 ); - // expect( getCurrencyFormatDecimal( 11, 'GBP' ) ).toBe( 11 ); - // expect( getCurrencyFormatDecimal( 7.0002, 'GBP' ) ).toBe( 7 ); - // } ); - - // it( 'should round a number to 0 decimal places in JPY', () => { - // expect( getCurrencyFormatDecimal( 1239.88, 'JPY' ) ).toBe( 1240 ); - // expect( getCurrencyFormatDecimal( 1500, 'JPY' ) ).toBe( 1500 ); - // expect( getCurrencyFormatDecimal( 33715.02, 'JPY' ) ).toBe( 33715 ); - // } ); + it( 'should round a number to 0 decimal places in JPY', () => { + global.wcSettings.currency.precision = 0; + expect( getCurrencyFormatDecimal( 1239.88 ) ).toBe( 1240 ); + expect( getCurrencyFormatDecimal( 1500 ) ).toBe( 1500 ); + expect( getCurrencyFormatDecimal( 33715.02 ) ).toBe( 33715 ); + } ); it( 'should correctly convert and round a string', () => { - expect( getCurrencyFormatDecimal( '19.80', 'USD' ) ).toBe( 19.8 ); + global.wcSettings.currency.precision = 2; + expect( getCurrencyFormatDecimal( '19.80' ) ).toBe( 19.8 ); + } ); + + it( 'should default to a precision of 2 if none set', () => { + delete global.wcSettings.currency.precision; + expect( getCurrencyFormatDecimal( 59.282 ) ).toBe( 59.28 ); } ); it( "should return 0 when given an input that isn't a number", () => { - expect( getCurrencyFormatDecimal( 'abc', 'USD' ) ).toBe( 0 ); - expect( getCurrencyFormatDecimal( false, 'USD' ) ).toBe( 0 ); - expect( getCurrencyFormatDecimal( null, 'USD' ) ).toBe( 0 ); + global.wcSettings.currency.precision = 2; + expect( getCurrencyFormatDecimal( 'abc' ) ).toBe( 0 ); + expect( getCurrencyFormatDecimal( false ) ).toBe( 0 ); + expect( getCurrencyFormatDecimal( null ) ).toBe( 0 ); } ); } ); describe( 'getCurrencyFormatString', () => { it( 'should round a number to 2 decimal places in USD', () => { - expect( getCurrencyFormatString( 9.49258, 'USD' ) ).toBe( '9.49' ); - expect( getCurrencyFormatString( 30, 'USD' ) ).toBe( '30.00' ); - expect( getCurrencyFormatString( 3.0002, 'USD' ) ).toBe( '3.00' ); + global.wcSettings.currency.precision = 2; + expect( getCurrencyFormatString( 9.49258 ) ).toBe( '9.49' ); + expect( getCurrencyFormatString( 30 ) ).toBe( '30.00' ); + expect( getCurrencyFormatString( 3.0002 ) ).toBe( '3.00' ); } ); - // @TODO: Add these tests back once we support multiple currencies - // it( 'should round a number to 2 decimal places in GBP', () => { - // expect( getCurrencyFormatString( 8.9272, 'GBP' ) ).toBe( '8.93' ); - // expect( getCurrencyFormatString( 11, 'GBP' ) ).toBe( '11.00' ); - // expect( getCurrencyFormatString( 7.0002, 'GBP' ) ).toBe( '7.00' ); - // } ); - - // it( 'should round a number to 0 decimal places in JPY', () => { - // expect( getCurrencyFormatString( 1239.88, 'JPY' ) ).toBe( '1240' ); - // expect( getCurrencyFormatString( 1500, 'JPY' ) ).toBe( '1500' ); - // expect( getCurrencyFormatString( 33715.02, 'JPY' ) ).toBe( '33715' ); - // } ); + it( 'should round a number to 0 decimal places in JPY', () => { + global.wcSettings.currency.precision = 0; + expect( getCurrencyFormatString( 1239.88 ) ).toBe( '1240' ); + expect( getCurrencyFormatString( 1500 ) ).toBe( '1500' ); + expect( getCurrencyFormatString( 33715.02 ) ).toBe( '33715' ); + } ); it( 'should correctly convert and round a string', () => { - expect( getCurrencyFormatString( '19.80', 'USD' ) ).toBe( '19.80' ); + global.wcSettings.currency.precision = 2; + expect( getCurrencyFormatString( '19.80' ) ).toBe( '19.80' ); + } ); + + it( 'should default to a precision of 2 if none set', () => { + delete global.wcSettings.currency.precision; + expect( getCurrencyFormatString( '59.282' ) ).toBe( '59.28' ); } ); it( "should return empty string when given an input that isn't a number", () => { - expect( getCurrencyFormatString( 'abc', 'USD' ) ).toBe( '' ); - expect( getCurrencyFormatString( false, 'USD' ) ).toBe( '' ); - expect( getCurrencyFormatString( null, 'USD' ) ).toBe( '' ); + global.wcSettings.currency.precision = 2; + expect( getCurrencyFormatString( 'abc' ) ).toBe( '' ); + expect( getCurrencyFormatString( false ) ).toBe( '' ); + expect( getCurrencyFormatString( null ) ).toBe( '' ); } ); } ); diff --git a/plugins/woocommerce-admin/lib/client-assets.php b/plugins/woocommerce-admin/lib/client-assets.php index d4583da4f16..255b2d50e02 100644 --- a/plugins/woocommerce-admin/lib/client-assets.php +++ b/plugins/woocommerce-admin/lib/client-assets.php @@ -41,6 +41,8 @@ function woo_dash_register_script() { $settings = array( 'adminUrl' => admin_url(), 'embedBreadcrumbs' => woo_dash_get_embed_breadcrumbs(), + 'locale' => esc_attr( get_bloginfo( 'language' ) ), + 'currency' => woo_dash_currency_settings(), ); wp_add_inline_script( diff --git a/plugins/woocommerce-admin/lib/common.php b/plugins/woocommerce-admin/lib/common.php index b283a82a886..2e12f25baa7 100644 --- a/plugins/woocommerce-admin/lib/common.php +++ b/plugins/woocommerce-admin/lib/common.php @@ -241,3 +241,26 @@ function woo_dash_get_embed_enabled_plugin_screen_ids() { function woo_dash_get_embed_enabled_screen_ids() { return array_merge( woo_dash_get_embed_enabled_core_screen_ids(), woo_dash_get_embed_enabled_plugin_screen_ids() ); } + +/** + * Return an object defining the currecy options for the site's current currency + * + * @return array Settings for the current currency { + * Array of settings. + * + * @type string $currency Currency code. + * @type string $precision Number of decimals. + * @type string $symbol Symbol for currency. + * } + */ +function woo_dash_currency_settings() { + $code = get_woocommerce_currency(); + + return apply_filters( + 'wc_currency_settings', array( + 'currency' => $code, + 'precision' => wc_get_price_decimals(), + 'symbol' => get_woocommerce_currency_symbol( $code ), + ) + ); +} diff --git a/plugins/woocommerce-admin/tests/js/setup-globals.js b/plugins/woocommerce-admin/tests/js/setup-globals.js index 52fdf018e18..2749c79c379 100644 --- a/plugins/woocommerce-admin/tests/js/setup-globals.js +++ b/plugins/woocommerce-admin/tests/js/setup-globals.js @@ -1,3 +1,5 @@ +/** @format */ + // Set up `wp.*` aliases. Doing this because any tests importing wp stuff will // likely run into this. global.wp = { @@ -31,3 +33,9 @@ Object.defineProperty( global.wp, 'element', { Object.defineProperty( global.wp, 'dom', { get: () => require( 'gutenberg/packages/dom' ), } ); + +global.wcSettings = { + adminUrl: 'https://vagrant.local/wp/wp-admin/', + locale: 'en-US', + currency: { code: 'USD', precision: 2, symbol: '$' }, +}; From ff80ca01222eb47022c8363acf512455109456ea Mon Sep 17 00:00:00 2001 From: Kelly Dwan Date: Wed, 20 Jun 2018 13:44:13 -0400 Subject: [PATCH 2/4] Use formatCurrency in the orders activity list --- .../client/components/activity-list/orders.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/woocommerce-admin/client/components/activity-list/orders.js b/plugins/woocommerce-admin/client/components/activity-list/orders.js index 89dc8f14254..9b12eeab4a3 100644 --- a/plugins/woocommerce-admin/client/components/activity-list/orders.js +++ b/plugins/woocommerce-admin/client/components/activity-list/orders.js @@ -11,7 +11,7 @@ import PropTypes from 'prop-types'; * Internal dependencies */ import ActivityCard from 'components/activity-card'; -import { getCurrencyFormatDecimal, getCurrencyFormatString } from 'lib/currency'; +import { formatCurrency, getCurrencyFormatDecimal } from 'lib/currency'; import { getOrderRefundTotal } from 'lib/order-values'; import { Section } from 'layout/section'; @@ -53,11 +53,11 @@ function OrdersList( { orders } ) { {' '} { refundValue ? ( - { getCurrencyFormatString( total ) }{' '} - { getCurrencyFormatString( remainingTotal ) } + { formatCurrency( total, order.currency ) }{' '} + { formatCurrency( remainingTotal, order.currency ) } ) : ( - { getCurrencyFormatString( total ) } + { formatCurrency( total, order.currency ) } ) } From f9ae8120e24cf549702504e665a76afb0b345769 Mon Sep 17 00:00:00 2001 From: Kelly Dwan Date: Wed, 20 Jun 2018 14:19:58 -0400 Subject: [PATCH 3/4] Update docs --- plugins/woocommerce-admin/client/lib/currency/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/woocommerce-admin/client/lib/currency/index.js b/plugins/woocommerce-admin/client/lib/currency/index.js index 06fcb93241f..90666decb12 100644 --- a/plugins/woocommerce-admin/client/lib/currency/index.js +++ b/plugins/woocommerce-admin/client/lib/currency/index.js @@ -24,7 +24,7 @@ export function formatCurrency( number, currency ) { } /** - * Get the rounded decimal value of a number at the precision used for a given currency. + * Get the rounded decimal value of a number at the precision used for the current currency. * This is a work-around for fraction-cents, meant to be used like `wc_format_decimal` * * @param {Number|String} number A floating point number (or integer), or string that converts to a number From bb4f23182a008f913cee9809aa8bf77a90b39fd8 Mon Sep 17 00:00:00 2001 From: Kelly Dwan Date: Wed, 20 Jun 2018 14:21:02 -0400 Subject: [PATCH 4/4] Update property name --- plugins/woocommerce-admin/lib/common.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/woocommerce-admin/lib/common.php b/plugins/woocommerce-admin/lib/common.php index 2e12f25baa7..1557a6e7f03 100644 --- a/plugins/woocommerce-admin/lib/common.php +++ b/plugins/woocommerce-admin/lib/common.php @@ -248,7 +248,7 @@ function woo_dash_get_embed_enabled_screen_ids() { * @return array Settings for the current currency { * Array of settings. * - * @type string $currency Currency code. + * @type string $code Currency code. * @type string $precision Number of decimals. * @type string $symbol Symbol for currency. * } @@ -258,7 +258,7 @@ function woo_dash_currency_settings() { return apply_filters( 'wc_currency_settings', array( - 'currency' => $code, + 'code' => $code, 'precision' => wc_get_price_decimals(), 'symbol' => get_woocommerce_currency_symbol( $code ), )