From 18883caa6dcf36ea828617a70c5d190b55634885 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 30 Aug 2020 20:31:31 +0200 Subject: [PATCH] Migrated tags helpers to TS --- package-lock.json | 43 +++++++++- package.json | 5 +- src/short-urls/CreateShortUrl.tsx | 5 +- src/short-urls/helpers/EditTagsModal.tsx | 3 +- src/tags/data/index.ts | 10 +++ ...firmModal.js => DeleteTagConfirmModal.tsx} | 23 +++-- src/tags/helpers/EditTagModal.js | 82 ------------------ src/tags/helpers/EditTagModal.tsx | 74 ++++++++++++++++ src/tags/helpers/Tag.js | 35 -------- src/tags/helpers/Tag.tsx | 24 ++++++ src/tags/helpers/TagBullet.js | 20 ----- src/tags/helpers/TagBullet.tsx | 17 ++++ src/tags/helpers/TagsSelector.js | 84 ------------------- src/tags/helpers/TagsSelector.tsx | 80 ++++++++++++++++++ src/tags/reducers/tagDelete.ts | 7 -- src/tags/reducers/tagsList.ts | 13 +-- ...test.js => DeleteTagConfirmModal.test.tsx} | 16 ++-- 17 files changed, 279 insertions(+), 262 deletions(-) create mode 100644 src/tags/data/index.ts rename src/tags/helpers/{DeleteTagConfirmModal.js => DeleteTagConfirmModal.tsx} (69%) delete mode 100644 src/tags/helpers/EditTagModal.js create mode 100644 src/tags/helpers/EditTagModal.tsx delete mode 100644 src/tags/helpers/Tag.js create mode 100644 src/tags/helpers/Tag.tsx delete mode 100644 src/tags/helpers/TagBullet.js create mode 100644 src/tags/helpers/TagBullet.tsx delete mode 100644 src/tags/helpers/TagsSelector.js create mode 100644 src/tags/helpers/TagsSelector.tsx rename test/tags/helpers/{DeleteTagConfirmModal.test.js => DeleteTagConfirmModal.test.tsx} (86%) diff --git a/package-lock.json b/package-lock.json index ee22ba94..7187451f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3384,6 +3384,25 @@ "csstype": "^3.0.2" } }, + "@types/react-autosuggest": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@types/react-autosuggest/-/react-autosuggest-10.0.0.tgz", + "integrity": "sha512-lcu3L3158I8DlgicRpyIuYzIc+E70RgXcVI8uJ+nw5JnAlhNht7dE1hJ0Q0x2A5VVBMOi5dcCQACqi5wWfm8ow==", + "dev": true, + "requires": { + "@types/react": "*" + } + }, + "@types/react-color": { + "version": "2.17.4", + "resolved": "https://registry.npmjs.org/@types/react-color/-/react-color-2.17.4.tgz", + "integrity": "sha512-pAO3+7uHoESg5QMqjnGjw9F7sALjEZsaU41yGiUZbmHiJMoSXH1UklFJ1bZkwhYskaJgiY+AS6wirl17yBh5GA==", + "dev": true, + "requires": { + "@types/react": "*", + "@types/reactcss": "*" + } + }, "@types/react-copy-to-clipboard": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/@types/react-copy-to-clipboard/-/react-copy-to-clipboard-4.3.0.tgz", @@ -3457,6 +3476,24 @@ "@types/react-router": "*" } }, + "@types/react-tagsinput": { + "version": "3.19.7", + "resolved": "https://registry.npmjs.org/@types/react-tagsinput/-/react-tagsinput-3.19.7.tgz", + "integrity": "sha512-yj/3iFBLoan/0vzXMxC9zGhO1uJ89qjQldekf0o3fX4mYdaAPW/VbP921fsyYt6PdHmJ9UMo+kERSMzUAml1xQ==", + "dev": true, + "requires": { + "@types/react": "*" + } + }, + "@types/reactcss": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@types/reactcss/-/reactcss-1.2.3.tgz", + "integrity": "sha512-d2gQQ0IL6hXLnoRfVYZukQNWHuVsE75DzFTLPUuyyEhJS8G2VvlE+qfQQ91SJjaMqlURRCNIsX7Jcsw6cEuJlA==", + "dev": true, + "requires": { + "@types/react": "*" + } + }, "@types/reactstrap": { "version": "8.5.1", "resolved": "https://registry.npmjs.org/@types/reactstrap/-/reactstrap-8.5.1.tgz", @@ -18146,9 +18183,9 @@ } }, "react-color": { - "version": "2.17.3", - "resolved": "https://registry.npmjs.org/react-color/-/react-color-2.17.3.tgz", - "integrity": "sha512-1dtO8LqAVotPIChlmo6kLtFS1FP89ll8/OiA8EcFRDR+ntcK+0ukJgByuIQHRtzvigf26dV5HklnxDIvhON9VQ==", + "version": "2.18.1", + "resolved": "https://registry.npmjs.org/react-color/-/react-color-2.18.1.tgz", + "integrity": "sha512-X5XpyJS6ncplZs74ak0JJoqPi+33Nzpv5RYWWxn17bslih+X7OlgmfpmGC1fNvdkK7/SGWYf1JJdn7D2n5gSuQ==", "requires": { "@icons/material": "^0.2.4", "lodash": "^4.17.11", diff --git a/package.json b/package.json index 4139b384..32b42d36 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,7 @@ "react": "^16.13.1", "react-autosuggest": "^9.4.3", "react-chartjs-2": "^2.8.0", - "react-color": "^2.17.3", + "react-color": "^2.17.4", "react-copy-to-clipboard": "^5.0.1", "react-datepicker": "~1.5.0", "react-dom": "^16.13.1", @@ -83,11 +83,14 @@ "@types/qs": "^6.9.4", "@types/ramda": "^0.27.14", "@types/react": "^16.9.46", + "@types/react-autosuggest": "^10.0.0", + "@types/react-color": "^2.17.4", "@types/react-copy-to-clipboard": "^4.3.0", "@types/react-datepicker": "~1.8.0", "@types/react-dom": "^16.9.8", "@types/react-redux": "^7.1.9", "@types/react-router-dom": "^5.1.5", + "@types/react-tagsinput": "^3.19.7", "@types/reactstrap": "^8.5.1", "@types/uuid": "^8.3.0", "adm-zip": "^0.4.13", diff --git a/src/short-urls/CreateShortUrl.tsx b/src/short-urls/CreateShortUrl.tsx index 82780fd3..9ade84ac 100644 --- a/src/short-urls/CreateShortUrl.tsx +++ b/src/short-urls/CreateShortUrl.tsx @@ -12,6 +12,7 @@ import { handleEventPreventingDefault, hasValue } from '../utils/utils'; import { useToggle } from '../utils/helpers/hooks'; import { isReachableServer, SelectedServer } from '../servers/data'; import { formatIsoDate } from '../utils/helpers/date'; +import { TagsSelectorProps } from '../tags/helpers/TagsSelector'; import { ShortUrlData } from './data'; import { ShortUrlCreation } from './reducers/shortUrlCreation'; import UseExistingIfFoundInfoIcon from './UseExistingIfFoundInfoIcon'; @@ -42,7 +43,7 @@ type NonDateFields = 'longUrl' | 'customSlug' | 'shortCodeLength' | 'domain' | ' type DateFields = 'validSince' | 'validUntil'; const CreateShortUrl = ( - TagsSelector: FC, + TagsSelector: FC, CreateShortUrlResult: FC, ForServerVersion: FC, ) => ({ createShortUrl, shortUrlCreationResult, resetCreateShortUrl, selectedServer }: CreateShortUrlProps) => { @@ -103,7 +104,7 @@ const CreateShortUrl = (
- +
diff --git a/src/short-urls/helpers/EditTagsModal.tsx b/src/short-urls/helpers/EditTagsModal.tsx index be7cf4ee..6c38e662 100644 --- a/src/short-urls/helpers/EditTagsModal.tsx +++ b/src/short-urls/helpers/EditTagsModal.tsx @@ -4,6 +4,7 @@ import { ExternalLink } from 'react-external-link'; import { ShortUrlTags } from '../reducers/shortUrlTags'; import { ShortUrlModalProps } from '../data'; import { OptionalString } from '../../utils/utils'; +import { TagsSelectorProps } from '../../tags/helpers/TagsSelector'; interface EditTagsModalProps extends ShortUrlModalProps { shortUrlTags: ShortUrlTags; @@ -11,7 +12,7 @@ interface EditTagsModalProps extends ShortUrlModalProps { resetShortUrlsTags: () => void; } -const EditTagsModal = (TagsSelector: FC) => ( // TODO Use TagsSelector type when available +const EditTagsModal = (TagsSelector: FC) => ( { isOpen, toggle, shortUrl, shortUrlTags, editShortUrlTags, resetShortUrlsTags }: EditTagsModalProps, ) => { const [ selectedTags, setSelectedTags ] = useState(shortUrl.tags || []); diff --git a/src/tags/data/index.ts b/src/tags/data/index.ts new file mode 100644 index 00000000..2a1e1aa8 --- /dev/null +++ b/src/tags/data/index.ts @@ -0,0 +1,10 @@ +export interface TagStats { + shortUrlsCount: number; + visitsCount: number; +} + +export interface TagModalProps { + tag: string; + isOpen: boolean; + toggle: () => void; +} diff --git a/src/tags/helpers/DeleteTagConfirmModal.js b/src/tags/helpers/DeleteTagConfirmModal.tsx similarity index 69% rename from src/tags/helpers/DeleteTagConfirmModal.js rename to src/tags/helpers/DeleteTagConfirmModal.tsx index 0cb8664e..da1c8720 100644 --- a/src/tags/helpers/DeleteTagConfirmModal.js +++ b/src/tags/helpers/DeleteTagConfirmModal.tsx @@ -1,18 +1,17 @@ import React from 'react'; import { Modal, ModalBody, ModalFooter, ModalHeader } from 'reactstrap'; -import PropTypes from 'prop-types'; -import { tagDeleteType } from '../reducers/tagDelete'; +import { TagDeletion } from '../reducers/tagDelete'; +import { TagModalProps } from '../data'; -const propTypes = { - tag: PropTypes.string.isRequired, - toggle: PropTypes.func.isRequired, - isOpen: PropTypes.bool.isRequired, - deleteTag: PropTypes.func, - tagDelete: tagDeleteType, - tagDeleted: PropTypes.func, -}; +interface DeleteTagConfirmModalProps extends TagModalProps { + deleteTag: (tag: string) => Promise; + tagDeleted: (tag: string) => void; + tagDelete: TagDeletion; +} -const DeleteTagConfirmModal = ({ tag, toggle, isOpen, deleteTag, tagDelete, tagDeleted }) => { +const DeleteTagConfirmModal = ( + { tag, toggle, isOpen, deleteTag, tagDelete, tagDeleted }: DeleteTagConfirmModalProps, +) => { const doDelete = async () => { await deleteTag(tag); tagDeleted(tag); @@ -42,6 +41,4 @@ const DeleteTagConfirmModal = ({ tag, toggle, isOpen, deleteTag, tagDelete, tagD ); }; -DeleteTagConfirmModal.propTypes = propTypes; - export default DeleteTagConfirmModal; diff --git a/src/tags/helpers/EditTagModal.js b/src/tags/helpers/EditTagModal.js deleted file mode 100644 index f6eaf456..00000000 --- a/src/tags/helpers/EditTagModal.js +++ /dev/null @@ -1,82 +0,0 @@ -import React, { useState } from 'react'; -import { Modal, ModalBody, ModalFooter, ModalHeader, Popover } from 'reactstrap'; -import { ChromePicker } from 'react-color'; -import { faPalette as colorIcon } from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import PropTypes from 'prop-types'; -import './EditTagModal.scss'; -import { useToggle } from '../../utils/helpers/hooks'; -import { handleEventPreventingDefault } from '../../utils/utils'; - -const propTypes = { - tag: PropTypes.string, - editTag: PropTypes.func, - toggle: PropTypes.func, - tagEdited: PropTypes.func, - isOpen: PropTypes.bool, - tagEdit: PropTypes.shape({ - error: PropTypes.bool, - editing: PropTypes.bool, - }), -}; - -const EditTagModal = ({ getColorForKey }) => { - const EditTagModalComp = ({ tag, editTag, toggle, tagEdited, isOpen, tagEdit }) => { - const [ newTagName, setNewTagName ] = useState(tag); - const [ color, setColor ] = useState(getColorForKey(tag)); - const [ showColorPicker, toggleColorPicker ] = useToggle(); - const saveTag = handleEventPreventingDefault(() => editTag(tag, newTagName, color) - .then(() => tagEdited(tag, newTagName, color)) - .then(toggle) - .catch(() => {})); - - return ( - -
- Edit tag - -
-
-
- -
-
- - setColor(hex)} /> - - setNewTagName(e.target.value)} - /> -
- - {tagEdit.error && ( -
- Something went wrong while editing the tag :( -
- )} -
- - - - -
-
- ); - }; - - EditTagModalComp.propTypes = propTypes; - - return EditTagModalComp; -}; - -export default EditTagModal; diff --git a/src/tags/helpers/EditTagModal.tsx b/src/tags/helpers/EditTagModal.tsx new file mode 100644 index 00000000..7b8f2cd3 --- /dev/null +++ b/src/tags/helpers/EditTagModal.tsx @@ -0,0 +1,74 @@ +import React, { useState } from 'react'; +import { Modal, ModalBody, ModalFooter, ModalHeader, Popover } from 'reactstrap'; +import { ChromePicker } from 'react-color'; +import { faPalette as colorIcon } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { useToggle } from '../../utils/helpers/hooks'; +import { handleEventPreventingDefault } from '../../utils/utils'; +import ColorGenerator from '../../utils/services/ColorGenerator'; +import { TagModalProps } from '../data'; +import { TagEdition } from '../reducers/tagEdit'; +import './EditTagModal.scss'; + +interface EditTagModalProps extends TagModalProps { + tagEdit: TagEdition; + editTag: (oldName: string, newName: string, color: string) => Promise; + tagEdited: (oldName: string, newName: string, color: string) => void; +} + +const EditTagModal = ({ getColorForKey }: ColorGenerator) => ( + { tag, editTag, toggle, tagEdited, isOpen, tagEdit }: EditTagModalProps, +) => { + const [ newTagName, setNewTagName ] = useState(tag); + const [ color, setColor ] = useState(getColorForKey(tag)); + const [ showColorPicker, toggleColorPicker ] = useToggle(); + const saveTag = handleEventPreventingDefault(async () => editTag(tag, newTagName, color) + .then(() => tagEdited(tag, newTagName, color)) + .then(toggle) + .catch(() => {})); + + return ( + +
+ Edit tag + +
+
+
+ +
+
+ + setColor(hex)} /> + + setNewTagName(e.target.value)} + /> +
+ + {tagEdit.error && ( +
+ Something went wrong while editing the tag :( +
+ )} +
+ + + + +
+
+ ); +}; + +export default EditTagModal; diff --git a/src/tags/helpers/Tag.js b/src/tags/helpers/Tag.js deleted file mode 100644 index e2a29e9a..00000000 --- a/src/tags/helpers/Tag.js +++ /dev/null @@ -1,35 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { colorGeneratorType } from '../../utils/services/ColorGenerator'; -import './Tag.scss'; - -const propTypes = { - text: PropTypes.string, - children: PropTypes.node, - clearable: PropTypes.bool, - colorGenerator: colorGeneratorType, - onClick: PropTypes.func, - onClose: PropTypes.func, -}; - -const Tag = ({ - text, - children, - clearable, - colorGenerator, - onClick, - onClose, -}) => ( - - {children || text} - {clearable && ×} - -); - -Tag.propTypes = propTypes; - -export default Tag; diff --git a/src/tags/helpers/Tag.tsx b/src/tags/helpers/Tag.tsx new file mode 100644 index 00000000..e0edcc0f --- /dev/null +++ b/src/tags/helpers/Tag.tsx @@ -0,0 +1,24 @@ +import React, { FC } from 'react'; +import ColorGenerator from '../../utils/services/ColorGenerator'; +import './Tag.scss'; + +interface TagProps { + colorGenerator: ColorGenerator; + text: string; + clearable?: boolean; + onClick?: () => void; + onClose?: () => void; +} + +const Tag: FC = ({ text, children, clearable, colorGenerator, onClick, onClose }) => ( + + {children ?? text} + {clearable && ×} + +); + +export default Tag; diff --git a/src/tags/helpers/TagBullet.js b/src/tags/helpers/TagBullet.js deleted file mode 100644 index 896eaf8f..00000000 --- a/src/tags/helpers/TagBullet.js +++ /dev/null @@ -1,20 +0,0 @@ -import React from 'react'; -import * as PropTypes from 'prop-types'; -import { colorGeneratorType } from '../../utils/services/ColorGenerator'; -import './TagBullet.scss'; - -const propTypes = { - tag: PropTypes.string.isRequired, - colorGenerator: colorGeneratorType, -}; - -const TagBullet = ({ tag, colorGenerator }) => ( -
-); - -TagBullet.propTypes = propTypes; - -export default TagBullet; diff --git a/src/tags/helpers/TagBullet.tsx b/src/tags/helpers/TagBullet.tsx new file mode 100644 index 00000000..aad11d7d --- /dev/null +++ b/src/tags/helpers/TagBullet.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import ColorGenerator from '../../utils/services/ColorGenerator'; +import './TagBullet.scss'; + +interface TagBulletProps { + tag: string; + colorGenerator: ColorGenerator; +} + +const TagBullet = ({ tag, colorGenerator }: TagBulletProps) => ( +
+); + +export default TagBullet; diff --git a/src/tags/helpers/TagsSelector.js b/src/tags/helpers/TagsSelector.js deleted file mode 100644 index 23d75127..00000000 --- a/src/tags/helpers/TagsSelector.js +++ /dev/null @@ -1,84 +0,0 @@ -import React, { useEffect } from 'react'; -import TagsInput from 'react-tagsinput'; -import PropTypes from 'prop-types'; -import Autosuggest from 'react-autosuggest'; -import { identity } from 'ramda'; -import TagBullet from './TagBullet'; -import './TagsSelector.scss'; - -const propTypes = { - tags: PropTypes.arrayOf(PropTypes.string).isRequired, - onChange: PropTypes.func.isRequired, - listTags: PropTypes.func, - placeholder: PropTypes.string, - tagsList: PropTypes.shape({ - tags: PropTypes.arrayOf(PropTypes.string), - }), -}; - -const TagsSelector = (colorGenerator) => { - const TagsSelectorComp = ({ tags, onChange, listTags, tagsList, placeholder = 'Add tags to the URL' }) => { - useEffect(() => { - listTags(); - }, []); - - // eslint-disable-next-line - const renderTag = ({ tag, key, disabled, onRemove, classNameRemove, getTagDisplayValue, ...other }) => ( - - {getTagDisplayValue(tag)} - {!disabled && onRemove(key)} />} - - ); - const renderAutocompleteInput = (data) => { - const { addTag, ...otherProps } = data; - const handleOnChange = (e, { method }) => { - method === 'enter' ? e.preventDefault() : otherProps.onChange(e); - }; - - const inputValue = (otherProps.value && otherProps.value.trim().toLowerCase()) || ''; - const inputLength = inputValue.length; - const suggestions = tagsList.tags.filter((state) => state.toLowerCase().slice(0, inputLength) === inputValue); - - return ( - value && value.trim().length > 0} - getSuggestionValue={(suggestion) => suggestion} - renderSuggestion={(suggestion) => ( - - - {suggestion} - - )} - onSuggestionSelected={(e, { suggestion }) => { - addTag(suggestion); - }} - onSuggestionsClearRequested={identity} - onSuggestionsFetchRequested={identity} - /> - ); - }; - - return ( - - ); - }; - - TagsSelectorComp.propTypes = propTypes; - - return TagsSelectorComp; -}; - -export default TagsSelector; diff --git a/src/tags/helpers/TagsSelector.tsx b/src/tags/helpers/TagsSelector.tsx new file mode 100644 index 00000000..63da0deb --- /dev/null +++ b/src/tags/helpers/TagsSelector.tsx @@ -0,0 +1,80 @@ +import React, { ChangeEvent, useEffect } from 'react'; +import TagsInput, { RenderInputProps, RenderTagProps } from 'react-tagsinput'; +import Autosuggest, { ChangeEvent as AutoChangeEvent, SuggestionSelectedEventData } from 'react-autosuggest'; +import ColorGenerator from '../../utils/services/ColorGenerator'; +import { TagsList } from '../reducers/tagsList'; +import TagBullet from './TagBullet'; +import './TagsSelector.scss'; + +export interface TagsSelectorProps { + tags: string[]; + onChange: (tags: string[]) => void; + placeholder?: string; +} + +interface TagsSelectorConnectProps extends TagsSelectorProps { + listTags: Function; + tagsList: TagsList; +} + +const TagsSelector = (colorGenerator: ColorGenerator) => ( + { tags, onChange, listTags, tagsList, placeholder = 'Add tags to the URL' }: TagsSelectorConnectProps, +) => { + useEffect(() => { + listTags(); + }, []); + + const renderTag = ( + { tag, key, disabled, onRemove, classNameRemove, getTagDisplayValue, ...other }: RenderTagProps, + ) => ( + + {getTagDisplayValue(tag)} + {!disabled && onRemove(key)} />} + + ); + const renderAutocompleteInput = (data: RenderInputProps) => { + const { addTag, ...otherProps } = data; + const handleOnChange = (e: ChangeEvent, { method }: AutoChangeEvent) => { + method === 'enter' ? e.preventDefault() : otherProps.onChange(e); + }; + + const inputValue = otherProps.value?.trim().toLowerCase() ?? ''; + const suggestions = tagsList.tags.filter((tag) => tag.startsWith(inputValue)); + + return ( + value.trim().length > 0} + getSuggestionValue={(suggestion) => suggestion} + renderSuggestion={(suggestion) => ( + + + {suggestion} + + )} + onSuggestionsFetchRequested={() => {}} + onSuggestionSelected={(_, { suggestion }: SuggestionSelectedEventData) => { + addTag(suggestion); + }} + /> + ); + }; + + return ( + + ); +}; + +export default TagsSelector; diff --git a/src/tags/reducers/tagDelete.ts b/src/tags/reducers/tagDelete.ts index e662a179..55c08b55 100644 --- a/src/tags/reducers/tagDelete.ts +++ b/src/tags/reducers/tagDelete.ts @@ -1,4 +1,3 @@ -import PropTypes from 'prop-types'; import { Action, Dispatch } from 'redux'; import { buildReducer } from '../../utils/helpers/redux'; import { GetState } from '../../container/types'; @@ -11,12 +10,6 @@ export const DELETE_TAG = 'shlink/deleteTag/DELETE_TAG'; export const TAG_DELETED = 'shlink/deleteTag/TAG_DELETED'; /* eslint-enable padding-line-between-statements */ -/** @deprecated Use TagDeletion interface */ -export const tagDeleteType = PropTypes.shape({ - deleting: PropTypes.bool, - error: PropTypes.bool, -}); - export interface TagDeletion { deleting: boolean; error: boolean; diff --git a/src/tags/reducers/tagsList.ts b/src/tags/reducers/tagsList.ts index 2e4cf1c0..56975c8f 100644 --- a/src/tags/reducers/tagsList.ts +++ b/src/tags/reducers/tagsList.ts @@ -5,9 +5,10 @@ import { CREATE_VISIT, CreateVisitAction } from '../../visits/reducers/visitCrea import { buildReducer } from '../../utils/helpers/redux'; import { ShlinkTags } from '../../utils/services/types'; import { GetState } from '../../container/types'; +import { ShlinkApiClientBuilder } from '../../utils/services/ShlinkApiClientBuilder'; +import { TagStats } from '../data'; import { DeleteTagAction, TAG_DELETED } from './tagDelete'; import { EditTagAction, TAG_EDITED } from './tagEdit'; -import { ShlinkApiClientBuilder } from '../../utils/services/ShlinkApiClientBuilder'; /* eslint-disable padding-line-between-statements */ export const LIST_TAGS_START = 'shlink/tagsList/LIST_TAGS_START'; @@ -28,19 +29,19 @@ export const TagsListType = PropTypes.shape({ error: PropTypes.bool, }); -type TagsStats = Record; +type TagsStatsMap = Record; export interface TagsList { tags: string[]; filteredTags: string[]; - stats: TagsStats; + stats: TagsStatsMap; loading: boolean; error: boolean; } interface ListTagsAction extends Action { tags: string[]; - stats: TagsStats; + stats: TagsStatsMap; } interface FilterTagsAction extends Action { @@ -59,7 +60,7 @@ const initialState = { const renameTag = (oldName: string, newName: string) => (tag: string) => tag === oldName ? newName : tag; const rejectTag = (tags: string[], tagToReject: string) => reject((tag) => tag === tagToReject, tags); -const increaseVisitsForTags = (tags: string[], stats: TagsStats) => tags.reduce((stats, tag) => { +const increaseVisitsForTags = (tags: string[], stats: TagsStatsMap) => tags.reduce((stats, tag) => { if (!stats[tag]) { return stats; } @@ -111,7 +112,7 @@ export const listTags = (buildShlinkApiClient: ShlinkApiClientBuilder, force = t try { const { listTags } = buildShlinkApiClient(getState); const { tags, stats = [] }: ShlinkTags = await listTags(); - const processedStats = stats.reduce((acc, { tag, shortUrlsCount, visitsCount }) => { + const processedStats = stats.reduce((acc, { tag, shortUrlsCount, visitsCount }) => { acc[tag] = { shortUrlsCount, visitsCount }; return acc; diff --git a/test/tags/helpers/DeleteTagConfirmModal.test.js b/test/tags/helpers/DeleteTagConfirmModal.test.tsx similarity index 86% rename from test/tags/helpers/DeleteTagConfirmModal.test.js rename to test/tags/helpers/DeleteTagConfirmModal.test.tsx index a13881a1..694f16d8 100644 --- a/test/tags/helpers/DeleteTagConfirmModal.test.js +++ b/test/tags/helpers/DeleteTagConfirmModal.test.tsx @@ -1,14 +1,15 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import { shallow, ShallowWrapper } from 'enzyme'; import { Modal, ModalBody, ModalFooter } from 'reactstrap'; import DeleteTagConfirmModal from '../../../src/tags/helpers/DeleteTagConfirmModal'; +import { TagDeletion } from '../../../src/tags/reducers/tagDelete'; describe('', () => { - let wrapper; + let wrapper: ShallowWrapper; const tag = 'nodejs'; const deleteTag = jest.fn(); const tagDeleted = jest.fn(); - const createWrapper = (tagDelete) => { + const createWrapper = (tagDelete: TagDeletion) => { wrapper = shallow( ', () => { return wrapper; }; - afterEach(() => { - wrapper && wrapper.unmount(); - jest.resetAllMocks(); - }); + afterEach(() => wrapper?.unmount()); + afterEach(jest.resetAllMocks); it('asks confirmation for provided tag to be deleted', () => { wrapper = createWrapper({ error: false, deleting: false }); @@ -60,7 +59,8 @@ describe('', () => { const footer = wrapper.find(ModalFooter); const delBtn = footer.find('.btn-danger'); - await delBtn.simulate('click'); + await delBtn.simulate('click'); // eslint-disable-line @typescript-eslint/await-thenable + expect(deleteTag).toHaveBeenCalledTimes(1); expect(deleteTag).toHaveBeenCalledWith(tag); expect(tagDeleted).toHaveBeenCalledTimes(1);