From 9511bcd2cbd72c604412f788aba00f7336d2680e Mon Sep 17 00:00:00 2001 From: David Chin Date: Thu, 8 Aug 2019 16:54:46 +1000 Subject: [PATCH] feat(common): CHECKOUT-4223 Add locale context provider --- src/app/i18n/README.md | 20 -- src/app/i18n/en.json | 292 --------------------- src/app/locale/LocaleContext.ts | 11 + src/app/locale/LocaleProvider.spec.tsx | 56 ++++ src/app/locale/LocaleProvider.tsx | 62 +++++ src/app/locale/createLocaleContext.spec.ts | 23 ++ src/app/locale/createLocaleContext.ts | 15 ++ src/app/locale/getLanguageService.ts | 7 + src/app/locale/index.ts | 14 + src/app/locale/localeContext.mock.tsx | 13 + src/app/locale/withCurrency.spec.tsx | 29 ++ src/app/locale/withCurrency.tsx | 16 ++ src/app/locale/withLanguage.spec.tsx | 29 ++ src/app/locale/withLanguage.tsx | 16 ++ 14 files changed, 291 insertions(+), 312 deletions(-) delete mode 100644 src/app/i18n/README.md delete mode 100644 src/app/i18n/en.json create mode 100644 src/app/locale/LocaleContext.ts create mode 100644 src/app/locale/LocaleProvider.spec.tsx create mode 100644 src/app/locale/LocaleProvider.tsx create mode 100644 src/app/locale/createLocaleContext.spec.ts create mode 100644 src/app/locale/createLocaleContext.ts create mode 100644 src/app/locale/getLanguageService.ts create mode 100644 src/app/locale/index.ts create mode 100644 src/app/locale/localeContext.mock.tsx create mode 100644 src/app/locale/withCurrency.spec.tsx create mode 100644 src/app/locale/withCurrency.tsx create mode 100644 src/app/locale/withLanguage.spec.tsx create mode 100644 src/app/locale/withLanguage.tsx diff --git a/src/app/i18n/README.md b/src/app/i18n/README.md deleted file mode 100644 index e440c3606f..0000000000 --- a/src/app/i18n/README.md +++ /dev/null @@ -1,20 +0,0 @@ -Translation key naming convention: -.
._(action|heading|label|text|) - - refers to top level areas of a store, or a major component. i.e.: checkout, cart etc... -
refers to sub-sections of the area. i.e.: address, customer etc... - "common" section contains generic strings that can be shared. It also contains strings that can't be categorised. - refers to names representing various words and phrases. i.e.: address_line_1 - It should end with suffixes (listed above) to further clarify its intended usage. - action - for buttons and links - heading - for headings and titles - label - for form labels - text - for paragraphs, sentences and short phrases. It should be the default suffix. - - for status text, i.e.: error, warning, success. - -address.address_line_1_label -address.address_line_1_required_error -address.confirm_address_action -customer.create_account_success -customer.customer_heading -customer.create_account_text diff --git a/src/app/i18n/en.json b/src/app/i18n/en.json deleted file mode 100644 index eb76e09b3c..0000000000 --- a/src/app/i18n/en.json +++ /dev/null @@ -1,292 +0,0 @@ -{ - "optimized_checkout": { - "address": { - "address_line_1_label": "Address", - "address_line_1_required_error": "Address is required", - "address_line_2_label": "Apartment/Suite/Building", - "address_line_2_required_error": "Apartment/Suite/Building is required", - "address_not_recognized_heading": "We did not recognize your address", - "city_label": "City", - "city_required_error": "City is required", - "company_name_label": "Company Name", - "company_name_required_error": "Company is required", - "confirm_address_action": "Proceed with current address", - "confirm_address_text": "We could not find a match for the address you entered. Please confirm the address.", - "country_label": "Country", - "country_required_error": "Country is required", - "custom_required_error": "{label} is required", - "custom_min_error": "{label} should be bigger than {min}", - "custom_max_error": "{label} should be smaller than {max}", - "custom_valid_error": "{label} is not valid", - "edit_address_action": "Edit address", - "enter_address_action": "Enter a new address", - "first_name_label": "First Name", - "first_name_required_error": "First Name is required", - "last_name_label": "Last Name", - "last_name_required_error": "Last Name is required", - "phone_number_label": "Phone Number", - "phone_number_required_error": "Phone Number is required", - "postal_code_label": "Postal Code", - "postal_code_required_error": "Postal Code is required", - "select_country_action": "Select a country", - "select_state_action": "Select a state", - "state_label": "State/Province", - "state_required_error": "State/Province is required", - "select": "Select", - "select_all": "All", - "select_none": "None" - }, - "billing": { - "billing_address_heading": "Billing Address", - "billing_heading": "Billing", - "save_billing_address_error": "An error occurred while saving the billing address to your price quote. Please try again.", - "billing_address_amazon": "Same as the Billing address set by you in your Amazon account.", - "use_shipping_address_label": "My Billing address is the same as my Shipping address" - }, - "cart": { - "billed_amount_text": "*You will be charged and invoiced {total} ({code}) for this order.", - "based_on_currency_text": "Based on {total} {code}", - "cart_heading": "Order Summary", - "digital_item_text": "Digital Item", - "discount_text": "Discount", - "downloads_action": "Go to Downloads", - "edit_cart_action": "Edit Cart", - "estimated_total_text": "Estimated Total", - "free_text": "Free", - "gift_certificate_text": "Gift Certificate", - "handling_text": "Handling", - "item_count_text": "{count, plural, one{1 Item} other{# Items} }", - "print_action": "Print", - "remove_action": "remove", - "see_all_action": "See All", - "see_less_action": "See Less", - "shipping_text": "Shipping", - "show_details_action": "Show Details", - "store_credit_text": "Store Credit", - "subtotal_text": "Subtotal", - "taxes_text": "Taxes", - "total_text": "Total", - "empty_cart_message": "Your cart is empty, you are being redirected. Please click here if your browser does not redirect you." - }, - "common": { - "cancel_action": "Cancel", - "close_action": "Close", - "continue_action": "Continue", - "edit_action": "Edit", - "delete_action": "Delete", - "error_heading": "Something's gone wrong", - "leave_warning": "Are you sure you want to leave? Data you have entered may not be saved.", - "loading_text": "Loading", - "ok_action": "Ok", - "error_code": "Error code:", - "optional_text": "(Optional)", - "unavailable_error": "Checkout is temporarily unavailable. Please try again later.", - "unavailable_heading": "Checkout is temporarily unavailable", - "order_loading_error": "There was an error loading your order. Please try again.", - "order_fatal_error_heading": "There was an error placing your order", - "order_fatal_error_extra": "Please choose another payment method or contact us for further assistance." - }, - "customer": { - "checkout_as_guest_text": "Checking out as a Guest? You'll be able to save your details to create an account with us later.", - "continue_as_guest_action": "Continue as guest", - "create_account_action": "Create Account", - "create_account_error": "An error occurred while creating your account. Please try again.", - "create_account_requirements_error_heading": "Password does not match requirements", - "create_account_success": "Your account has been created!", - "create_account_text": "Create an account for a faster checkout in the future", - "create_account_to_continue_text": "Don’t have an account? Create an account to continue.", - "customer_heading": "Customer", - "email_invalid_error": "Email address must be valid", - "email_label": "Email Address", - "email_required_error": "Email address is required", - "forgot_password_action": "Forgot password?", - "guest_customer_text": "Guest Customer", - "guest_subscribe_to_newsletter_text": "Subscribe to our newsletter.", - "login_action": "Sign in now", - "login_text": "Already have an account?", - "password_confirmation_error": "Passwords do not match", - "password_confirmation_label": "Confirm Password", - "password_confirmation_required_error": "This field is required", - "password_label": "Password", - "password_letter_required_error": "Password needs to contain a letter", - "password_minimum_character_label": "character minimum, case sensitive", - "password_number_required_error": "Password needs to contain a number", - "password_over_maximum_length_error": "Password is too long", - "password_required_error": "Password is required", - "password_under_minimum_length_error": "Password is too short", - "reset_password_before_login_error": "Thank you, you will receive an e-mail in the next 5 minutes with instructions for resetting your password. If you don't receive this e-mail, please check your junk mail folder or contact us for further assistance.", - "returning_customer_text": "Returning Customer", - "sign_in_action": "Sign In", - "sign_in_error": "The email or password you entered is not valid.", - "sign_in_throttled_error": "Due to excessive login attempts, please wait 10 seconds before attempting to log in again.", - "sign_out_action": "Sign Out", - "sign_out_error": "An error occurred while signing out. Please try again.", - "subscribe_to_newsletter_text": "Yes, I'd like to receive updates." - }, - "embedded_checkout": { - "unsupported_error": "The following payment methods are not supported by Embedded Checkout: {methods}. Please contact us for assistance." - }, - "payment": { - "affirm_name_text": "Affirm", - "affirm_display_name_text": "Monthly Payments", - "affirm_body_text": "You will be redirected to Affirm to securely complete your purchase. Just fill out a few pieces of basic information and get a real-time decision. Checking your eligibility won't affect your credit score.", - "afterpay_name_text": "Afterpay", - "afterpay_description": "Checkout with Afterpay", - "amazon_continue_action": "Continue with Amazon", - "amazon_name_text": "Amazon Pay", - "braintreevisacheckout_continue_action": "Continue with Visa Checkout", - "ccavenuemars_description_text": "Checkout with CCAvenue", - "chasepay_continue_action": "Continue with Chase Pay", - "chasepay_name_text": "Chase Pay", - "chasepay_edit_card": "Edit card", - "chasepay_logout": "Log out Chase Pay", - "chasepay_fail_load": "Chase Pay failed to load", - "credit_card_text": "Credit card", - "credit_card_customer_code_label": "Customer Code", - "credit_card_cvv_help_text": "For VISA and Mastercard, the CVV is a three-digit code printed on the back. For American Express it is the four-digit code printed on the front. The CVV is a security measure to ensure that you are in possession of the card.", - "credit_card_cvv_invalid_error": "CVV must be valid", - "credit_card_cvv_label": "CVV", - "credit_card_cvv_required_error": "CVV is required", - "credit_card_expiration_invalid_error": "Expiration date must be a valid future date in MM / YY format", - "credit_card_expiration_label": "Expiration", - "credit_card_expiration_date_label": "Expiration Date", - "credit_card_expiration_required_error": "Expiration Date is required", - "credit_card_name_label": "Name on Card", - "credit_card_name_required_error": "Full name is required", - "credit_card_number_invalid_error": "Credit Card Number must be valid", - "credit_card_number_label": "Credit Card Number", - "credit_card_number_last_four": "Enter card number for {cardType} ending in {lastFour}", - "credit_card_number_required_error": "Credit Card Number is required", - "credit_card_number_mismatch_error": "The card number entered does not match the card stored in your account", - "google_pay_name_text": "Google Pay", - "klarna_name_text": "Klarna", - "masterpass_name_text": "Masterpass", - "orbital_continue_action": "Place Order", - "orbital_description_text": "Pay using your ChasePay Account", - "payment_cancelled": "Payment was cancelled", - "payment_error": "An error occurred while processing your payment. Please try again.", - "payment_error_heading": "Payment Failed", - "payment_heading": "Payment", - "payment_invalid_error_heading": "Payment Unavailable", - "payment_method_disabled_error": "The selected payment method is no longer valid. Click OK to see the most up-to-date payment methods.", - "payment_method_error": "Response from payment provider: {message}", - "payment_method_invalid_error": "There's a problem processing your payment. Please contact us for assistance or choose another payment method.", - "payment_method_label": "Payment Method", - "payment_method_unavailable_error": "This payment provider is temporarily unavailable. Please try again later.", - "payment_not_required_text": "Payment is not required for this order.", - "paypal_continue_action": "Continue with PayPal", - "paypal_credit_continue_action": "Continue with PayPal Credit", - "paypal_credit_description_text": "Buy Now, Pay Over Time", - "paypal_description_text": "Pay using your PayPal account", - "paypal_name_text": "PayPal", - "paypal_credit_name_text": "PayPal Credit", - "place_order_action": "Place Order", - "place_order_error": "There was an error placing your order. Please contact us.", - "place_order_error_heading": "Failed to place order", - "postal_code_label": "Postal Code", - "instrument_text": "Stored credit cards", - "instrument_add_card_action": "Use a different card", - "instrument_default_ending_in_text": "Card ending in {endingIn}", - "instrument_ending_in_text": "{cardTitle} ending in {endingIn}", - "instrument_expired_text": "Expired {expiryDate}", - "instrument_expires_text": "Expires {expiryDate}", - "instrument_manage_button": "Manage", - "instrument_manage_modal_confirmation_action": "Yes, delete", - "instrument_manage_modal_confirmation_label": "Are you sure you want to delete this Stored credit card?", - "instrument_manage_modal_title_text": "Manage Stored credit cards", - "instrument_manage_modal_empty_text": "You do not have any Stored credit cards.", - "instrument_manage_table_header_ending_in_text": "Ending in", - "instrument_manage_table_header_expiry_date_text": "Expiry date", - "instrument_manage_table_header_payment_method_text": "Payment method", - "instrument_manage_delete_server_error": "There was an error when attempting to delete that Stored credit card, please try again.", - "instrument_manage_delete_auth_error": "There was a problem authorizing your request. Please try signing in again", - "instrument_manage_delete_client_error": "There was an error when attempting to delete the Stored credit card: credit card no longer exists or cannot be deleted.", - "instrument_save_payment_method_label": "Save this card for future transactions", - "instrument_trusted_shipping_address_text": "This additional security step is applied to your card when shipping to an address for the first time or if the shipping address was edited recently.", - "instrument_trusted_shipping_address_title_text": "Please re-enter your card number to authorize this transaction.", - "vco_name_text": "Visa Checkout", - "visa_checkout_continue_action": "Continue with Visa Checkout", - "zip_continue_action": "Continue with Zip", - "zip_name_text": "Zip", - "zip_display_name_text": "Own it now, pay later" - }, - "redeemable": { - "applied_text": "Applied", - "apply_action": "Apply", - "apply_store_credit_after_action": "store credit to order", - "apply_store_credit_before_action": "Apply", - "code_invalid_error": "The gift certificate or coupon code is invalid", - "code_label": "Gift Certificate or Coupon Code", - "code_required_error": "Please enter a gift certificate or coupon code", - "coupon_location_error": "Your shipping address doesn't meet the location requirements for the coupon code you entered.", - "coupon_text": "Coupon", - "gift_certificate_remaining_text": "Remaining", - "gift_certificate_text": "Gift Certificate", - "remove_action": "Remove", - "store_credit_available_text": "Your account currently has {storeCredit} total store credit available", - "toggle_action": "Coupon/Gift Certificate" - }, - "remote": { - "browser_unsupported": "The selected payment method requires a different web browser. Please choose another payment method.", - "connection_error": "Connection to remote checkout refused, please try later.", - "continue_with_text": "Or continue with", - "payment_method_error": "There was an error retrieving your remote payment method. Please try again.", - "select_different_card_action": "Select a different card", - "session_error": "Your remote session has expired. Please log in again.", - "shipping_address_error": "There was an error retrieving your remote shipping address. Please try again.", - "sign_in_action": "Sign in to {providerName}", - "sign_out_action": "Sign out of {providerName}", - "sign_out_after_action": "to view other payment methods", - "sign_in_required_error": "Remote sign in required before payment", - "sign_out_before_action": "" - }, - "shipping": { - "cart_change_error": "An update to your shopping cart has been detected and your available shipping costs have been updated. Please re-select a shipping method to continue.", - "enter_shipping_address_text": "Please enter a shipping address in order to see shipping quotes", - "order_comment_label": "Order Comments", - "assign_item_error": "An error occurred while assigning item to address. Please try again", - "assign_item_invalid_address_error_heading": "Invalid Address", - "assign_item_invalid_address_error": "The selected address is not valid. Please go to your account page and update it.", - "unassign_item_error": "An error occurred while unassigning item to address.", - "save_shipping_address_error": "An error occurred while saving the shipping address to your price quote. Please try again.", - "save_shipping_option_error": "An error occurred while saving the shipping quote to your order. Please try again.", - "select_shipping_address_text": "Please select a shipping address in order to see shipping quotes", - "shipping_address_heading": "Shipping Address", - "multishipping_address_heading": "Choose where to ship each item", - "multishipping_address_heading_guest": "Please sign in first", - "multishipping_guest_intro": "To ship your items to multiple addresses you need to", - "multishipping_guest_sign_in": "sign in to your account", - "multishipping_guest_create": "or create an account prior to proceeding.", - "ship_to_multi": "Ship to multiple addresses", - "ship_to_single": "Ship to a single address", - "shipping_heading": "Shipping", - "shipping_method_label": "Shipping Method", - "shipping_option_expired_error": "The shipping price you were quoted is no longer valid. Click OK to see the most up-to-date shipping prices.", - "shipping_option_expired_heading": "Your shipping price has been updated", - "view_shipping_options_action": "See Other Options" - }, - "social": { - "share_action": "Share", - "share_heading": "Tell your friends about your purchase!", - "tweet_action": "Tweet" - }, - "terms_and_conditions": { - "agreement_required_error": "Please agree to the terms and conditions", - "agreement_text": "Yes, I agree with the above terms and conditions", - "agreement_with_link_text": "Yes, I agree with the terms and conditions", - "terms_and_conditions_heading": "Terms and Conditions" - }, - "order_confirmation": { - "order_number_text": "Your order number is {orderNumber}", - "order_pending_review_text": "Your order was sent to us but is currently awaiting payment. Once we receive the payment for your order, it will be completed. If you've already provided payment details then we will process your order manually and send you an email when it's completed.", - "order_with_downloadable_digital_items_text": "You can download your digital purchases by clicking the links on this page, or by logging into your account at any time. There is also a download link in your confirmation email, which should be arriving shortly.", - "order_with_support_number_text": "An email will be sent containing information about your purchase. If you have any questions about your purchase, email us at {supportEmail} or call us at {supportPhoneNumber}.", - "order_without_downloadable_digital_items_text": "Once we receive your payment, we’ll send a confirmation email with a link to download your digital purchases.", - "order_without_support_number_text": "An email will be sent containing information about your purchase. If you have any questions about your purchase, email us at {supportEmail}.", - "thank_you_customer_heading": "Thank you {name}!", - "thank_you_heading": "Thank you!", - "continue_shopping": "Continue Shopping »", - "order_status_update_facebook_messenger_heading": "Get instant updates of your order to Messenger" - } - } -} diff --git a/src/app/locale/LocaleContext.ts b/src/app/locale/LocaleContext.ts new file mode 100644 index 0000000000..449b331e5c --- /dev/null +++ b/src/app/locale/LocaleContext.ts @@ -0,0 +1,11 @@ +import { CurrencyService, LanguageService } from '@bigcommerce/checkout-sdk'; +import { createContext } from 'react'; + +export interface LocaleContextType { + language: LanguageService; + currency?: CurrencyService; +} + +const LocaleContext = createContext(undefined); + +export default LocaleContext; diff --git a/src/app/locale/LocaleProvider.spec.tsx b/src/app/locale/LocaleProvider.spec.tsx new file mode 100644 index 0000000000..410dc8b941 --- /dev/null +++ b/src/app/locale/LocaleProvider.spec.tsx @@ -0,0 +1,56 @@ +import { createCheckoutService, CheckoutService } from '@bigcommerce/checkout-sdk'; +import { mount } from 'enzyme'; +import React, { FunctionComponent } from 'react'; + +import { getStoreConfig } from '../config/config.mock'; + +import LocaleContext, { LocaleContextType } from './LocaleContext'; +import LocaleProvider from './LocaleProvider'; + +describe('LocaleProvider', () => { + let checkoutService: CheckoutService; + + beforeEach(() => { + checkoutService = createCheckoutService(); + + jest.spyOn(checkoutService.getState().data, 'getConfig') + .mockReturnValue(getStoreConfig()); + }); + + it('provides locale context to child components', () => { + const Child: FunctionComponent = jest.fn(() => null); + const component = mount( + + + { props => props && } + + + ); + + expect(component.find(Child).prop('currency')) + .toBeDefined(); + + expect(component.find(Child).prop('language')) + .toBeDefined(); + }); + + it('provides locale context without currency service to child components when config is not available yet', () => { + jest.spyOn(checkoutService.getState().data, 'getConfig') + .mockReturnValue(undefined); + + const Child: FunctionComponent = jest.fn(() => null); + const component = mount( + + + { props => props && } + + + ); + + expect(component.find(Child).prop('currency')) + .not.toBeDefined(); + + expect(component.find(Child).prop('language')) + .toBeDefined(); + }); +}); diff --git a/src/app/locale/LocaleProvider.tsx b/src/app/locale/LocaleProvider.tsx new file mode 100644 index 0000000000..6c88d15b72 --- /dev/null +++ b/src/app/locale/LocaleProvider.tsx @@ -0,0 +1,62 @@ +import { createCurrencyService, CheckoutService, LanguageService, StoreConfig } from '@bigcommerce/checkout-sdk'; +import React, { Component, ReactNode } from 'react'; + +import getLanguageService from './getLanguageService'; +import LocaleContext from './LocaleContext'; + +export interface LocaleProviderProps { + checkoutService: CheckoutService; +} + +export interface LocaleProviderState { + config?: StoreConfig; +} + +class LocaleProvider extends Component { + state: Readonly; + + private languageService: LanguageService; + private unsubscribe?: () => void; + + constructor(props: Readonly) { + super(props); + + this.state = {}; + this.languageService = getLanguageService(); + } + + componentDidMount(): void { + const { checkoutService } = this.props; + + this.unsubscribe = checkoutService.subscribe( + ({ data }) => { + this.setState({ config: data.getConfig() }); + }, + ({ data }) => data.getConfig() + ); + } + + componentWillUnmount(): void { + if (this.unsubscribe) { + this.unsubscribe(); + this.unsubscribe = undefined; + } + } + + render(): ReactNode { + const { children } = this.props; + const { config } = this.state; + const context = { + currency: config ? createCurrencyService(config) : undefined, + language: this.languageService, + }; + + return ( + + { children } + + ); + } +} + +export default LocaleProvider; diff --git a/src/app/locale/createLocaleContext.spec.ts b/src/app/locale/createLocaleContext.spec.ts new file mode 100644 index 0000000000..45a4e173cc --- /dev/null +++ b/src/app/locale/createLocaleContext.spec.ts @@ -0,0 +1,23 @@ +import { getStoreConfig } from '../config/config.mock'; + +import createLocaleContext from './createLocaleContext'; +import { LocaleContextType } from './LocaleContext.js'; + +describe('createLocaleContext', () => { + let localeContext: LocaleContextType; + + beforeEach(() => { + localeContext = createLocaleContext(getStoreConfig()); + }); + + it('returns an object with currency', () => { + expect(localeContext).toHaveProperty('currency'); + // tslint:disable-next-line:no-non-null-assertion + expect(localeContext.currency!.toStoreCurrency).toBeDefined(); + }); + + it('returns an object with language', () => { + expect(localeContext).toHaveProperty('language'); + expect(localeContext.language.translate).toBeDefined(); + }); +}); diff --git a/src/app/locale/createLocaleContext.ts b/src/app/locale/createLocaleContext.ts new file mode 100644 index 0000000000..2760698bee --- /dev/null +++ b/src/app/locale/createLocaleContext.ts @@ -0,0 +1,15 @@ +import { createCurrencyService, StoreConfig } from '@bigcommerce/checkout-sdk'; + +import getLanguageService from './getLanguageService'; +import { LocaleContextType } from './LocaleContext'; + +export default function createLocaleContext(config: StoreConfig): Required { + if (!config) { + throw new Error('Missing configuration data'); + } + + return { + currency: createCurrencyService(config), + language: getLanguageService(), + }; +} diff --git a/src/app/locale/getLanguageService.ts b/src/app/locale/getLanguageService.ts new file mode 100644 index 0000000000..b9ed9ffa24 --- /dev/null +++ b/src/app/locale/getLanguageService.ts @@ -0,0 +1,7 @@ +import { createLanguageService, LanguageService } from '@bigcommerce/checkout-sdk'; + +import defaultTranslations from '../language/en.json'; + +export default function getLanguageService(): LanguageService { + return createLanguageService({ ...(window as any).language, defaultTranslations }); +} diff --git a/src/app/locale/index.ts b/src/app/locale/index.ts new file mode 100644 index 0000000000..460a440cd2 --- /dev/null +++ b/src/app/locale/index.ts @@ -0,0 +1,14 @@ +import { WithCurrencyProps } from './withCurrency'; +import { WithLanguageProps } from './withLanguage'; +import { LocaleContextType } from './LocaleContext'; + +export type LocaleContextType = LocaleContextType; +export type WithCurrencyProps = WithCurrencyProps; +export type WithLanguageProps = WithLanguageProps; + +export { default as LocaleContext } from './LocaleContext'; +export { default as createLocaleContext } from './createLocaleContext'; +export { default as getLanguageService } from './getLanguageService'; +export { default as withCurrency } from './withCurrency'; +export { default as withLanguage } from './withLanguage'; +export { default as LocaleProvider } from './LocaleProvider'; diff --git a/src/app/locale/localeContext.mock.tsx b/src/app/locale/localeContext.mock.tsx new file mode 100644 index 0000000000..3dde2f72d7 --- /dev/null +++ b/src/app/locale/localeContext.mock.tsx @@ -0,0 +1,13 @@ +import { createCurrencyService, createLanguageService } from '@bigcommerce/checkout-sdk'; + +import { getStoreConfig } from '../config/config.mock'; +import defaultTranslations from '../language/en.json'; + +import { LocaleContextType } from './LocaleContext'; + +export function getLocaleContext(): Required { + return { + currency: createCurrencyService(getStoreConfig()), + language: createLanguageService({ ...(window as any).language, defaultTranslations }), + }; +} diff --git a/src/app/locale/withCurrency.spec.tsx b/src/app/locale/withCurrency.spec.tsx new file mode 100644 index 0000000000..4c473c8672 --- /dev/null +++ b/src/app/locale/withCurrency.spec.tsx @@ -0,0 +1,29 @@ +import { mount } from 'enzyme'; +import React from 'react'; + +import { getStoreConfig } from '../config/config.mock'; + +import createLocaleContext from './createLocaleContext'; +import withCurrency from './withCurrency'; +import LocaleContext, { LocaleContextType } from './LocaleContext'; + +describe('withCurrency()', () => { + let contextValue: LocaleContextType; + + beforeEach(() => { + contextValue = createLocaleContext(getStoreConfig()); + }); + + it('injects currency service to inner component', () => { + const Inner = () =>
; + const Outer = withCurrency(Inner); + const container = mount( + + + + ); + + expect(container.find(Inner).prop('currency')) + .toEqual(contextValue.currency); + }); +}); diff --git a/src/app/locale/withCurrency.tsx b/src/app/locale/withCurrency.tsx new file mode 100644 index 0000000000..736895a9f3 --- /dev/null +++ b/src/app/locale/withCurrency.tsx @@ -0,0 +1,16 @@ +import { CurrencyService } from '@bigcommerce/checkout-sdk'; + +import { createInjectHoc, InjectHoc } from '../common/hoc'; + +import LocaleContext from './LocaleContext'; + +export interface WithCurrencyProps { + currency: CurrencyService; +} + +const withCurrency: InjectHoc = createInjectHoc(LocaleContext, { + displayNamePrefix: 'WithCurrency', + pickProps: (value, key) => key === 'currency' && !!value, +}); + +export default withCurrency; diff --git a/src/app/locale/withLanguage.spec.tsx b/src/app/locale/withLanguage.spec.tsx new file mode 100644 index 0000000000..8b101b6d55 --- /dev/null +++ b/src/app/locale/withLanguage.spec.tsx @@ -0,0 +1,29 @@ +import { mount } from 'enzyme'; +import React from 'react'; + +import { getStoreConfig } from '../config/config.mock'; + +import createLocaleContext from './createLocaleContext'; +import withLanguage from './withLanguage'; +import LocaleContext, { LocaleContextType } from './LocaleContext'; + +describe('withLanguage()', () => { + let contextValue: LocaleContextType; + + beforeEach(() => { + contextValue = createLocaleContext(getStoreConfig()); + }); + + it('injects language service to inner component', () => { + const Inner = () =>
; + const Outer = withLanguage(Inner); + const container = mount( + + + + ); + + expect(container.find(Inner).prop('language')) + .toEqual(contextValue.language); + }); +}); diff --git a/src/app/locale/withLanguage.tsx b/src/app/locale/withLanguage.tsx new file mode 100644 index 0000000000..ea3e980b96 --- /dev/null +++ b/src/app/locale/withLanguage.tsx @@ -0,0 +1,16 @@ +import { LanguageService } from '@bigcommerce/checkout-sdk'; + +import { createInjectHoc, InjectHoc } from '../common/hoc'; + +import LocaleContext from './LocaleContext'; + +export interface WithLanguageProps { + language: LanguageService; +} + +const withLanguage: InjectHoc = createInjectHoc(LocaleContext, { + displayNamePrefix: 'WithLanguage', + pickProps: (value, key) => key === 'language' && !!value, +}); + +export default withLanguage;