Skip to content

Commit

Permalink
feat: add uiLang parameter (#714)
Browse files Browse the repository at this point in the history
  • Loading branch information
javieri-empathy committed Sep 13, 2022
1 parent 5dac07f commit f4108d4
Show file tree
Hide file tree
Showing 9 changed files with 194 additions and 94 deletions.
16 changes: 12 additions & 4 deletions packages/x-components/src/x-installer/api/api.types.ts
@@ -1,3 +1,4 @@
import { RequiredProperties } from '@empathyco/x-utils';
import { XBus } from '../../plugins/x-bus.types';
import { DocumentDirection } from '../../plugins/x-plugin.types';
import { XEvent, XEventPayload } from '../../wiring/events.types';
Expand Down Expand Up @@ -94,13 +95,13 @@ export interface SnippetConfig {
/** Customer instance. */
instance: string;
/** Backend services environment. */
env?: 'live' | 'staging';
env?: 'staging';
/** Execution scope (desktop, mobile, app, ...). */
scope: string;
/** Language to display. */
/** Language for the API request, and default value for {@link SnippetConfig.uiLang}. */
lang: string;
/** Language to send to backend services. */
searchLang?: string;
/** Language to use for the messages. Defaults to {@link SnippetConfig.lang}. */
uiLang?: string;
/** User GDPR consent. */
consent?: boolean;
/** Document direction. */
Expand All @@ -121,6 +122,13 @@ export interface SnippetConfig {
[extra: string]: unknown;
}

/**
* A normalised version of the snippet config.
*
* @public
*/
export type NormalisedSnippetConfig = RequiredProperties<SnippetConfig, 'uiLang'>;

/**
* Information to render a query preview with.
*
Expand Down
Expand Up @@ -8,6 +8,7 @@ import { XComponentsAdapterDummy } from '../../../__tests__/adapter.dummy';
import { AnyXModule } from '../../../x-modules/x-modules.types';
import { InitWrapper, InstallXOptions } from '../types';
import { XInstaller } from '../x-installer';
import { SnippetConfig } from '../../api/index';

describe('testing `XInstaller` utility', () => {
const adapter = XComponentsAdapterDummy;
Expand All @@ -25,11 +26,30 @@ describe('testing `XInstaller` utility', () => {
mounted: jest.fn()
};

const snippetConfig = {
const getMinimumSnippetConfig = (): SnippetConfig => ({
instance: 'test',
lang: 'test',
scope: 'test'
};
});

/**
* Creates a Vue component injecting the snippet config.
*
* @param snippetProperty
* @returns A Vue component with the injected snippet config.
*
* @internal
*/
function createSnippetConfigComponent(
snippetProperty: keyof SnippetConfig = 'instance'
): VueConstructor {
return Vue.extend({
inject: ['snippetConfig'],
render(h) {
return h('h1', [(this as any).snippetConfig[snippetProperty]]);
}
});
}

beforeEach(() => {
delete window.initX;
Expand All @@ -45,7 +65,7 @@ describe('testing `XInstaller` utility', () => {
__PRIVATE__xModules,
initialXModules: [initialXModule],
vue: createLocalVue()
}).init(snippetConfig);
}).init(getMinimumSnippetConfig());
const params = xPluginMock.install.mock.calls[0][1];

expect(xPluginMock.install).toHaveBeenCalledTimes(1);
Expand All @@ -59,21 +79,23 @@ describe('testing `XInstaller` utility', () => {

it('creates the public API in global scope by default', () => {
delete window.InterfaceX;
new XInstaller({ adapter, plugin, vue: createLocalVue() }).init(snippetConfig);
new XInstaller({ adapter, plugin, vue: createLocalVue() }).init(getMinimumSnippetConfig());

expect(window.InterfaceX).toBeDefined();
delete window.InterfaceX;
});

it('does not create the public API passing the api parameter to false', () => {
new XInstaller({ adapter, plugin, api: false, vue: createLocalVue() }).init(snippetConfig);
new XInstaller({ adapter, plugin, api: false, vue: createLocalVue() }).init(
getMinimumSnippetConfig()
);

expect(window.InterfaceX).not.toBeDefined();
});

it('installs the XPlugin using the passed vue', () => {
const localVue = createLocalVue();
new XInstaller({ adapter, plugin, vue: localVue }).init(snippetConfig);
new XInstaller({ adapter, plugin, vue: localVue }).init(getMinimumSnippetConfig());
const vueParam = xPluginMock.install.mock.calls[0][0];

expect(xPluginMock.install).toHaveBeenCalledTimes(1);
Expand All @@ -82,7 +104,7 @@ describe('testing `XInstaller` utility', () => {

it('creates a Vue application using the component passed in the app option', async () => {
await new XInstaller({ adapter, plugin, app: component, vue: createLocalVue() }).init(
snippetConfig
getMinimumSnippetConfig()
);

// eslint-disable-next-line @typescript-eslint/unbound-method
Expand All @@ -102,7 +124,7 @@ describe('testing `XInstaller` utility', () => {
vue,
vueOptions,
app: component
}).init(snippetConfig);
}).init(getMinimumSnippetConfig());

expect(app).toHaveProperty('testMethod');
expect(app).toHaveProperty('$router');
Expand All @@ -128,57 +150,57 @@ describe('testing `XInstaller` utility', () => {
};
},
app: component
}).init(snippetConfig);
}).init(getMinimumSnippetConfig());

expect(app).toHaveProperty('$router');
expect(app).toHaveProperty('bus');
expect(app).toHaveProperty('snippet', snippetConfig);
expect(app).toHaveProperty('snippet', { ...getMinimumSnippetConfig(), uiLang: 'test' });
});

it('initializes the app with the provided snippet config', async () => {
const vue = createLocalVue();
const { app } = await new XInstaller({
const { app, api } = await new XInstaller({
adapter,
vue,
app: injectSnippetConfigComponent()
}).init(snippetConfig);
app: createSnippetConfigComponent()
}).init(getMinimumSnippetConfig());

expect(app?.$el).toHaveTextContent(snippetConfig.instance);
expect(app?.$el).toHaveTextContent(getMinimumSnippetConfig().instance);

snippetConfig.instance = 'test-2';
api?.setSnippetConfig({ instance: 'test-2' });
await vue.nextTick();
expect(app?.$el).toHaveTextContent('test-2');
});

it('initializes the app when window.initX has the snippet config object', async () => {
const vue = createLocalVue();
window.initX = snippetConfig;
const { app } = (await new XInstaller({
window.initX = getMinimumSnippetConfig();
const { app, api } = (await new XInstaller({
adapter,
vue,
app: injectSnippetConfigComponent()
app: createSnippetConfigComponent()
}).init()) as InitWrapper;

expect(app?.$el).toHaveTextContent(snippetConfig.instance);
expect(app?.$el).toHaveTextContent(getMinimumSnippetConfig().instance);

snippetConfig.instance = 'test-2';
api?.setSnippetConfig({ instance: 'test-2' });
await vue.nextTick();
expect(app?.$el).toHaveTextContent('test-2');
});

// eslint-disable-next-line max-len
it('initializes the app when window.initX is a function retrieving the snippet config', async () => {
const vue = createLocalVue();
window.initX = () => snippetConfig;
const { app } = (await new XInstaller({
window.initX = () => getMinimumSnippetConfig();
const { app, api } = (await new XInstaller({
adapter,
vue,
app: injectSnippetConfigComponent()
app: createSnippetConfigComponent()
}).init()) as InitWrapper;

expect(app?.$el).toHaveTextContent(snippetConfig.instance);
expect(app?.$el).toHaveTextContent(getMinimumSnippetConfig().instance);

snippetConfig.instance = 'test-2';
api?.setSnippetConfig({ instance: 'test-2' });
await vue.nextTick();
expect(app?.$el).toHaveTextContent('test-2');
});
Expand All @@ -187,22 +209,50 @@ describe('testing `XInstaller` utility', () => {
it('does not initialize XComponents when no snippet config is passed and no window.initX is not defined', async () => {
expect(await new XInstaller({ adapter, plugin, vue: createLocalVue() }).init()).toBeUndefined();
});
});

/**
* Creates a Vue component injecting the snippet config.
*
* @returns A Vue component with the injected snippet config.
*
* @internal
*/
function injectSnippetConfigComponent(): VueConstructor {
return Vue.extend({
inject: ['snippetConfig'],
render(h) {
// Vue does not provide type safety for inject
const instance = (this as any).snippetConfig.instance;
return h('h1', [instance]);
}
describe('`lang` & `uiLang`', () => {
it('provides a `uiLang` by default', async () => {
const { app } = await new XInstaller({
adapter,
plugin,
vue: createLocalVue(),
app: createSnippetConfigComponent('uiLang')
}).init({ ...getMinimumSnippetConfig(), lang: 'en' });

expect(app?.$el).toHaveTextContent('en');
});

it('respects user `uiLang` value', async () => {
const { app } = await new XInstaller({
adapter,
plugin,
vue: createLocalVue(),
app: createSnippetConfigComponent('uiLang')
}).init({ ...getMinimumSnippetConfig(), lang: 'en', uiLang: 'it' });
expect(app?.$el).toHaveTextContent('it');
});

it('updates `uiLang` when `lang` is changed', async () => {
const vue = createLocalVue();
const { app, api } = await new XInstaller({
adapter,
plugin,
vue,
app: createSnippetConfigComponent('uiLang')
}).init({ ...getMinimumSnippetConfig(), lang: 'en', uiLang: 'it' });
expect(app?.$el).toHaveTextContent('it');

api!.setSnippetConfig({ lang: 'es' });
await vue.nextTick();
expect(app?.$el).toHaveTextContent('es');

api!.setSnippetConfig({ uiLang: 'en' });
await vue.nextTick();
expect(app?.$el).toHaveTextContent('en');

api!.setSnippetConfig({ lang: 'fr', uiLang: 'it' });
await vue.nextTick();
expect(app?.$el).toHaveTextContent('it');
});
});
}
});
47 changes: 31 additions & 16 deletions packages/x-components/src/x-installer/x-installer/types.ts
@@ -1,7 +1,7 @@
import { ComponentOptions, PluginObject, VueConstructor } from 'vue';
import { XBus } from '../../plugins/x-bus.types';
import { XPluginOptions } from '../../plugins/x-plugin.types';
import { SnippetConfig, XAPI } from '../api/api.types';
import { NormalisedSnippetConfig, XAPI } from '../api/api.types';

/**
* Interface for the parameter of the constructor of {@link XInstaller} function. It is an extended
Expand All @@ -10,25 +10,37 @@ import { SnippetConfig, XAPI } from '../api/api.types';
* @public
*/
export interface InstallXOptions<API extends XAPI = XAPI> extends XPluginOptions {
/** The Vue component used as root of the application. If is not passed no Vue Application is
* initialized, only plugin installed. */
/**
* The Vue component used as root of the application. If it is not passed, no Vue Application is
* initialized, only plugin installed.
*/
app?: VueConstructor | ComponentOptions<Vue>;
/** The API to expose globally. If is not passed the default {@link BaseXAPI} will be used. If
* a `false` value is passed then the API is not created.*/
/**
* The API to expose globally. If is not passed the default {@link BaseXAPI} will be used. If
* a `false` value is passed then the API is not created.
*/
api?: API | false;
/** The {@link XBus} used in the {@link XPlugin}. If not passed an instance of {@link BaseXBus}
* will be used.*/
/**
* The {@link XBus} used in the {@link XPlugin}. If not passed an instance of {@link BaseXBus}
* will be used.
*/
bus?: XBus;
/** An Element | string to indicate the HTML element that will contain the Vue
/**
* An Element | string to indicate the HTML element that will contain the Vue
* application. If string selector is passed and the element doesn't exits, the
* {@link XInstaller} will create it. */
* {@link XInstaller} will create it.
*/
domElement?: Element | string;
/** The XPlugin which will be installed. If not passed, an instance of {@link XPlugin} will be
* installed.*/
/**
* The XPlugin which will be installed. If not passed, an instance of {@link XPlugin} will be
* installed.
*/
plugin?: PluginObject<XPluginOptions>;
/** The Vue instance used to install the plugin and to create the Application. If not
/**
* The Vue instance used to install the plugin and to create the Application. If not
* passed the default Vue instance is used. This can be useful to use the `localVue`
* in the unit tests.*/
* in the unit tests.
*/
vue?: VueConstructor;
/**
* This object can contain any option to pass to Vue instance at the moment of creating the App
Expand All @@ -45,6 +57,7 @@ export interface InstallXOptions<API extends XAPI = XAPI> extends XPluginOptions
* ```
*/
vueOptions?: VueConstructorPartialArgument;

/**
* Adds the option to install more Vue plugins, giving access to the {@link SnippetConfig} and
* the {@link XBus}.
Expand All @@ -68,9 +81,11 @@ export interface ExtraPluginsOptions {
vue: VueConstructor;
/** The events bus instance used to communicate different part of the x-components. */
bus: XBus;
/** Configuration coming from the client website with options like the lang, or the active
* currency. */
snippet: SnippetConfig;
/**
* Configuration coming from the client website with options like the lang, or the active
* currency.
*/
snippet: NormalisedSnippetConfig;
}

/**
Expand Down

0 comments on commit f4108d4

Please sign in to comment.