[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:
Robin Gagnon 2020-10-08 14:51:36 -04:00 committed by GitHub
parent d21a769bd1
commit 2c10fb0417
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 207 additions and 138 deletions

View file

@ -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

View file

@ -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

View file

@ -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('');
});
});

View file

@ -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 = () => {

View file

@ -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;

View file

@ -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);
});

View file

@ -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>;
});

View file

@ -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"

View file

@ -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"

View file

@ -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 %>