describe('SurveyTakingQuestionClient', () => { let SurveyTakingQuestionClient: any; let mockConfig: any; let mockConfigWithStateData: any; let mockQuestionDef = { QuestionID: 'QID1', QuestionText: 'plugin context question def', questionTypePluginProperties: { BaseCssUrl: 'www.example.com' } }; let mockResponseDef = 'plugin context response def'; let mockStateData = { foo: 'bar', baz: [] }; let mockContext: any = { language: '', questionDef: mockQuestionDef, response: mockResponseDef, surveyId: '', }; let mockPluginClient: any; let mockPluginClientInstance: any; let mockLinkElement: any; jest.mock('@qualtrics/plugin-client', () => { mockPluginClient = { initialize: jest.fn().mockImplementation( () => { mockPluginClientInstance = { getContext: jest.fn().mockReturnValue(mockContext), postMessage: jest.fn(), getText: jest.fn().mockImplementation((key, placeholders, defaultVal) => `(PluginClient) Key: "${key}" Placeholders: ${JSON.stringify(placeholders)} DefaultVal: "${defaultVal}"`), }; return Promise.resolve(mockPluginClientInstance); }), }; return mockPluginClient; }); jest.mock('../../survey-taking-shared/src/window', () => ({ document: { createElement: jest.fn(tag => { mockLinkElement = class { constructor() { setTimeout(() => { // @ts-ignore this.onload(); // simulate success }, 200); } } return new mockLinkElement() }), head: { appendChild: jest.fn(child => {}) } }, })); SurveyTakingQuestionClient = require('./index').SurveyTakingQuestionClient; mockConfig = { handlers: { onHostHasChangedResponse: jest.fn().mockResolvedValue({}), onHostHasChangedPluginStateData: jest.fn().mockResolvedValue({}), }, }; mockConfigWithStateData = { handlers: { onHostHasChangedResponse: jest.fn(), onHostHasChangedPluginStateData: jest.fn(), }, stateData: mockStateData, }; describe('initialize', () => { it('creates a new SurveyTakingQuestionClient instance (without stateData in config)', async () => { const client = await SurveyTakingQuestionClient.initialize(mockConfig); expect(client).toBeInstanceOf(SurveyTakingQuestionClient); }); it('creates a new SurveyTakingQuestionClient instance (with stateData in config)', async () => { const client = await SurveyTakingQuestionClient.initialize(mockConfigWithStateData); expect(client).toBeInstanceOf(SurveyTakingQuestionClient); }); it('instance contains a PluginClient instance (without state data in config)', async () => { const client = await SurveyTakingQuestionClient.initialize(mockConfig); expect(client.pluginClient).toMatchObject({ getContext: expect.any(Function), postMessage: expect.any(Function), }); }); it('instance contains a PluginClient instance (with state data in config)', async () => { const client = await SurveyTakingQuestionClient.initialize(mockConfigWithStateData); expect(client.pluginClient).toMatchObject({ getContext: expect.any(Function), postMessage: expect.any(Function), }); }); it('instance contains a question', async () => { const client = await SurveyTakingQuestionClient.initialize(mockConfig); expect(client.question).toMatchObject(mockQuestionDef); }); it('instance contains a response', async () => { const client = await SurveyTakingQuestionClient.initialize(mockConfig); expect(client.response).toBe(mockResponseDef); }); it('passes response updates to the provided onHostHasChangedResponse handler', async () => { const client = await SurveyTakingQuestionClient.initialize(mockConfig); const MockPluginClient = require('@qualtrics/plugin-client'); const { onHostHasChangedResponse } = MockPluginClient.initialize.mock.calls[0][0]; expect(mockConfig.handlers.onHostHasChangedResponse).not.toBeCalled(); await onHostHasChangedResponse({response: mockResponseDef}); expect(mockConfig.handlers.onHostHasChangedResponse).toBeCalledWith(mockResponseDef); }); it('passes stateData updates to the provided onHostHasChangedPluginStateData handler', async () => { const client = await SurveyTakingQuestionClient.initialize(mockConfigWithStateData); const MockPluginClient = require('@qualtrics/plugin-client'); const { onHostHasChangedPluginStateData } = MockPluginClient.initialize.mock.calls[0][0]; expect(mockConfigWithStateData.handlers.onHostHasChangedPluginStateData).not.toBeCalled(); await onHostHasChangedPluginStateData({stateData: mockStateData}); expect(mockConfigWithStateData.handlers.onHostHasChangedPluginStateData).toBeCalledWith(mockStateData); }); }); describe('updateResponse', () => { it('invokes postMessage with the expected arguments', async () => { const client = await SurveyTakingQuestionClient.initialize(mockConfig); expect(client.pluginClient.postMessage).toBeCalledTimes(2); // called twice during initialization client.updateResponse(mockResponseDef); expect(client.pluginClient.postMessage).toBeCalledWith('onPluginHasChangedResponse', { response: mockResponseDef }, 10000); }); }); describe('updatePluginStateDataObject', () => { it('invokes postMessage with the expected arguments', async () => { const client = await SurveyTakingQuestionClient.initialize(mockConfigWithStateData); expect(client.pluginClient.postMessage).toBeCalledTimes(2); // called twice during initialization client.updatePluginStateDataObject({'foo': [],}); expect(client.pluginClient.postMessage).toBeCalledWith('onPluginHasChangedStateData', { stateData: {'foo': [],} }, 10000); }); }); describe('updatePluginStateDataField', () => { it('invokes postMessage with the expected arguments', async () => { const client = await SurveyTakingQuestionClient.initialize(mockConfigWithStateData); expect(client.pluginClient.postMessage).toBeCalledTimes(2); // called twice during initialization client.updatePluginStateDataField({'foo': [],}, 'foo', [1,2,3]); expect(client.pluginClient.postMessage).toBeCalledWith('onPluginHasChangedStateData', { stateData: {'foo': [1,2,3],} }, 10000); }); }); describe('setContainerHeight', () => { it('invokes postMessage with the expected arguments', async () => { const client = await SurveyTakingQuestionClient.initialize(mockConfig); expect(client.pluginClient.postMessage).toBeCalledTimes(2); // called twice during initialization client.setContainerHeight(123); expect(client.pluginClient.postMessage).toBeCalledWith('setContainerHeight', 123, 10000); }); }); describe('onPluginHasLoaded', () => { it('invokes postMessage with the expected arguments', async () => { const client = await SurveyTakingQuestionClient.initialize(mockConfig); expect(client.pluginClient.postMessage).toBeCalledTimes(2); // called twice during initialization expect(client.pluginClient.postMessage).toBeCalledWith("onPluginHasChangedStateData", {"stateData": undefined}, 10000); expect(client.pluginClient.postMessage).toBeCalledWith("onPluginHasLoaded", {}, 10000); }); }); describe('onHostHasChangedResponse', () => { it('updates the client-local response', async () => { const client = await SurveyTakingQuestionClient.initialize(mockConfig); expect(client.response).toBe(mockResponseDef); const newRepsonseDef = 'new response definition'; const clientHandlers = mockPluginClient.initialize.mock.calls[0][0]; clientHandlers.onHostHasChangedResponse({response: newRepsonseDef}); expect(client.response).toBe(newRepsonseDef); }); it('fires the provided `onHostHasChangedResponse` handler when provided', async () => { expect(mockConfig.handlers.onHostHasChangedResponse).not.toBeCalled(); await SurveyTakingQuestionClient.initialize(mockConfig); expect(mockConfig.handlers.onHostHasChangedResponse).not.toBeCalled(); const newRepsonseDef = 'new response definition'; const clientHandlers = mockPluginClient.initialize.mock.calls[0][0]; clientHandlers.onHostHasChangedResponse({response: newRepsonseDef}); expect(mockConfig.handlers.onHostHasChangedResponse).toBeCalledWith(newRepsonseDef); }); it('does not error if no `onHostHasChangedResponse` handler was provided', async () => { delete mockConfig.handlers.onHostHasChangedResponse; await SurveyTakingQuestionClient.initialize(mockConfig); const newRepsonseDef = 'new response definition'; const clientHandlers = mockPluginClient.initialize.mock.calls[0][0]; clientHandlers.onHostHasChangedResponse({response: newRepsonseDef}); }); }); describe('onHostHasChangedPluginStateData', () => { it('updates the client-local stateData', async () => { const client = await SurveyTakingQuestionClient.initialize(mockConfig); expect(client.stateData).toBe(null); const newStateData = { 'foo': 'bar', 'baz': [] }; const clientHandlers = mockPluginClient.initialize.mock.calls[0][0]; clientHandlers.onHostHasChangedPluginStateData({stateData: newStateData}); expect(client.stateData).toBe(newStateData); }); it('fires the provided `onHostHasChangedPluginStateData` handler when provided', async () => { expect(mockConfig.handlers.onHostHasChangedPluginStateData).not.toBeCalled(); await SurveyTakingQuestionClient.initialize(mockConfig); expect(mockConfig.handlers.onHostHasChangedPluginStateData).not.toBeCalled(); const newStateData = { 'foo': 'bar', 'baz': [] }; const clientHandlers = mockPluginClient.initialize.mock.calls[0][0]; clientHandlers.onHostHasChangedPluginStateData({stateData: newStateData}); expect(mockConfig.handlers.onHostHasChangedPluginStateData).toBeCalledWith(newStateData); }); it('does not error if no `onHostHasChangedPluginStateData` handler was provided', async () => { delete mockConfigWithStateData.handlers.onHostHasChangedPluginStateData; await SurveyTakingQuestionClient.initialize(mockConfigWithStateData); const newStateData = { 'foo': 'bar', 'baz': [] }; const clientHandlers = mockPluginClient.initialize.mock.calls[0][0]; clientHandlers.onHostHasChangedPluginStateData(newStateData); }); }); describe('invokePredefinedRequest', () => { it('invokes postMessage with the expected arguments', async () => { const client = await SurveyTakingQuestionClient.initialize(mockConfig); expect(client.pluginClient.postMessage).toBeCalledTimes(2); await client.invokePredefinedRequest({ name: 'doTheThing', doer: 'Zhu Li' }, 1337); expect(client.pluginClient.postMessage).toBeCalledTimes(3); expect(client.pluginClient.postMessage).toBeCalledWith('invokePredefinedRequestProxy', { name: 'doTheThing', doer: 'Zhu Li' }, 1337); }); }); });