import * as React from 'react';
import { shallow, mount } from 'enzyme';
import { MockConnectHost } from './MockConnectHost';
import { MockConnectIframeProvider } from './MockConnectIframeProvider';
import { MockIframeLoadingIndicator } from './MockIframeLoadingIndicator';
import { MockIframeTimeoutIndicator } from './MockIframeTimeoutIndicator';
import { MockIframeFailedToLoadIndicator } from './MockIframeFailedToLoadIndicator';
import { ConnectIframe, ConnectIframeDefinitions, LoadingState } from '../ConnectIframe';
import { analytics } from '../../../adaptors/analytics/AnalyticsAdaptor';
import IFrameLifecycleEventManager from '../IFrameLifecycleEventManager';
import { AnalyticsCategories, AnalyticsActions } from '../../../adaptors/analytics/AnalyticsConstants';
function flushPromises() {
return new Promise(resolve => setImmediate(resolve));
}
jest.mock('../IFrameLifecycleEventManager', () => {
return require('./MockIframeLifecycleEventManager');
});
jest.mock('../../../adaptors/analytics/AnalyticsAdaptor', () => {
const { mockAnalytics } = require('./MockAnalyticsAdaptor');
return { analytics: mockAnalytics };
});
const props:ConnectIframeDefinitions.Props = {
appKey: 'appKey',
moduleKey: 'moduleKey',
url: 'url',
width: 'width',
height: 'height',
options: {},
connectHost: new MockConnectHost(),
connectIframeProvider: new MockConnectIframeProvider()
};
describe('ConnectIframe', () => {
beforeEach(() => {
analytics.trigger.mockClear();
props.connectHost.createExtension.mockClear();
props.connectHost.destroy.mockClear();
props.connectIframeProvider.onHideInlineDialog.mockClear();
props.connectIframeProvider.onStoreNestedIframeJSON.mockClear();
props.connectIframeProvider.buildIframeStyles.mockClear();
});
describe('#resize', () => {
it('renders correct height and width', async() => {
const wrapper:ConnectIframe = mount() as ConnectIframe;
await flushPromises();
wrapper.instance().resize('newWidth', 'newHeight');
wrapper.update();
expect(wrapper.find('div').props().style.width).toBe('auto');
expect(wrapper.find('div').props().style.height).toBe('auto');
expect(wrapper.find('iframe').props().width).toBe('newWidth');
expect(wrapper.find('iframe').props().height).toBe('newHeight');
});
});
describe('#sizeToParent', () => {
it('renders correct height and width', async() => {
const wrapper:ConnectIframe = mount() as ConnectIframe;
await flushPromises();
wrapper.instance().sizeToParent();
wrapper.update();
expect(wrapper.find('div').props().style.width).toBe('100%');
expect(wrapper.find('div').props().style.height).toBe('100%');
expect(wrapper.find('iframe').props().width).toBe('100%');
expect(wrapper.find('iframe').props().height).toBe('100%');
});
});
describe('#iframeContainer', () => {
it('renders iframe without div container', async() => {
const wrapper:ConnectIframe = shallow( children}/>) as ConnectIframe;
await flushPromises();
expect(wrapper.find('div').length).toBe(0);
expect(wrapper.find('iframe').length).toBe(1);
});
});
describe('#isSubIframe', () => {
describe('in a non-nested iframe', () => {
it('returns false', () => {
const wrapper:ConnectIframe = shallow() as ConnectIframe;
expect(wrapper.instance()._isSubIframe()).toBe(false);
});
});
describe('in a nested iframe', () => {
it('returns true', () => {
const wrapper:ConnectIframe = shallow() as ConnectIframe;
wrapper.setState({hostFrameOffset: 2});
expect(wrapper.instance()._isSubIframe()).toBe(true);
});
});
});
describe('#hideInlineDialog', () => {
it('does not cause error if not in an inline dialog', () => {
const wrapper:ConnectIframe = shallow() as ConnectIframe;
wrapper.instance().hideInlineDialog();
expect(props.connectIframeProvider.onHideInlineDialog).not.toHaveBeenCalled();
});
it('invokes the provider method if registered', () => {
const wrapper:ConnectIframe = shallow() as ConnectIframe;
wrapper.instance().hideInlineDialog();
expect(props.connectIframeProvider.onHideInlineDialog).toHaveBeenCalledTimes(1);
});
});
describe('#render', () => {
it('renders a provided timeout indicator', async() => {
const wrapper:ConnectIframe = shallow() as ConnectIframe;
await flushPromises();
wrapper.instance().iframeTimeoutCallback();
wrapper.update();
expect(wrapper.html()).toEqual(expect.stringContaining(MockIframeTimeoutIndicator.getHtml()));
});
it('renders a provided failed to load indicator', async() => {
const wrapper:ConnectIframe = shallow() as ConnectIframe;
await flushPromises();
wrapper.instance().iframeFailedToLoadCallback();
wrapper.update();
expect(wrapper.html()).toEqual(expect.stringContaining(MockIframeFailedToLoadIndicator.getHtml()));
});
describe('Loading indicator', () => {
let wrapper:ConnectIframe;
beforeEach(() => {
wrapper = shallow() as ConnectIframe;
});
it('displays a provided loading indicator while loading', async() => {
await flushPromises();
wrapper.update();
expect(wrapper.html()).toEqual(expect.stringContaining(MockIframeLoadingIndicator.getHtml());
});
it('does not display a provided loading indicator once loaded', async() => {
await flushPromises();
wrapper.instance().iframeEstablishedCallback();
wrapper.update();
expect(wrapper.html()).not.toEqual(expect.stringContaining(MockIframeLoadingIndicator.getHtml());
});
});
it('Hides the iframe while loading', async() => {
const wrapper = shallow();
await flushPromises();
wrapper.update();
expect(JSON.stringify(wrapper.find('iframe').props().style)).toEqual(JSON.stringify({opacity: 0}));
});
it('Shows the iframe once loaded', async() => {
const wrapper = shallow();
await flushPromises();
wrapper.instance().iframeEstablishedCallback();
wrapper.update();
expect(JSON.stringify(wrapper.find('iframe').props().style)).toEqual(JSON.stringify({}));
});
it('Using product-provided iframe styles', () => {
const wrapper = shallow();
wrapper.instance().componentDidMount();
expect(props.connectIframeProvider.buildIframeStyles).toHaveBeenCalledTimes(1);
expect(JSON.stringify(wrapper.find('iframe').props().style)).toEqual(JSON.stringify({key: 'value'}));
});
it('uses the iframe atttributes provided by simple-xdm', async() => {
const wrapper = shallow();
await flushPromises();
wrapper.update();
expect(wrapper.find('iframe').props().id).toEqual('mock-iframe-id');
expect(wrapper.find('iframe').props().src).toEqual(props.url);
expect(wrapper.find('iframe').props().name).toEqual('mock-iframe-name');
});
});
describe('#componentWillUnmount', () => {
let wrapper:ConnectIframe;
beforeEach(() => {
wrapper = shallow() as ConnectIframe;
});
it('invokes all registered callbacks', () => {
const cb1 = jest.fn();
const cb2 = jest.fn();
wrapper.instance().registerUnmountCallback(cb1);
wrapper.instance().registerUnmountCallback(cb2);
wrapper.instance().componentWillUnmount();
expect(cb1).toHaveBeenCalledTimes(1);
expect(cb2).toHaveBeenCalledTimes(1);
});
});
describe('#componentDidMount', () => {
let wrapper:ConnectIframe;
describe('in a non-nested iframe', () => {
beforeEach(() => {
wrapper = shallow() as ConnectIframe;
});
it('it does not store nested iframe JSON', async() => {
await flushPromises();
wrapper.update();
expect(props.connectIframeProvider.onStoreNestedIframeJSON).not.toHaveBeenCalled();
});
it('builds an xdm bridge', async() => {
await flushPromises();
wrapper.update();
expect(props.connectHost.createExtension).toHaveBeenCalledTimes(1);
});
it('triggers an analytics event', async() => {
await flushPromises();
wrapper.update();
expect(analytics.trigger).toHaveBeenCalledTimes(1);
expect(analytics.trigger).toHaveBeenCalledWith(
AnalyticsCategories.iframe,
AnalyticsActions.create,
'appKey',
expect.anything()
);
});
});
describe('in a nested iframe', () => {
beforeEach(() => {
const nestedProps = Object.assign({}, props, {hostFrameOffset: 2});
wrapper = shallow() as ConnectIframe;
});
it('stores the nested iframe JSON', async() => {
await flushPromises();
wrapper.update();
expect(props.connectIframeProvider.onStoreNestedIframeJSON).toHaveBeenCalledTimes(1);
});
});
});
describe('#componentDidUpdate', () => {
let wrapper:ConnectIframe;
describe('in a non-nested iframe', () => {
beforeEach(() => {
wrapper = shallow() as ConnectIframe;
});
it('it does not store nested iframe JSON', async() => {
await flushPromises();
wrapper.update();
expect(props.connectIframeProvider.onStoreNestedIframeJSON).not.toHaveBeenCalled();
});
it('builds an xdm bridge', async() => {
await flushPromises();
wrapper.update();
expect(props.connectHost.createExtension).toHaveBeenCalledTimes(1);
});
it('triggers an analytics event', async() => {
await flushPromises();
wrapper.update();
expect(analytics.trigger).toHaveBeenCalledTimes(1);
expect(analytics.trigger).toHaveBeenCalledWith(
AnalyticsCategories.iframe,
AnalyticsActions.create,
'appKey',
expect.anything()
);
});
});
describe('in a nested iframe', () => {
beforeEach(() => {
const nestedProps = Object.assign({}, props, {hostFrameOffset: 2});
wrapper = shallow() as ConnectIframe;
});
it('stores the nested iframe JSON', async() => {
await flushPromises();
wrapper.update();
expect(props.connectIframeProvider.onStoreNestedIframeJSON).toHaveBeenCalledTimes(1);
});
});
});
it('resets on appKey change', async() => {
const wrapper:ConnectIframe = mount() as ConnectIframe;
await flushPromises();
wrapper.update();
const firstIFrameLifecycleEventManager:IFrameLifecycleEventManager =
wrapper.instance().getIFrameLifecycleEventManager();
wrapper.setProps(Object.assign({}, props, {appKey: props.appKey+'1'}));
await flushPromises();
wrapper.update();
const secondIFrameLifecycleEventManager:IFrameLifecycleEventManager =
wrapper.instance().getIFrameLifecycleEventManager();
expect(props.connectHost.destroy).toHaveBeenCalledTimes(1);
expect(props.connectHost.createExtension).toHaveBeenCalledTimes(2);
expect(firstIFrameLifecycleEventManager.unregister).toHaveBeenCalledTimes(1);
expect(secondIFrameLifecycleEventManager.unregister).not.toHaveBeenCalled();
});
});
it('resets on moduleKey change', async() => {
const wrapper:ConnectIframe = mount() as ConnectIframe;
await flushPromises();
wrapper.update();
const firstIFrameLifecycleEventManager:IFrameLifecycleEventManager =
wrapper.instance().getIFrameLifecycleEventManager();
wrapper.setProps(Object.assign({}, props, {moduleKey: props.moduleKey+'1'}));
await flushPromises();
wrapper.update();
const secondIFrameLifecycleEventManager:IFrameLifecycleEventManager =
wrapper.instance().getIFrameLifecycleEventManager();
expect(props.connectHost.destroy).toHaveBeenCalledTimes(1);
expect(props.connectHost.createExtension).toHaveBeenCalledTimes(2);
expect(firstIFrameLifecycleEventManager.unregister).toHaveBeenCalledTimes(1);
expect(secondIFrameLifecycleEventManager.unregister).not.toHaveBeenCalled();
});
it('does not reset on width change', async() => {
const wrapper:ConnectIframe = mount() as ConnectIframe;
await flushPromises();
wrapper.update();
const firstIFrameLifecycleEventManager:IFrameLifecycleEventManager =
wrapper.instance().getIFrameLifecycleEventManager();
wrapper.setProps(Object.assign({}, props, {width: props.width+'1'}));
expect(props.connectHost.destroy).not.toHaveBeenCalled();
expect(props.connectHost.createExtension).toHaveBeenCalledTimes(1);
});
it('does not reset on height change', async() => {
const wrapper:ConnectIframe = mount() as ConnectIframe;
await flushPromises();
wrapper.update();
const firstIFrameLifecycleEventManager:IFrameLifecycleEventManager =
wrapper.instance().getIFrameLifecycleEventManager();
wrapper.setProps(Object.assign({}, props, {height: props.height+'1'}));
expect(props.connectHost.destroy).not.toHaveBeenCalled();
expect(props.connectHost.createExtension).toHaveBeenCalledTimes(1);
});
it('creates only a single xdm bridge', async() => {
const wrapper:ConnectIframe = mount() as ConnectIframe;
await flushPromises();
wrapper.update();
expect(props.connectHost.createExtension).toHaveBeenCalledTimes(1);
});
describe('iframe context resolver', () => {
it('runs if required', async() => {
let customProps = Object.assign({}, props, {connectIframeProvider: new MockConnectIframeProvider()});
customProps.connectIframeProvider.resolveIframeContext =
jest.fn(iframeContext =>
new Promise(resolve => resolve(Object.assign({}, iframeContext, {url: 'resolved-url'}))));
const wrapper:ConnectIframe = mount() as ConnectIframe;
await flushPromises();
wrapper.update();
expect(customProps.connectIframeProvider.resolveIframeContext).toHaveBeenCalledTimes(1);
expect(wrapper.find('iframe').props().src).toEqual('resolved-url');
});
it('does not break the component if not registered', async() => {
let customProps = Object.assign({}, props, {connectIframeProvider: new MockConnectIframeProvider()});
customProps.connectIframeProvider.resolveIframeContext = undefined;
const wrapper:ConnectIframe = mount() as ConnectIframe;
await flushPromises();
wrapper.update();
expect(wrapper.find('iframe').props().src).toEqual(customProps.url);
});
});
});