Ensured tags list is not updated until the edit modal is closed

This commit is contained in:
Alejandro Celaya 2022-11-07 21:32:19 +01:00
parent 2183b09ffe
commit 5ecc791b38
4 changed files with 19 additions and 21 deletions

View file

@ -1,3 +1,4 @@
import { pipe } from 'ramda';
import { useState } from 'react'; import { useState } from 'react';
import { Button, Input, Modal, ModalBody, ModalFooter, ModalHeader, Popover, InputGroup } from 'reactstrap'; import { Button, Input, Modal, ModalBody, ModalFooter, ModalHeader, Popover, InputGroup } from 'reactstrap';
import { HexColorPicker } from 'react-colorful'; import { HexColorPicker } from 'react-colorful';
@ -24,16 +25,16 @@ export const EditTagModal = ({ getColorForKey }: ColorGenerator) => (
const [newTagName, setNewTagName] = useState(tag); const [newTagName, setNewTagName] = useState(tag);
const [color, setColor] = useState(getColorForKey(tag)); const [color, setColor] = useState(getColorForKey(tag));
const [showColorPicker, toggleColorPicker, , hideColorPicker] = useToggle(); const [showColorPicker, toggleColorPicker, , hideColorPicker] = useToggle();
const { editing, error, errorData } = tagEdit; const { editing, error, edited, errorData } = tagEdit;
const saveTag = handleEventPreventingDefault( const saveTag = handleEventPreventingDefault(
async () => editTag(tag, newTagName, color) async () => editTag(tag, newTagName, color)
.then(() => tagEdited(tag, newTagName, color))
.then(toggle) .then(toggle)
.catch(() => {}), .catch(() => {}),
); );
const onClosed = pipe(hideColorPicker, () => edited && tagEdited(tag, newTagName, color));
return ( return (
<Modal isOpen={isOpen} toggle={toggle} centered onClosed={hideColorPicker}> <Modal isOpen={isOpen} toggle={toggle} centered onClosed={onClosed}>
<form name="editTag" onSubmit={saveTag}> <form name="editTag" onSubmit={saveTag}>
<ModalHeader toggle={toggle}>Edit tag</ModalHeader> <ModalHeader toggle={toggle}>Edit tag</ModalHeader>
<ModalBody> <ModalBody>

View file

@ -11,13 +11,13 @@ import { ProblemDetailsError } from '../../api/types/errors';
export const EDIT_TAG_START = 'shlink/editTag/EDIT_TAG_START'; export const EDIT_TAG_START = 'shlink/editTag/EDIT_TAG_START';
export const EDIT_TAG_ERROR = 'shlink/editTag/EDIT_TAG_ERROR'; export const EDIT_TAG_ERROR = 'shlink/editTag/EDIT_TAG_ERROR';
export const EDIT_TAG = 'shlink/editTag/EDIT_TAG'; export const EDIT_TAG = 'shlink/editTag/EDIT_TAG';
export const TAG_EDITED = 'shlink/editTag/TAG_EDITED'; export const TAG_EDITED = 'shlink/editTag/TAG_EDITED';
export interface TagEdition { export interface TagEdition {
oldName: string; oldName?: string;
newName: string; newName?: string;
editing: boolean; editing: boolean;
edited: boolean;
error: boolean; error: boolean;
errorData?: ProblemDetailsError; errorData?: ProblemDetailsError;
} }
@ -29,18 +29,18 @@ export interface EditTagAction extends Action<string> {
} }
const initialState: TagEdition = { const initialState: TagEdition = {
oldName: '',
newName: '',
editing: false, editing: false,
edited: false,
error: false, error: false,
}; };
export default buildReducer<TagEdition, EditTagAction & ApiErrorAction>({ export default buildReducer<TagEdition, EditTagAction & ApiErrorAction>({
[EDIT_TAG_START]: (state) => ({ ...state, editing: true, error: false }), [EDIT_TAG_START]: () => ({ editing: true, edited: false, error: false }),
[EDIT_TAG_ERROR]: (state, { errorData }) => ({ ...state, editing: false, error: true, errorData }), [EDIT_TAG_ERROR]: (_, { errorData }) => ({ editing: false, edited: false, error: true, errorData }),
[EDIT_TAG]: (_, action) => ({ [EDIT_TAG]: (_, action) => ({
...pick(['oldName', 'newName'], action), ...pick(['oldName', 'newName'], action),
editing: false, editing: false,
edited: true,
error: false, error: false,
}), }),
}, initialState); }, initialState);
@ -56,7 +56,7 @@ export const editTag = (buildShlinkApiClient: ShlinkApiClientBuilder, colorGener
try { try {
await shlinkEditTag(oldName, newName); await shlinkEditTag(oldName, newName);
colorGenerator.setColorForKey(newName, color); colorGenerator.setColorForKey(newName, color);
dispatch({ type: EDIT_TAG, oldName, newName }); dispatch({ type: EDIT_TAG, oldName, newName, color });
} catch (e: any) { } catch (e: any) {
dispatch<ApiErrorAction>({ type: EDIT_TAG_ERROR, errorData: parseApiError(e) }); dispatch<ApiErrorAction>({ type: EDIT_TAG_ERROR, errorData: parseApiError(e) });

View file

@ -9,12 +9,11 @@ import { ProblemDetailsError } from '../../../src/api/types/errors';
describe('<EditTagModal />', () => { describe('<EditTagModal />', () => {
const EditTagModal = createEditTagModal(Mock.of<ColorGenerator>({ getColorForKey: jest.fn(() => 'green') })); const EditTagModal = createEditTagModal(Mock.of<ColorGenerator>({ getColorForKey: jest.fn(() => 'green') }));
const editTag = jest.fn().mockReturnValue(Promise.resolve()); const editTag = jest.fn().mockReturnValue(Promise.resolve());
const tagEdited = jest.fn().mockReturnValue(Promise.resolve());
const toggle = jest.fn(); const toggle = jest.fn();
const setUp = (tagEdit: Partial<TagEdition> = {}) => { const setUp = (tagEdit: Partial<TagEdition> = {}) => {
const edition = Mock.of<TagEdition>(tagEdit); const edition = Mock.of<TagEdition>(tagEdit);
return renderWithEvents( return renderWithEvents(
<EditTagModal isOpen tag="foo" tagEdit={edition} editTag={editTag} tagEdited={tagEdited} toggle={toggle} />, <EditTagModal isOpen tag="foo" tagEdit={edition} editTag={editTag} tagEdited={jest.fn()} toggle={toggle} />,
); );
}; };
@ -30,7 +29,6 @@ describe('<EditTagModal />', () => {
expect(toggle).toHaveBeenCalledTimes(2); expect(toggle).toHaveBeenCalledTimes(2);
expect(editTag).not.toHaveBeenCalled(); expect(editTag).not.toHaveBeenCalled();
expect(tagEdited).not.toHaveBeenCalled();
}); });
it.each([ it.each([
@ -63,12 +61,12 @@ describe('<EditTagModal />', () => {
const { user } = setUp(); const { user } = setUp();
expect(editTag).not.toHaveBeenCalled(); expect(editTag).not.toHaveBeenCalled();
expect(tagEdited).not.toHaveBeenCalled(); expect(toggle).not.toHaveBeenCalled();
await user.click(screen.getByRole('button', { name: 'Save' })); await user.click(screen.getByRole('button', { name: 'Save' }));
expect(editTag).toHaveBeenCalled(); expect(editTag).toHaveBeenCalled();
expect(tagEdited).toHaveBeenCalled(); expect(toggle).toHaveBeenCalled();
}); });
it('changes color when changing on color picker', async () => { it('changes color when changing on color picker', async () => {

View file

@ -21,24 +21,23 @@ describe('tagEditReducer', () => {
it('returns loading on EDIT_TAG_START', () => { it('returns loading on EDIT_TAG_START', () => {
expect(reducer(undefined, Mock.of<EditTagAction>({ type: EDIT_TAG_START }))).toEqual({ expect(reducer(undefined, Mock.of<EditTagAction>({ type: EDIT_TAG_START }))).toEqual({
editing: true, editing: true,
edited: false,
error: false, error: false,
oldName: '',
newName: '',
}); });
}); });
it('returns error on EDIT_TAG_ERROR', () => { it('returns error on EDIT_TAG_ERROR', () => {
expect(reducer(undefined, Mock.of<EditTagAction>({ type: EDIT_TAG_ERROR }))).toEqual({ expect(reducer(undefined, Mock.of<EditTagAction>({ type: EDIT_TAG_ERROR }))).toEqual({
editing: false, editing: false,
edited: false,
error: true, error: true,
oldName: '',
newName: '',
}); });
}); });
it('returns tag names on EDIT_TAG', () => { it('returns tag names on EDIT_TAG', () => {
expect(reducer(undefined, { type: EDIT_TAG, oldName, newName, color })).toEqual({ expect(reducer(undefined, { type: EDIT_TAG, oldName, newName, color })).toEqual({
editing: false, editing: false,
edited: true,
error: false, error: false,
oldName: 'foo', oldName: 'foo',
newName: 'bar', newName: 'bar',
@ -82,7 +81,7 @@ describe('tagEditReducer', () => {
expect(dispatch).toHaveBeenCalledTimes(2); expect(dispatch).toHaveBeenCalledTimes(2);
expect(dispatch).toHaveBeenNthCalledWith(1, { type: EDIT_TAG_START }); expect(dispatch).toHaveBeenNthCalledWith(1, { type: EDIT_TAG_START });
expect(dispatch).toHaveBeenNthCalledWith(2, { type: EDIT_TAG, oldName, newName }); expect(dispatch).toHaveBeenNthCalledWith(2, { type: EDIT_TAG, oldName, newName, color });
}); });
it('throws on error', async () => { it('throws on error', async () => {