Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add uiLang parameter #714

Merged
merged 8 commits into from
Sep 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions packages/x-components/src/x-installer/api/api.types.ts
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Loading