diff --git a/__mocks__/matrix-js-sdk.js b/__mocks__/matrix-js-sdk.js index d6bd58a..6565450 100644 --- a/__mocks__/matrix-js-sdk.js +++ b/__mocks__/matrix-js-sdk.js @@ -1,11 +1,69 @@ -export const mockCreateClient = jest.fn(); -export const mockStartClient = jest.fn(); +export const mockRegisterRequest = jest + .fn() + .mockImplementation((params) => { + if (!params.auth) { + return Promise.reject({ + data: { session: "session_id_1234" } + }) + } else { + return Promise.resolve({ + data: { + device_id: 'device_id_1234', + access_token: 'token_1234', + user_id: 'user_id_1234', + session: "session_id_1234" + } + }) + } + }) -const mockMatrix = jest.fn().mockImplementation(() => { - return { - createClient: mockCreateClient, - startClient: mockStartClient - }; +export const mockLeave = jest.fn(() => { + return Promise.resolve('value'); }); +export const mockInitCrypto = jest.fn() +export const mockStartClient = jest.fn(() => { + return Promise.resolve('value'); +}); +export const mockOnce = jest.fn() +export const mockStopClient = jest.fn(() => { + return Promise.resolve('value'); +}); +export const mockClearStores = jest.fn(() => { + return Promise.resolve('value'); +}); +export const mockGetRoom = jest.fn() +export const mockDownloadKeys = jest.fn() +export const mockSetDeviceVerified = jest.fn() +export const mockIsCryptoEnabled = jest.fn() +export const mockCreateRoom = jest.fn().mockReturnValue({ room_id: 'room_id_1234' }) +export const mockSetPowerLevel = jest.fn() +export const mockSendTextMessage = jest.fn(() => { + return Promise.resolve('value'); +}); +export const mockSetDeviceKnown = jest.fn() +export const mockDeactivateAccount = jest.fn(() => { + return Promise.resolve('value'); +}); +export const mockOn = jest.fn() -export default mockMatrix; \ No newline at end of file +export const mockClient = { + registerRequest: mockRegisterRequest, + initCrypto: mockInitCrypto, + startClient: mockStartClient, + on: mockOn, + once: mockOnce, + leave: mockLeave, + stopClient: mockStopClient, + clearStores: mockClearStores, + getRoom: mockGetRoom, + downloadKeys: mockDownloadKeys, + setDeviceVerified: mockSetDeviceVerified, + setDeviceKnown: mockSetDeviceKnown, + isCryptoEnabled: mockIsCryptoEnabled, + createRoom: mockCreateRoom, + setPowerLevel: mockSetPowerLevel, + sendTextMessage: mockSendTextMessage, + deactivateAccount: mockDeactivateAccount, +} + +export const createClient = jest.fn().mockReturnValue(mockClient) diff --git a/package.json b/package.json index fdd314a..224f1f6 100644 --- a/package.json +++ b/package.json @@ -108,6 +108,7 @@ "postcss-loader": "3.0.0", "sass-loader": "8.0.0", "style-loader": "1.1.2", + "wait-for-expect": "^3.0.2", "webpack": "4.41.5", "webpack-cli": "3.3.10", "webpack-dev-server": "3.10.1", diff --git a/src/components/_chat.scss b/src/components/_chat.scss index fe30bdc..8e983dc 100644 --- a/src/components/_chat.scss +++ b/src/components/_chat.scss @@ -430,3 +430,7 @@ height: calc(180px + 60vh); } } + +.hidden { + display: none; +} diff --git a/src/components/chatbox.jsx b/src/components/chatbox.jsx index c01ac8d..beebf71 100644 --- a/src/components/chatbox.jsx +++ b/src/components/chatbox.jsx @@ -120,16 +120,17 @@ class ChatBox extends React.Component { } initializeChat = () => { - // empty registration request to get session this.setState({ ready: false }) let client; try { client = matrix.createClient(this.props.matrixServerUrl) - } catch { + } catch(error) { + console.log("Error creating client", error) return this.handleInitError() } + // empty registration request to get session return client.registerRequest({}) .then(data => { console.log("Empty registration request to get session", data) @@ -399,16 +400,10 @@ class ChatBox extends React.Component { }); } - if (!prevState.ready && this.state.ready) { - this.chatboxInput.current.focus() - } - - if (!prevState.opened && this.state.opened) { - this.chatboxInput.current.focus() - } - if (prevState.messages.length !== this.state.messages.length) { - this.messageWindow.current.scrollTo(0, this.messageWindow.current.scrollHeight) + if (this.messageWindow.current.scrollTo) { + this.messageWindow.current.scrollTo(0, this.messageWindow.current.scrollHeight) + } } } @@ -424,7 +419,7 @@ class ChatBox extends React.Component { } handleInputChange = e => { - this.setState({ inputValue: e.currentTarget.value }) + this.setState({ inputValue: e.target.value }) } handleAcceptTerms = () => { @@ -493,14 +488,13 @@ class ChatBox extends React.Component {
{typingStatus}
} - { - !ready &&
loading...
- } + { !ready &&
loading...
}
- +
diff --git a/src/components/chatbox.test.js b/src/components/chatbox.test.js index a84a36e..1deb6e9 100644 --- a/src/components/chatbox.test.js +++ b/src/components/chatbox.test.js @@ -1,9 +1,25 @@ import React from 'react'; import Chatbox from './chatbox'; -import mockMatrix, { mockCreateClient } from "matrix-js-sdk"; +import { + createClient, + mockClient, + mockRegisterRequest, + mockInitCrypto, + mockStartClient, + mockSetPowerLevel, + mockCreateRoom, + mockLeave, + mockDeactivateAccount, + mockStopClient, + mockClearStores, + mockOn, + mockOnce, + mockSendTextMessage +} from "matrix-js-sdk"; import { mount, shallow } from 'enzyme'; import { createWaitForElement } from 'enzyme-wait'; import { config } from 'react-transition-group'; +import waitForExpect from 'wait-for-expect' config.disabled = true @@ -23,6 +39,21 @@ const testConfig = { describe('Chatbox', () => { + beforeEach(() => { + createClient.mockClear() + mockInitCrypto.mockClear() + mockStartClient.mockClear() + mockRegisterRequest.mockClear() + mockSetPowerLevel.mockClear() + mockCreateRoom.mockClear() + mockLeave.mockClear() + mockDeactivateAccount.mockClear() + mockStopClient.mockClear() + mockClearStores.mockClear() + mockOnce.mockClear() + mockOn.mockClear() + }) + test('chat window should open and close', async () => { const chatbox = mount() @@ -61,107 +92,128 @@ describe('Chatbox', () => { expect(messages.text()).toContain(props.agreementMessage) }); - test('#handleExitChat should call exitChat if the client has been initialized', () => { - - }) - - test('#exitChat should leave the room and destroy client', () => { - // leave room - // deactivate account - // stop client - // clear stores - // reset initial state - }) - test('agreeing to terms should start encrypted chat', async () => { const chatbox = mount() const dock = chatbox.find('button.dock') dock.simulate('click') - const yesButton = chatbox.find('#accept') - yesButton.simulate('click') + const openChatWindow = await createWaitForElement('.widget-entered')(chatbox) + let acceptButton = await createWaitForElement('button#accept')(chatbox) + acceptButton = chatbox.find('button#accept') - expect(mockCreateClient).toHaveBeenCalled() + acceptButton.simulate('click') + + const ready = await createWaitForElement('.loader')(chatbox) + expect(ready.length).toEqual(1) + + expect(createClient).toHaveBeenCalled() + expect(mockInitCrypto).toHaveBeenCalled() + expect(mockStartClient).toHaveBeenCalled() + expect(mockCreateRoom).toHaveBeenCalled() + expect(mockSetPowerLevel).toHaveBeenCalled() + expect(mockSetPowerLevel).toHaveBeenCalled() + expect(mockOn).toHaveBeenCalled() + expect(mockOnce).toHaveBeenCalled() }) - // test('rejecting terms should not start chat', async () => { - // const chatbox = mount() - // const dock = chatbox.find('button.dock') + test('rejecting terms should not start chat', async () => { + const chatbox = mount() + const dock = chatbox.find('button.dock') - // dock.simulate('click') + dock.simulate('click') - // const noButton = chatbox.find('#reject') - // noButton.simulate('click') + const openChatWindow = await createWaitForElement('.widget-entered')(chatbox) + let rejectButton = await createWaitForElement('button#reject')(chatbox) + rejectButton = chatbox.find('button#reject') - // expect(mockMatrix.mockCreateClient.mock.calls.length).toEqual(0) - // }) + rejectButton.simulate('click') - test('#initializeChat should notify user if client fails to initialize', () => { - // handleInitError + expect(createClient.mock.calls.length).toEqual(0) }) - test('#initializeChat should create unencypted chat if initCrypto fails', () => { - // initializeUnencryptedChat + test('notification should appear when facilitator joins chat', () => { + // }) - test('#initializeUnencryptedChat should initialize an unencrypted client', () => { - // initializeUnencryptedChat + test('submitted messages should be sent to matrix', async () => { + const chatbox = mount() + const dock = chatbox.find('button.dock') + + dock.simulate('click') + + let acceptButton = await createWaitForElement('button#accept')(chatbox) + acceptButton = chatbox.find('button#accept') + + acceptButton.simulate('click') + + await waitForExpect(() => { + expect(mockCreateRoom).toHaveBeenCalled() + }); + + const input = chatbox.find('#message-input') + const form = chatbox.find('form') + const message = 'Hello' + + input.simulate('change', { target: { value: message }}) + + await waitForExpect(() => { + chatbox.update() + expect(chatbox.state().inputValue).toEqual(message) + }) + + form.simulate('submit') + + await waitForExpect(() => { + expect(mockSendTextMessage).toHaveBeenCalledWith(chatbox.state().roomId, message) + }); }) - test('#handleDecryptionError should restart client without encryption and notify user', () => { - // initializeUnencryptedChat + test('received messages should appear in chat window', () => { + // }) - test('#verifyAllRoomDevices should mark all devices in the room as verified devices', () => { - + test('decryption failure should lead to a new unencrypted chat', () => { + // }) - test('#createRoom should create a new encrypted room with bot as admin', () => { + test('exiting the chat should leave the room and destroy client', async () => { + const chatbox = mount() + const dock = chatbox.find('button.dock') - }) + dock.simulate('click') - test('#createRoom should create a new unencrypted room if encryption is not enabled', () => { + const openChatWindow = await createWaitForElement('.widget-entered')(chatbox) + let acceptButton = await createWaitForElement('button#accept')(chatbox) + acceptButton = chatbox.find('button#accept') - }) + acceptButton.simulate('click') - test('#sendMessage should send text message with input value', () => { + await waitForExpect(() => { + expect(mockCreateRoom).toHaveBeenCalled() + }); - }) + const exitButton = chatbox.find('button.widget-header-close') - test('#sendMessage should mark devices as known and retry sending on UnknownDeviceError', () => { + exitButton.simulate('click') - }) + let closed = await createWaitForElement('button.dock') + expect(closed.length).toEqual(1) - test('#sendMessage should mark devices as known and retry sending on UnknownDeviceError', () => { + await waitForExpect(() => { + expect(mockLeave).toHaveBeenCalled() + }); - }) + await waitForExpect(() => { + expect(mockDeactivateAccount).toHaveBeenCalled() + }); - test('#displayFakeMessage should add a message object to message list', () => { - - }) - - test('#displayBotMessage should add a message object with bot as sender to message list', () => { - - }) - - test('#handleMessageEvent should add received message to message list', () => { - - }) - - test('#componentDidUpdate should set state listeners', () => { - - }) - - test('#handleSubmit should listen for yes if awaiting agreement and initialize client', () => { - - }) - - test('#handleSubmit should listen for no if awaiting agreement and do nothing', () => { - - }) - - test('#handleSubmit should send message if awaitingAgreement is false', () => { + await waitForExpect(() => { + expect(mockStopClient).toHaveBeenCalled() + }); + await waitForExpect(() => { + expect(mockClearStores).toHaveBeenCalled() + }); }) }); diff --git a/yarn.lock b/yarn.lock index 0fcf7e4..b241199 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12157,6 +12157,11 @@ w3c-hr-time@^1.0.1: dependencies: browser-process-hrtime "^0.1.2" +wait-for-expect@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/wait-for-expect/-/wait-for-expect-3.0.2.tgz#d2f14b2f7b778c9b82144109c8fa89ceaadaa463" + integrity sha512-cfS1+DZxuav1aBYbaO/kE06EOS8yRw7qOFoD3XtjTkYvCvh3zUvNST8DXK/nPaeqIzIv3P3kL3lRJn8iwOiSag== + walker@^1.0.7, walker@~1.0.5: version "1.0.7" resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.7.tgz#2f7f9b8fd10d677262b18a884e28d19618e028fb"