Skip to content

Commit

Permalink
feat(common): CHECKOUT-4223 Add locale context provider
Browse files Browse the repository at this point in the history
  • Loading branch information
davidchin committed Aug 8, 2019
1 parent bbe283e commit 9511bcd
Show file tree
Hide file tree
Showing 14 changed files with 291 additions and 312 deletions.
20 changes: 0 additions & 20 deletions src/app/i18n/README.md

This file was deleted.

292 changes: 0 additions & 292 deletions src/app/i18n/en.json

This file was deleted.

11 changes: 11 additions & 0 deletions src/app/locale/LocaleContext.ts
Original file line number Diff line number Diff line change
@@ -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<LocaleContextType | undefined>(undefined);

export default LocaleContext;
56 changes: 56 additions & 0 deletions src/app/locale/LocaleProvider.spec.tsx
Original file line number Diff line number Diff line change
@@ -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<LocaleContextType> = jest.fn(() => null);
const component = mount(
<LocaleProvider checkoutService={ checkoutService }>
<LocaleContext.Consumer>
{ props => props && <Child { ...props } /> }
</LocaleContext.Consumer>
</LocaleProvider>
);

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<LocaleContextType> = jest.fn(() => null);
const component = mount(
<LocaleProvider checkoutService={ checkoutService }>
<LocaleContext.Consumer>
{ props => props && <Child { ...props } /> }
</LocaleContext.Consumer>
</LocaleProvider>
);

expect(component.find(Child).prop('currency'))
.not.toBeDefined();

expect(component.find(Child).prop('language'))
.toBeDefined();
});
});
62 changes: 62 additions & 0 deletions src/app/locale/LocaleProvider.tsx
Original file line number Diff line number Diff line change
@@ -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<LocaleProviderProps> {
state: Readonly<LocaleProviderState>;

private languageService: LanguageService;
private unsubscribe?: () => void;

constructor(props: Readonly<LocaleProviderProps>) {
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 (
<LocaleContext.Provider value={ context }>
{ children }
</LocaleContext.Provider>
);
}
}

export default LocaleProvider;
23 changes: 23 additions & 0 deletions src/app/locale/createLocaleContext.spec.ts
Original file line number Diff line number Diff line change
@@ -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();
});
});
15 changes: 15 additions & 0 deletions src/app/locale/createLocaleContext.ts
Original file line number Diff line number Diff line change
@@ -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<LocaleContextType> {
if (!config) {
throw new Error('Missing configuration data');
}

return {
currency: createCurrencyService(config),
language: getLanguageService(),
};
}
7 changes: 7 additions & 0 deletions src/app/locale/getLanguageService.ts
Original file line number Diff line number Diff line change
@@ -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 });
}
14 changes: 14 additions & 0 deletions src/app/locale/index.ts
Original file line number Diff line number Diff line change
@@ -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';
13 changes: 13 additions & 0 deletions src/app/locale/localeContext.mock.tsx
Original file line number Diff line number Diff line change
@@ -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<LocaleContextType> {
return {
currency: createCurrencyService(getStoreConfig()),
language: createLanguageService({ ...(window as any).language, defaultTranslations }),
};
}
29 changes: 29 additions & 0 deletions src/app/locale/withCurrency.spec.tsx
Original file line number Diff line number Diff line change
@@ -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 = () => <div />;
const Outer = withCurrency(Inner);
const container = mount(
<LocaleContext.Provider value={ contextValue }>
<Outer />
</LocaleContext.Provider>
);

expect(container.find(Inner).prop('currency'))
.toEqual(contextValue.currency);
});
});
16 changes: 16 additions & 0 deletions src/app/locale/withCurrency.tsx
Original file line number Diff line number Diff line change
@@ -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<WithCurrencyProps> = createInjectHoc(LocaleContext, {
displayNamePrefix: 'WithCurrency',
pickProps: (value, key) => key === 'currency' && !!value,
});

export default withCurrency;
29 changes: 29 additions & 0 deletions src/app/locale/withLanguage.spec.tsx
Original file line number Diff line number Diff line change
@@ -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 = () => <div />;
const Outer = withLanguage(Inner);
const container = mount(
<LocaleContext.Provider value={ contextValue }>
<Outer />
</LocaleContext.Provider>
);

expect(container.find(Inner).prop('language'))
.toEqual(contextValue.language);
});
});
16 changes: 16 additions & 0 deletions src/app/locale/withLanguage.tsx
Original file line number Diff line number Diff line change
@@ -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<WithLanguageProps> = createInjectHoc(LocaleContext, {
displayNamePrefix: 'WithLanguage',
pickProps: (value, key) => key === 'language' && !!value,
});

export default withLanguage;

0 comments on commit 9511bcd

Please sign in to comment.