Simplified EditTagsModal component and shortUrlTags reducer

This commit is contained in:
Alejandro Celaya 2020-01-31 20:06:28 +01:00
parent 93f33b6218
commit 30117bd121
5 changed files with 16 additions and 73 deletions

View file

@ -2,6 +2,7 @@ import React from 'react';
import { Modal, ModalBody, ModalFooter, ModalHeader } from 'reactstrap'; import { Modal, ModalBody, ModalFooter, ModalHeader } from 'reactstrap';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { ExternalLink } from 'react-external-link'; import { ExternalLink } from 'react-external-link';
import { pipe } from 'ramda';
import { shortUrlTagsType } from '../reducers/shortUrlTags'; import { shortUrlTagsType } from '../reducers/shortUrlTags';
import { shortUrlType } from '../reducers/shortUrlsList'; import { shortUrlType } from '../reducers/shortUrlsList';
@ -12,7 +13,6 @@ const EditTagsModal = (TagsSelector) => class EditTagsModal extends React.Compon
shortUrl: shortUrlType.isRequired, shortUrl: shortUrlType.isRequired,
shortUrlTags: shortUrlTagsType, shortUrlTags: shortUrlTagsType,
editShortUrlTags: PropTypes.func, editShortUrlTags: PropTypes.func,
shortUrlTagsEdited: PropTypes.func,
resetShortUrlsTags: PropTypes.func, resetShortUrlsTags: PropTypes.func,
}; };
@ -20,28 +20,14 @@ const EditTagsModal = (TagsSelector) => class EditTagsModal extends React.Compon
const { editShortUrlTags, shortUrl, toggle } = this.props; const { editShortUrlTags, shortUrl, toggle } = this.props;
editShortUrlTags(shortUrl.shortCode, this.state.tags) editShortUrlTags(shortUrl.shortCode, this.state.tags)
.then(() => { .then(toggle)
this.tagsSaved = true;
toggle();
})
.catch(() => {}); .catch(() => {});
}; };
refreshShortUrls = () => {
if (!this.tagsSaved) {
return;
}
const { shortUrlTagsEdited, shortUrl, shortUrlTags } = this.props;
const { tags } = shortUrlTags;
shortUrlTagsEdited(shortUrl.shortCode, tags);
};
componentDidMount() { componentDidMount() {
const { resetShortUrlsTags } = this.props; const { resetShortUrlsTags } = this.props;
resetShortUrlsTags(); resetShortUrlsTags();
this.tagsSaved = false;
} }
constructor(props) { constructor(props) {
@ -50,12 +36,13 @@ const EditTagsModal = (TagsSelector) => class EditTagsModal extends React.Compon
} }
render() { render() {
const { isOpen, toggle, shortUrl, shortUrlTags } = this.props; const { isOpen, toggle, shortUrl, shortUrlTags, resetShortUrlsTags } = this.props;
const url = shortUrl && (shortUrl.shortUrl || ''); const url = shortUrl && (shortUrl.shortUrl || '');
const close = pipe(resetShortUrlsTags, toggle);
return ( return (
<Modal isOpen={isOpen} toggle={toggle} centered onClosed={() => this.refreshShortUrls()}> <Modal isOpen={isOpen} toggle={close} centered>
<ModalHeader toggle={toggle}> <ModalHeader toggle={close}>
Edit tags for <ExternalLink href={url} /> Edit tags for <ExternalLink href={url} />
</ModalHeader> </ModalHeader>
<ModalBody> <ModalBody>
@ -67,7 +54,7 @@ const EditTagsModal = (TagsSelector) => class EditTagsModal extends React.Compon
)} )}
</ModalBody> </ModalBody>
<ModalFooter> <ModalFooter>
<button className="btn btn-link" onClick={toggle}>Cancel</button> <button className="btn btn-link" onClick={close}>Cancel</button>
<button <button
className="btn btn-primary" className="btn btn-primary"
type="button" type="button"

View file

@ -4,9 +4,8 @@ import PropTypes from 'prop-types';
/* eslint-disable padding-line-between-statements */ /* eslint-disable padding-line-between-statements */
export const EDIT_SHORT_URL_TAGS_START = 'shlink/shortUrlTags/EDIT_SHORT_URL_TAGS_START'; export const EDIT_SHORT_URL_TAGS_START = 'shlink/shortUrlTags/EDIT_SHORT_URL_TAGS_START';
export const EDIT_SHORT_URL_TAGS_ERROR = 'shlink/shortUrlTags/EDIT_SHORT_URL_TAGS_ERROR'; export const EDIT_SHORT_URL_TAGS_ERROR = 'shlink/shortUrlTags/EDIT_SHORT_URL_TAGS_ERROR';
export const EDIT_SHORT_URL_TAGS = 'shlink/shortUrlTags/EDIT_SHORT_URL_TAGS';
export const RESET_EDIT_SHORT_URL_TAGS = 'shlink/shortUrlTags/RESET_EDIT_SHORT_URL_TAGS';
export const SHORT_URL_TAGS_EDITED = 'shlink/shortUrlTags/SHORT_URL_TAGS_EDITED'; export const SHORT_URL_TAGS_EDITED = 'shlink/shortUrlTags/SHORT_URL_TAGS_EDITED';
export const RESET_EDIT_SHORT_URL_TAGS = 'shlink/shortUrlTags/RESET_EDIT_SHORT_URL_TAGS';
/* eslint-enable padding-line-between-statements */ /* eslint-enable padding-line-between-statements */
export const shortUrlTagsType = PropTypes.shape({ export const shortUrlTagsType = PropTypes.shape({
@ -26,7 +25,7 @@ const initialState = {
export default handleActions({ export default handleActions({
[EDIT_SHORT_URL_TAGS_START]: (state) => ({ ...state, saving: true, error: false }), [EDIT_SHORT_URL_TAGS_START]: (state) => ({ ...state, saving: true, error: false }),
[EDIT_SHORT_URL_TAGS_ERROR]: (state) => ({ ...state, saving: false, error: true }), [EDIT_SHORT_URL_TAGS_ERROR]: (state) => ({ ...state, saving: false, error: true }),
[EDIT_SHORT_URL_TAGS]: (state, { shortCode, tags }) => ({ shortCode, tags, saving: false, error: false }), [SHORT_URL_TAGS_EDITED]: (state, { shortCode, tags }) => ({ shortCode, tags, saving: false, error: false }),
[RESET_EDIT_SHORT_URL_TAGS]: () => initialState, [RESET_EDIT_SHORT_URL_TAGS]: () => initialState,
}, initialState); }, initialState);
@ -37,7 +36,7 @@ export const editShortUrlTags = (buildShlinkApiClient) => (shortCode, tags) => a
try { try {
const normalizedTags = await updateShortUrlTags(shortCode, tags); const normalizedTags = await updateShortUrlTags(shortCode, tags);
dispatch({ tags: normalizedTags, shortCode, type: EDIT_SHORT_URL_TAGS }); dispatch({ tags: normalizedTags, shortCode, type: SHORT_URL_TAGS_EDITED });
} catch (e) { } catch (e) {
dispatch({ type: EDIT_SHORT_URL_TAGS_ERROR }); dispatch({ type: EDIT_SHORT_URL_TAGS_ERROR });
@ -46,9 +45,3 @@ export const editShortUrlTags = (buildShlinkApiClient) => (shortCode, tags) => a
}; };
export const resetShortUrlsTags = createAction(RESET_EDIT_SHORT_URL_TAGS); export const resetShortUrlsTags = createAction(RESET_EDIT_SHORT_URL_TAGS);
export const shortUrlTagsEdited = (shortCode, tags) => ({
tags,
shortCode,
type: SHORT_URL_TAGS_EDITED,
});

View file

@ -13,7 +13,7 @@ import CreateShortUrlResult from '../helpers/CreateShortUrlResult';
import { listShortUrls } from '../reducers/shortUrlsList'; import { listShortUrls } from '../reducers/shortUrlsList';
import { createShortUrl, resetCreateShortUrl } from '../reducers/shortUrlCreation'; import { createShortUrl, resetCreateShortUrl } from '../reducers/shortUrlCreation';
import { deleteShortUrl, resetDeleteShortUrl, shortUrlDeleted } from '../reducers/shortUrlDeletion'; import { deleteShortUrl, resetDeleteShortUrl, shortUrlDeleted } from '../reducers/shortUrlDeletion';
import { editShortUrlTags, resetShortUrlsTags, shortUrlTagsEdited } from '../reducers/shortUrlTags'; import { editShortUrlTags, resetShortUrlsTags } from '../reducers/shortUrlTags';
import { editShortUrlMeta, resetShortUrlMeta } from '../reducers/shortUrlMeta'; import { editShortUrlMeta, resetShortUrlMeta } from '../reducers/shortUrlMeta';
import { resetShortUrlParams } from '../reducers/shortUrlsListParams'; import { resetShortUrlParams } from '../reducers/shortUrlsListParams';
@ -58,10 +58,7 @@ const provideServices = (bottle, connect) => {
)); ));
bottle.serviceFactory('EditTagsModal', EditTagsModal, 'TagsSelector'); bottle.serviceFactory('EditTagsModal', EditTagsModal, 'TagsSelector');
bottle.decorator('EditTagsModal', connect( bottle.decorator('EditTagsModal', connect([ 'shortUrlTags' ], [ 'editShortUrlTags', 'resetShortUrlsTags' ]));
[ 'shortUrlTags' ],
[ 'editShortUrlTags', 'resetShortUrlsTags', 'shortUrlTagsEdited' ]
));
bottle.serviceFactory('EditMetaModal', () => EditMetaModal); bottle.serviceFactory('EditMetaModal', () => EditMetaModal);
bottle.decorator('EditMetaModal', connect([ 'shortUrlMeta' ], [ 'editShortUrlMeta', 'resetShortUrlMeta' ])); bottle.decorator('EditMetaModal', connect([ 'shortUrlMeta' ], [ 'editShortUrlMeta', 'resetShortUrlMeta' ]));
@ -69,7 +66,6 @@ const provideServices = (bottle, connect) => {
// Actions // Actions
bottle.serviceFactory('editShortUrlTags', editShortUrlTags, 'buildShlinkApiClient'); bottle.serviceFactory('editShortUrlTags', editShortUrlTags, 'buildShlinkApiClient');
bottle.serviceFactory('resetShortUrlsTags', () => resetShortUrlsTags); bottle.serviceFactory('resetShortUrlsTags', () => resetShortUrlsTags);
bottle.serviceFactory('shortUrlTagsEdited', () => shortUrlTagsEdited);
bottle.serviceFactory('listShortUrls', listShortUrls, 'buildShlinkApiClient'); bottle.serviceFactory('listShortUrls', listShortUrls, 'buildShlinkApiClient');
bottle.serviceFactory('resetShortUrlParams', () => resetShortUrlParams); bottle.serviceFactory('resetShortUrlParams', () => resetShortUrlParams);

View file

@ -8,7 +8,6 @@ describe('<EditTagsModal />', () => {
const shortCode = 'abc123'; const shortCode = 'abc123';
const TagsSelector = () => ''; const TagsSelector = () => '';
const editShortUrlTags = jest.fn(() => Promise.resolve()); const editShortUrlTags = jest.fn(() => Promise.resolve());
const shortUrlTagsEdited = jest.fn();
const resetShortUrlsTags = jest.fn(); const resetShortUrlsTags = jest.fn();
const toggle = jest.fn(); const toggle = jest.fn();
const createWrapper = (shortUrlTags) => { const createWrapper = (shortUrlTags) => {
@ -25,7 +24,6 @@ describe('<EditTagsModal />', () => {
shortUrlTags={shortUrlTags} shortUrlTags={shortUrlTags}
toggle={toggle} toggle={toggle}
editShortUrlTags={editShortUrlTags} editShortUrlTags={editShortUrlTags}
shortUrlTagsEdited={shortUrlTagsEdited}
resetShortUrlsTags={resetShortUrlsTags} resetShortUrlsTags={resetShortUrlsTags}
/> />
); );
@ -107,28 +105,7 @@ describe('<EditTagsModal />', () => {
const modal = wrapper.find(Modal); const modal = wrapper.find(Modal);
modal.simulate('closed'); modal.simulate('closed');
expect(shortUrlTagsEdited).not.toHaveBeenCalled(); expect(editShortUrlTags).not.toHaveBeenCalled();
});
it('notifies tags have been edited when window is closed after saving', (done) => {
const wrapper = createWrapper({
shortCode,
tags: [],
saving: true,
error: false,
});
const saveBtn = wrapper.find('.btn-primary');
const modal = wrapper.find(Modal);
saveBtn.simulate('click');
// Wrap this expect in a setImmediate since it is called as a result of an inner promise
setImmediate(() => {
modal.simulate('closed');
expect(shortUrlTagsEdited).toHaveBeenCalledTimes(1);
expect(shortUrlTagsEdited).toHaveBeenCalledWith(shortCode, []);
done();
});
}); });
it('toggles modal when cancel button is clicked', () => { it('toggles modal when cancel button is clicked', () => {

View file

@ -1,12 +1,10 @@
import reducer, { import reducer, {
EDIT_SHORT_URL_TAGS,
EDIT_SHORT_URL_TAGS_ERROR, EDIT_SHORT_URL_TAGS_ERROR,
EDIT_SHORT_URL_TAGS_START, EDIT_SHORT_URL_TAGS_START,
RESET_EDIT_SHORT_URL_TAGS, RESET_EDIT_SHORT_URL_TAGS,
resetShortUrlsTags, resetShortUrlsTags,
SHORT_URL_TAGS_EDITED, SHORT_URL_TAGS_EDITED,
editShortUrlTags, editShortUrlTags,
shortUrlTagsEdited,
} from '../../../src/short-urls/reducers/shortUrlTags'; } from '../../../src/short-urls/reducers/shortUrlTags';
describe('shortUrlTagsReducer', () => { describe('shortUrlTagsReducer', () => {
@ -28,8 +26,8 @@ describe('shortUrlTagsReducer', () => {
}); });
}); });
it('returns provided tags and shortCode on EDIT_SHORT_URL_TAGS', () => { it('returns provided tags and shortCode on SHORT_URL_TAGS_EDITED', () => {
expect(reducer({}, { type: EDIT_SHORT_URL_TAGS, tags, shortCode })).toEqual({ expect(reducer({}, { type: SHORT_URL_TAGS_EDITED, tags, shortCode })).toEqual({
tags, tags,
shortCode, shortCode,
saving: false, saving: false,
@ -51,14 +49,6 @@ describe('shortUrlTagsReducer', () => {
it('creates expected action', () => expect(resetShortUrlsTags()).toEqual({ type: RESET_EDIT_SHORT_URL_TAGS })); it('creates expected action', () => expect(resetShortUrlsTags()).toEqual({ type: RESET_EDIT_SHORT_URL_TAGS }));
}); });
describe('shortUrlTagsEdited', () => {
it('creates expected action', () => expect(shortUrlTagsEdited(shortCode, tags)).toEqual({
tags,
shortCode,
type: SHORT_URL_TAGS_EDITED,
}));
});
describe('editShortUrlTags', () => { describe('editShortUrlTags', () => {
const updateShortUrlTags = jest.fn(); const updateShortUrlTags = jest.fn();
const buildShlinkApiClient = jest.fn().mockResolvedValue({ updateShortUrlTags }); const buildShlinkApiClient = jest.fn().mockResolvedValue({ updateShortUrlTags });
@ -82,7 +72,7 @@ describe('shortUrlTagsReducer', () => {
expect(updateShortUrlTags).toHaveBeenCalledWith(shortCode, tags); expect(updateShortUrlTags).toHaveBeenCalledWith(shortCode, tags);
expect(dispatch).toHaveBeenCalledTimes(2); expect(dispatch).toHaveBeenCalledTimes(2);
expect(dispatch).toHaveBeenNthCalledWith(1, { type: EDIT_SHORT_URL_TAGS_START }); expect(dispatch).toHaveBeenNthCalledWith(1, { type: EDIT_SHORT_URL_TAGS_START });
expect(dispatch).toHaveBeenNthCalledWith(2, { type: EDIT_SHORT_URL_TAGS, tags: normalizedTags, shortCode }); expect(dispatch).toHaveBeenNthCalledWith(2, { type: SHORT_URL_TAGS_EDITED, tags: normalizedTags, shortCode });
}); });
it('dispatches error on failure', async () => { it('dispatches error on failure', async () => {