mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2024-12-23 17:40:23 +03:00
Fixed ShortUrlsList test
This commit is contained in:
parent
5f33059de1
commit
ed038b9799
3 changed files with 50 additions and 47 deletions
|
@ -1,25 +1,20 @@
|
||||||
import { head, keys, values } from 'ramda';
|
import { head, keys, pipe, values } from 'ramda';
|
||||||
import { FC, useEffect, useState } from 'react';
|
import { FC, useEffect, useMemo, useState } from 'react';
|
||||||
import { RouteComponentProps } from 'react-router';
|
import { RouteComponentProps } from 'react-router';
|
||||||
import { Card } from 'reactstrap';
|
import { Card } from 'reactstrap';
|
||||||
import SortingDropdown from '../utils/SortingDropdown';
|
import SortingDropdown from '../utils/SortingDropdown';
|
||||||
import { determineOrderDir, Order, OrderDir } from '../utils/helpers/ordering';
|
import { determineOrderDir, Order, OrderDir } from '../utils/helpers/ordering';
|
||||||
import { getServerId, SelectedServer } from '../servers/data';
|
import { getServerId, SelectedServer } from '../servers/data';
|
||||||
import { boundToMercureHub } from '../mercure/helpers/boundToMercureHub';
|
import { boundToMercureHub } from '../mercure/helpers/boundToMercureHub';
|
||||||
import { parseQuery } from '../utils/helpers/query';
|
|
||||||
import { Topics } from '../mercure/helpers/Topics';
|
import { Topics } from '../mercure/helpers/Topics';
|
||||||
import { TableOrderIcon } from '../utils/table/TableOrderIcon';
|
import { TableOrderIcon } from '../utils/table/TableOrderIcon';
|
||||||
import { ShortUrlsList as ShortUrlsListState } from './reducers/shortUrlsList';
|
import { ShortUrlsList as ShortUrlsListState } from './reducers/shortUrlsList';
|
||||||
import { OrderableFields, ShortUrlsListParams, SORTABLE_FIELDS } from './reducers/shortUrlsListParams';
|
import { OrderableFields, ShortUrlsListParams, SORTABLE_FIELDS } from './reducers/shortUrlsListParams';
|
||||||
import { ShortUrlsTableProps } from './ShortUrlsTable';
|
import { ShortUrlsTableProps } from './ShortUrlsTable';
|
||||||
import Paginator from './Paginator';
|
import Paginator from './Paginator';
|
||||||
|
import { ShortUrlListRouteParams, useShortUrlsQuery } from './helpers/hooks';
|
||||||
|
|
||||||
interface RouteParams {
|
interface ShortUrlsListProps extends RouteComponentProps<ShortUrlListRouteParams> {
|
||||||
page: string;
|
|
||||||
serverId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ShortUrlsListProps extends RouteComponentProps<RouteParams> {
|
|
||||||
selectedServer: SelectedServer;
|
selectedServer: SelectedServer;
|
||||||
shortUrlsList: ShortUrlsListState;
|
shortUrlsList: ShortUrlsListState;
|
||||||
listShortUrls: (params: ShortUrlsListParams) => void;
|
listShortUrls: (params: ShortUrlsListParams) => void;
|
||||||
|
@ -29,21 +24,26 @@ export interface ShortUrlsListProps extends RouteComponentProps<RouteParams> {
|
||||||
|
|
||||||
type ShortUrlsOrder = Order<OrderableFields>;
|
type ShortUrlsOrder = Order<OrderableFields>;
|
||||||
|
|
||||||
const ShortUrlsList = (ShortUrlsTable: FC<ShortUrlsTableProps>) => boundToMercureHub(({
|
const ShortUrlsList = (ShortUrlsTable: FC<ShortUrlsTableProps>, SearchBar: FC) => boundToMercureHub(({
|
||||||
listShortUrls,
|
listShortUrls,
|
||||||
resetShortUrlParams,
|
resetShortUrlParams,
|
||||||
shortUrlsListParams,
|
shortUrlsListParams,
|
||||||
match,
|
match,
|
||||||
location,
|
location,
|
||||||
|
history,
|
||||||
shortUrlsList,
|
shortUrlsList,
|
||||||
selectedServer,
|
selectedServer,
|
||||||
}: ShortUrlsListProps) => {
|
}: ShortUrlsListProps) => {
|
||||||
|
const serverId = getServerId(selectedServer);
|
||||||
const { orderBy } = shortUrlsListParams;
|
const { orderBy } = shortUrlsListParams;
|
||||||
const [ order, setOrder ] = useState<ShortUrlsOrder>({
|
const [ order, setOrder ] = useState<ShortUrlsOrder>({
|
||||||
field: orderBy && (head(keys(orderBy)) as OrderableFields),
|
field: orderBy && (head(keys(orderBy)) as OrderableFields),
|
||||||
dir: orderBy && head(values(orderBy)),
|
dir: orderBy && head(values(orderBy)),
|
||||||
});
|
});
|
||||||
|
const [{ tags, search }, toFirstPage ] = useShortUrlsQuery({ history, match, location });
|
||||||
|
const decodedTags = useMemo(() => tags?.split(',').map(decodeURIComponent) ?? [], [ tags ]);
|
||||||
const { pagination } = shortUrlsList?.shortUrls ?? {};
|
const { pagination } = shortUrlsList?.shortUrls ?? {};
|
||||||
|
|
||||||
const refreshList = (extraParams: ShortUrlsListParams) => listShortUrls({ ...shortUrlsListParams, ...extraParams });
|
const refreshList = (extraParams: ShortUrlsListParams) => listShortUrls({ ...shortUrlsListParams, ...extraParams });
|
||||||
const handleOrderBy = (field?: OrderableFields, dir?: OrderDir) => {
|
const handleOrderBy = (field?: OrderableFields, dir?: OrderDir) => {
|
||||||
setOrder({ field, dir });
|
setOrder({ field, dir });
|
||||||
|
@ -52,30 +52,31 @@ const ShortUrlsList = (ShortUrlsTable: FC<ShortUrlsTableProps>) => boundToMercur
|
||||||
const orderByColumn = (field: OrderableFields) => () =>
|
const orderByColumn = (field: OrderableFields) => () =>
|
||||||
handleOrderBy(field, determineOrderDir(field, order.field, order.dir));
|
handleOrderBy(field, determineOrderDir(field, order.field, order.dir));
|
||||||
const renderOrderIcon = (field: OrderableFields) => <TableOrderIcon currentOrder={order} field={field} />;
|
const renderOrderIcon = (field: OrderableFields) => <TableOrderIcon currentOrder={order} field={field} />;
|
||||||
|
const addTag = pipe(
|
||||||
|
(newTag: string) => [ ...new Set([ ...decodedTags, newTag ]) ].join(','),
|
||||||
|
(tags) => toFirstPage({ tags }),
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => resetShortUrlParams, []);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const { tag } = parseQuery<{ tag?: string }>(location.search);
|
refreshList({ page: match.params.page, searchTerm: search, tags: decodedTags, itemsPerPage: undefined });
|
||||||
const tags = tag ? [ decodeURIComponent(tag) ] : shortUrlsListParams.tags;
|
}, [ match.params.page, search, decodedTags ]);
|
||||||
|
|
||||||
refreshList({ page: match.params.page, tags, itemsPerPage: undefined });
|
|
||||||
|
|
||||||
return resetShortUrlParams;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<div className="mb-3"><SearchBar /></div>
|
||||||
<div className="d-block d-lg-none mb-3">
|
<div className="d-block d-lg-none mb-3">
|
||||||
<SortingDropdown items={SORTABLE_FIELDS} order={order} onChange={handleOrderBy} />
|
<SortingDropdown items={SORTABLE_FIELDS} order={order} onChange={handleOrderBy} />
|
||||||
</div>
|
</div>
|
||||||
<Card body className="pb-1">
|
<Card body className="pb-1">
|
||||||
<ShortUrlsTable
|
<ShortUrlsTable
|
||||||
orderByColumn={orderByColumn}
|
|
||||||
renderOrderIcon={renderOrderIcon}
|
|
||||||
selectedServer={selectedServer}
|
selectedServer={selectedServer}
|
||||||
shortUrlsList={shortUrlsList}
|
shortUrlsList={shortUrlsList}
|
||||||
onTagClick={(tag) => refreshList({ tags: [ ...shortUrlsListParams.tags ?? [], tag ] })}
|
orderByColumn={orderByColumn}
|
||||||
|
renderOrderIcon={renderOrderIcon}
|
||||||
|
onTagClick={addTag}
|
||||||
/>
|
/>
|
||||||
<Paginator paginator={pagination} serverId={getServerId(selectedServer)} />
|
<Paginator paginator={pagination} serverId={serverId} currentQueryString={location.search} />
|
||||||
</Card>
|
</Card>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -35,7 +35,9 @@ export const ShortUrlsTable = (ShortUrlsRow: FC<ShortUrlsRowProps>) => ({
|
||||||
if (error) {
|
if (error) {
|
||||||
return (
|
return (
|
||||||
<tr>
|
<tr>
|
||||||
<td colSpan={6} className="text-center table-danger">Something went wrong while loading short URLs :(</td>
|
<td colSpan={6} className="text-center table-danger text-dark">
|
||||||
|
Something went wrong while loading short URLs :(
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +1,24 @@
|
||||||
import { shallow, ShallowWrapper } from 'enzyme';
|
import { shallow, ShallowWrapper } from 'enzyme';
|
||||||
import { ReactElement } from 'react';
|
import { ReactElement } from 'react';
|
||||||
import { Mock } from 'ts-mockery';
|
import { Mock } from 'ts-mockery';
|
||||||
import shortUrlsListCreator, { ShortUrlsListProps } from '../../src/short-urls/ShortUrlsList';
|
import { History, Location } from 'history';
|
||||||
|
import { match } from 'react-router';
|
||||||
|
import shortUrlsListCreator from '../../src/short-urls/ShortUrlsList';
|
||||||
import { ShortUrl } from '../../src/short-urls/data';
|
import { ShortUrl } from '../../src/short-urls/data';
|
||||||
import { MercureBoundProps } from '../../src/mercure/helpers/boundToMercureHub';
|
import { MercureBoundProps } from '../../src/mercure/helpers/boundToMercureHub';
|
||||||
import { ShortUrlsList as ShortUrlsListModel } from '../../src/short-urls/reducers/shortUrlsList';
|
import { ShortUrlsList as ShortUrlsListModel } from '../../src/short-urls/reducers/shortUrlsList';
|
||||||
import SortingDropdown from '../../src/utils/SortingDropdown';
|
import SortingDropdown from '../../src/utils/SortingDropdown';
|
||||||
import { OrderableFields, OrderBy } from '../../src/short-urls/reducers/shortUrlsListParams';
|
import { OrderableFields, OrderBy } from '../../src/short-urls/reducers/shortUrlsListParams';
|
||||||
import Paginator from '../../src/short-urls/Paginator';
|
import Paginator from '../../src/short-urls/Paginator';
|
||||||
|
import { ReachableServer } from '../../src/servers/data';
|
||||||
|
import { ShortUrlListRouteParams } from '../../src/short-urls/helpers/hooks';
|
||||||
|
|
||||||
describe('<ShortUrlsList />', () => {
|
describe('<ShortUrlsList />', () => {
|
||||||
let wrapper: ShallowWrapper;
|
let wrapper: ShallowWrapper;
|
||||||
const ShortUrlsTable = () => null;
|
const ShortUrlsTable = () => null;
|
||||||
|
const SearchBar = () => null;
|
||||||
const listShortUrlsMock = jest.fn();
|
const listShortUrlsMock = jest.fn();
|
||||||
const resetShortUrlParamsMock = jest.fn();
|
const push = jest.fn();
|
||||||
const shortUrlsList = Mock.of<ShortUrlsListModel>({
|
const shortUrlsList = Mock.of<ShortUrlsListModel>({
|
||||||
shortUrls: {
|
shortUrls: {
|
||||||
data: [
|
data: [
|
||||||
|
@ -26,22 +31,18 @@ describe('<ShortUrlsList />', () => {
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const ShortUrlsList = shortUrlsListCreator(ShortUrlsTable);
|
const ShortUrlsList = shortUrlsListCreator(ShortUrlsTable, SearchBar);
|
||||||
const createWrapper = (orderBy: OrderBy = {}) => shallow(
|
const createWrapper = (orderBy: OrderBy = {}) => shallow(
|
||||||
<ShortUrlsList
|
<ShortUrlsList
|
||||||
{...Mock.all<ShortUrlsListProps>()}
|
|
||||||
{...Mock.of<MercureBoundProps>({ mercureInfo: { loading: true } })}
|
{...Mock.of<MercureBoundProps>({ mercureInfo: { loading: true } })}
|
||||||
listShortUrls={listShortUrlsMock}
|
listShortUrls={listShortUrlsMock}
|
||||||
resetShortUrlParams={resetShortUrlParamsMock}
|
resetShortUrlParams={jest.fn()}
|
||||||
shortUrlsListParams={{
|
shortUrlsListParams={{ page: '1', orderBy }}
|
||||||
page: '1',
|
match={Mock.of<match<ShortUrlListRouteParams>>({ params: {} })}
|
||||||
tags: [ 'test tag' ],
|
location={Mock.of<Location>({ search: '?tags=test%20tag&search=example.com' })}
|
||||||
searchTerm: 'example.com',
|
|
||||||
orderBy,
|
|
||||||
}}
|
|
||||||
match={{ params: {} } as any}
|
|
||||||
location={{} as any}
|
|
||||||
shortUrlsList={shortUrlsList}
|
shortUrlsList={shortUrlsList}
|
||||||
|
history={Mock.of<History>({ push })}
|
||||||
|
selectedServer={Mock.of<ReachableServer>({ id: '1' })}
|
||||||
/>,
|
/>,
|
||||||
).dive(); // Dive is needed as this component is wrapped in a HOC
|
).dive(); // Dive is needed as this component is wrapped in a HOC
|
||||||
|
|
||||||
|
@ -56,6 +57,11 @@ describe('<ShortUrlsList />', () => {
|
||||||
expect(wrapper.find(ShortUrlsTable)).toHaveLength(1);
|
expect(wrapper.find(ShortUrlsTable)).toHaveLength(1);
|
||||||
expect(wrapper.find(SortingDropdown)).toHaveLength(1);
|
expect(wrapper.find(SortingDropdown)).toHaveLength(1);
|
||||||
expect(wrapper.find(Paginator)).toHaveLength(1);
|
expect(wrapper.find(Paginator)).toHaveLength(1);
|
||||||
|
expect(wrapper.find(SearchBar)).toHaveLength(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('passes current query to paginator', () => {
|
||||||
|
expect(wrapper.find(Paginator).prop('currentQueryString')).toEqual('?tags=test%20tag&search=example.com');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('gets list refreshed every time a tag is clicked', () => {
|
it('gets list refreshed every time a tag is clicked', () => {
|
||||||
|
@ -63,16 +69,10 @@ describe('<ShortUrlsList />', () => {
|
||||||
wrapper.find(ShortUrlsTable).simulate('tagClick', 'bar');
|
wrapper.find(ShortUrlsTable).simulate('tagClick', 'bar');
|
||||||
wrapper.find(ShortUrlsTable).simulate('tagClick', 'baz');
|
wrapper.find(ShortUrlsTable).simulate('tagClick', 'baz');
|
||||||
|
|
||||||
expect(listShortUrlsMock).toHaveBeenCalledTimes(3);
|
expect(push).toHaveBeenCalledTimes(3);
|
||||||
expect(listShortUrlsMock).toHaveBeenNthCalledWith(1, expect.objectContaining({
|
expect(push).toHaveBeenNthCalledWith(1, expect.stringContaining(`tags=${encodeURIComponent('test tag,foo')}`));
|
||||||
tags: [ 'test tag', 'foo' ],
|
expect(push).toHaveBeenNthCalledWith(2, expect.stringContaining(`tags=${encodeURIComponent('test tag,bar')}`));
|
||||||
}));
|
expect(push).toHaveBeenNthCalledWith(3, expect.stringContaining(`tags=${encodeURIComponent('test tag,baz')}`));
|
||||||
expect(listShortUrlsMock).toHaveBeenNthCalledWith(2, expect.objectContaining({
|
|
||||||
tags: [ 'test tag', 'bar' ],
|
|
||||||
}));
|
|
||||||
expect(listShortUrlsMock).toHaveBeenNthCalledWith(3, expect.objectContaining({
|
|
||||||
tags: [ 'test tag', 'baz' ],
|
|
||||||
}));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('invokes order icon rendering', () => {
|
it('invokes order icon rendering', () => {
|
||||||
|
@ -88,7 +88,7 @@ describe('<ShortUrlsList />', () => {
|
||||||
expect(renderIcon('visits').props.currentOrder).toEqual({ field: 'visits', dir: 'ASC' });
|
expect(renderIcon('visits').props.currentOrder).toEqual({ field: 'visits', dir: 'ASC' });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handles order by through table', () => {
|
it('handles order through table', () => {
|
||||||
const orderByColumn: (field: OrderableFields) => Function = wrapper.find(ShortUrlsTable).prop('orderByColumn');
|
const orderByColumn: (field: OrderableFields) => Function = wrapper.find(ShortUrlsTable).prop('orderByColumn');
|
||||||
|
|
||||||
orderByColumn('visits')();
|
orderByColumn('visits')();
|
||||||
|
@ -107,7 +107,7 @@ describe('<ShortUrlsList />', () => {
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handles order by through dropdown', () => {
|
it('handles order through dropdown', () => {
|
||||||
expect(wrapper.find(SortingDropdown).prop('order')).toEqual({});
|
expect(wrapper.find(SortingDropdown).prop('order')).toEqual({});
|
||||||
|
|
||||||
wrapper.find(SortingDropdown).simulate('change', 'visits', 'ASC');
|
wrapper.find(SortingDropdown).simulate('change', 'visits', 'ASC');
|
||||||
|
|
Loading…
Reference in a new issue