From f8ea1ae3d55d21bc0b089e3bfa2b15f9e9d5a0a7 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 30 Aug 2020 20:48:09 +0200 Subject: [PATCH] Migrated remaining tags-related elements to TS --- src/servers/prop-types/index.js | 21 ----- src/tags/TagCard.js | 84 ----------------- src/tags/TagCard.tsx | 82 +++++++++++++++++ src/tags/TagsList.js | 91 ------------------- src/tags/TagsList.tsx | 85 +++++++++++++++++ src/tags/reducers/tagsList.ts | 13 --- .../{TagCard.test.js => TagCard.test.tsx} | 19 +++- .../{TagsList.test.js => TagsList.test.tsx} | 28 +++--- 8 files changed, 198 insertions(+), 225 deletions(-) delete mode 100644 src/servers/prop-types/index.js delete mode 100644 src/tags/TagCard.js create mode 100644 src/tags/TagCard.tsx delete mode 100644 src/tags/TagsList.js create mode 100644 src/tags/TagsList.tsx rename test/tags/{TagCard.test.js => TagCard.test.tsx} (77%) rename test/tags/{TagsList.test.js => TagsList.test.tsx} (75%) diff --git a/src/servers/prop-types/index.js b/src/servers/prop-types/index.js deleted file mode 100644 index f86d1744..00000000 --- a/src/servers/prop-types/index.js +++ /dev/null @@ -1,21 +0,0 @@ -import PropTypes from 'prop-types'; - -const regularServerType = PropTypes.shape({ - id: PropTypes.string, - name: PropTypes.string, - url: PropTypes.string, - apiKey: PropTypes.string, - version: PropTypes.string, - printableVersion: PropTypes.string, - serverNotReachable: PropTypes.bool, -}); - -const notFoundServerType = PropTypes.shape({ - serverNotFound: PropTypes.bool.isRequired, -}); - -/** @deprecated Use SelectedServer type instead */ -export const serverType = PropTypes.oneOfType([ - regularServerType, - notFoundServerType, -]); diff --git a/src/tags/TagCard.js b/src/tags/TagCard.js deleted file mode 100644 index 9ec68982..00000000 --- a/src/tags/TagCard.js +++ /dev/null @@ -1,84 +0,0 @@ -import { Card, CardHeader, CardBody, Button, Collapse } from 'reactstrap'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { faTrash as deleteIcon, faPencilAlt as editIcon, faLink, faEye } from '@fortawesome/free-solid-svg-icons'; -import PropTypes from 'prop-types'; -import React from 'react'; -import { Link } from 'react-router-dom'; -import { serverType } from '../servers/prop-types'; -import { prettify } from '../utils/helpers/numbers'; -import { useToggle } from '../utils/helpers/hooks'; -import TagBullet from './helpers/TagBullet'; -import './TagCard.scss'; - -const propTypes = { - tag: PropTypes.string, - tagStats: PropTypes.shape({ - shortUrlsCount: PropTypes.number, - visitsCount: PropTypes.number, - }), - selectedServer: serverType, - displayed: PropTypes.bool, - toggle: PropTypes.func, -}; - -const TagCard = (DeleteTagConfirmModal, EditTagModal, ForServerVersion, colorGenerator) => { - const TagCardComp = ({ tag, tagStats, selectedServer, displayed, toggle }) => { - const [ isDeleteModalOpen, toggleDelete ] = useToggle(); - const [ isEditModalOpen, toggleEdit ] = useToggle(); - - const { id } = selectedServer; - const shortUrlsLink = `/server/${id}/list-short-urls/1?tag=${tag}`; - - return ( - - - - -
- - - {tag} - - - {tag} - -
-
- - {tagStats && ( - - - - Short URLs - {prettify(tagStats.shortUrlsCount)} - - - Visits - {prettify(tagStats.visitsCount)} - - - - )} - - - -
- ); - }; - - TagCardComp.propTypes = propTypes; - - return TagCardComp; -}; - -export default TagCard; diff --git a/src/tags/TagCard.tsx b/src/tags/TagCard.tsx new file mode 100644 index 00000000..6fe774f9 --- /dev/null +++ b/src/tags/TagCard.tsx @@ -0,0 +1,82 @@ +import { Card, CardHeader, CardBody, Button, Collapse } from 'reactstrap'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faTrash as deleteIcon, faPencilAlt as editIcon, faLink, faEye } from '@fortawesome/free-solid-svg-icons'; +import React, { FC } from 'react'; +import { Link } from 'react-router-dom'; +import { prettify } from '../utils/helpers/numbers'; +import { useToggle } from '../utils/helpers/hooks'; +import { Versions } from '../utils/helpers/version'; +import ColorGenerator from '../utils/services/ColorGenerator'; +import { isServerWithId, SelectedServer } from '../servers/data'; +import TagBullet from './helpers/TagBullet'; +import { TagModalProps, TagStats } from './data'; +import './TagCard.scss'; + +export interface TagCardProps { + tag: string; + tagStats?: TagStats; + selectedServer: SelectedServer; + displayed: boolean; + toggle: () => void; +} + +const TagCard = ( + DeleteTagConfirmModal: FC, + EditTagModal: FC, + ForServerVersion: FC, + colorGenerator: ColorGenerator, +) => ({ tag, tagStats, selectedServer, displayed, toggle }: TagCardProps) => { + const [ isDeleteModalOpen, toggleDelete ] = useToggle(); + const [ isEditModalOpen, toggleEdit ] = useToggle(); + + const serverId = isServerWithId(selectedServer) ? selectedServer.id : ''; + const shortUrlsLink = `/server/${serverId}/list-short-urls/1?tag=${tag}`; + + return ( + + + + +
+ + + {tag} + + + {tag} + +
+
+ + {tagStats && ( + + + + Short URLs + {prettify(tagStats.shortUrlsCount)} + + + Visits + {prettify(tagStats.visitsCount)} + + + + )} + + + +
+ ); +}; + +export default TagCard; diff --git a/src/tags/TagsList.js b/src/tags/TagsList.js deleted file mode 100644 index a923bf1b..00000000 --- a/src/tags/TagsList.js +++ /dev/null @@ -1,91 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import { splitEvery } from 'ramda'; -import PropTypes from 'prop-types'; -import Message from '../utils/Message'; -import SearchField from '../utils/SearchField'; -import { serverType } from '../servers/prop-types'; -import { MercureInfoType } from '../mercure/reducers/mercureInfo'; -import { useMercureTopicBinding } from '../mercure/helpers'; -import { TagsListType } from './reducers/tagsList'; - -const { ceil } = Math; -const TAGS_GROUPS_AMOUNT = 4; - -const propTypes = { - filterTags: PropTypes.func, - forceListTags: PropTypes.func, - tagsList: TagsListType, - selectedServer: serverType, - createNewVisit: PropTypes.func, - loadMercureInfo: PropTypes.func, - mercureInfo: MercureInfoType, -}; - -const TagsList = (TagCard) => { - const TagListComp = ( - { filterTags, forceListTags, tagsList, selectedServer, createNewVisit, loadMercureInfo, mercureInfo }, - ) => { - const [ displayedTag, setDisplayedTag ] = useState(); - - useEffect(() => { - forceListTags(); - }, []); - useMercureTopicBinding(mercureInfo, 'https://shlink.io/new-visit', createNewVisit, loadMercureInfo); - - const renderContent = () => { - if (tagsList.loading) { - return ; - } - - if (tagsList.error) { - return ( -
-
Error loading tags :(
-
- ); - } - - const tagsCount = tagsList.filteredTags.length; - - if (tagsCount < 1) { - return No tags found; - } - - const tagsGroups = splitEvery(ceil(tagsCount / TAGS_GROUPS_AMOUNT), tagsList.filteredTags); - - return ( - - {tagsGroups.map((group, index) => ( -
- {group.map((tag) => ( - setDisplayedTag(displayedTag !== tag ? tag : undefined)} - /> - ))} -
- ))} -
- ); - }; - - return ( - - {!tagsList.loading && } -
- {renderContent()} -
-
- ); - }; - - TagListComp.propTypes = propTypes; - - return TagListComp; -}; - -export default TagsList; diff --git a/src/tags/TagsList.tsx b/src/tags/TagsList.tsx new file mode 100644 index 00000000..0da20b1b --- /dev/null +++ b/src/tags/TagsList.tsx @@ -0,0 +1,85 @@ +import React, { FC, useEffect, useState } from 'react'; +import { splitEvery } from 'ramda'; +import Message from '../utils/Message'; +import SearchField from '../utils/SearchField'; +import { MercureInfo } from '../mercure/reducers/mercureInfo'; +import { useMercureTopicBinding } from '../mercure/helpers'; +import { SelectedServer } from '../servers/data'; +import { TagsList as TagsListState } from './reducers/tagsList'; +import { TagCardProps } from './TagCard'; + +const { ceil } = Math; +const TAGS_GROUPS_AMOUNT = 4; + +export interface TagsListProps { + filterTags: (searchTerm: string) => void; + forceListTags: Function; + tagsList: TagsListState; + selectedServer: SelectedServer; + createNewVisit: () => void; + loadMercureInfo: Function; + mercureInfo: MercureInfo; +} + +const TagsList = (TagCard: FC) => ( + { filterTags, forceListTags, tagsList, selectedServer, createNewVisit, loadMercureInfo, mercureInfo }: TagsListProps, +) => { + const [ displayedTag, setDisplayedTag ] = useState(); + + useEffect(() => { + forceListTags(); + }, []); + useMercureTopicBinding(mercureInfo, 'https://shlink.io/new-visit', createNewVisit, loadMercureInfo); + + const renderContent = () => { + if (tagsList.loading) { + return ; + } + + if (tagsList.error) { + return ( +
+
Error loading tags :(
+
+ ); + } + + const tagsCount = tagsList.filteredTags.length; + + if (tagsCount < 1) { + return No tags found; + } + + const tagsGroups = splitEvery(ceil(tagsCount / TAGS_GROUPS_AMOUNT), tagsList.filteredTags); + + return ( + + {tagsGroups.map((group, index) => ( +
+ {group.map((tag) => ( + setDisplayedTag(displayedTag !== tag ? tag : undefined)} + /> + ))} +
+ ))} +
+ ); + }; + + return ( + + {!tagsList.loading && } +
+ {renderContent()} +
+
+ ); +}; + +export default TagsList; diff --git a/src/tags/reducers/tagsList.ts b/src/tags/reducers/tagsList.ts index 56975c8f..ea6a6711 100644 --- a/src/tags/reducers/tagsList.ts +++ b/src/tags/reducers/tagsList.ts @@ -1,5 +1,4 @@ import { isEmpty, reject } from 'ramda'; -import PropTypes from 'prop-types'; import { Action, Dispatch } from 'redux'; import { CREATE_VISIT, CreateVisitAction } from '../../visits/reducers/visitCreation'; import { buildReducer } from '../../utils/helpers/redux'; @@ -17,18 +16,6 @@ export const LIST_TAGS = 'shlink/tagsList/LIST_TAGS'; export const FILTER_TAGS = 'shlink/tagsList/FILTER_TAGS'; /* eslint-enable padding-line-between-statements */ -/** @deprecated Use TagsList interface instead */ -export const TagsListType = PropTypes.shape({ - tags: PropTypes.arrayOf(PropTypes.string), - filteredTags: PropTypes.arrayOf(PropTypes.string), - stats: PropTypes.objectOf(PropTypes.shape({ - shortUrlsCount: PropTypes.number, - visitsCount: PropTypes.number, - })), // Record - loading: PropTypes.bool, - error: PropTypes.bool, -}); - type TagsStatsMap = Record; export interface TagsList { diff --git a/test/tags/TagCard.test.js b/test/tags/TagCard.test.tsx similarity index 77% rename from test/tags/TagCard.test.js rename to test/tags/TagCard.test.tsx index 6d806752..e1e3bc6f 100644 --- a/test/tags/TagCard.test.js +++ b/test/tags/TagCard.test.tsx @@ -1,11 +1,14 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import { shallow, ShallowWrapper } from 'enzyme'; import { Link } from 'react-router-dom'; +import { Mock } from 'ts-mockery'; import createTagCard from '../../src/tags/TagCard'; import TagBullet from '../../src/tags/helpers/TagBullet'; +import ColorGenerator from '../../src/utils/services/ColorGenerator'; +import { ReachableServer } from '../../src/servers/data'; describe('', () => { - let wrapper; + let wrapper: ShallowWrapper; const tagStats = { shortUrlsCount: 48, visitsCount: 23257, @@ -14,9 +17,17 @@ describe('', () => { const EditTagModal = jest.fn(); beforeEach(() => { - const TagCard = createTagCard(DeleteTagConfirmModal, EditTagModal, () => '', {}); + const TagCard = createTagCard(DeleteTagConfirmModal, EditTagModal, () => null, Mock.all()); - wrapper = shallow(); + wrapper = shallow( + ({ id: '1' })} + tagStats={tagStats} + displayed={true} + toggle={() => {}} + />, + ); }); afterEach(() => wrapper.unmount()); diff --git a/test/tags/TagsList.test.js b/test/tags/TagsList.test.tsx similarity index 75% rename from test/tags/TagsList.test.js rename to test/tags/TagsList.test.tsx index cd20409e..4cfe8790 100644 --- a/test/tags/TagsList.test.js +++ b/test/tags/TagsList.test.tsx @@ -1,30 +1,34 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import { shallow, ShallowWrapper } from 'enzyme'; import { identity } from 'ramda'; -import createTagsList from '../../src/tags/TagsList'; +import { Mock } from 'ts-mockery'; +import createTagsList, { TagsListProps } from '../../src/tags/TagsList'; import Message from '../../src/utils/Message'; import SearchField from '../../src/utils/SearchField'; import { rangeOf } from '../../src/utils/utils'; +import { TagsList } from '../../src/tags/reducers/tagsList'; describe('', () => { - let wrapper; + let wrapper: ShallowWrapper; const filterTags = jest.fn(); - const TagCard = () => ''; - const createWrapper = (tagsList) => { - const params = { serverId: '1' }; - const TagsList = createTagsList(TagCard); + const TagCard = () => null; + const createWrapper = (tagsList: Partial) => { + const TagsListComp = createTagsList(TagCard); wrapper = shallow( - , + ()} + forceListTags={identity} + filterTags={filterTags} + tagsList={Mock.of(tagsList)} + />, ); return wrapper; }; - afterEach(() => { - wrapper && wrapper.unmount(); - filterTags.mockReset(); - }); + afterEach(() => wrapper?.unmount()); + afterEach(jest.clearAllMocks); it('shows a loading message when tags are being loaded', () => { const wrapper = createWrapper({ loading: true });