Fix clear localstorage after comment sending
* add functions for saving json to localstorage and tests * remove comment form localstorage after posting
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
import { setJsonItem, getJsonItem, updateJsonItem } from './local-storage';
|
||||
|
||||
const LS_KEY = 'test';
|
||||
|
||||
describe('getJsonItem', () => {
|
||||
afterAll(() => {
|
||||
localStorage.clear();
|
||||
});
|
||||
it('should set json to empty localStorage', () => {
|
||||
setJsonItem<Record<string, string>>(LS_KEY, {});
|
||||
expect(localStorage.getItem(LS_KEY)).toBe('{}');
|
||||
});
|
||||
|
||||
it('should update json in localStoeage', () => {
|
||||
setJsonItem<any[]>(LS_KEY, []);
|
||||
expect(localStorage.getItem(LS_KEY)).toBe('[]');
|
||||
});
|
||||
});
|
||||
|
||||
describe('setJsonItem', () => {
|
||||
let consoleSpy: jest.SpyInstance;
|
||||
|
||||
beforeEach(() => {
|
||||
consoleSpy = jest.spyOn(console, 'error').mockImplementation();
|
||||
});
|
||||
afterEach(() => {
|
||||
localStorage.clear();
|
||||
});
|
||||
|
||||
it('should return null when localStorage is empty', () => {
|
||||
expect(getJsonItem(LS_KEY)).toBe(null);
|
||||
});
|
||||
|
||||
it('should return value of key', () => {
|
||||
localStorage.setItem(LS_KEY, JSON.stringify({}));
|
||||
expect(getJsonItem(LS_KEY)).toEqual({});
|
||||
|
||||
localStorage.setItem(LS_KEY, JSON.stringify([]));
|
||||
expect(getJsonItem(LS_KEY)).toEqual([]);
|
||||
|
||||
localStorage.setItem(LS_KEY, JSON.stringify(null));
|
||||
expect(getJsonItem(LS_KEY)).toBe(null);
|
||||
|
||||
localStorage.setItem(LS_KEY, JSON.stringify(1));
|
||||
expect(getJsonItem(LS_KEY)).toBe(1);
|
||||
|
||||
localStorage.setItem(LS_KEY, JSON.stringify(1));
|
||||
expect(getJsonItem(LS_KEY)).toBe(1);
|
||||
});
|
||||
|
||||
it('should return `null` if value in localStorage is not JSON', () => {
|
||||
localStorage.setItem(LS_KEY, '"{:"""');
|
||||
|
||||
expect(getJsonItem(LS_KEY)).toBe(null);
|
||||
expect(consoleSpy).toHaveBeenCalled();
|
||||
|
||||
localStorage.setItem(LS_KEY, 'asdas');
|
||||
|
||||
expect(getJsonItem(LS_KEY)).toBe(null);
|
||||
expect(consoleSpy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateJsonItem', () => {
|
||||
afterEach(() => {
|
||||
localStorage.clear();
|
||||
});
|
||||
|
||||
it('should set data to empty localStorage', () => {
|
||||
updateJsonItem<Record<string, string>>(LS_KEY, {});
|
||||
|
||||
expect(localStorage.getItem(LS_KEY)).toBe(JSON.stringify({}));
|
||||
});
|
||||
|
||||
it('should update object in localStorage', () => {
|
||||
localStorage.setItem(LS_KEY, JSON.stringify({ x: 1 }));
|
||||
updateJsonItem(LS_KEY, { y: 1 });
|
||||
|
||||
expect(localStorage.getItem(LS_KEY)).toBe(JSON.stringify({ x: 1, y: 1 }));
|
||||
});
|
||||
|
||||
it('should update array in localStorage', () => {
|
||||
localStorage.setItem(LS_KEY, JSON.stringify([1, 2, 3]));
|
||||
updateJsonItem(LS_KEY, [4, 5, 6]);
|
||||
|
||||
expect(localStorage.getItem(LS_KEY)).toBe(JSON.stringify([1, 2, 3, 4, 5, 6]));
|
||||
});
|
||||
|
||||
it('should update data in localStorage with merge', () => {
|
||||
localStorage.setItem(LS_KEY, JSON.stringify([3, 4, 5]));
|
||||
updateJsonItem<any[]>(LS_KEY, data => [1, 2, ...data]);
|
||||
|
||||
expect(localStorage.getItem(LS_KEY)).toBe(JSON.stringify([1, 2, 3, 4, 5]));
|
||||
});
|
||||
});
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { IS_STORAGE_AVAILABLE } from './constants';
|
||||
|
||||
const failMessage = 'remark42: localStorage access denied, check browser preferences';
|
||||
@@ -20,3 +21,51 @@ export const removeItem = IS_STORAGE_AVAILABLE
|
||||
: () => {
|
||||
console.error(failMessage); // eslint-disable-line no-console
|
||||
};
|
||||
|
||||
export function getJsonItem<T = any>(key: string): T | null {
|
||||
try {
|
||||
const json = getItem(key);
|
||||
|
||||
if (json === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const data = JSON.parse(json);
|
||||
|
||||
return data;
|
||||
} catch (e) {
|
||||
console.error(`remark42: error on read JSON from ${key} in localStorage`, e); // eslint-disable-line no-console
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function setJsonItem<T = any>(key: string, data: T) {
|
||||
try {
|
||||
setItem(key, JSON.stringify(data));
|
||||
} catch (e) {
|
||||
console.error(`remark42: error on parse JSON from ${key} in localStorage`, e); // eslint-disable-line no-console
|
||||
}
|
||||
}
|
||||
|
||||
export function updateJsonItem<T = Record<string, any> | any[]>(key: string, value: (data: T) => T): void;
|
||||
export function updateJsonItem<T = any[]>(key: string, value: T): void;
|
||||
export function updateJsonItem<T = Record<string, any>>(key: string, value: T) {
|
||||
const savedData = getJsonItem<any>(key);
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
setJsonItem(key, [...savedData, ...value]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (value !== null && typeof value === 'object') {
|
||||
setJsonItem(key, { ...savedData, ...value });
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof value === 'function') {
|
||||
setJsonItem(key, value(savedData));
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Error(`remark42: error on update JSON for ${key} in localStorage`);
|
||||
}
|
||||
|
||||
@@ -4,9 +4,20 @@ import { shallow } from 'enzyme';
|
||||
|
||||
import { user } from '@app/testUtils/mocks/user';
|
||||
import { StaticStore } from '@app/common/static_store';
|
||||
import { LS_SAVED_COMMENT_VALUE } from '@app/common/constants';
|
||||
import * as localStorageModule from '@app/common/local-storage';
|
||||
|
||||
import { CommentForm, Props } from './comment-form';
|
||||
import { SubscribeByEmail } from './__subscribe-by-email';
|
||||
import TextareaAutosize from './textarea-autosize';
|
||||
|
||||
function createEvent<T = any>(type: string, value: T) {
|
||||
const event = new Event(type);
|
||||
|
||||
Object.defineProperty(event, 'target', { value });
|
||||
|
||||
return event;
|
||||
}
|
||||
|
||||
const DEFAULT_PROPS: Readonly<Omit<Props, 'intl'>> = {
|
||||
mode: 'main',
|
||||
@@ -50,4 +61,74 @@ describe('<CommentForm />', () => {
|
||||
|
||||
expect(wrapper.exists(SubscribeByEmail)).toEqual(false);
|
||||
});
|
||||
|
||||
describe('initial value of comment', () => {
|
||||
afterEach(() => {
|
||||
localStorage.clear();
|
||||
});
|
||||
it('should has empty value', () => {
|
||||
localStorage.setItem(LS_SAVED_COMMENT_VALUE, JSON.stringify({ 2: 'text' }));
|
||||
|
||||
const props = { ...DEFAULT_PROPS, user, intl };
|
||||
const wrapper = shallow(<CommentForm {...props} />);
|
||||
|
||||
expect(wrapper.state('text')).toBe('');
|
||||
expect(wrapper.find(TextareaAutosize).prop('value')).toBe('');
|
||||
});
|
||||
|
||||
it('should get initial value from localStorage', () => {
|
||||
const COMMENT_VALUE = 'text';
|
||||
|
||||
localStorage.setItem(LS_SAVED_COMMENT_VALUE, JSON.stringify({ 1: COMMENT_VALUE }));
|
||||
|
||||
const props = { ...DEFAULT_PROPS, user, intl };
|
||||
const wrapper = shallow(<CommentForm {...props} />);
|
||||
|
||||
expect(wrapper.state('text')).toBe(COMMENT_VALUE);
|
||||
expect(wrapper.find(TextareaAutosize).prop('value')).toBe(COMMENT_VALUE);
|
||||
});
|
||||
|
||||
it('should get initial value from props instead localStorage', () => {
|
||||
const COMMENT_VALUE = 'text from props';
|
||||
|
||||
localStorage.setItem(LS_SAVED_COMMENT_VALUE, JSON.stringify({ 1: 'text from localStorage' }));
|
||||
|
||||
const props = { ...DEFAULT_PROPS, user, intl, value: COMMENT_VALUE };
|
||||
const wrapper = shallow(<CommentForm {...props} />);
|
||||
|
||||
expect(wrapper.state('text')).toBe(COMMENT_VALUE);
|
||||
expect(wrapper.find(TextareaAutosize).prop('value')).toBe(COMMENT_VALUE);
|
||||
});
|
||||
});
|
||||
|
||||
describe('update value of comment in localStorage', () => {
|
||||
afterEach(() => {
|
||||
localStorage.clear();
|
||||
});
|
||||
it('should update value', () => {
|
||||
const props = { ...DEFAULT_PROPS, user, intl };
|
||||
const wrapper = shallow(<CommentForm {...props} />);
|
||||
// @ts-ignore
|
||||
const instance: CommentForm = wrapper.instance();
|
||||
|
||||
instance.onInput(createEvent('input', { value: '1' }));
|
||||
expect(localStorage.getItem(LS_SAVED_COMMENT_VALUE)).toBe('{"1":"1"}');
|
||||
|
||||
instance.onInput(createEvent('input', { value: '11' }));
|
||||
expect(localStorage.getItem(LS_SAVED_COMMENT_VALUE)).toBe('{"1":"11"}');
|
||||
});
|
||||
|
||||
it('should clear value after send', async () => {
|
||||
localStorage.setItem(LS_SAVED_COMMENT_VALUE, JSON.stringify({ '1': 'asd' }));
|
||||
const updateJsonItemSpy = jest.spyOn(localStorageModule, 'updateJsonItem');
|
||||
const props = { ...DEFAULT_PROPS, user, intl };
|
||||
const wrapper = shallow(<CommentForm {...props} />);
|
||||
// @ts-ignore
|
||||
const instance: CommentForm = wrapper.instance();
|
||||
|
||||
await instance.send(createEvent('send', { preventDefault: () => undefined }));
|
||||
expect(updateJsonItemSpy).toHaveBeenCalled();
|
||||
expect(localStorage.getItem(LS_SAVED_COMMENT_VALUE)).toBe(JSON.stringify({}));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -11,7 +11,7 @@ import { sleep } from '@app/utils/sleep';
|
||||
import { replaceSelection } from '@app/utils/replaceSelection';
|
||||
import { Button } from '@app/components/button';
|
||||
import Auth from '@app/components/auth';
|
||||
import { getItem, setItem } from '@app/common/local-storage';
|
||||
import { getJsonItem, updateJsonItem } from '@app/common/local-storage';
|
||||
import { LS_SAVED_COMMENT_VALUE } from '@app/common/constants';
|
||||
|
||||
import { SubscribeByEmail } from './__subscribe-by-email';
|
||||
@@ -42,7 +42,7 @@ export interface Props {
|
||||
intl: IntlShape;
|
||||
}
|
||||
|
||||
interface State {
|
||||
export interface State {
|
||||
preview: string | null;
|
||||
isErrorShown: boolean;
|
||||
/** error message, if contains newlines, it will be splitted to multiple errors */
|
||||
@@ -104,13 +104,16 @@ export class CommentForm extends Component<Props, State> {
|
||||
textareaId = textareaId + 1;
|
||||
this.textareaId = `textarea_${textareaId}`;
|
||||
|
||||
const savedCommentsJSON = getItem(LS_SAVED_COMMENT_VALUE);
|
||||
let savedValue = '';
|
||||
try {
|
||||
if (typeof savedCommentsJSON === 'string') {
|
||||
savedValue = JSON.parse(savedCommentsJSON)[this.props.id] || '';
|
||||
}
|
||||
} catch (e) {}
|
||||
const savedComments = getJsonItem(LS_SAVED_COMMENT_VALUE);
|
||||
let text = '';
|
||||
|
||||
if (savedComments !== null && savedComments[props.id]) {
|
||||
text = savedComments[props.id];
|
||||
}
|
||||
|
||||
if (props.value) {
|
||||
text = props.value;
|
||||
}
|
||||
|
||||
this.state = {
|
||||
preview: null,
|
||||
@@ -119,13 +122,11 @@ export class CommentForm extends Component<Props, State> {
|
||||
errorLock: false,
|
||||
isDisabled: false,
|
||||
maxLength: StaticStore.config.max_comment_size,
|
||||
text: props.value || savedValue,
|
||||
text,
|
||||
buttonText: null,
|
||||
};
|
||||
|
||||
this.send = this.send.bind(this);
|
||||
this.getPreview = this.getPreview.bind(this);
|
||||
this.onInput = this.onInput.bind(this);
|
||||
this.onKeyDown = this.onKeyDown.bind(this);
|
||||
this.onDragOver = this.onDragOver.bind(this);
|
||||
this.onDrop = this.onDrop.bind(this);
|
||||
@@ -169,12 +170,10 @@ export class CommentForm extends Component<Props, State> {
|
||||
}
|
||||
}
|
||||
|
||||
onInput(e: Event) {
|
||||
onInput = (e: Event) => {
|
||||
const { value } = e.target as HTMLInputElement;
|
||||
|
||||
try {
|
||||
setItem(LS_SAVED_COMMENT_VALUE, JSON.stringify({ [this.props.id]: value }));
|
||||
} catch (e) {}
|
||||
updateJsonItem(LS_SAVED_COMMENT_VALUE, { [this.props.id]: value });
|
||||
|
||||
if (this.state.errorLock) {
|
||||
this.setState({
|
||||
@@ -183,13 +182,14 @@ export class CommentForm extends Component<Props, State> {
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
isErrorShown: false,
|
||||
errorMessage: null,
|
||||
preview: null,
|
||||
text: value,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
async onPaste(e: ClipboardEvent) {
|
||||
if (!(e.clipboardData && e.clipboardData.files.length > 0)) {
|
||||
@@ -200,32 +200,35 @@ export class CommentForm extends Component<Props, State> {
|
||||
await this.uploadImages(files);
|
||||
}
|
||||
|
||||
send(e: Event) {
|
||||
const text = this.textAreaRef.current ? this.textAreaRef.current.getValue() : this.state.text;
|
||||
const props = this.props;
|
||||
send = async (e: Event) => {
|
||||
const { text } = this.state;
|
||||
|
||||
if (e) e.preventDefault();
|
||||
|
||||
if (!text || !text.trim()) return;
|
||||
|
||||
if (text === this.props.value) {
|
||||
this.props.onCancel && this.props.onCancel();
|
||||
this.setState({ preview: null, text: '' });
|
||||
}
|
||||
|
||||
this.setState({ isDisabled: true, isErrorShown: false, text });
|
||||
try {
|
||||
await this.props.onSubmit(text, pageTitle || document.title);
|
||||
updateJsonItem<Record<string, string>>(LS_SAVED_COMMENT_VALUE, data => {
|
||||
delete data[this.props.id];
|
||||
|
||||
props
|
||||
.onSubmit(text, pageTitle || document.title)
|
||||
.then(() => {
|
||||
this.setState({ preview: null, text: '' });
|
||||
})
|
||||
.catch(e => {
|
||||
const errorMessage = extractErrorMessageFromResponse(e, this.props.intl);
|
||||
this.setState({ isErrorShown: true, errorMessage });
|
||||
})
|
||||
.finally(() => this.setState({ isDisabled: false }));
|
||||
}
|
||||
return data;
|
||||
});
|
||||
this.setState({ preview: null, text: '' });
|
||||
} catch (e) {
|
||||
this.setState({
|
||||
isErrorShown: true,
|
||||
errorMessage: extractErrorMessageFromResponse(e, this.props.intl),
|
||||
});
|
||||
}
|
||||
|
||||
this.setState({ isDisabled: false });
|
||||
};
|
||||
|
||||
getPreview() {
|
||||
const text = this.textAreaRef.current ? this.textAreaRef.current.getValue() : this.state.text;
|
||||
|
||||
@@ -835,7 +835,6 @@ class Comment extends Component<Props, State> {
|
||||
intl={this.props.intl}
|
||||
user={props.user}
|
||||
theme={props.theme}
|
||||
value=""
|
||||
mode="reply"
|
||||
mix="comment__input"
|
||||
onSubmit={(text, title) => this.addComment(text, title, o.id)}
|
||||
|
||||
Reference in New Issue
Block a user