[deploy] fix: Clear chat input after Enter key submit (#10487)
* fix: Clear chat input after Enter key submit * fix: Rename Composer component * test: Add integration tests for input value * After pressing Enter * After clicking Send * After clicking Save edit * After clicking Close edit
This commit is contained in:
parent
d21a769bd1
commit
2c10fb0417
10 changed files with 207 additions and 138 deletions
|
|
@ -1 +1,3 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M3 3h18a1 1 0 011 1v16a1 1 0 01-1 1H3a1 1 0 01-1-1V4a1 1 0 011-1zm4 12.5v-4l2 2 2-2v4h2v-7h-2l-2 2-2-2H5v7h2zm11-3v-4h-2v4h-2l3 3 3-3h-2z"/></svg>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3 3h18a1 1 0 011 1v16a1 1 0 01-1 1H3a1 1 0 01-1-1V4a1 1 0 011-1zm4 12.5v-4l2 2 2-2v4h2v-7h-2l-2 2-2-2H5v7h2zm11-3v-4h-2v4h-2l3 3 3-3h-2z"/>
|
||||
</svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 239 B After Width: | Height: | Size: 245 B |
|
|
@ -1 +1,3 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M3 18.5V5a3 3 0 013-3h14a1 1 0 011 1v18a1 1 0 01-1 1H6.5A3.5 3.5 0 013 18.5zM19 20v-3H6.5a1.5 1.5 0 100 3H19zM10 4H6a1 1 0 00-1 1v10.337A3.485 3.485 0 016.5 15H19V4h-2v8l-3.5-2-3.5 2V4z"/></svg>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3 18.5V5a3 3 0 013-3h14a1 1 0 011 1v18a1 1 0 01-1 1H6.5A3.5 3.5 0 013 18.5zM19 20v-3H6.5a1.5 1.5 0 100 3H19zM10 4H6a1 1 0 00-1 1v10.337A3.485 3.485 0 016.5 15H19V4h-2v8l-3.5-2-3.5 2V4z"/>
|
||||
</svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 287 B After Width: | Height: | Size: 293 B |
|
|
@ -1,5 +1,5 @@
|
|||
import { h } from 'preact';
|
||||
import { render, fireEvent } from '@testing-library/preact';
|
||||
import { render, fireEvent, createEvent } from '@testing-library/preact';
|
||||
import { axe } from 'jest-axe';
|
||||
import Compose from '../compose';
|
||||
|
||||
|
|
@ -30,7 +30,7 @@ const handleKeyDownFake = (e) => {
|
|||
}
|
||||
};
|
||||
|
||||
const getCompose = (tf) => {
|
||||
const getCompose = (tf, props = {}) => {
|
||||
// true -> not empty, false -> empty
|
||||
if (tf) {
|
||||
return (
|
||||
|
|
@ -38,6 +38,7 @@ const getCompose = (tf) => {
|
|||
handleSubmitOnClick={handleSubmitFake}
|
||||
handleKeyDown={handleKeyDownFake}
|
||||
activeChannelId={12345}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
@ -46,6 +47,7 @@ const getCompose = (tf) => {
|
|||
handleSubmitOnClick={handleSubmitEmpty}
|
||||
handleKeyDown={handleKeyDownFake}
|
||||
activeChannelId={12345}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
@ -68,7 +70,7 @@ describe('<Compose />', () => {
|
|||
it('should click submit', () => {
|
||||
const { getByText } = render(getCompose(false));
|
||||
const button = getByText(/Send/i);
|
||||
|
||||
|
||||
button.click();
|
||||
expect(submitNoMessage).toEqual(true);
|
||||
expect(submitWithMessage).toEqual(false);
|
||||
|
|
@ -135,4 +137,85 @@ describe('<Compose />', () => {
|
|||
expect(textfieldIsEmpty).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
// Check for the actual input value after pressing enter
|
||||
it('should press enter and check for empty input', () => {
|
||||
const compose = getCompose(true);
|
||||
const { getByTestId, rerender } = render(compose);
|
||||
|
||||
const input = getByTestId('messageform');
|
||||
|
||||
fireEvent(input, createEvent('input', input, { target: { value: 'T' } }));
|
||||
|
||||
expect(input.value).toBe('T');
|
||||
|
||||
fireEvent.keyDown(input, { keyCode: 13 });
|
||||
|
||||
rerender(compose);
|
||||
|
||||
expect(input.value).toBe('');
|
||||
});
|
||||
|
||||
// Check for the actual input value after clicking send
|
||||
it('should click send and check for empty input', () => {
|
||||
const compose = getCompose(true);
|
||||
const { getByTestId, getByText, rerender } = render(compose);
|
||||
|
||||
const input = getByTestId('messageform');
|
||||
const sendButton = getByText(/send/i);
|
||||
|
||||
fireEvent(input, createEvent('input', input, { target: { value: 'T' } }));
|
||||
|
||||
expect(input.value).toBe('T');
|
||||
|
||||
sendButton.click();
|
||||
|
||||
rerender(compose);
|
||||
|
||||
expect(input.value).toBe('');
|
||||
});
|
||||
|
||||
// Check for the actual input value after saving an edit
|
||||
it('should click send edit and check for empty input', () => {
|
||||
const compose = getCompose(true, {
|
||||
markdownEdited: false,
|
||||
startEditing: true,
|
||||
editMessageMarkdown: 'Test',
|
||||
handleSubmitOnClickEdit: () => null,
|
||||
});
|
||||
const { getByTestId, getByText, rerender } = render(compose);
|
||||
|
||||
const input = getByTestId('messageform');
|
||||
const saveButton = getByText(/save/i);
|
||||
|
||||
expect(input.value).toBe('Test');
|
||||
|
||||
saveButton.click();
|
||||
|
||||
rerender(compose);
|
||||
|
||||
expect(input.value).toBe('');
|
||||
});
|
||||
|
||||
// Check for the actual input value after canceling an edit
|
||||
it('should click close edit and check for empty input', () => {
|
||||
const compose = getCompose(true, {
|
||||
markdownEdited: false,
|
||||
startEditing: true,
|
||||
editMessageMarkdown: 'Test',
|
||||
handleEditMessageClose: () => null,
|
||||
});
|
||||
const { getByTestId, getByText, rerender } = render(compose);
|
||||
|
||||
const input = getByTestId('messageform');
|
||||
const closeButton = getByText(/close/i);
|
||||
|
||||
expect(input.value).toBe('Test');
|
||||
|
||||
closeButton.click();
|
||||
|
||||
rerender(compose);
|
||||
|
||||
expect(input.value).toBe('');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -602,7 +602,6 @@ export default class Chat extends Component {
|
|||
} else if (!messageIsEmpty && !shiftPressed) {
|
||||
e.preventDefault();
|
||||
this.handleMessageSubmit(e.target.value);
|
||||
e.target.value = '';
|
||||
}
|
||||
}
|
||||
if (e.target.value.includes('@')) {
|
||||
|
|
@ -679,7 +678,6 @@ export default class Chat extends Component {
|
|||
} else if (!messageIsEmpty && !shiftPressed) {
|
||||
e.preventDefault();
|
||||
this.handleMessageSubmitEdit(e.target.value);
|
||||
e.target.value = '';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -845,7 +843,6 @@ export default class Chat extends Component {
|
|||
const message = document.getElementById('messageform').value;
|
||||
if (message.length > 0) {
|
||||
this.handleMessageSubmit(message);
|
||||
document.getElementById('messageform').value = '';
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -854,7 +851,6 @@ export default class Chat extends Component {
|
|||
const message = document.getElementById('messageform').value;
|
||||
if (message.length > 0) {
|
||||
this.handleMessageSubmitEdit(message);
|
||||
document.getElementById('messageform').value = '';
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -1747,13 +1743,11 @@ export default class Chat extends Component {
|
|||
};
|
||||
|
||||
handleEditMessageClose = () => {
|
||||
const textarea = document.getElementById('messageform');
|
||||
this.setState({
|
||||
startEditing: false,
|
||||
markdownEdited: false,
|
||||
activeEditMessage: { message: '', markdown: '' },
|
||||
});
|
||||
textarea.value = '';
|
||||
};
|
||||
|
||||
renderDeleteModal = () => {
|
||||
|
|
|
|||
|
|
@ -1,142 +1,122 @@
|
|||
import { h, Component } from 'preact';
|
||||
import { h } from 'preact';
|
||||
import { useState, useEffect, useMemo } from 'preact/hooks';
|
||||
import PropTypes from 'prop-types';
|
||||
import Textarea from 'preact-textarea-autosize';
|
||||
|
||||
export default class Chat extends Component {
|
||||
static propTypes = {
|
||||
handleKeyDown: PropTypes.func.isRequired,
|
||||
handleKeyDownEdit: PropTypes.func.isRequired,
|
||||
handleSubmitOnClick: PropTypes.func.isRequired,
|
||||
handleSubmitOnClickEdit: PropTypes.func.isRequired,
|
||||
handleMention: PropTypes.func.isRequired,
|
||||
handleKeyUp: PropTypes.func.isRequired,
|
||||
startEditing: PropTypes.bool.isRequired,
|
||||
markdownEdited: PropTypes.bool.isRequired,
|
||||
editMessageMarkdown: PropTypes.string.isRequired,
|
||||
handleEditMessageClose: PropTypes.func.isRequired,
|
||||
handleFilePaste: PropTypes.func.isRequired,
|
||||
const Compose = ({
|
||||
handleKeyDown,
|
||||
handleKeyDownEdit,
|
||||
handleSubmitOnClick,
|
||||
handleSubmitOnClickEdit,
|
||||
handleMention,
|
||||
handleKeyUp,
|
||||
startEditing,
|
||||
markdownEdited,
|
||||
editMessageMarkdown,
|
||||
handleEditMessageClose,
|
||||
handleFilePaste,
|
||||
}) => {
|
||||
const [value, setValue] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
if (!markdownEdited && startEditing) {
|
||||
setValue(editMessageMarkdown);
|
||||
}
|
||||
}, [markdownEdited, startEditing, editMessageMarkdown]);
|
||||
|
||||
const onKeyDown = (event) => {
|
||||
if (startEditing) handleKeyDownEdit(event);
|
||||
else handleKeyDown(event);
|
||||
|
||||
if (event.keyCode === 13) {
|
||||
setValue('');
|
||||
}
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const placeholder = useMemo(
|
||||
() => (startEditing ? "Let's connect" : 'Write message...'),
|
||||
[startEditing],
|
||||
);
|
||||
const label = useMemo(
|
||||
() => (startEditing ? "Let's connect" : 'Compose a message'),
|
||||
[startEditing],
|
||||
);
|
||||
const saveButtonText = useMemo(() => (startEditing ? 'Save' : 'Send'), [
|
||||
startEditing,
|
||||
]);
|
||||
|
||||
this.state = {
|
||||
value: null,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
const { editMessageMarkdown, markdownEdited, startEditing } = this.props;
|
||||
const textarea = document.getElementById('messageform');
|
||||
if (!markdownEdited && startEditing) {
|
||||
textarea.value = editMessageMarkdown;
|
||||
}
|
||||
}
|
||||
|
||||
messageCompose = () => {
|
||||
const {
|
||||
handleSubmitOnClickEdit,
|
||||
handleKeyDownEdit,
|
||||
handleEditMessageClose,
|
||||
handleMention,
|
||||
handleKeyUp,
|
||||
handleFilePaste,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<div className="composer-container__edit">
|
||||
return (
|
||||
<div className="compose__outer__container">
|
||||
<div
|
||||
className={
|
||||
startEditing ? 'composer-container__edit' : 'messagecomposer'
|
||||
}
|
||||
>
|
||||
<Textarea
|
||||
className="crayons-textfield composer-textarea__edit"
|
||||
className={
|
||||
startEditing
|
||||
? 'crayons-textfield composer-textarea__edit'
|
||||
: 'crayons-textfield composer-textarea'
|
||||
}
|
||||
id="messageform"
|
||||
placeholder="Let's connect"
|
||||
onKeyDown={handleKeyDownEdit}
|
||||
data-testid="messageform"
|
||||
placeholder={placeholder}
|
||||
onKeyDown={onKeyDown}
|
||||
onKeyPress={handleMention}
|
||||
onKeyUp={handleKeyUp}
|
||||
onPaste={handleFilePaste}
|
||||
maxLength="1000"
|
||||
aria-label="Let's connect"
|
||||
value={value}
|
||||
onInput={(event) => setValue(event.target.value)}
|
||||
aria-label={label}
|
||||
/>
|
||||
<div className="composer-btn-group">
|
||||
<button
|
||||
type="button"
|
||||
className="composer-submit composer-submit__edit crayons-btn"
|
||||
onClick={handleSubmitOnClickEdit}
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
<div
|
||||
role="button"
|
||||
className="composer-close__edit crayons-btn crayons-btn--secondary"
|
||||
onClick={handleEditMessageClose}
|
||||
tabIndex="0"
|
||||
onKeyUp={(e) => {
|
||||
if (e.keyCode === 13) handleEditMessageClose();
|
||||
className={
|
||||
startEditing
|
||||
? 'composer-submit composer-submit__edit crayons-btn'
|
||||
: 'crayons-btn composer-submit'
|
||||
}
|
||||
onClick={(event) => {
|
||||
if (startEditing) handleSubmitOnClickEdit(event);
|
||||
else handleSubmitOnClick(event);
|
||||
|
||||
setValue('');
|
||||
}}
|
||||
>
|
||||
Close
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
textAreaSection = () => {
|
||||
const {
|
||||
handleSubmitOnClick,
|
||||
handleKeyDown,
|
||||
handleMention,
|
||||
handleKeyUp,
|
||||
handleFilePaste,
|
||||
} = this.props;
|
||||
|
||||
const handleInput = (e) => {
|
||||
this.setState({
|
||||
value: e.target.value,
|
||||
});
|
||||
};
|
||||
|
||||
const handleOnSubmitAction = (e) => {
|
||||
handleSubmitOnClick(e);
|
||||
|
||||
this.setState({
|
||||
value: '',
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="messagecomposer">
|
||||
<Textarea
|
||||
className="crayons-textfield composer-textarea"
|
||||
id="messageform"
|
||||
placeholder="Write message..."
|
||||
onKeyDown={handleKeyDown}
|
||||
onKeyPress={handleMention}
|
||||
onKeyUp={handleKeyUp}
|
||||
onPaste={handleFilePaste}
|
||||
maxLength="1000"
|
||||
value={this.state.value}
|
||||
onInput={handleInput}
|
||||
aria-label="Compose a message"
|
||||
/>
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
className="crayons-btn composer-submit"
|
||||
onClick={handleOnSubmitAction}
|
||||
>
|
||||
Send
|
||||
{saveButtonText}
|
||||
</button>
|
||||
{startEditing && (
|
||||
<button
|
||||
type="button"
|
||||
className="composer-close__edit crayons-btn crayons-btn--secondary"
|
||||
onClick={(event) => {
|
||||
handleEditMessageClose(event);
|
||||
setValue('');
|
||||
}}
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { startEditing } = this.props;
|
||||
return (
|
||||
<div className="compose__outer__container">
|
||||
{!startEditing ? this.textAreaSection() : this.messageCompose()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
Compose.propTypes = {
|
||||
handleKeyDown: PropTypes.func.isRequired,
|
||||
handleKeyDownEdit: PropTypes.func.isRequired,
|
||||
handleSubmitOnClick: PropTypes.func.isRequired,
|
||||
handleSubmitOnClickEdit: PropTypes.func.isRequired,
|
||||
handleMention: PropTypes.func.isRequired,
|
||||
handleKeyUp: PropTypes.func.isRequired,
|
||||
startEditing: PropTypes.bool.isRequired,
|
||||
markdownEdited: PropTypes.bool.isRequired,
|
||||
editMessageMarkdown: PropTypes.string.isRequired,
|
||||
handleEditMessageClose: PropTypes.func.isRequired,
|
||||
handleFilePaste: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default Compose;
|
||||
|
|
|
|||
|
|
@ -28,9 +28,7 @@ describe('<BodyMarkdown />', () => {
|
|||
|
||||
const textArea = getByLabelText('Body Markdown');
|
||||
|
||||
expect(textArea.getAttribute('placeholder')).toEqual(
|
||||
'...',
|
||||
);
|
||||
expect(textArea.getAttribute('placeholder')).toEqual('...');
|
||||
expect(textArea.getAttribute('maxLength')).toEqual('400');
|
||||
expect(textArea.value).toEqual(defaultValue);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -20,7 +20,11 @@ class Categories extends Component {
|
|||
details = () => {
|
||||
const { categoriesForDetails } = this.props;
|
||||
const rules = categoriesForDetails.map((category) => {
|
||||
const paragraphText = <li><strong>{category.name}:</strong> {category.rules}</li>;
|
||||
const paragraphText = (
|
||||
<li>
|
||||
<strong>{category.name}:</strong> {category.rules}
|
||||
</li>
|
||||
);
|
||||
return <ul>{paragraphText}</ul>;
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,9 @@ const domId = 1;
|
|||
|
||||
const Title = ({ onChange, defaultValue }) => (
|
||||
<div className="crayons-field">
|
||||
<label className="crayons-field__label" htmlFor={domId}>Title</label>
|
||||
<label className="crayons-field__label" htmlFor={domId}>
|
||||
Title
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
className="crayons-textfield"
|
||||
|
|
|
|||
|
|
@ -431,7 +431,11 @@ class Tags extends Component {
|
|||
|
||||
return (
|
||||
<div className={`${classPrefix}__tagswrapper crayons-field`}>
|
||||
{listing && <label htmlFor="Tags" class="crayons-field__label">Tags</label>}
|
||||
{listing && (
|
||||
<label htmlFor="Tags" class="crayons-field__label">
|
||||
Tags
|
||||
</label>
|
||||
)}
|
||||
<input
|
||||
data-testid="tag-input"
|
||||
aria-label="Post Tags"
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@
|
|||
<% if @organizations.present? && @organizations.size > 0 %>
|
||||
<div class="crayons-field">
|
||||
<%= form.label "organization_id", "Post under an organization", class: "crayons-field__label" %>
|
||||
<%= form.select :organization_id, { "None" => "None"}, {}, {class: "crayons-select m:max-w-50"} %>
|
||||
<%= form.select :organization_id, { "None" => "None" }, {}, { class: "crayons-select m:max-w-50" } %>
|
||||
<p class="crayons-field__description">Posting on behalf of an organization spends the organization's credits.</p>
|
||||
</div>
|
||||
<% end %>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue