Implemented dependency injection in all tag related components

This commit is contained in:
Alejandro Celaya 2018-12-18 11:28:15 +01:00
parent 79a0a5e4ea
commit 471322f4db
13 changed files with 75 additions and 81 deletions

View file

@ -20,7 +20,6 @@ import SearchBar from '../short-urls/SearchBar';
import { listShortUrls } from '../short-urls/reducers/shortUrlsList';
import ShortUrlsList from '../short-urls/ShortUrlsList';
import { resetShortUrlParams } from '../short-urls/reducers/shortUrlsListParams';
import Tag from '../tags/helpers/Tag';
import { ColorGenerator } from '../utils/ColorGenerator';
import { Storage } from '../utils/Storage';
import ShortUrlsRow from '../short-urls/helpers/ShortUrlsRow';
@ -41,6 +40,11 @@ import { deleteShortUrl, resetDeleteShortUrl, shortUrlDeleted } from '../short-u
import EditTagsModal from '../short-urls/helpers/EditTagsModal';
import { editShortUrlTags, resetShortUrlsTags, shortUrlTagsEdited } from '../short-urls/reducers/shortUrlTags';
import buildShlinkApiClient from '../api/ShlinkApiClientBuilder';
import TagCard from '../tags/TagCard';
import DeleteTagConfirmModal from '../tags/helpers/DeleteTagConfirmModal';
import { deleteTag, tagDeleted } from '../tags/reducers/tagDelete';
import EditTagModal from '../tags/helpers/EditTagModal';
import { editTag, tagEdited } from '../tags/reducers/tagEdit';
const bottle = new Bottle();
const { container } = bottle;
@ -78,7 +82,7 @@ bottle.serviceFactory('App', App, 'MainHeader', 'Home', 'MenuLayout', 'CreateSer
bottle.serviceFactory('ServersDropdown', ServersDropdown, 'ServersExporter');
bottle.decorator('ServersDropdown', connectDecorator([ 'servers', 'selectedServer' ], { listServers, selectServer }));
bottle.serviceFactory('TagsList', () => TagsList);
bottle.serviceFactory('TagsList', TagsList, 'TagCard');
bottle.decorator('TagsList', connectDecorator([ 'tagsList' ], { forceListTags, filterTags }));
bottle.serviceFactory('ShortUrls', ShortUrls, 'SearchBar', 'ShortUrlsList');
@ -86,7 +90,7 @@ bottle.decorator('ShortUrls', connect(
(state) => assoc('shortUrlsList', state.shortUrlsList.shortUrls, state.shortUrlsList)
));
bottle.serviceFactory('SearchBar', SearchBar, 'Tag');
bottle.serviceFactory('SearchBar', SearchBar, 'ColorGenerator');
bottle.decorator('SearchBar', connectDecorator([ 'shortUrlsListParams' ], { listShortUrls }));
bottle.serviceFactory('ShortUrlsList', ShortUrlsList, 'ShortUrlsRow');
@ -95,13 +99,11 @@ bottle.decorator('ShortUrlsList', connectDecorator(
{ listShortUrls, resetShortUrlParams }
));
bottle.serviceFactory('Tag', Tag, 'ColorGenerator');
bottle.constant('localStorage', global.localStorage);
bottle.service('Storage', Storage, 'localStorage');
bottle.service('ColorGenerator', ColorGenerator, 'Storage');
bottle.serviceFactory('ShortUrlsRow', ShortUrlsRow, 'Tag', 'ShortUrlsRowMenu');
bottle.serviceFactory('ShortUrlsRow', ShortUrlsRow, 'ShortUrlsRowMenu', 'ColorGenerator');
bottle.serviceFactory('ShortUrlsRowMenu', ShortUrlsRowMenu, 'DeleteShortUrlModal', 'EditTagsModal');
@ -151,4 +153,12 @@ bottle.serviceFactory('shortUrlTagsEdited', () => shortUrlTagsEdited);
bottle.serviceFactory('buildShlinkApiClient', buildShlinkApiClient, 'axios');
bottle.serviceFactory('TagCard', TagCard, 'DeleteTagConfirmModal', 'EditTagModal', 'ColorGenerator');
bottle.serviceFactory('DeleteTagConfirmModal', () => DeleteTagConfirmModal);
bottle.decorator('DeleteTagConfirmModal', connectDecorator([ 'tagDelete' ], { deleteTag, tagDeleted }));
bottle.serviceFactory('EditTagModal', EditTagModal, 'ColorGenerator');
bottle.decorator('EditTagModal', connectDecorator([ 'tagEdit' ], { editTag, tagEdited }));
export default container;

View file

@ -4,6 +4,7 @@ import React from 'react';
import { isEmpty } from 'ramda';
import PropTypes from 'prop-types';
import SearchField from '../utils/SearchField';
import Tag from '../tags/helpers/Tag';
import { shortUrlsListParamsType } from './reducers/shortUrlsListParams';
import './SearchBar.scss';
@ -12,7 +13,7 @@ const propTypes = {
shortUrlsListParams: shortUrlsListParamsType,
};
const SearchBar = (Tag) => {
const SearchBar = (colorGenerator) => {
const SearchBar = ({ listShortUrls, shortUrlsListParams }) => {
const selectedTags = shortUrlsListParams.tags || [];
@ -29,6 +30,7 @@ const SearchBar = (Tag) => {
 
{selectedTags.map((tag) => (
<Tag
colorGenerator={colorGenerator}
key={tag}
text={tag}
clearable

View file

@ -7,9 +7,10 @@ import { serverType } from '../../servers/prop-types';
import ExternalLink from '../../utils/ExternalLink';
import { shortUrlType } from '../reducers/shortUrlsList';
import { stateFlagTimeout } from '../../utils/utils';
import Tag from '../../tags/helpers/Tag';
import './ShortUrlsRow.scss';
const ShortUrlsRow = (Tag, ShortUrlsRowMenu) => class ShortUrlsRow extends React.Component {
const ShortUrlsRow = (ShortUrlsRowMenu, colorGenerator) => class ShortUrlsRow extends React.Component {
static propTypes = {
refreshList: PropTypes.func,
shortUrlsListParams: shortUrlsListParamsType,
@ -29,6 +30,7 @@ const ShortUrlsRow = (Tag, ShortUrlsRowMenu) => class ShortUrlsRow extends React
return tags.map((tag) => (
<Tag
colorGenerator={colorGenerator}
key={tag}
text={tag}
onClick={() => refreshList({ tags: [ ...selectedTags, tag ] })}

View file

@ -7,10 +7,8 @@ import React from 'react';
import { Link } from 'react-router-dom';
import TagBullet from './helpers/TagBullet';
import './TagCard.scss';
import DeleteTagConfirmModal from './helpers/DeleteTagConfirmModal';
import EditTagModal from './helpers/EditTagModal';
export default class TagCard extends React.Component {
const TagCard = (DeleteTagConfirmModal, EditTagModal, colorGenerator) => class TagCard extends React.Component {
static propTypes = {
tag: PropTypes.string,
currentServerId: PropTypes.string,
@ -35,7 +33,7 @@ export default class TagCard extends React.Component {
<FontAwesomeIcon icon={editIcon} />
</button>
<h5 className="tag-card__tag-title">
<TagBullet tag={tag} />
<TagBullet tag={tag} colorGenerator={colorGenerator} />
<Link to={`/server/${currentServerId}/list-short-urls/1?tag=${tag}`}>{tag}</Link>
</h5>
</CardBody>
@ -45,4 +43,6 @@ export default class TagCard extends React.Component {
</Card>
);
}
}
};
export default TagCard;

View file

@ -3,12 +3,11 @@ import { splitEvery } from 'ramda';
import PropTypes from 'prop-types';
import MuttedMessage from '../utils/MuttedMessage';
import SearchField from '../utils/SearchField';
import TagCard from './TagCard';
const { ceil } = Math;
const TAGS_GROUPS_AMOUNT = 4;
export default class TagsList extends React.Component {
const TagsList = (TagCard) => class TagsList extends React.Component {
static propTypes = {
filterTags: PropTypes.func,
forceListTags: PropTypes.func,
@ -80,4 +79,6 @@ export default class TagsList extends React.Component {
</div>
);
}
}
};
export default TagsList;

View file

@ -1,11 +1,9 @@
import React from 'react';
import { connect } from 'react-redux';
import { Modal, ModalBody, ModalFooter, ModalHeader } from 'reactstrap';
import PropTypes from 'prop-types';
import { pick } from 'ramda';
import { deleteTag, tagDeleted, tagDeleteType } from '../reducers/tagDelete';
import { tagDeleteType } from '../reducers/tagDelete';
export class DeleteTagConfirmModalComponent extends React.Component {
export default class DeleteTagConfirmModal extends React.Component {
static propTypes = {
tag: PropTypes.string.isRequired,
toggle: PropTypes.func.isRequired,
@ -67,10 +65,3 @@ export class DeleteTagConfirmModalComponent extends React.Component {
);
}
}
const DeleteTagConfirmModal = connect(
pick([ 'tagDelete' ]),
{ deleteTag, tagDeleted }
)(DeleteTagConfirmModalComponent);
export default DeleteTagConfirmModal;

View file

@ -1,31 +1,23 @@
import React from 'react';
import { connect } from 'react-redux';
import { Modal, ModalBody, ModalFooter, ModalHeader, Popover } from 'reactstrap';
import { pick } from 'ramda';
import { ChromePicker } from 'react-color';
import colorIcon from '@fortawesome/fontawesome-free-solid/faPalette';
import FontAwesomeIcon from '@fortawesome/react-fontawesome';
import PropTypes from 'prop-types';
import colorGenerator, { colorGeneratorType } from '../../utils/ColorGenerator';
import { editTag, tagEdited } from '../reducers/tagEdit';
import './EditTagModal.scss';
export class EditTagModalComponent extends React.Component {
const EditTagModal = ({ getColorForKey }) => class EditTagModal extends React.Component {
static propTypes = {
tag: PropTypes.string,
editTag: PropTypes.func,
toggle: PropTypes.func,
tagEdited: PropTypes.func,
colorGenerator: colorGeneratorType,
isOpen: PropTypes.bool,
tagEdit: PropTypes.shape({
error: PropTypes.bool,
editing: PropTypes.bool,
}),
};
static defaultProps = {
colorGenerator,
};
saveTag = (e) => {
e.preventDefault();
@ -53,12 +45,12 @@ export class EditTagModalComponent extends React.Component {
constructor(props) {
super(props);
const { colorGenerator, tag } = props;
const { tag } = props;
this.state = {
showColorPicker: false,
tag,
color: colorGenerator.getColorForKey(tag),
color: getColorForKey(tag),
};
}
@ -131,8 +123,6 @@ export class EditTagModalComponent extends React.Component {
</Modal>
);
}
}
const EditTagModal = connect(pick([ 'tagEdit' ]), { editTag, tagEdited })(EditTagModalComponent);
};
export default EditTagModal;

View file

@ -1,36 +1,35 @@
import React from 'react';
import PropTypes from 'prop-types';
import './Tag.scss';
import { colorGeneratorType } from '../../utils/ColorGenerator';
const propTypes = {
text: PropTypes.string,
children: PropTypes.node,
clearable: PropTypes.bool,
colorGenerator: colorGeneratorType,
onClick: PropTypes.func,
onClose: PropTypes.func,
};
const Tag = (colorGenerator) => {
const Tag = ({
text,
children,
clearable,
onClick = () => {},
onClose = () => {},
}) => (
<span
className="badge tag"
style={{ backgroundColor: colorGenerator.getColorForKey(text), cursor: clearable ? 'auto' : 'pointer' }}
onClick={onClick}
>
{children || text}
{clearable && <span className="close tag__close-selected-tag" onClick={onClose}>&times;</span>}
</span>
);
const Tag = ({
text,
children,
clearable,
colorGenerator,
onClick = () => {},
onClose = () => {},
}) => (
<span
className="badge tag"
style={{ backgroundColor: colorGenerator.getColorForKey(text), cursor: clearable ? 'auto' : 'pointer' }}
onClick={onClick}
>
{children || text}
{clearable && <span className="close tag__close-selected-tag" onClick={onClose}>&times;</span>}
</span>
);
Tag.propTypes = propTypes;
return Tag;
};
Tag.propTypes = propTypes;
export default Tag;

View file

@ -1,24 +1,20 @@
import React from 'react';
import * as PropTypes from 'prop-types';
import colorGenerator, { colorGeneratorType } from '../../utils/ColorGenerator';
import { colorGeneratorType } from '../../utils/ColorGenerator';
import './TagBullet.scss';
const propTypes = {
tag: PropTypes.string.isRequired,
colorGenerator: colorGeneratorType,
};
const defaultProps = {
colorGenerator,
};
export default function TagBullet({ tag, colorGenerator }) {
return (
<div
style={{ backgroundColor: colorGenerator.getColorForKey(tag) }}
className="tag-bullet"
/>
);
}
const TagBullet = ({ tag, colorGenerator }) => (
<div
style={{ backgroundColor: colorGenerator.getColorForKey(tag) }}
className="tag-bullet"
/>
);
TagBullet.propTypes = propTypes;
TagBullet.defaultProps = defaultProps;
export default TagBullet;

View file

@ -54,7 +54,7 @@ const TagsSelector = (colorGenerator) => class TagsSelector extends React.Compon
getSuggestionValue={(suggestion) => suggestion}
renderSuggestion={(suggestion) => (
<React.Fragment>
<TagBullet tag={suggestion} />
<TagBullet tag={suggestion} colorGenerator={colorGenerator} />
{suggestion}
</React.Fragment>
)}

View file

@ -3,12 +3,12 @@ import { shallow } from 'enzyme';
import sinon from 'sinon';
import searchBarCreator from '../../src/short-urls/SearchBar';
import SearchField from '../../src/utils/SearchField';
import Tag from '../../src/tags/helpers/Tag';
describe('<SearchBar />', () => {
let wrapper;
const listShortUrlsMock = sinon.spy();
const Tag = () => '';
const SearchBar = searchBarCreator(Tag);
const SearchBar = searchBarCreator({});
afterEach(() => {
listShortUrlsMock.resetHistory();

View file

@ -1,13 +1,15 @@
import React from 'react';
import { shallow } from 'enzyme';
import { Link } from 'react-router-dom';
import TagCard from '../../src/tags/TagCard';
import createTagCard from '../../src/tags/TagCard';
import TagBullet from '../../src/tags/helpers/TagBullet';
describe('<TagCard />', () => {
let wrapper;
beforeEach(() => {
const TagCard = createTagCard(() => '', () => '', {});
wrapper = shallow(<TagCard tag="ssr" currentServerId="1" />);
});
afterEach(() => wrapper.unmount());

View file

@ -2,16 +2,17 @@ import React from 'react';
import { shallow } from 'enzyme';
import { identity, range } from 'ramda';
import * as sinon from 'sinon';
import TagsList from '../../src/tags/TagsList';
import createTagsList from '../../src/tags/TagsList';
import MuttedMessage from '../../src/utils/MuttedMessage';
import TagCard from '../../src/tags/TagCard';
import SearchField from '../../src/utils/SearchField';
describe('<TagsList />', () => {
let wrapper;
const filterTags = sinon.spy();
const TagCard = () => '';
const createWrapper = (tagsList) => {
const params = { serverId: '1' };
const TagsList = createTagsList(TagCard);
wrapper = shallow(
<TagsList forceListTags={identity} filterTags={filterTags} match={{ params }} tagsList={tagsList} />