mirror of
https://github.com/Safe-Support-Chat/ocrcc-chatbox
synced 2024-11-25 20:24:54 +00:00
added test suite for chatbox, use accept/reject buttons for ToS instead of typing answer
This commit is contained in:
parent
1c96d11443
commit
a97696f687
11
__mocks__/matrix-js-sdk.js
Normal file
11
__mocks__/matrix-js-sdk.js
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
export const mockCreateClient = jest.fn();
|
||||||
|
export const mockStartClient = jest.fn();
|
||||||
|
|
||||||
|
const mockMatrix = jest.fn().mockImplementation(() => {
|
||||||
|
return {
|
||||||
|
createClient: mockCreateClient,
|
||||||
|
startClient: mockStartClient
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
export default mockMatrix;
|
@ -88,9 +88,10 @@
|
|||||||
"copy-webpack-plugin": "5.1.1",
|
"copy-webpack-plugin": "5.1.1",
|
||||||
"css-loader": "3.4.1",
|
"css-loader": "3.4.1",
|
||||||
"cssimportant-loader": "0.4.0",
|
"cssimportant-loader": "0.4.0",
|
||||||
"enzyme": "3.11.0",
|
"enzyme": "^3.11.0",
|
||||||
"enzyme-adapter-react-16": "1.15.2",
|
"enzyme-adapter-react-16": "^1.15.2",
|
||||||
"enzyme-to-json": "3.4.3",
|
"enzyme-to-json": "3.4.3",
|
||||||
|
"enzyme-wait": "^1.0.9",
|
||||||
"eslint": "6.8.0",
|
"eslint": "6.8.0",
|
||||||
"eslint-config-airbnb": "18.0.1",
|
"eslint-config-airbnb": "18.0.1",
|
||||||
"eslint-import-resolver-webpack": "0.12.0",
|
"eslint-import-resolver-webpack": "0.12.0",
|
||||||
@ -120,6 +121,7 @@
|
|||||||
"prop-types": "^15.6.2",
|
"prop-types": "^15.6.2",
|
||||||
"react": "^16.8.6",
|
"react": "^16.8.6",
|
||||||
"react-dom": "^16.8.6",
|
"react-dom": "^16.8.6",
|
||||||
|
"react-test-renderer": "^16.13.0",
|
||||||
"react-transition-group": "^4.0.0",
|
"react-transition-group": "^4.0.0",
|
||||||
"uuidv4": "^6.0.2"
|
"uuidv4": "^6.0.2"
|
||||||
}
|
}
|
||||||
|
@ -18,9 +18,9 @@
|
|||||||
roomName: 'Support Chat',
|
roomName: 'Support Chat',
|
||||||
termsUrl: 'https://tosdr.org/',
|
termsUrl: 'https://tosdr.org/',
|
||||||
introMessage: 'This chat application does not collect any of your personal data or any data from your use of this service.',
|
introMessage: 'This chat application does not collect any of your personal data or any data from your use of this service.',
|
||||||
agreementMessage: '👉 Do you want to continue? Type yes or no.',
|
agreementMessage: 'Do you want to continue?',
|
||||||
confirmationMessage: 'Waiting for a facilitator to join the chat...',
|
confirmationMessage: 'Waiting for a facilitator to join the chat...',
|
||||||
exitMessage: 'The chat was not started.',
|
exitMessage: 'The chat is closed. You may close this window.',
|
||||||
chatUnavailableMessage: 'The chat service is not available right now. Please try again later.',
|
chatUnavailableMessage: 'The chat service is not available right now. Please try again later.',
|
||||||
anonymousDisplayName: 'Anonymous',
|
anonymousDisplayName: 'Anonymous',
|
||||||
}
|
}
|
||||||
|
@ -1,197 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`<Widget /> open/close 1`] = `
|
|
||||||
<Widget
|
|
||||||
bodyText="Body"
|
|
||||||
footerText="Footer"
|
|
||||||
headerText="Header"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="docked-widget"
|
|
||||||
>
|
|
||||||
<Transition
|
|
||||||
appear={false}
|
|
||||||
enter={true}
|
|
||||||
exit={true}
|
|
||||||
in={false}
|
|
||||||
mountOnEnter={false}
|
|
||||||
onEnter={[Function]}
|
|
||||||
onEntered={[Function]}
|
|
||||||
onEntering={[Function]}
|
|
||||||
onExit={[Function]}
|
|
||||||
onExited={[Function]}
|
|
||||||
onExiting={[Function]}
|
|
||||||
timeout={250}
|
|
||||||
unmountOnExit={false}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="widget widget-exited"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="widget-header"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="widget-header-title"
|
|
||||||
>
|
|
||||||
Header
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
className="widget-header-icon"
|
|
||||||
onClick={[Function]}
|
|
||||||
onKeyPress={[Function]}
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
X
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="widget-body"
|
|
||||||
>
|
|
||||||
Body
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="widget-footer"
|
|
||||||
>
|
|
||||||
Footer
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Transition>
|
|
||||||
<button
|
|
||||||
className="dock"
|
|
||||||
onClick={[Function]}
|
|
||||||
onKeyPress={[Function]}
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
^ OPEN ^
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</Widget>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`<Widget /> open/close 2`] = `
|
|
||||||
<Widget
|
|
||||||
bodyText="Body"
|
|
||||||
footerText="Footer"
|
|
||||||
headerText="Header"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="docked-widget"
|
|
||||||
>
|
|
||||||
<Transition
|
|
||||||
appear={false}
|
|
||||||
enter={true}
|
|
||||||
exit={true}
|
|
||||||
in={true}
|
|
||||||
mountOnEnter={false}
|
|
||||||
onEnter={[Function]}
|
|
||||||
onEntered={[Function]}
|
|
||||||
onEntering={[Function]}
|
|
||||||
onExit={[Function]}
|
|
||||||
onExited={[Function]}
|
|
||||||
onExiting={[Function]}
|
|
||||||
timeout={250}
|
|
||||||
unmountOnExit={false}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="widget widget-entering"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="widget-header"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="widget-header-title"
|
|
||||||
>
|
|
||||||
Header
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
className="widget-header-icon"
|
|
||||||
onClick={[Function]}
|
|
||||||
onKeyPress={[Function]}
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
X
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="widget-body"
|
|
||||||
>
|
|
||||||
Body
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="widget-footer"
|
|
||||||
>
|
|
||||||
Footer
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Transition>
|
|
||||||
</div>
|
|
||||||
</Widget>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`<Widget /> open/close 3`] = `
|
|
||||||
<Widget
|
|
||||||
bodyText="Body"
|
|
||||||
footerText="Footer"
|
|
||||||
headerText="Header"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="docked-widget"
|
|
||||||
>
|
|
||||||
<Transition
|
|
||||||
appear={false}
|
|
||||||
enter={true}
|
|
||||||
exit={true}
|
|
||||||
in={false}
|
|
||||||
mountOnEnter={false}
|
|
||||||
onEnter={[Function]}
|
|
||||||
onEntered={[Function]}
|
|
||||||
onEntering={[Function]}
|
|
||||||
onExit={[Function]}
|
|
||||||
onExited={[Function]}
|
|
||||||
onExiting={[Function]}
|
|
||||||
timeout={250}
|
|
||||||
unmountOnExit={false}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="widget widget-exited"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="widget-header"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="widget-header-title"
|
|
||||||
>
|
|
||||||
Header
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
className="widget-header-icon"
|
|
||||||
onClick={[Function]}
|
|
||||||
onKeyPress={[Function]}
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
X
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="widget-body"
|
|
||||||
>
|
|
||||||
Body
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="widget-footer"
|
|
||||||
>
|
|
||||||
Footer
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Transition>
|
|
||||||
<button
|
|
||||||
className="dock"
|
|
||||||
onClick={[Function]}
|
|
||||||
onKeyPress={[Function]}
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
^ OPEN ^
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</Widget>
|
|
||||||
`;
|
|
@ -270,6 +270,42 @@
|
|||||||
width: fit-content;
|
width: fit-content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.buttons {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 1rem;
|
||||||
|
|
||||||
|
button {
|
||||||
|
background-color: transparent;
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: inherit;
|
||||||
|
font-weight: bold;
|
||||||
|
font-family: $theme-font;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
flex: 0 1 auto;
|
||||||
|
border: 1px solid $theme-color;
|
||||||
|
transition: all 0.2s ease-in-out;
|
||||||
|
border-radius: 10px;
|
||||||
|
margin-left: 4px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border: 1px solid $dark-color;
|
||||||
|
box-shadow: inset 0px 0px 0px 1px $dark-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
color: $white;
|
||||||
|
border: 1px solid $dark-color;
|
||||||
|
box-shadow: inset 0px 0px 0px 1px $dark-color;
|
||||||
|
background-color: $theme-highlight-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
&.from-bot {
|
&.from-bot {
|
||||||
color: $gray-color;
|
color: $gray-color;
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
|
@ -24,11 +24,14 @@ const ENCRYPTION_NOTICE = "Messages in this chat are secured with end-to-end enc
|
|||||||
const UNENCRYPTION_NOTICE = "End-to-end message encryption is not available on this browser."
|
const UNENCRYPTION_NOTICE = "End-to-end message encryption is not available on this browser."
|
||||||
const RESTARTING_UNENCRYPTED_CHAT_MESSAGE = "Restarting chat without encryption."
|
const RESTARTING_UNENCRYPTED_CHAT_MESSAGE = "Restarting chat without encryption."
|
||||||
|
|
||||||
|
const DEFAULT_MATRIX_SERVER = "https://matrix.rhok.space/"
|
||||||
|
const DEFAULT_BOT_USERNAME = "@help-bot:rhok.space"
|
||||||
|
const DEFAULT_TERMS_URL = "https://tosdr.org/"
|
||||||
const DEFAULT_ROOM_NAME = "Support Chat"
|
const DEFAULT_ROOM_NAME = "Support Chat"
|
||||||
const DEFAULT_INTRO_MESSAGE = "This chat application does not collect any of your personal data or any data from your use of this service."
|
const DEFAULT_INTRO_MESSAGE = "This chat application does not collect any of your personal data or any data from your use of this service."
|
||||||
const DEFAULT_AGREEMENT_MESSAGE = "👉 Do you want to continue? Type yes or no."
|
const DEFAULT_AGREEMENT_MESSAGE = "Do you want to continue?"
|
||||||
const DEFAULT_CONFIRMATION_MESSAGE = "Waiting for a facilitator to join the chat..."
|
const DEFAULT_CONFIRMATION_MESSAGE = "Waiting for a facilitator to join the chat..."
|
||||||
const DEFAULT_EXIT_MESSAGE = "The chat was not started."
|
const DEFAULT_EXIT_MESSAGE = "The chat is closed. You may close this window."
|
||||||
const DEFAULT_ANONYMOUS_DISPLAY_NAME="Anonymous"
|
const DEFAULT_ANONYMOUS_DISPLAY_NAME="Anonymous"
|
||||||
const DEFAULT_CHAT_UNAVAILABLE_MESSAGE = "The chat service is not available right now. Please try again later."
|
const DEFAULT_CHAT_UNAVAILABLE_MESSAGE = "The chat service is not available right now. Please try again later."
|
||||||
|
|
||||||
@ -36,7 +39,6 @@ const DEFAULT_CHAT_UNAVAILABLE_MESSAGE = "The chat service is not available righ
|
|||||||
class ChatBox extends React.Component {
|
class ChatBox extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
const client = matrix.createClient(this.props.matrixServerUrl)
|
|
||||||
this.initialState = {
|
this.initialState = {
|
||||||
opened: false,
|
opened: false,
|
||||||
showDock: true,
|
showDock: true,
|
||||||
@ -46,38 +48,17 @@ class ChatBox extends React.Component {
|
|||||||
userId: null,
|
userId: null,
|
||||||
password: null,
|
password: null,
|
||||||
localStorage: null,
|
localStorage: null,
|
||||||
messages: [
|
messages: [],
|
||||||
{
|
|
||||||
id: 'intro-msg-id',
|
|
||||||
type: 'm.room.message',
|
|
||||||
sender: this.props.botUsername,
|
|
||||||
content: { body: this.props.introMessage },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'terms-msg-id',
|
|
||||||
type: 'm.room.message',
|
|
||||||
sender: this.props.botUsername,
|
|
||||||
content: {
|
|
||||||
body: `Please read the full terms and conditions at ${this.props.termsUrl}.`,
|
|
||||||
formatted_body: `Please read the full <a href="${this.props.termsUrl}">terms and conditions</a>.`
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'agreement-msg-id',
|
|
||||||
type: 'm.room.message',
|
|
||||||
sender: this.props.botUsername,
|
|
||||||
content: { body: this.props.agreementMessage }, },
|
|
||||||
],
|
|
||||||
inputValue: "",
|
inputValue: "",
|
||||||
errors: [],
|
errors: [],
|
||||||
roomId: null,
|
roomId: null,
|
||||||
typingStatus: null,
|
typingStatus: null,
|
||||||
awaitingAgreement: true,
|
awaitingAgreement: true,
|
||||||
awaitingFacilitator: false,
|
|
||||||
}
|
}
|
||||||
this.state = this.initialState
|
this.state = this.initialState
|
||||||
this.chatboxInput = React.createRef();
|
this.chatboxInput = React.createRef();
|
||||||
this.messageWindow = React.createRef();
|
this.messageWindow = React.createRef();
|
||||||
|
this.termsUrl = React.createRef();
|
||||||
}
|
}
|
||||||
|
|
||||||
handleToggleOpen = () => {
|
handleToggleOpen = () => {
|
||||||
@ -100,8 +81,12 @@ class ChatBox extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleWidgetEnter = () => {
|
handleWidgetEnter = () => {
|
||||||
|
if (this.state.awaitingAgreement) {
|
||||||
|
this.termsUrl.current.focus()
|
||||||
|
} else {
|
||||||
this.chatboxInput.current.focus()
|
this.chatboxInput.current.focus()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
handleExitChat = () => {
|
handleExitChat = () => {
|
||||||
if (this.state.client) {
|
if (this.state.client) {
|
||||||
@ -442,24 +427,20 @@ class ChatBox extends React.Component {
|
|||||||
this.setState({ inputValue: e.currentTarget.value })
|
this.setState({ inputValue: e.currentTarget.value })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleAcceptTerms = () => {
|
||||||
|
this.setState({ awaitingAgreement: false })
|
||||||
|
this.initializeChat()
|
||||||
|
}
|
||||||
|
|
||||||
|
handleRejectTerms = () => {
|
||||||
|
this.exitChat()
|
||||||
|
this.displayBotMessage({ body: this.props.exitMessage })
|
||||||
|
}
|
||||||
|
|
||||||
handleSubmit = e => {
|
handleSubmit = e => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
if (!Boolean(this.state.inputValue)) return null;
|
if (!Boolean(this.state.inputValue)) return null;
|
||||||
|
|
||||||
if (this.state.awaitingAgreement && !this.state.client) {
|
|
||||||
if (this.state.inputValue.toLowerCase() === 'yes') {
|
|
||||||
this.displayFakeMessage({ body: this.state.inputValue }, 'from-me')
|
|
||||||
this.setState({ inputValue: "" })
|
|
||||||
|
|
||||||
return this.initializeChat()
|
|
||||||
|
|
||||||
} else {
|
|
||||||
this.displayFakeMessage({ body: this.state.inputValue }, 'from-me')
|
|
||||||
this.displayBotMessage({ body: this.props.exitMessage })
|
|
||||||
return this.setState({ inputValue: "" })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.state.client && this.state.roomId) {
|
if (this.state.client && this.state.roomId) {
|
||||||
return this.sendMessage()
|
return this.sendMessage()
|
||||||
}
|
}
|
||||||
@ -480,6 +461,26 @@ class ChatBox extends React.Component {
|
|||||||
|
|
||||||
<div className="message-window" ref={this.messageWindow}>
|
<div className="message-window" ref={this.messageWindow}>
|
||||||
<div className="messages">
|
<div className="messages">
|
||||||
|
<div className={`message from-bot`}>
|
||||||
|
<div className="text">{ this.props.introMessage }</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={`message from-bot`}>
|
||||||
|
<div className="text">Please read the full <a href={this.props.termsUrl} ref={this.termsUrl}>terms and conditions</a>. By using this chat, you agree to these terms.</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={`message from-bot`}>
|
||||||
|
<div className="text">{ this.props.agreementMessage }</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={`message from-bot`}>
|
||||||
|
<div className="text buttons">
|
||||||
|
{`👉`}
|
||||||
|
<button id="accept" onClick={this.handleAcceptTerms}>YES</button>
|
||||||
|
<button id="reject" onClick={this.handleRejectTerms}>NO</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{
|
{
|
||||||
messages.map((message, index) => {
|
messages.map((message, index) => {
|
||||||
return(
|
return(
|
||||||
@ -537,6 +538,9 @@ ChatBox.propTypes = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ChatBox.defaultProps = {
|
ChatBox.defaultProps = {
|
||||||
|
matrixServerUrl: DEFAULT_MATRIX_SERVER,
|
||||||
|
botUsername: DEFAULT_BOT_USERNAME,
|
||||||
|
termsUrl: DEFAULT_TERMS_URL,
|
||||||
roomName: DEFAULT_ROOM_NAME,
|
roomName: DEFAULT_ROOM_NAME,
|
||||||
introMessage: DEFAULT_INTRO_MESSAGE,
|
introMessage: DEFAULT_INTRO_MESSAGE,
|
||||||
agreementMessage: DEFAULT_AGREEMENT_MESSAGE,
|
agreementMessage: DEFAULT_AGREEMENT_MESSAGE,
|
||||||
|
167
src/components/chatbox.test.js
Normal file
167
src/components/chatbox.test.js
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import Chatbox from './chatbox';
|
||||||
|
import mockMatrix, { mockCreateClient } from "matrix-js-sdk";
|
||||||
|
import { mount, shallow } from 'enzyme';
|
||||||
|
import { createWaitForElement } from 'enzyme-wait';
|
||||||
|
import { config } from 'react-transition-group';
|
||||||
|
|
||||||
|
config.disabled = true
|
||||||
|
|
||||||
|
const testConfig = {
|
||||||
|
matrixServerUrl: 'https://matrix.rhok.space',
|
||||||
|
botUsername: '@help-bot:rhok.space',
|
||||||
|
roomName: 'Support Chat',
|
||||||
|
termsUrl: 'https://tosdr.org/',
|
||||||
|
introMessage: 'This chat application does not collect any of your personal data or any data from your use of this service.',
|
||||||
|
agreementMessage: '👉 Do you want to continue? Type yes or no.',
|
||||||
|
confirmationMessage: 'Waiting for a facilitator to join the chat...',
|
||||||
|
exitMessage: 'The chat was not started.',
|
||||||
|
chatUnavailableMessage: 'The chat service is not available right now. Please try again later.',
|
||||||
|
anonymousDisplayName: 'Anonymous',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
describe('Chatbox', () => {
|
||||||
|
|
||||||
|
test('chat window should open and close', async () => {
|
||||||
|
const chatbox = mount(<Chatbox {...testConfig} />)
|
||||||
|
|
||||||
|
let dock = chatbox.find('button.dock')
|
||||||
|
let chatWindow = chatbox.find('.widget')
|
||||||
|
|
||||||
|
expect(dock.length).toEqual(1)
|
||||||
|
expect(chatWindow.hasClass('widget-exited')).toEqual(true)
|
||||||
|
|
||||||
|
// open chat window
|
||||||
|
dock.simulate('click')
|
||||||
|
|
||||||
|
const openChatWindow = await createWaitForElement('.widget-entered')(chatbox)
|
||||||
|
dock = chatbox.find('button.dock')
|
||||||
|
expect(openChatWindow.length).toEqual(1)
|
||||||
|
expect(dock.length).toEqual(0)
|
||||||
|
|
||||||
|
// close chat window
|
||||||
|
const closeButton = chatbox.find('button.widget-header-close')
|
||||||
|
closeButton.simulate('click')
|
||||||
|
|
||||||
|
chatWindow = chatbox.find('.widget')
|
||||||
|
dock = chatbox.find('button.dock')
|
||||||
|
|
||||||
|
expect(dock.length).toEqual(1)
|
||||||
|
expect(chatWindow.hasClass('widget-exited')).toEqual(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('chat window should contain the right messages', () => {
|
||||||
|
const chatbox = mount(<Chatbox {...testConfig} />)
|
||||||
|
const props = chatbox.props()
|
||||||
|
const messages = chatbox.find('.messages')
|
||||||
|
|
||||||
|
expect(messages.text()).toContain(props.introMessage)
|
||||||
|
expect(messages.html()).toContain(props.termsUrl)
|
||||||
|
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(<Chatbox {...testConfig} />)
|
||||||
|
const dock = chatbox.find('button.dock')
|
||||||
|
|
||||||
|
dock.simulate('click')
|
||||||
|
|
||||||
|
const yesButton = chatbox.find('#accept')
|
||||||
|
yesButton.simulate('click')
|
||||||
|
|
||||||
|
expect(mockCreateClient).toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
// test('rejecting terms should not start chat', async () => {
|
||||||
|
// const chatbox = mount(<Chatbox {...testConfig} />)
|
||||||
|
// const dock = chatbox.find('button.dock')
|
||||||
|
|
||||||
|
// dock.simulate('click')
|
||||||
|
|
||||||
|
// const noButton = chatbox.find('#reject')
|
||||||
|
// noButton.simulate('click')
|
||||||
|
|
||||||
|
// expect(mockMatrix.mockCreateClient.mock.calls.length).toEqual(0)
|
||||||
|
// })
|
||||||
|
|
||||||
|
test('#initializeChat should notify user if client fails to initialize', () => {
|
||||||
|
// handleInitError
|
||||||
|
})
|
||||||
|
|
||||||
|
test('#initializeChat should create unencypted chat if initCrypto fails', () => {
|
||||||
|
// initializeUnencryptedChat
|
||||||
|
})
|
||||||
|
|
||||||
|
test('#initializeUnencryptedChat should initialize an unencrypted client', () => {
|
||||||
|
// initializeUnencryptedChat
|
||||||
|
})
|
||||||
|
|
||||||
|
test('#handleDecryptionError should restart client without encryption and notify user', () => {
|
||||||
|
// initializeUnencryptedChat
|
||||||
|
})
|
||||||
|
|
||||||
|
test('#verifyAllRoomDevices should mark all devices in the room as verified devices', () => {
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
test('#createRoom should create a new encrypted room with bot as admin', () => {
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
test('#createRoom should create a new unencrypted room if encryption is not enabled', () => {
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
test('#sendMessage should send text message with input value', () => {
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
test('#sendMessage should mark devices as known and retry sending on UnknownDeviceError', () => {
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
test('#sendMessage should mark devices as known and retry sending on UnknownDeviceError', () => {
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
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', () => {
|
||||||
|
|
||||||
|
})
|
||||||
|
});
|
@ -6,12 +6,20 @@ export default function bookmarklet() {
|
|||||||
}
|
}
|
||||||
window.EmbeddableChatbox = EmbeddableChatbox;
|
window.EmbeddableChatbox = EmbeddableChatbox;
|
||||||
|
|
||||||
EmbeddableChatbox.mount({
|
var config = {
|
||||||
termsUrl: 'https://tosdr.org/',
|
|
||||||
privacyStatement: 'This chat application does not collect any of your personal data or any data from your use of this service.',
|
|
||||||
matrixServerUrl: 'https://matrix.rhok.space',
|
matrixServerUrl: 'https://matrix.rhok.space',
|
||||||
|
botUsername: '@help-bot:rhok.space',
|
||||||
roomName: 'Support Chat',
|
roomName: 'Support Chat',
|
||||||
});
|
termsUrl: 'https://tosdr.org/',
|
||||||
|
introMessage: 'This chat application does not collect any of your personal data or any data from your use of this service.',
|
||||||
|
agreementMessage: '👉 Do you want to continue? Type yes or no.',
|
||||||
|
confirmationMessage: 'Waiting for a facilitator to join the chat...',
|
||||||
|
exitMessage: 'The chat was not started.',
|
||||||
|
chatUnavailableMessage: 'The chat service is not available right now. Please try again later.',
|
||||||
|
anonymousDisplayName: 'Anonymous',
|
||||||
|
}
|
||||||
|
|
||||||
|
EmbeddableChatbox.mount(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
bookmarklet();
|
bookmarklet();
|
||||||
|
@ -6,11 +6,11 @@ describe('bookmarklet', () => {
|
|||||||
const el = document.querySelectorAll('body > div');
|
const el = document.querySelectorAll('body > div');
|
||||||
ReactDOM.unmountComponentAtNode(el[0]);
|
ReactDOM.unmountComponentAtNode(el[0]);
|
||||||
el[0].parentNode.removeChild(el[0]);
|
el[0].parentNode.removeChild(el[0]);
|
||||||
window.EmbeddableWidget = null;
|
window.EmbeddableChatbox = null;
|
||||||
});
|
});
|
||||||
|
|
||||||
test('#mount document becomes ready', async () => {
|
test('#mount document becomes ready', async () => {
|
||||||
expect(window.EmbeddableWidget).not.toBeNull();
|
expect(window.EmbeddableChatbox).not.toBeNull();
|
||||||
bookmarklet();
|
bookmarklet();
|
||||||
const el = document.querySelectorAll('body > div');
|
const el = document.querySelectorAll('body > div');
|
||||||
expect(el).toHaveLength(1);
|
expect(el).toHaveLength(1);
|
||||||
|
@ -1,23 +1,24 @@
|
|||||||
import EmbeddableWidget from './embeddable-widget';
|
import EmbeddableChatbox from './embeddable-chatbox';
|
||||||
import { waitForSelection } from '../test-helpers';
|
import { waitForSelection } from '../test-helpers';
|
||||||
|
|
||||||
describe('EmbeddableWidget', () => {
|
|
||||||
|
describe('EmbeddableChatbox', () => {
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
document.readyState = 'complete';
|
document.readyState = 'complete';
|
||||||
if (EmbeddableWidget.el) {
|
if (EmbeddableChatbox.el) {
|
||||||
EmbeddableWidget.unmount();
|
EmbeddableChatbox.unmount();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test('#mount document becomes ready', async () => {
|
test('#mount document becomes ready', async () => {
|
||||||
document.readyState = 'loading';
|
document.readyState = 'loading';
|
||||||
EmbeddableWidget.mount();
|
EmbeddableChatbox.mount();
|
||||||
window.dispatchEvent(new Event('load', {}));
|
window.dispatchEvent(new Event('load', {}));
|
||||||
await waitForSelection(document, 'div');
|
await waitForSelection(document, 'div');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('#mount document complete', async () => {
|
test('#mount document complete', async () => {
|
||||||
EmbeddableWidget.mount();
|
EmbeddableChatbox.mount();
|
||||||
await waitForSelection(document, 'div');
|
await waitForSelection(document, 'div');
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -26,7 +27,7 @@ describe('EmbeddableWidget', () => {
|
|||||||
newElement.setAttribute('id', 'widget-mount');
|
newElement.setAttribute('id', 'widget-mount');
|
||||||
document.body.appendChild(newElement);
|
document.body.appendChild(newElement);
|
||||||
|
|
||||||
EmbeddableWidget.mount({
|
EmbeddableChatbox.mount({
|
||||||
parentElement: '#widget-mount',
|
parentElement: '#widget-mount',
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -36,8 +37,8 @@ describe('EmbeddableWidget', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('#mount twice', async () => {
|
test('#mount twice', async () => {
|
||||||
EmbeddableWidget.mount();
|
EmbeddableChatbox.mount();
|
||||||
expect(() => EmbeddableWidget.mount()).toThrow('already mounted');
|
expect(() => EmbeddableChatbox.mount()).toThrow('already mounted');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('#unmount', async () => {
|
test('#unmount', async () => {
|
||||||
@ -45,13 +46,13 @@ describe('EmbeddableWidget', () => {
|
|||||||
document.body.appendChild(el);
|
document.body.appendChild(el);
|
||||||
expect(document.querySelectorAll('div')).toHaveLength(1);
|
expect(document.querySelectorAll('div')).toHaveLength(1);
|
||||||
|
|
||||||
EmbeddableWidget.el = el;
|
EmbeddableChatbox.el = el;
|
||||||
EmbeddableWidget.unmount();
|
EmbeddableChatbox.unmount();
|
||||||
|
|
||||||
expect(document.querySelectorAll('div')).toHaveLength(0);
|
expect(document.querySelectorAll('div')).toHaveLength(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('#unmount without mounting', async () => {
|
test('#unmount without mounting', async () => {
|
||||||
expect(() => EmbeddableWidget.unmount()).toThrow('not mounted');
|
expect(() => EmbeddableChatbox.unmount()).toThrow('not mounted');
|
||||||
});
|
});
|
||||||
});
|
});
|
34
yarn.lock
34
yarn.lock
@ -2598,6 +2598,11 @@ assert@^1.1.1:
|
|||||||
object-assign "^4.1.1"
|
object-assign "^4.1.1"
|
||||||
util "0.10.3"
|
util "0.10.3"
|
||||||
|
|
||||||
|
assertion-error@^1.0.2:
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b"
|
||||||
|
integrity sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==
|
||||||
|
|
||||||
assign-symbols@^1.0.0:
|
assign-symbols@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367"
|
resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367"
|
||||||
@ -4828,7 +4833,7 @@ entities@^2.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.0.tgz#68d6084cab1b079767540d80e56a39b423e4abf4"
|
resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.0.tgz#68d6084cab1b079767540d80e56a39b423e4abf4"
|
||||||
integrity sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw==
|
integrity sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw==
|
||||||
|
|
||||||
enzyme-adapter-react-16@1.15.2:
|
enzyme-adapter-react-16@^1.15.2:
|
||||||
version "1.15.2"
|
version "1.15.2"
|
||||||
resolved "https://registry.yarnpkg.com/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.15.2.tgz#b16db2f0ea424d58a808f9df86ab6212895a4501"
|
resolved "https://registry.yarnpkg.com/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.15.2.tgz#b16db2f0ea424d58a808f9df86ab6212895a4501"
|
||||||
integrity sha512-SkvDrb8xU3lSxID8Qic9rB8pvevDbLybxPK6D/vW7PrT0s2Cl/zJYuXvsd1EBTz0q4o3iqG3FJhpYz3nUNpM2Q==
|
integrity sha512-SkvDrb8xU3lSxID8Qic9rB8pvevDbLybxPK6D/vW7PrT0s2Cl/zJYuXvsd1EBTz0q4o3iqG3FJhpYz3nUNpM2Q==
|
||||||
@ -4870,7 +4875,14 @@ enzyme-to-json@3.4.3:
|
|||||||
dependencies:
|
dependencies:
|
||||||
lodash "^4.17.15"
|
lodash "^4.17.15"
|
||||||
|
|
||||||
enzyme@3.11.0:
|
enzyme-wait@^1.0.9:
|
||||||
|
version "1.0.9"
|
||||||
|
resolved "https://registry.yarnpkg.com/enzyme-wait/-/enzyme-wait-1.0.9.tgz#f7a08bf49c7047358fa03e1f411a565c3a15101a"
|
||||||
|
integrity sha1-96CL9JxwRzWPoD4fQRpWXDoVEBo=
|
||||||
|
dependencies:
|
||||||
|
assertion-error "^1.0.2"
|
||||||
|
|
||||||
|
enzyme@^3.11.0:
|
||||||
version "3.11.0"
|
version "3.11.0"
|
||||||
resolved "https://registry.yarnpkg.com/enzyme/-/enzyme-3.11.0.tgz#71d680c580fe9349f6f5ac6c775bc3e6b7a79c28"
|
resolved "https://registry.yarnpkg.com/enzyme/-/enzyme-3.11.0.tgz#71d680c580fe9349f6f5ac6c775bc3e6b7a79c28"
|
||||||
integrity sha512-Dw8/Gs4vRjxY6/6i9wU0V+utmQO9kvh9XLnz3LIudviOnVYDEe2ec+0k+NQoMamn1VrjKgCUOWj5jG/5M5M0Qw==
|
integrity sha512-Dw8/Gs4vRjxY6/6i9wU0V+utmQO9kvh9XLnz3LIudviOnVYDEe2ec+0k+NQoMamn1VrjKgCUOWj5jG/5M5M0Qw==
|
||||||
@ -10089,6 +10101,16 @@ react-test-renderer@^16.0.0-0:
|
|||||||
react-is "^16.8.6"
|
react-is "^16.8.6"
|
||||||
scheduler "^0.18.0"
|
scheduler "^0.18.0"
|
||||||
|
|
||||||
|
react-test-renderer@^16.13.0:
|
||||||
|
version "16.13.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.13.0.tgz#39ba3bf72cedc8210c3f81983f0bb061b14a3014"
|
||||||
|
integrity sha512-NQ2S9gdMUa7rgPGpKGyMcwl1d6D9MCF0lftdI3kts6kkiX+qvpC955jNjAZXlIDTjnN9jwFI8A8XhRh/9v0spA==
|
||||||
|
dependencies:
|
||||||
|
object-assign "^4.1.1"
|
||||||
|
prop-types "^15.6.2"
|
||||||
|
react-is "^16.8.6"
|
||||||
|
scheduler "^0.19.0"
|
||||||
|
|
||||||
react-textarea-autosize@^7.1.0:
|
react-textarea-autosize@^7.1.0:
|
||||||
version "7.1.2"
|
version "7.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/react-textarea-autosize/-/react-textarea-autosize-7.1.2.tgz#70fdb333ef86bcca72717e25e623e90c336e2cda"
|
resolved "https://registry.yarnpkg.com/react-textarea-autosize/-/react-textarea-autosize-7.1.2.tgz#70fdb333ef86bcca72717e25e623e90c336e2cda"
|
||||||
@ -10706,6 +10728,14 @@ scheduler@^0.18.0:
|
|||||||
loose-envify "^1.1.0"
|
loose-envify "^1.1.0"
|
||||||
object-assign "^4.1.1"
|
object-assign "^4.1.1"
|
||||||
|
|
||||||
|
scheduler@^0.19.0:
|
||||||
|
version "0.19.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.19.0.tgz#a715d56302de403df742f4a9be11975b32f5698d"
|
||||||
|
integrity sha512-xowbVaTPe9r7y7RUejcK73/j8tt2jfiyTednOvHbA8JoClvMYCp+r8QegLwK/n8zWQAtZb1fFnER4XLBZXrCxA==
|
||||||
|
dependencies:
|
||||||
|
loose-envify "^1.1.0"
|
||||||
|
object-assign "^4.1.1"
|
||||||
|
|
||||||
schema-utils@^1.0.0:
|
schema-utils@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-1.0.0.tgz#0b79a93204d7b600d4b2850d1f66c2a34951c770"
|
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-1.0.0.tgz#0b79a93204d7b600d4b2850d1f66c2a34951c770"
|
||||||
|
Loading…
Reference in New Issue
Block a user