separate persist logic from compnennt

This commit is contained in:
Paul Mineev
2022-07-11 16:21:44 -07:00
committed by Paul Mineev
parent aac6af40cc
commit d6013b10e6
5 changed files with 103 additions and 86 deletions
@@ -0,0 +1,29 @@
import { getJsonItem, updateJsonItem } from 'common/local-storage';
import { LS_SAVED_COMMENT_VALUE } from 'common/constants';
export function getPersistedComments() {
return getJsonItem<Record<string, string>>(LS_SAVED_COMMENT_VALUE);
}
export function getPersistedComment(id: string | undefined): string | undefined {
const comments = getPersistedComments();
if (!comments || !id) {
return;
}
return comments[id];
}
export function updatePersistedComments(id: string, value: string) {
updateJsonItem(LS_SAVED_COMMENT_VALUE, { ...getPersistedComments(), [id]: value });
}
export function removePersistedComment(id: string) {
updateJsonItem<Record<string, string> | null>(LS_SAVED_COMMENT_VALUE, (data) => {
if (!data) {
return null;
}
delete data[id];
return data;
});
}
@@ -4,12 +4,12 @@ import { useIntl } from 'react-intl';
import { render } from 'tests/utils';
import { StaticStore } from 'common/static-store';
import { LS_SAVED_COMMENT_VALUE } from 'common/constants';
import * as localStorageModule from 'common/local-storage';
import { CommentForm, CommentFormProps, messages } from './comment-form';
import { CommentForm, Props, messages } from './comment-form';
import { updatePersistedComments, getPersistedComments } from './comment-form.persist';
const user: CommentFormProps['user'] = {
const user: Props['user'] = {
name: 'username',
id: 'id_1',
picture: '',
@@ -19,10 +19,7 @@ const user: CommentFormProps['user'] = {
verified: false,
};
function setup(
overrideProps: Partial<CommentFormProps> = {},
overrideConfig: Partial<typeof StaticStore['config']> = {}
) {
function setup(overrideProps: Partial<Props> = {}, overrideConfig: Partial<typeof StaticStore['config']> = {}) {
Object.assign(StaticStore.config, overrideConfig);
const props = {
@@ -33,25 +30,25 @@ function setup(
user: null,
id: '1',
...overrideProps,
} as CommentFormProps;
} as Props;
const CommentFormWithIntl = () => <CommentForm {...props} intl={useIntl()} />;
return render(<CommentFormWithIntl />);
}
describe('<CommentForm />', () => {
afterEach(() => {
// reset textarea id in order to have `textarea_1` for every test
CommentForm.textareaId = 0;
localStorage.clear();
});
describe('with initial comment value', () => {
afterEach(() => {
localStorage.clear();
});
it('should has empty value', () => {
const value = 'text';
localStorage.setItem(LS_SAVED_COMMENT_VALUE, JSON.stringify({ 1: value }));
updatePersistedComments('1', value);
setup();
expect(screen.getByTestId('textarea_1')).toHaveValue(value);
});
@@ -59,36 +56,33 @@ describe('<CommentForm />', () => {
it('should get initial value from localStorage', () => {
const value = 'text';
localStorage.setItem(LS_SAVED_COMMENT_VALUE, JSON.stringify({ 1: value }));
updatePersistedComments('1', value);
setup();
expect(screen.getByTestId('textarea_1')).toHaveValue(value);
});
it('should get initial value from props instead localStorage', () => {
const value = 'text from props';
localStorage.setItem(LS_SAVED_COMMENT_VALUE, JSON.stringify({ 1: 'text from localStorage' }));
updatePersistedComments('1', 'text from localStorage');
setup({ value });
expect(screen.getByTestId('textarea_1')).toHaveValue(value);
});
});
describe('update initial value', () => {
afterEach(() => {
localStorage.clear();
});
it('should update value', () => {
setup();
fireEvent.input(screen.getByTestId('textarea_1'), { target: { value: '1' } });
expect(localStorage.getItem(LS_SAVED_COMMENT_VALUE)).toBe('{"1":"1"}');
expect(getPersistedComments()).toEqual({ '1': '1' });
fireEvent.input(screen.getByTestId('textarea_1'), { target: { value: '11' } });
expect(localStorage.getItem(LS_SAVED_COMMENT_VALUE)).toBe('{"1":"11"}');
expect(getPersistedComments()).toEqual({ '1': '11' });
});
it('should clear value after send', async () => {
localStorage.setItem(LS_SAVED_COMMENT_VALUE, JSON.stringify({ 1: 'asd' }));
updatePersistedComments('1', 'asd');
const updateJsonItemSpy = jest.spyOn(localStorageModule, 'updateJsonItem');
setup();
@@ -96,7 +90,7 @@ describe('<CommentForm />', () => {
await waitFor(() => {
expect(updateJsonItemSpy).toHaveBeenCalled();
});
expect(localStorage.getItem(LS_SAVED_COMMENT_VALUE)).toBe('{}');
expect(getPersistedComments()).toEqual({});
});
});
@@ -12,16 +12,16 @@ import { replaceSelection } from 'utils/replaceSelection';
import { Button } from 'components/button';
import { TextareaAutosize } from 'components/textarea-autosize';
import { Auth } from 'components/auth';
import { getJsonItem, updateJsonItem } from 'common/local-storage';
import { LS_SAVED_COMMENT_VALUE } from 'common/constants';
import { SubscribeByEmail } from './__subscribe-by-email';
import { SubscribeByRSS } from './__subscribe-by-rss';
import { MarkdownToolbar } from './markdown-toolbar';
import { TextExpander } from './text-expander';
import { updatePersistedComments, getPersistedComment, removePersistedComment } from './comment-form.persist';
export type CommentFormProps = {
export type Props = {
id: string;
user: User | null;
errorMessage?: string;
@@ -39,7 +39,7 @@ export type CommentFormProps = {
intl: IntlShape;
};
export type CommentFormState = {
export type State = {
preview: string | null;
isErrorShown: boolean;
/** error message, if contains newlines, it will be split to multiple errors */
@@ -55,47 +55,7 @@ export type CommentFormState = {
const ImageMimeRegex = /image\//i;
export const messages = defineMessages({
placeholder: {
id: 'commentForm.input-placeholder',
defaultMessage: 'Your comment here',
},
uploadFileFail: {
id: 'commentForm.upload-file-fail',
defaultMessage: '{fileName} upload failed with "{errorMessage}"',
},
uploading: {
id: 'commentForm.uploading',
defaultMessage: 'Uploading...',
},
uploadingFile: {
id: 'commentForm.uploading-file',
defaultMessage: 'uploading {fileName}...',
},
exceededSize: {
id: 'commentForm.exceeded-size',
defaultMessage: '{fileName} exceeds size limit of {maxImageSize}',
},
newComment: {
id: 'commentForm.new-comment',
defaultMessage: 'New comment',
},
unexpectedError: {
id: 'commentForm.unexpected-error',
defaultMessage: 'Something went wrong. Please try again a bit later.',
},
unauthorizedUploadingDisabled: {
id: 'commentForm.unauthorized-uploading-disabled',
defaultMessage: 'Image uploading is disabled for unauthorized users. You should login before uploading.',
},
anonymousUploadingDisabled: {
id: 'commentForm.anonymous-uploading-disabled',
defaultMessage:
'Image uploading is disabled for anonymous users. Please log in not as anonymous user to be able to attach images.',
},
});
export class CommentForm extends Component<CommentFormProps, CommentFormState> {
export class CommentForm extends Component<Props, State> {
/** reference to textarea element */
textareaRef = createRef<HTMLTextAreaElement>();
static textareaId = 0;
@@ -110,15 +70,15 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
buttonText: null,
};
constructor(props: CommentFormProps) {
constructor(props: Props) {
super(props);
const savedComments = getJsonItem<Record<string, string>>(LS_SAVED_COMMENT_VALUE);
this.state.text = props.value ?? savedComments?.[props.id] ?? '';
const savedComment = getPersistedComment(props.id);
this.state.text = props.value ?? savedComment ?? '';
CommentForm.textareaId += 1;
}
componentWillReceiveProps(nextProps: CommentFormProps) {
componentWillReceiveProps(nextProps: Props) {
if (nextProps.value !== this.props.value) {
this.setState({ text: nextProps.value || '' });
}
@@ -130,7 +90,7 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
}
}
shouldComponentUpdate(nextProps: CommentFormProps, nextState: CommentFormState) {
shouldComponentUpdate(nextProps: Props, nextState: State) {
const userId = this.props.user !== null && this.props.user.id;
const nextUserId = nextProps.user !== null && nextProps.user.id;
@@ -155,7 +115,7 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
const { value } = e.target as HTMLInputElement;
const text = value.substr(0, StaticStore.config.max_comment_size);
updateJsonItem(LS_SAVED_COMMENT_VALUE, { [this.props.id]: value });
updatePersistedComments(this.props.id, value)
if (this.state.errorLock) {
this.setState({
@@ -206,13 +166,7 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
return;
}
updateJsonItem<Record<string, string> | null>(LS_SAVED_COMMENT_VALUE, (data) => {
if (data === null) {
return null;
}
delete data[this.props.id];
return data;
});
removePersistedComment(this.props.id)
this.setState({ isDisabled: false, preview: null, text: '' });
};
@@ -368,8 +322,8 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
continue;
}
this.setState({ text: replaceSelection(this.state.text, selection, uploadPlaceholder) }, () => {
updateJsonItem(LS_SAVED_COMMENT_VALUE, { [this.props.id]: this.state.text });
this.setState({ text: replaceSelection(this.state.text, selection, uploadPlaceholder) }, () => {
updatePersistedComments(this.props.id, this.state.text);
});
!isFirst && (await sleep(uploadDelay));
@@ -391,8 +345,8 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
markdownString
),
},
() => {
updateJsonItem(LS_SAVED_COMMENT_VALUE, { [this.props.id]: this.state.text });
() => {
updatePersistedComments(this.props.id, this.state.text);
}
);
/** sleeping awhile so textarea catch state change and its selection */
@@ -566,3 +520,43 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
);
}
}
export const messages = defineMessages({
placeholder: {
id: 'commentForm.input-placeholder',
defaultMessage: 'Your comment here',
},
uploadFileFail: {
id: 'commentForm.upload-file-fail',
defaultMessage: '{fileName} upload failed with "{errorMessage}"',
},
uploading: {
id: 'commentForm.uploading',
defaultMessage: 'Uploading...',
},
uploadingFile: {
id: 'commentForm.uploading-file',
defaultMessage: 'uploading {fileName}...',
},
exceededSize: {
id: 'commentForm.exceeded-size',
defaultMessage: '{fileName} exceeds size limit of {maxImageSize}',
},
newComment: {
id: 'commentForm.new-comment',
defaultMessage: 'New comment',
},
unexpectedError: {
id: 'commentForm.unexpected-error',
defaultMessage: 'Something went wrong. Please try again a bit later.',
},
unauthorizedUploadingDisabled: {
id: 'commentForm.unauthorized-uploading-disabled',
defaultMessage: 'Image uploading is disabled for unauthorized users. You should login before uploading.',
},
anonymousUploadingDisabled: {
id: 'commentForm.anonymous-uploading-disabled',
defaultMessage:
'Image uploading is disabled for anonymous users. Please log in not as anonymous user to be able to attach images.',
},
});
@@ -29,4 +29,4 @@ import './_theme/_light/comment-form_theme_light.css';
import './_simple/comment-form_simple.css';
export { CommentForm } from './comment-form';
export type { CommentFormProps } from './comment-form';
export type { Props } from './comment-form';
+1 -1
View File
@@ -11,7 +11,7 @@ import { copy } from 'common/copy';
import { Theme, BlockTTL, Comment as CommentType, PostInfo, User, CommentMode, Profile } from 'common/types';
import { isUserAnonymous } from 'utils/isUserAnonymous';
import { CommentFormProps } from 'components/comment-form';
import { Props as CommentFormProps } from 'components/comment-form';
import { Avatar } from 'components/avatar';
import { VerificationIcon } from 'components/icons/verification';
import { getPreview, uploadImage } from 'common/api';