From 8a9c694fbcb950da16623ab423f2e73ebdc05a8e Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 30 Aug 2020 19:45:17 +0200 Subject: [PATCH] Migrated all remaining short-url elements to TS --- src/short-urls/CreateShortUrl.js | 171 ----------------- src/short-urls/CreateShortUrl.tsx | 175 +++++++++++++++++ .../{Paginator.js => Paginator.tsx} | 19 +- src/short-urls/SearchBar.js | 81 -------- src/short-urls/SearchBar.tsx | 78 ++++++++ src/short-urls/ShortUrls.js | 41 ---- src/short-urls/ShortUrls.tsx | 33 ++++ src/short-urls/ShortUrlsList.js | 178 ------------------ src/short-urls/ShortUrlsList.tsx | 177 +++++++++++++++++ ...Icon.js => UseExistingIfFoundInfoIcon.tsx} | 4 +- src/short-urls/data/index.ts | 5 +- .../helpers/CreateShortUrlResult.tsx | 6 +- src/short-urls/reducers/shortUrlCreation.ts | 10 - src/short-urls/reducers/shortUrlsList.ts | 18 +- .../reducers/shortUrlsListParams.ts | 14 +- src/short-urls/services/provideServices.ts | 4 +- src/utils/utils.ts | 2 +- ...ortUrl.test.js => CreateShortUrl.test.tsx} | 29 +-- .../{Paginator.test.js => Paginator.test.tsx} | 6 +- .../{SearchBar.test.js => SearchBar.test.tsx} | 22 +-- .../{ShortUrls.test.js => ShortUrls.test.tsx} | 19 +- ...rlsList.test.js => ShortUrlsList.test.tsx} | 31 +-- ...js => UseExistingIfFoundInfoIcon.test.tsx} | 4 +- .../short-urls/reducers/shortUrlsList.test.ts | 23 +-- 24 files changed, 555 insertions(+), 595 deletions(-) delete mode 100644 src/short-urls/CreateShortUrl.js create mode 100644 src/short-urls/CreateShortUrl.tsx rename src/short-urls/{Paginator.js => Paginator.tsx} (80%) delete mode 100644 src/short-urls/SearchBar.js create mode 100644 src/short-urls/SearchBar.tsx delete mode 100644 src/short-urls/ShortUrls.js create mode 100644 src/short-urls/ShortUrls.tsx delete mode 100644 src/short-urls/ShortUrlsList.js create mode 100644 src/short-urls/ShortUrlsList.tsx rename src/short-urls/{UseExistingIfFoundInfoIcon.js => UseExistingIfFoundInfoIcon.tsx} (93%) rename test/short-urls/{CreateShortUrl.test.js => CreateShortUrl.test.tsx} (72%) rename test/short-urls/{Paginator.test.js => Paginator.test.tsx} (85%) rename test/short-urls/{SearchBar.test.js => SearchBar.test.tsx} (75%) rename test/short-urls/{ShortUrls.test.js => ShortUrls.test.tsx} (61%) rename test/short-urls/{ShortUrlsList.test.js => ShortUrlsList.test.tsx} (80%) rename test/short-urls/{UseExistingIfFoundInfoIcon.test.js => UseExistingIfFoundInfoIcon.test.tsx} (89%) diff --git a/src/short-urls/CreateShortUrl.js b/src/short-urls/CreateShortUrl.js deleted file mode 100644 index e1591fd1..00000000 --- a/src/short-urls/CreateShortUrl.js +++ /dev/null @@ -1,171 +0,0 @@ -import { faAngleDoubleDown as downIcon, faAngleDoubleUp as upIcon } from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { isEmpty, isNil, pipe, replace, trim } from 'ramda'; -import React, { useState } from 'react'; -import { Collapse, FormGroup, Input } from 'reactstrap'; -import * as PropTypes from 'prop-types'; -import DateInput from '../utils/DateInput'; -import Checkbox from '../utils/Checkbox'; -import { serverType } from '../servers/prop-types'; -import { versionMatch } from '../utils/helpers/version'; -import { handleEventPreventingDefault, hasValue } from '../utils/utils'; -import { useToggle } from '../utils/helpers/hooks'; -import { createShortUrlResultType } from './reducers/shortUrlCreation'; -import UseExistingIfFoundInfoIcon from './UseExistingIfFoundInfoIcon'; - -const normalizeTag = pipe(trim, replace(/ /g, '-')); -const formatDate = (date) => isNil(date) ? date : date.format(); - -const propTypes = { - createShortUrl: PropTypes.func, - shortUrlCreationResult: createShortUrlResultType, - resetCreateShortUrl: PropTypes.func, - selectedServer: serverType, -}; - -const initialState = { - longUrl: '', - tags: [], - customSlug: '', - shortCodeLength: '', - domain: '', - validSince: undefined, - validUntil: undefined, - maxVisits: '', - findIfExists: false, -}; - -const CreateShortUrl = (TagsSelector, CreateShortUrlResult, ForServerVersion) => { - const CreateShortUrlComp = ({ createShortUrl, shortUrlCreationResult, resetCreateShortUrl, selectedServer }) => { - const [ shortUrlCreation, setShortUrlCreation ] = useState(initialState); - const [ moreOptionsVisible, toggleMoreOptionsVisible ] = useToggle(); - - const changeTags = (tags) => setShortUrlCreation({ ...shortUrlCreation, tags: tags.map(normalizeTag) }); - const reset = () => setShortUrlCreation(initialState); - const save = handleEventPreventingDefault(() => { - const shortUrlData = { - ...shortUrlCreation, - validSince: formatDate(shortUrlCreation.validSince), - validUntil: formatDate(shortUrlCreation.validUntil), - }; - - createShortUrl(shortUrlData).then(reset).catch(() => {}); - }); - const renderOptionalInput = (id, placeholder, type = 'text', props = {}) => ( - - setShortUrlCreation({ ...shortUrlCreation, [id]: e.target.value })} - {...props} - /> - - ); - const renderDateInput = (id, placeholder, props = {}) => ( -
- setShortUrlCreation({ ...shortUrlCreation, [id]: date })} - {...props} - /> -
- ); - - const currentServerVersion = selectedServer && selectedServer.version; - const disableDomain = !versionMatch(currentServerVersion, { minVersion: '1.19.0-beta.1' }); - const disableShortCodeLength = !versionMatch(currentServerVersion, { minVersion: '2.1.0' }); - - return ( -
-
- setShortUrlCreation({ ...shortUrlCreation, longUrl: e.target.value })} - /> -
- - -
- -
- -
-
- {renderOptionalInput('customSlug', 'Custom slug')} -
-
- {renderOptionalInput('shortCodeLength', 'Short code length', 'number', { - min: 4, - disabled: disableShortCodeLength || hasValue(shortUrlCreation.customSlug), - ...disableShortCodeLength && { - title: 'Shlink 2.1.0 or higher is required to be able to provide the short code length', - }, - })} -
-
- {renderOptionalInput('domain', 'Domain', 'text', { - disabled: disableDomain, - ...disableDomain && { title: 'Shlink 1.19.0 or higher is required to be able to provide the domain' }, - })} -
-
- -
-
- {renderOptionalInput('maxVisits', 'Maximum number of visits allowed', 'number', { min: 1 })} -
-
- {renderDateInput('validSince', 'Enabled since...', { maxDate: shortUrlCreation.validUntil })} -
-
- {renderDateInput('validUntil', 'Enabled until...', { minDate: shortUrlCreation.validSince })} -
-
- - -
- setShortUrlCreation({ ...shortUrlCreation, findIfExists })} - > - Use existing URL if found - - -
-
-
- -
- - -
- - - - ); - }; - - CreateShortUrlComp.propTypes = propTypes; - - return CreateShortUrlComp; -}; - -export default CreateShortUrl; diff --git a/src/short-urls/CreateShortUrl.tsx b/src/short-urls/CreateShortUrl.tsx new file mode 100644 index 00000000..93f8ee0f --- /dev/null +++ b/src/short-urls/CreateShortUrl.tsx @@ -0,0 +1,175 @@ +import { faAngleDoubleDown as downIcon, faAngleDoubleUp as upIcon } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { isEmpty, pipe, replace, trim } from 'ramda'; +import React, { FC, useState } from 'react'; +import { Collapse, FormGroup, Input } from 'reactstrap'; +import { InputType } from 'reactstrap/lib/Input'; +import * as m from 'moment'; +import DateInput, { DateInputProps } from '../utils/DateInput'; +import Checkbox from '../utils/Checkbox'; +import { versionMatch, Versions } from '../utils/helpers/version'; +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 { ShortUrlData } from './data'; +import { ShortUrlCreation } from './reducers/shortUrlCreation'; +import UseExistingIfFoundInfoIcon from './UseExistingIfFoundInfoIcon'; +import { CreateShortUrlResultProps } from './helpers/CreateShortUrlResult'; + +const normalizeTag = pipe(trim, replace(/ /g, '-')); + +interface CreateShortUrlProps { + shortUrlCreationResult: ShortUrlCreation; + selectedServer: SelectedServer; + createShortUrl: Function; + resetCreateShortUrl: () => void; +} + +const initialState: ShortUrlData = { + longUrl: '', + tags: [], + customSlug: '', + shortCodeLength: undefined, + domain: '', + validSince: undefined, + validUntil: undefined, + maxVisits: undefined, + findIfExists: false, +}; + +type NonDateFields = 'longUrl' | 'customSlug' | 'shortCodeLength' | 'domain' | 'maxVisits'; +type DateFields = 'validSince' | 'validUntil'; + +const CreateShortUrl = ( + TagsSelector: FC, + CreateShortUrlResult: FC, + ForServerVersion: FC, +) => ({ createShortUrl, shortUrlCreationResult, resetCreateShortUrl, selectedServer }: CreateShortUrlProps) => { + const [ shortUrlCreation, setShortUrlCreation ] = useState(initialState); + const [ moreOptionsVisible, toggleMoreOptionsVisible ] = useToggle(); + + const changeTags = (tags: string[]) => setShortUrlCreation({ ...shortUrlCreation, tags: tags.map(normalizeTag) }); + const reset = () => setShortUrlCreation(initialState); + const save = handleEventPreventingDefault(() => { + const shortUrlData = { + ...shortUrlCreation, + validSince: formatIsoDate(shortUrlCreation.validSince), + validUntil: formatIsoDate(shortUrlCreation.validUntil), + }; + + createShortUrl(shortUrlData).then(reset).catch(() => {}); + }); + const renderOptionalInput = (id: NonDateFields, placeholder: string, type: InputType = 'text', props = {}) => ( + + setShortUrlCreation({ ...shortUrlCreation, [id]: e.target.value })} + {...props} + /> + + ); + const renderDateInput = (id: DateFields, placeholder: string, props: Partial = {}) => ( +
+ setShortUrlCreation({ ...shortUrlCreation, [id]: date })} + {...props} + /> +
+ ); + + const currentServerVersion = isReachableServer(selectedServer) ? selectedServer.version : ''; + const disableDomain = !versionMatch(currentServerVersion, { minVersion: '1.19.0-beta.1' }); + const disableShortCodeLength = !versionMatch(currentServerVersion, { minVersion: '2.1.0' }); + + return ( +
+
+ setShortUrlCreation({ ...shortUrlCreation, longUrl: e.target.value })} + /> +
+ + +
+ +
+ +
+
+ {renderOptionalInput('customSlug', 'Custom slug')} +
+
+ {renderOptionalInput('shortCodeLength', 'Short code length', 'number', { + min: 4, + disabled: disableShortCodeLength || hasValue(shortUrlCreation.customSlug), + ...disableShortCodeLength && { + title: 'Shlink 2.1.0 or higher is required to be able to provide the short code length', + }, + })} +
+
+ {renderOptionalInput('domain', 'Domain', 'text', { + disabled: disableDomain, + ...disableDomain && { title: 'Shlink 1.19.0 or higher is required to be able to provide the domain' }, + })} +
+
+ +
+
+ {renderOptionalInput('maxVisits', 'Maximum number of visits allowed', 'number', { min: 1 })} +
+
+ {renderDateInput('validSince', 'Enabled since...', { maxDate: shortUrlCreation.validUntil as m.Moment | undefined })} +
+
+ {renderDateInput('validUntil', 'Enabled until...', { minDate: shortUrlCreation.validSince as m.Moment | undefined })} +
+
+ + +
+ setShortUrlCreation({ ...shortUrlCreation, findIfExists })} + > + Use existing URL if found + + +
+
+
+ +
+ + +
+ + + + ); +}; + +export default CreateShortUrl; diff --git a/src/short-urls/Paginator.js b/src/short-urls/Paginator.tsx similarity index 80% rename from src/short-urls/Paginator.js rename to src/short-urls/Paginator.tsx index 4b051811..eeccb727 100644 --- a/src/short-urls/Paginator.js +++ b/src/short-urls/Paginator.tsx @@ -1,20 +1,17 @@ import React from 'react'; import { Link } from 'react-router-dom'; import { Pagination, PaginationItem, PaginationLink } from 'reactstrap'; -import PropTypes from 'prop-types'; import { pageIsEllipsis, keyForPage, progressivePagination } from '../utils/helpers/pagination'; +import { ShlinkPaginator } from '../utils/services/types'; import './Paginator.scss'; -const propTypes = { - serverId: PropTypes.string.isRequired, - paginator: PropTypes.shape({ - currentPage: PropTypes.number, - pagesCount: PropTypes.number, - }), -}; +interface PaginatorProps { + paginator?: ShlinkPaginator; + serverId: string; +} -const Paginator = ({ paginator = {}, serverId }) => { - const { currentPage, pagesCount = 0 } = paginator; +const Paginator = ({ paginator, serverId }: PaginatorProps) => { + const { currentPage = 0, pagesCount = 0 } = paginator ?? {}; if (pagesCount <= 1) { return null; @@ -57,6 +54,4 @@ const Paginator = ({ paginator = {}, serverId }) => { ); }; -Paginator.propTypes = propTypes; - export default Paginator; diff --git a/src/short-urls/SearchBar.js b/src/short-urls/SearchBar.js deleted file mode 100644 index 53232f4e..00000000 --- a/src/short-urls/SearchBar.js +++ /dev/null @@ -1,81 +0,0 @@ -import { faTags as tagsIcon } from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import React from 'react'; -import { isEmpty, pipe } from 'ramda'; -import PropTypes from 'prop-types'; -import moment from 'moment'; -import SearchField from '../utils/SearchField'; -import Tag from '../tags/helpers/Tag'; -import DateRangeRow from '../utils/DateRangeRow'; -import { formatDate } from '../utils/helpers/date'; -import { shortUrlsListParamsType } from './reducers/shortUrlsListParams'; -import './SearchBar.scss'; - -const propTypes = { - listShortUrls: PropTypes.func, - shortUrlsListParams: shortUrlsListParamsType, -}; - -const dateOrUndefined = (date) => date ? moment(date) : undefined; - -const SearchBar = (colorGenerator, ForServerVersion) => { - const SearchBar = ({ listShortUrls, shortUrlsListParams }) => { - const selectedTags = shortUrlsListParams.tags || []; - const setDate = (dateName) => pipe( - formatDate(), - (date) => listShortUrls({ ...shortUrlsListParams, [dateName]: date }), - ); - - return ( -
- listShortUrls({ ...shortUrlsListParams, searchTerm }) - } - /> - - -
-
-
- -
-
-
-
- - {!isEmpty(selectedTags) && ( -

- -   - {selectedTags.map((tag) => ( - listShortUrls( - { - ...shortUrlsListParams, - tags: selectedTags.filter((selectedTag) => selectedTag !== tag), - }, - )} - /> - ))} -

- )} -
- ); - }; - - SearchBar.propTypes = propTypes; - - return SearchBar; -}; - -export default SearchBar; diff --git a/src/short-urls/SearchBar.tsx b/src/short-urls/SearchBar.tsx new file mode 100644 index 00000000..85150608 --- /dev/null +++ b/src/short-urls/SearchBar.tsx @@ -0,0 +1,78 @@ +import { faTags as tagsIcon } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import React, { FC } from 'react'; +import { isEmpty, pipe } from 'ramda'; +import moment from 'moment'; +import SearchField from '../utils/SearchField'; +import Tag from '../tags/helpers/Tag'; +import DateRangeRow from '../utils/DateRangeRow'; +import { formatDate } from '../utils/helpers/date'; +import ColorGenerator from '../utils/services/ColorGenerator'; +import { Versions } from '../utils/helpers/version'; +import { ShortUrlsListParams } from './reducers/shortUrlsListParams'; +import './SearchBar.scss'; + +interface SearchBarProps { + listShortUrls: (params: ShortUrlsListParams) => void; + shortUrlsListParams: ShortUrlsListParams; +} + +const dateOrUndefined = (date?: string) => date ? moment(date) : undefined; + +const SearchBar = (colorGenerator: ColorGenerator, ForServerVersion: FC) => ( + { listShortUrls, shortUrlsListParams }: SearchBarProps, +) => { + const selectedTags = shortUrlsListParams.tags ?? []; + const setDate = (dateName: 'startDate' | 'endDate') => pipe( + formatDate(), + (date) => listShortUrls({ ...shortUrlsListParams, [dateName]: date }), + ); + + return ( +
+ listShortUrls({ ...shortUrlsListParams, searchTerm }) + } + /> + + +
+
+
+ +
+
+
+
+ + {!isEmpty(selectedTags) && ( +

+ +   + {selectedTags.map((tag) => ( + listShortUrls( + { + ...shortUrlsListParams, + tags: selectedTags.filter((selectedTag) => selectedTag !== tag), + }, + )} + /> + ))} +

+ )} +
+ ); +}; + +export default SearchBar; diff --git a/src/short-urls/ShortUrls.js b/src/short-urls/ShortUrls.js deleted file mode 100644 index a4cc23c4..00000000 --- a/src/short-urls/ShortUrls.js +++ /dev/null @@ -1,41 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import PropTypes from 'prop-types'; -import Paginator from './Paginator'; - -const ShortUrls = (SearchBar, ShortUrlsList) => { - const propTypes = { - match: PropTypes.shape({ - params: PropTypes.object, - }), - shortUrlsList: PropTypes.object, - }; - - const ShortUrlsComponent = (props) => { - const { match: { params }, shortUrlsList } = props; - const { page, serverId } = params; - const { data = [], pagination } = shortUrlsList; - const [ urlsListKey, setUrlsListKey ] = useState(`${serverId}_${page}`); - - // Using a key on a component makes react to create a new instance every time the key changes - // Without it, pagination on the URL will not make the component to be refreshed - useEffect(() => { - setUrlsListKey(`${serverId}_${page}`); - }, [ serverId, page ]); - - return ( - -
-
- - -
-
- ); - }; - - ShortUrlsComponent.propTypes = propTypes; - - return ShortUrlsComponent; -}; - -export default ShortUrls; diff --git a/src/short-urls/ShortUrls.tsx b/src/short-urls/ShortUrls.tsx new file mode 100644 index 00000000..0a283bb1 --- /dev/null +++ b/src/short-urls/ShortUrls.tsx @@ -0,0 +1,33 @@ +import React, { FC, useEffect, useState } from 'react'; +import { ShlinkShortUrlsResponse } from '../utils/services/types'; +import Paginator from './Paginator'; +import { ShortUrlsListProps, WithList } from './ShortUrlsList'; + +interface ShortUrlsProps extends ShortUrlsListProps { + shortUrlsList?: ShlinkShortUrlsResponse; +} + +const ShortUrls = (SearchBar: FC, ShortUrlsList: FC) => (props: ShortUrlsProps) => { + const { match, shortUrlsList } = props; + const { page = '1', serverId = '' } = match?.params ?? {}; + const { data = [], pagination } = shortUrlsList ?? {}; + const [ urlsListKey, setUrlsListKey ] = useState(`${serverId}_${page}`); + + // Using a key on a component makes react to create a new instance every time the key changes + // Without it, pagination on the URL will not make the component to be refreshed + useEffect(() => { + setUrlsListKey(`${serverId}_${page}`); + }, [ serverId, page ]); + + return ( + +
+
+ + +
+
+ ); +}; + +export default ShortUrls; diff --git a/src/short-urls/ShortUrlsList.js b/src/short-urls/ShortUrlsList.js deleted file mode 100644 index a7b275e9..00000000 --- a/src/short-urls/ShortUrlsList.js +++ /dev/null @@ -1,178 +0,0 @@ -import { faCaretDown as caretDownIcon, faCaretUp as caretUpIcon } from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { head, isEmpty, keys, values } from 'ramda'; -import React, { useState, useEffect } from 'react'; -import qs from 'qs'; -import PropTypes from 'prop-types'; -import { serverType } from '../servers/prop-types'; -import SortingDropdown from '../utils/SortingDropdown'; -import { determineOrderDir } from '../utils/utils'; -import { MercureInfoType } from '../mercure/reducers/mercureInfo'; -import { useMercureTopicBinding } from '../mercure/helpers'; -import { shortUrlType } from './reducers/shortUrlsList'; -import { shortUrlsListParamsType } from './reducers/shortUrlsListParams'; -import './ShortUrlsList.scss'; - -export const SORTABLE_FIELDS = { - dateCreated: 'Created at', - shortCode: 'Short URL', - longUrl: 'Long URL', - visits: 'Visits', -}; - -const propTypes = { - listShortUrls: PropTypes.func, - resetShortUrlParams: PropTypes.func, - shortUrlsListParams: shortUrlsListParamsType, - match: PropTypes.object, - location: PropTypes.object, - loading: PropTypes.bool, - error: PropTypes.bool, - shortUrlsList: PropTypes.arrayOf(shortUrlType), - selectedServer: serverType, - createNewVisit: PropTypes.func, - loadMercureInfo: PropTypes.func, - mercureInfo: MercureInfoType, -}; - -// FIXME Replace with typescript: (ShortUrlsRow component) -const ShortUrlsList = (ShortUrlsRow) => { - const ShortUrlsListComp = ({ - listShortUrls, - resetShortUrlParams, - shortUrlsListParams, - match, - location, - loading, - error, - shortUrlsList, - selectedServer, - createNewVisit, - loadMercureInfo, - mercureInfo, - }) => { - const { orderBy } = shortUrlsListParams; - const [ order, setOrder ] = useState({ - orderField: orderBy && head(keys(orderBy)), - orderDir: orderBy && head(values(orderBy)), - }); - const refreshList = (extraParams) => listShortUrls({ ...shortUrlsListParams, ...extraParams }); - const handleOrderBy = (orderField, orderDir) => { - setOrder({ orderField, orderDir }); - refreshList({ orderBy: { [orderField]: orderDir } }); - }; - const orderByColumn = (columnName) => () => - handleOrderBy(columnName, determineOrderDir(columnName, order.orderField, order.orderDir)); - const renderOrderIcon = (field) => { - if (order.orderField !== field) { - return null; - } - - if (!order.orderDir) { - return null; - } - - return ( - - ); - }; - const renderShortUrls = () => { - if (error) { - return ( - - Something went wrong while loading short URLs :( - - ); - } - - if (loading) { - return Loading...; - } - - if (!loading && isEmpty(shortUrlsList)) { - return No results found; - } - - return shortUrlsList.map((shortUrl) => ( - - )); - }; - - useEffect(() => { - const { params } = match; - const query = qs.parse(location.search, { ignoreQueryPrefix: true }); - const tags = query.tag ? [ query.tag ] : shortUrlsListParams.tags; - - refreshList({ page: params.page, tags }); - - return resetShortUrlParams; - }, []); - useMercureTopicBinding(mercureInfo, 'https://shlink.io/new-visit', createNewVisit, loadMercureInfo); - - return ( - -
- -
- - - - - - - - - - - - - {renderShortUrls()} - -
- {renderOrderIcon('dateCreated')} - Created at - - {renderOrderIcon('shortCode')} - Short URL - - {renderOrderIcon('longUrl')} - Long URL - Tags - {renderOrderIcon('visits')} Visits -  
-
- ); - }; - - ShortUrlsListComp.propTypes = propTypes; - - return ShortUrlsListComp; -}; - -export default ShortUrlsList; diff --git a/src/short-urls/ShortUrlsList.tsx b/src/short-urls/ShortUrlsList.tsx new file mode 100644 index 00000000..1273894d --- /dev/null +++ b/src/short-urls/ShortUrlsList.tsx @@ -0,0 +1,177 @@ +import { faCaretDown as caretDownIcon, faCaretUp as caretUpIcon } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { head, isEmpty, keys, values } from 'ramda'; +import React, { useState, useEffect, FC } from 'react'; +import qs from 'qs'; +import { RouteChildrenProps } from 'react-router'; +import SortingDropdown from '../utils/SortingDropdown'; +import { determineOrderDir, OrderDir } from '../utils/utils'; +import { MercureInfo } from '../mercure/reducers/mercureInfo'; +import { useMercureTopicBinding } from '../mercure/helpers'; +import { SelectedServer } from '../servers/data'; +import { ShortUrlsList as ShortUrlsListState } from './reducers/shortUrlsList'; +import { ShortUrlsRowProps } from './helpers/ShortUrlsRow'; +import { ShortUrl } from './data'; +import { ShortUrlsListParams } from './reducers/shortUrlsListParams'; +import './ShortUrlsList.scss'; + +export const SORTABLE_FIELDS = { + dateCreated: 'Created at', + shortCode: 'Short URL', + longUrl: 'Long URL', + visits: 'Visits', +}; +type OrderableFields = keyof typeof SORTABLE_FIELDS; + +interface RouteParams { + page: string; + serverId: string; +} + +export interface WithList { + shortUrlsList: ShortUrl[]; +} + +export interface ShortUrlsListProps extends ShortUrlsListState, RouteChildrenProps { + selectedServer: SelectedServer; + listShortUrls: (params: ShortUrlsListParams) => void; + shortUrlsListParams: ShortUrlsListParams; + resetShortUrlParams: () => void; + createNewVisit: (message: any) => void; + loadMercureInfo: Function; + mercureInfo: MercureInfo; +} + +const ShortUrlsList = (ShortUrlsRow: FC) => ({ + listShortUrls, + resetShortUrlParams, + shortUrlsListParams, + match, + location, + loading, + error, + shortUrlsList, + selectedServer, + createNewVisit, + loadMercureInfo, + mercureInfo, +}: ShortUrlsListProps & WithList) => { + const { orderBy } = shortUrlsListParams; + const [ order, setOrder ] = useState<{ orderField?: OrderableFields; orderDir?: OrderDir }>({ + orderField: orderBy && (head(keys(orderBy)) as OrderableFields), + orderDir: orderBy && head(values(orderBy)), + }); + const refreshList = (extraParams: ShortUrlsListParams) => listShortUrls({ ...shortUrlsListParams, ...extraParams }); + const handleOrderBy = (orderField: OrderableFields, orderDir: OrderDir) => { + setOrder({ orderField, orderDir }); + refreshList({ orderBy: { [orderField]: orderDir } }); + }; + const orderByColumn = (field: OrderableFields) => () => + handleOrderBy(field, determineOrderDir(field, order.orderField, order.orderDir)); + const renderOrderIcon = (field: OrderableFields) => { + if (order.orderField !== field) { + return null; + } + + if (!order.orderDir) { + return null; + } + + return ( + + ); + }; + const renderShortUrls = () => { + if (error) { + return ( + + Something went wrong while loading short URLs :( + + ); + } + + if (loading) { + return Loading...; + } + + if (!loading && isEmpty(shortUrlsList)) { + return No results found; + } + + return shortUrlsList.map((shortUrl) => ( + + )); + }; + + useEffect(() => { + const query = qs.parse(location.search, { ignoreQueryPrefix: true }); + const tags = query.tag ? [ query.tag as string ] : shortUrlsListParams.tags; + + refreshList({ page: match?.params.page, tags }); + + return resetShortUrlParams; + }, []); + useMercureTopicBinding(mercureInfo, 'https://shlink.io/new-visit', createNewVisit, loadMercureInfo); + + return ( + +
+ +
+ + + + + + + + + + + + + {renderShortUrls()} + +
+ {renderOrderIcon('dateCreated')} + Created at + + {renderOrderIcon('shortCode')} + Short URL + + {renderOrderIcon('longUrl')} + Long URL + Tags + {renderOrderIcon('visits')} Visits +  
+
+ ); +}; + +export default ShortUrlsList; diff --git a/src/short-urls/UseExistingIfFoundInfoIcon.js b/src/short-urls/UseExistingIfFoundInfoIcon.tsx similarity index 93% rename from src/short-urls/UseExistingIfFoundInfoIcon.js rename to src/short-urls/UseExistingIfFoundInfoIcon.tsx index 803d4b23..e1c13fa1 100644 --- a/src/short-urls/UseExistingIfFoundInfoIcon.js +++ b/src/short-urls/UseExistingIfFoundInfoIcon.tsx @@ -5,7 +5,7 @@ import { Modal, ModalBody, ModalHeader } from 'reactstrap'; import './UseExistingIfFoundInfoIcon.scss'; import { useToggle } from '../utils/helpers/hooks'; -const renderInfoModal = (isOpen, toggle) => ( +const InfoModal = ({ isOpen, toggle }: { isOpen: boolean; toggle: () => void }) => ( Info @@ -45,7 +45,7 @@ const UseExistingIfFoundInfoIcon = () => { - {renderInfoModal(isModalOpen, toggleModal)} + ); }; diff --git a/src/short-urls/data/index.ts b/src/short-urls/data/index.ts index f0045e83..ba7d7c6e 100644 --- a/src/short-urls/data/index.ts +++ b/src/short-urls/data/index.ts @@ -1,3 +1,4 @@ +import * as m from 'moment'; import { Nullable, OptionalString } from '../../utils/utils'; export interface ShortUrlData { @@ -6,8 +7,8 @@ export interface ShortUrlData { customSlug?: string; shortCodeLength?: number; domain?: string; - validSince?: string; - validUntil?: string; + validSince?: m.Moment | string; + validUntil?: m.Moment | string; maxVisits?: number; findIfExists?: boolean; } diff --git a/src/short-urls/helpers/CreateShortUrlResult.tsx b/src/short-urls/helpers/CreateShortUrlResult.tsx index 83701cf8..8a561d28 100644 --- a/src/short-urls/helpers/CreateShortUrlResult.tsx +++ b/src/short-urls/helpers/CreateShortUrlResult.tsx @@ -5,11 +5,11 @@ import React, { useEffect } from 'react'; import CopyToClipboard from 'react-copy-to-clipboard'; import { Card, CardBody, Tooltip } from 'reactstrap'; import { ShortUrlCreation } from '../reducers/shortUrlCreation'; -import './CreateShortUrlResult.scss'; import { StateFlagTimeout } from '../../utils/helpers/hooks'; +import './CreateShortUrlResult.scss'; -interface CreateShortUrlResultProps extends ShortUrlCreation { - resetCreateShortUrl: Function; +export interface CreateShortUrlResultProps extends ShortUrlCreation { + resetCreateShortUrl: () => void; } const CreateShortUrlResult = (useStateFlagTimeout: StateFlagTimeout) => ( diff --git a/src/short-urls/reducers/shortUrlCreation.ts b/src/short-urls/reducers/shortUrlCreation.ts index f062b694..3f066655 100644 --- a/src/short-urls/reducers/shortUrlCreation.ts +++ b/src/short-urls/reducers/shortUrlCreation.ts @@ -1,4 +1,3 @@ -import PropTypes from 'prop-types'; import { Action, Dispatch } from 'redux'; import { GetState } from '../../container/types'; import { ShortUrl, ShortUrlData } from '../data'; @@ -12,15 +11,6 @@ export const CREATE_SHORT_URL = 'shlink/createShortUrl/CREATE_SHORT_URL'; export const RESET_CREATE_SHORT_URL = 'shlink/createShortUrl/RESET_CREATE_SHORT_URL'; /* eslint-enable padding-line-between-statements */ -/** @deprecated Use ShortUrlCreation interface instead */ -export const createShortUrlResultType = PropTypes.shape({ - result: PropTypes.shape({ - shortUrl: PropTypes.string, - }), - saving: PropTypes.bool, - error: PropTypes.bool, -}); - export interface ShortUrlCreation { result: ShortUrl | null; saving: boolean; diff --git a/src/short-urls/reducers/shortUrlsList.ts b/src/short-urls/reducers/shortUrlsList.ts index de4a2370..71a153e5 100644 --- a/src/short-urls/reducers/shortUrlsList.ts +++ b/src/short-urls/reducers/shortUrlsList.ts @@ -7,6 +7,7 @@ import { ShortUrl, ShortUrlIdentifier } from '../data'; import { buildReducer } from '../../utils/helpers/redux'; import { GetState } from '../../container/types'; import { ShlinkApiClientBuilder } from '../../utils/services/ShlinkApiClientBuilder'; +import { ShlinkShortUrlsResponse } from '../../utils/services/types'; import { EditShortUrlTagsAction, SHORT_URL_TAGS_EDITED } from './shortUrlTags'; import { SHORT_URL_DELETED } from './shortUrlDeletion'; import { SHORT_URL_META_EDITED, ShortUrlMetaEditedAction, shortUrlMetaType } from './shortUrlMeta'; @@ -30,18 +31,14 @@ export const shortUrlType = PropTypes.shape({ domain: PropTypes.string, }); -interface ShortUrlsData { - data: ShortUrl[]; -} - export interface ShortUrlsList { - shortUrls: ShortUrlsData; + shortUrls?: ShlinkShortUrlsResponse; loading: boolean; error: boolean; } export interface ListShortUrlsAction extends Action { - shortUrls: ShortUrlsData; + shortUrls: ShlinkShortUrlsResponse; params: ShortUrlsListParams; } @@ -50,9 +47,6 @@ export type ListShortUrlsCombinedAction = ( ); const initialState: ShortUrlsList = { - shortUrls: { - data: [], - }, loading: true, error: false, }; @@ -60,7 +54,7 @@ const initialState: ShortUrlsList = { const setPropFromActionOnMatchingShortUrl = (prop: keyof T) => ( state: ShortUrlsList, { shortCode, domain, [prop]: propValue }: T, -): ShortUrlsList => assocPath( +): ShortUrlsList => !state.shortUrls ? state : assocPath( [ 'shortUrls', 'data' ], state.shortUrls.data.map( (shortUrl: ShortUrl) => @@ -71,9 +65,9 @@ const setPropFromActionOnMatchingShortUrl = (prop: export default buildReducer({ [LIST_SHORT_URLS_START]: (state) => ({ ...state, loading: true, error: false }), - [LIST_SHORT_URLS_ERROR]: () => ({ loading: false, error: true, shortUrls: { data: [] } }), + [LIST_SHORT_URLS_ERROR]: () => ({ loading: false, error: true }), [LIST_SHORT_URLS]: (_, { shortUrls }) => ({ loading: false, error: false, shortUrls }), - [SHORT_URL_DELETED]: (state, { shortCode, domain }) => assocPath( + [SHORT_URL_DELETED]: (state, { shortCode, domain }) => !state.shortUrls ? state : assocPath( [ 'shortUrls', 'data' ], reject((shortUrl) => shortUrlMatches(shortUrl, shortCode, domain), state.shortUrls.data), state, diff --git a/src/short-urls/reducers/shortUrlsListParams.ts b/src/short-urls/reducers/shortUrlsListParams.ts index e5ed91d5..db9ba232 100644 --- a/src/short-urls/reducers/shortUrlsListParams.ts +++ b/src/short-urls/reducers/shortUrlsListParams.ts @@ -1,26 +1,16 @@ -import PropTypes from 'prop-types'; import { buildActionCreator, buildReducer } from '../../utils/helpers/redux'; +import { OrderDir } from '../../utils/utils'; import { LIST_SHORT_URLS, ListShortUrlsAction } from './shortUrlsList'; export const RESET_SHORT_URL_PARAMS = 'shlink/shortUrlsListParams/RESET_SHORT_URL_PARAMS'; -/** @deprecated Use ShortUrlsListParams interface instead */ -export const shortUrlsListParamsType = PropTypes.shape({ - page: PropTypes.string, - tags: PropTypes.arrayOf(PropTypes.string), - searchTerm: PropTypes.string, - startDate: PropTypes.string, - endDate: PropTypes.string, - orderBy: PropTypes.object, -}); - export interface ShortUrlsListParams { page?: string; tags?: string[]; searchTerm?: string; startDate?: string; endDate?: string; - orderBy?: string | Record; + orderBy?: Record; } const initialState: ShortUrlsListParams = { page: '1' }; diff --git a/src/short-urls/services/provideServices.ts b/src/short-urls/services/provideServices.ts index a5f43f4c..8e86e7c9 100644 --- a/src/short-urls/services/provideServices.ts +++ b/src/short-urls/services/provideServices.ts @@ -19,13 +19,13 @@ import { editShortUrlTags, resetShortUrlsTags } from '../reducers/shortUrlTags'; import { editShortUrlMeta, resetShortUrlMeta } from '../reducers/shortUrlMeta'; import { resetShortUrlParams } from '../reducers/shortUrlsListParams'; import { editShortUrl } from '../reducers/shortUrlEdition'; -import { ConnectDecorator } from '../../container/types'; +import { ConnectDecorator, ShlinkState } from '../../container/types'; const provideServices = (bottle: Bottle, connect: ConnectDecorator) => { // Components bottle.serviceFactory('ShortUrls', ShortUrls, 'SearchBar', 'ShortUrlsList'); bottle.decorator('ShortUrls', reduxConnect( - (state: any) => assoc('shortUrlsList', state.shortUrlsList.shortUrls, state.shortUrlsList), + (state: ShlinkState) => assoc('shortUrlsList', state.shortUrlsList.shortUrls, state.shortUrlsList), )); // Services diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 45592f26..e3a061e4 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -3,7 +3,7 @@ import { SyntheticEvent } from 'react'; export type OrderDir = 'ASC' | 'DESC' | undefined; -export const determineOrderDir = (currentField: string, newField: string, currentOrderDir?: OrderDir): OrderDir => { +export const determineOrderDir = (currentField: string, newField?: string, currentOrderDir?: OrderDir): OrderDir => { if (currentField !== newField) { return 'ASC'; } diff --git a/test/short-urls/CreateShortUrl.test.js b/test/short-urls/CreateShortUrl.test.tsx similarity index 72% rename from test/short-urls/CreateShortUrl.test.js rename to test/short-urls/CreateShortUrl.test.tsx index e9ca10cb..18081131 100644 --- a/test/short-urls/CreateShortUrl.test.js +++ b/test/short-urls/CreateShortUrl.test.tsx @@ -1,29 +1,32 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import { shallow, ShallowWrapper } from 'enzyme'; import moment from 'moment'; import { identity } from 'ramda'; +import { Mock } from 'ts-mockery'; import createShortUrlsCreator from '../../src/short-urls/CreateShortUrl'; import DateInput from '../../src/utils/DateInput'; +import { ShortUrlCreation } from '../../src/short-urls/reducers/shortUrlCreation'; describe('', () => { - let wrapper; - const TagsSelector = () => ''; - const shortUrlCreationResult = { - loading: false, - }; - const createShortUrl = jest.fn(() => Promise.resolve()); + let wrapper: ShallowWrapper; + const TagsSelector = () => null; + const shortUrlCreationResult = Mock.all(); + const createShortUrl = jest.fn(async () => Promise.resolve()); beforeEach(() => { - const CreateShortUrl = createShortUrlsCreator(TagsSelector, () => '', () => ''); + const CreateShortUrl = createShortUrlsCreator(TagsSelector, () => null, () => null); wrapper = shallow( - , + {}} + />, ); }); - afterEach(() => { - wrapper.unmount(); - createShortUrl.mockClear(); - }); + afterEach(() => wrapper.unmount()); + afterEach(jest.clearAllMocks); it('saves short URL with data set in form controls', () => { const validSince = moment('2017-01-01'); diff --git a/test/short-urls/Paginator.test.js b/test/short-urls/Paginator.test.tsx similarity index 85% rename from test/short-urls/Paginator.test.js rename to test/short-urls/Paginator.test.tsx index f292dea9..7e63d972 100644 --- a/test/short-urls/Paginator.test.js +++ b/test/short-urls/Paginator.test.tsx @@ -1,12 +1,12 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import { shallow, ShallowWrapper } from 'enzyme'; import { PaginationItem } from 'reactstrap'; import Paginator from '../../src/short-urls/Paginator'; describe('', () => { - let wrapper; + let wrapper: ShallowWrapper; - afterEach(() => wrapper && wrapper.unmount()); + afterEach(() => wrapper?.unmount()); it('renders nothing if the number of pages is below 2', () => { wrapper = shallow(); diff --git a/test/short-urls/SearchBar.test.js b/test/short-urls/SearchBar.test.tsx similarity index 75% rename from test/short-urls/SearchBar.test.js rename to test/short-urls/SearchBar.test.tsx index efbdddc1..97f155f6 100644 --- a/test/short-urls/SearchBar.test.js +++ b/test/short-urls/SearchBar.test.tsx @@ -1,34 +1,34 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import { shallow, ShallowWrapper } from 'enzyme'; +import { Mock } from 'ts-mockery'; import searchBarCreator from '../../src/short-urls/SearchBar'; import SearchField from '../../src/utils/SearchField'; import Tag from '../../src/tags/helpers/Tag'; import DateRangeRow from '../../src/utils/DateRangeRow'; +import ColorGenerator from '../../src/utils/services/ColorGenerator'; describe('', () => { - let wrapper; + let wrapper: ShallowWrapper; const listShortUrlsMock = jest.fn(); - const SearchBar = searchBarCreator({}, () => ''); + const SearchBar = searchBarCreator(Mock.all(), () => null); - afterEach(() => { - listShortUrlsMock.mockReset(); - wrapper && wrapper.unmount(); - }); + afterEach(jest.clearAllMocks); + afterEach(() => wrapper?.unmount()); it('renders a SearchField', () => { - wrapper = shallow(); + wrapper = shallow(); expect(wrapper.find(SearchField)).toHaveLength(1); }); it('renders a DateRangeRow', () => { - wrapper = shallow(); + wrapper = shallow(); expect(wrapper.find(DateRangeRow)).toHaveLength(1); }); it('renders no tags when the list of tags is empty', () => { - wrapper = shallow(); + wrapper = shallow(); expect(wrapper.find(Tag)).toHaveLength(0); }); @@ -36,7 +36,7 @@ describe('', () => { it('renders the proper amount of tags', () => { const tags = [ 'foo', 'bar', 'baz' ]; - wrapper = shallow(); + wrapper = shallow(); expect(wrapper.find(Tag)).toHaveLength(tags.length); }); diff --git a/test/short-urls/ShortUrls.test.js b/test/short-urls/ShortUrls.test.tsx similarity index 61% rename from test/short-urls/ShortUrls.test.js rename to test/short-urls/ShortUrls.test.tsx index d2327a31..45d845b8 100644 --- a/test/short-urls/ShortUrls.test.js +++ b/test/short-urls/ShortUrls.test.tsx @@ -1,22 +1,21 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import { shallow, ShallowWrapper } from 'enzyme'; +import { Mock } from 'ts-mockery'; import shortUrlsCreator from '../../src/short-urls/ShortUrls'; import Paginator from '../../src/short-urls/Paginator'; +import { ShortUrlsListProps } from '../../src/short-urls/ShortUrlsList'; describe('', () => { - let wrapper; - const SearchBar = () => ''; - const ShortUrlsList = () => ''; + let wrapper: ShallowWrapper; + const SearchBar = () => null; + const ShortUrlsList = () => null; beforeEach(() => { - const params = { - serverId: '1', - page: '1', - }; - const ShortUrls = shortUrlsCreator(SearchBar, ShortUrlsList); - wrapper = shallow(); + wrapper = shallow( + ()} />, + ); }); afterEach(() => wrapper.unmount()); diff --git a/test/short-urls/ShortUrlsList.test.js b/test/short-urls/ShortUrlsList.test.tsx similarity index 80% rename from test/short-urls/ShortUrlsList.test.js rename to test/short-urls/ShortUrlsList.test.tsx index 65246833..60161780 100644 --- a/test/short-urls/ShortUrlsList.test.js +++ b/test/short-urls/ShortUrlsList.test.tsx @@ -1,12 +1,14 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import { shallow, ShallowWrapper } from 'enzyme'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faCaretDown as caretDownIcon, faCaretUp as caretUpIcon } from '@fortawesome/free-solid-svg-icons'; -import shortUrlsListCreator, { SORTABLE_FIELDS } from '../../src/short-urls/ShortUrlsList'; +import { Mock } from 'ts-mockery'; +import shortUrlsListCreator, { ShortUrlsListProps, SORTABLE_FIELDS } from '../../src/short-urls/ShortUrlsList'; +import { ShortUrl } from '../../src/short-urls/data'; describe('', () => { - let wrapper; - const ShortUrlsRow = () => ''; + let wrapper: ShallowWrapper; + const ShortUrlsRow = () => null; const listShortUrlsMock = jest.fn(); const resetShortUrlParamsMock = jest.fn(); @@ -15,6 +17,7 @@ describe('', () => { beforeEach(() => { wrapper = shallow( ()} listShortUrls={listShortUrlsMock} resetShortUrlParams={resetShortUrlParamsMock} shortUrlsListParams={{ @@ -22,29 +25,27 @@ describe('', () => { tags: [ 'test tag' ], searchTerm: 'example.com', }} - match={{ params: {} }} - location={{}} + match={{ params: {} } as any} + location={{} as any} loading={false} error={false} shortUrlsList={ [ - { + Mock.of({ shortCode: 'testShortCode', shortUrl: 'https://www.example.com/testShortUrl', longUrl: 'https://www.example.com/testLongUrl', tags: [ 'test tag' ], - }, + }), ] } - mercureInfo={{ loading: true }} + mercureInfo={{ loading: true } as any} />, ); }); - afterEach(() => { - jest.resetAllMocks(); - wrapper && wrapper.unmount(); - }); + afterEach(jest.resetAllMocks); + afterEach(() => wrapper?.unmount()); it('wraps a ShortUrlsList with 1 ShortUrlsRow', () => { expect(wrapper.find(ShortUrlsRow)).toHaveLength(1); @@ -71,11 +72,11 @@ describe('', () => { }); it('should render 6 table header cells with conditional order by icon', () => { - const getThElementForSortableField = (sortableField) => wrapper.find('table') + const getThElementForSortableField = (sortableField: string) => wrapper.find('table') .find('thead') .find('tr') .find('th') - .filterWhere((e) => e.text().includes(SORTABLE_FIELDS[sortableField])); + .filterWhere((e) => e.text().includes(SORTABLE_FIELDS[sortableField as keyof typeof SORTABLE_FIELDS])); Object.keys(SORTABLE_FIELDS).forEach((sortableField) => { expect(getThElementForSortableField(sortableField).find(FontAwesomeIcon)).toHaveLength(0); diff --git a/test/short-urls/UseExistingIfFoundInfoIcon.test.js b/test/short-urls/UseExistingIfFoundInfoIcon.test.tsx similarity index 89% rename from test/short-urls/UseExistingIfFoundInfoIcon.test.js rename to test/short-urls/UseExistingIfFoundInfoIcon.test.tsx index 12f25a0d..4cac6e05 100644 --- a/test/short-urls/UseExistingIfFoundInfoIcon.test.js +++ b/test/short-urls/UseExistingIfFoundInfoIcon.test.tsx @@ -1,11 +1,11 @@ import React from 'react'; -import { mount } from 'enzyme'; +import { mount, ReactWrapper } from 'enzyme'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Modal } from 'reactstrap'; import UseExistingIfFoundInfoIcon from '../../src/short-urls/UseExistingIfFoundInfoIcon'; describe('', () => { - let wrapped; + let wrapped: ReactWrapper; beforeEach(() => { wrapped = mount(); diff --git a/test/short-urls/reducers/shortUrlsList.test.ts b/test/short-urls/reducers/shortUrlsList.test.ts index 00aa1740..ea77b662 100644 --- a/test/short-urls/reducers/shortUrlsList.test.ts +++ b/test/short-urls/reducers/shortUrlsList.test.ts @@ -11,14 +11,12 @@ import { SHORT_URL_META_EDITED } from '../../../src/short-urls/reducers/shortUrl import { CREATE_VISIT } from '../../../src/visits/reducers/visitCreation'; import { ShortUrl } from '../../../src/short-urls/data'; import ShlinkApiClient from '../../../src/utils/services/ShlinkApiClient'; +import { ShlinkShortUrlsResponse } from '../../../src/utils/services/types'; describe('shortUrlsListReducer', () => { describe('reducer', () => { it('returns loading on LIST_SHORT_URLS_START', () => expect(reducer(undefined, { type: LIST_SHORT_URLS_START } as any)).toEqual({ - shortUrls: { - data: [], - }, loading: true, error: false, })); @@ -32,9 +30,6 @@ describe('shortUrlsListReducer', () => { it('returns error on LIST_SHORT_URLS_ERROR', () => expect(reducer(undefined, { type: LIST_SHORT_URLS_ERROR } as any)).toEqual({ - shortUrls: { - data: [], - }, loading: false, error: true, })); @@ -43,13 +38,13 @@ describe('shortUrlsListReducer', () => { const shortCode = 'abc123'; const tags = [ 'foo', 'bar', 'baz' ]; const state = { - shortUrls: { + shortUrls: Mock.of({ data: [ Mock.of({ shortCode, tags: [] }), Mock.of({ shortCode, tags: [], domain: 'example.com' }), Mock.of({ shortCode: 'foo', tags: [] }), ], - }, + }), loading: false, error: false, }; @@ -75,13 +70,13 @@ describe('shortUrlsListReducer', () => { validSince: '2020-05-05', }; const state = { - shortUrls: { + shortUrls: Mock.of({ data: [ Mock.of({ shortCode, meta: { maxVisits: 10 }, domain }), Mock.of({ shortCode, meta: { maxVisits: 50 } }), Mock.of({ shortCode: 'foo', meta: {} }), ], - }, + }), loading: false, error: false, }; @@ -102,13 +97,13 @@ describe('shortUrlsListReducer', () => { it('removes matching URL on SHORT_URL_DELETED', () => { const shortCode = 'abc123'; const state = { - shortUrls: { + shortUrls: Mock.of({ data: [ Mock.of({ shortCode }), Mock.of({ shortCode, domain: 'example.com' }), Mock.of({ shortCode: 'foo' }), ], - }, + }), loading: false, error: false, }; @@ -129,13 +124,13 @@ describe('shortUrlsListReducer', () => { visitsCount: 11, }; const state = { - shortUrls: { + shortUrls: Mock.of({ data: [ Mock.of({ shortCode, domain: 'example.com', visitsCount: 5 }), Mock.of({ shortCode, visitsCount: 10 }), Mock.of({ shortCode: 'foo', visitsCount: 8 }), ], - }, + }), loading: false, error: false, };