diff --git a/__mocks__/matrix-js-sdk.js b/__mocks__/matrix-js-sdk.js index 8f1bc35..c8515f2 100644 --- a/__mocks__/matrix-js-sdk.js +++ b/__mocks__/matrix-js-sdk.js @@ -24,7 +24,13 @@ export const mockInitCrypto = jest.fn() export const mockStartClient = jest.fn(() => { return Promise.resolve('value'); }); -export const mockOnce = jest.fn() +export const mockOnce = jest + .fn() + .mockImplementation((event, callback) => { + if (event === 'sync') { + callback('PREPARED') + } + }) export const mockStopClient = jest.fn(() => { return Promise.resolve('value'); }); diff --git a/public/index.html b/public/index.html index 2614593..b4cc3d8 100644 --- a/public/index.html +++ b/public/index.html @@ -25,7 +25,7 @@ anonymousDisplayName: 'Anonymous', position: 'bottom right', size: 'large', - maxWaitTime: 6000*3, + maxWaitTime: 1000*60*3, // 3 minutes } EmbeddableChatbox.mount(config); diff --git a/src/components/chatbox.jsx b/src/components/chatbox.jsx index f803b83..43c6ad1 100644 --- a/src/components/chatbox.jsx +++ b/src/components/chatbox.jsx @@ -167,9 +167,12 @@ class ChatBox extends React.Component { await this.state.client.stopClient() await this.state.client.clearStores() this.setState({ client: null }) + window.clearInterval(this.state.waitIntervalId) // no more waiting messages } - this.state.localStorage.clear() + if (this.state.localStorage) { + this.state.localStorage.clear() + } if (resetState) { this.setState(this.initialState) @@ -487,8 +490,6 @@ class ChatBox extends React.Component { handleChatOffline = () => { this.exitChat(false) // close the chat connection but keep chatbox state - window.clearInterval(this.state.waitIntervalId) // no more waiting messages - window.clearInterval(this.state.waitTimeoutId) // no more waiting messages this.setState({ ready: true }) // no more loading animation } @@ -546,7 +547,6 @@ class ChatBox extends React.Component { this.verifyAllRoomDevices(client, room) this.setState({ facilitatorId: sender, ready: true }) window.clearInterval(this.state.waitIntervalId) - window.clearInterval(this.state.waitTimeoutId) } }); @@ -568,6 +568,23 @@ class ChatBox extends React.Component { this.setState({ typingStatus: null }) } }); + + client.on("event", (event) => { + const eventType = event.getType() + const content = event.getContent() + + if (eventType === 'm.bot.signal') { + this.handleBotSignal(content.signal) + } + }) + } + + handleBotSignal = (signal) => { + switch (signal) { + case 'END_CHAT': + this.displayBotMessage({ body: this.props.exitMessage }) + return this.exitChat(false); // keep chat state + } } componentDidUpdate(prevProps, prevState) { @@ -624,14 +641,7 @@ class ChatBox extends React.Component { } }, this.props.waitInterval) - const waitTimeoutId = window.setTimeout(() => { - if (!this.state.facilitatorId && !this.state.ready) { - this.displayBotMessage({ body: this.props.chatUnavailableMessage }) - this.handleChatOffline() - } - }, this.props.maxWaitTime) - - this.setState({ waitIntervalId, waitTimeoutId }) + this.setState({ waitIntervalId }) } handleRejectTerms = () => { diff --git a/src/components/chatbox.test.js b/src/components/chatbox.test.js index 5fd9d8b..a7cafb6 100644 --- a/src/components/chatbox.test.js +++ b/src/components/chatbox.test.js @@ -115,9 +115,7 @@ describe('Chatbox', () => { expect(createClient).toHaveBeenCalled() expect(mockInitCrypto).toHaveBeenCalled() expect(mockStartClient).toHaveBeenCalled() - expect(mockCreateRoom).toHaveBeenCalled() - expect(mockSetPowerLevel).toHaveBeenCalled() - expect(mockOn).toHaveBeenCalled() + expect(mockOnce).toHaveBeenCalled() }) test('rejecting terms should not start chat', async () => { @@ -146,6 +144,10 @@ describe('Chatbox', () => { acceptButton.simulate('click') + await waitForExpect(() => { + expect(mockOnce).toHaveBeenCalled() + }); + await waitForExpect(() => { expect(mockCreateRoom).toHaveBeenCalled() }); @@ -169,7 +171,7 @@ describe('Chatbox', () => { }) - test('decryption failure should lead to a new unencrypted chat', async () => { + test('decryption failure should handle the message event and save the event ID in state', async () => { const chatbox = mount() const dock = chatbox.find('button.dock') const instance = chatbox.instance() @@ -183,25 +185,25 @@ describe('Chatbox', () => { acceptButton.simulate('click') await waitForExpect(() => { - expect(mockCreateRoom).toHaveBeenCalled() + expect(mockOnce).toHaveBeenCalled() }); - jest.spyOn(instance, 'initializeUnencryptedChat') - instance.handleDecryptionError() + jest.spyOn(instance, 'handleMessageEvent') + + instance.handleDecryptionError({ + getId: () => 'test_event_id', + getType: () => 'm.message', + getSender: () => 'sender', + getRoomId: () => 'room id', + getContent: () => ({ body: 'test msg' }), + getTs: () => '123', + }) await waitForExpect(() => { - expect(mockLeave).toHaveBeenCalled() - }); + expect(instance.handleMessageEvent).toHaveBeenCalled() + }) - await waitForExpect(() => { - expect(mockStopClient).toHaveBeenCalled() - }); - - await waitForExpect(() => { - expect(mockClearStores).toHaveBeenCalled() - }); - - expect(instance.initializeUnencryptedChat).toHaveBeenCalled() + expect(chatbox.state().decryptionErrors).toEqual({ 'test_event_id': true }) }) test('creating an unencrypted chat', async () => { diff --git a/src/setupTests.js b/src/setupTests.js new file mode 100644 index 0000000..de70497 --- /dev/null +++ b/src/setupTests.js @@ -0,0 +1,3 @@ +import { configure } from 'enzyme'; +import Adapter from 'enzyme-adapter-react-16'; +configure({ adapter: new Adapter() }); \ No newline at end of file