mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2025-01-09 09:47:28 +03:00
Added routes to subsections in visits
This commit is contained in:
parent
214b952e84
commit
83221c1066
8 changed files with 80 additions and 73 deletions
|
@ -65,8 +65,8 @@ const MenuLayout = (
|
||||||
<Route exact path="/server/:serverId/overview" component={Overview} />
|
<Route exact path="/server/:serverId/overview" component={Overview} />
|
||||||
<Route exact path="/server/:serverId/list-short-urls/:page" component={ShortUrls} />
|
<Route exact path="/server/:serverId/list-short-urls/:page" component={ShortUrls} />
|
||||||
<Route exact path="/server/:serverId/create-short-url" component={CreateShortUrl} />
|
<Route exact path="/server/:serverId/create-short-url" component={CreateShortUrl} />
|
||||||
<Route exact path="/server/:serverId/short-code/:shortCode/visits" component={ShortUrlVisits} />
|
<Route path="/server/:serverId/short-code/:shortCode/visits" component={ShortUrlVisits} />
|
||||||
{addTagsVisitsRoute && <Route exact path="/server/:serverId/tag/:tag/visits" component={TagVisits} />}
|
{addTagsVisitsRoute && <Route path="/server/:serverId/tag/:tag/visits" component={TagVisits} />}
|
||||||
<Route exact path="/server/:serverId/manage-tags" component={TagsList} />
|
<Route exact path="/server/:serverId/manage-tags" component={TagsList} />
|
||||||
<Route
|
<Route
|
||||||
render={() => <NotFound to={`/server/${selectedServer.id}/list-short-urls/1`}>List short URLs</NotFound>}
|
render={() => <NotFound to={`/server/${selectedServer.id}/list-short-urls/1`}>List short URLs</NotFound>}
|
||||||
|
|
|
@ -2,12 +2,12 @@ import { faCaretDown as caretDownIcon, faCaretUp as caretUpIcon } from '@fortawe
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import { head, keys, values } from 'ramda';
|
import { head, keys, values } from 'ramda';
|
||||||
import { FC, useEffect, useState } from 'react';
|
import { FC, useEffect, useState } from 'react';
|
||||||
import qs from 'qs';
|
|
||||||
import { RouteComponentProps } from 'react-router';
|
import { RouteComponentProps } from 'react-router';
|
||||||
import SortingDropdown from '../utils/SortingDropdown';
|
import SortingDropdown from '../utils/SortingDropdown';
|
||||||
import { determineOrderDir, OrderDir } from '../utils/utils';
|
import { determineOrderDir, OrderDir } from '../utils/utils';
|
||||||
import { SelectedServer } from '../servers/data';
|
import { SelectedServer } from '../servers/data';
|
||||||
import { boundToMercureHub } from '../mercure/helpers/boundToMercureHub';
|
import { boundToMercureHub } from '../mercure/helpers/boundToMercureHub';
|
||||||
|
import { parseQuery } from '../utils/helpers/query';
|
||||||
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';
|
||||||
|
@ -65,8 +65,8 @@ const ShortUrlsList = (ShortUrlsTable: FC<ShortUrlsTableProps>) => boundToMercur
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const query = qs.parse(location.search, { ignoreQueryPrefix: true });
|
const { tag } = parseQuery<{ tag?: string }>(location.search);
|
||||||
const tags = query.tag ? [ query.tag as string ] : shortUrlsListParams.tags;
|
const tags = tag ? [ tag ] : shortUrlsListParams.tags;
|
||||||
|
|
||||||
refreshList({ page: match.params.page, tags, itemsPerPage: undefined });
|
refreshList({ page: match.params.page, tags, itemsPerPage: undefined });
|
||||||
|
|
||||||
|
|
5
src/utils/helpers/query.ts
Normal file
5
src/utils/helpers/query.ts
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
import qs from 'qs';
|
||||||
|
|
||||||
|
export const parseQuery = <T>(search: string) => qs.parse(search, { ignoreQueryPrefix: true }) as unknown as T;
|
||||||
|
|
||||||
|
export const stringifyQuery = (query: any): string => qs.stringify(query, { arrayFormat: 'brackets' });
|
|
@ -1,8 +1,8 @@
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import qs from 'qs';
|
|
||||||
import { RouteComponentProps } from 'react-router';
|
import { RouteComponentProps } from 'react-router';
|
||||||
import { boundToMercureHub } from '../mercure/helpers/boundToMercureHub';
|
import { boundToMercureHub } from '../mercure/helpers/boundToMercureHub';
|
||||||
import { ShlinkVisitsParams } from '../utils/services/types';
|
import { ShlinkVisitsParams } from '../utils/services/types';
|
||||||
|
import { parseQuery } from '../utils/helpers/query';
|
||||||
import { ShortUrlVisits as ShortUrlVisitsState } from './reducers/shortUrlVisits';
|
import { ShortUrlVisits as ShortUrlVisitsState } from './reducers/shortUrlVisits';
|
||||||
import ShortUrlVisitsHeader from './ShortUrlVisitsHeader';
|
import ShortUrlVisitsHeader from './ShortUrlVisitsHeader';
|
||||||
import { ShortUrlDetail } from './reducers/shortUrlDetail';
|
import { ShortUrlDetail } from './reducers/shortUrlDetail';
|
||||||
|
@ -18,7 +18,7 @@ export interface ShortUrlVisitsProps extends RouteComponentProps<{ shortCode: st
|
||||||
|
|
||||||
const ShortUrlVisits = boundToMercureHub(({
|
const ShortUrlVisits = boundToMercureHub(({
|
||||||
history: { goBack },
|
history: { goBack },
|
||||||
match,
|
match: { params, url },
|
||||||
location: { search },
|
location: { search },
|
||||||
shortUrlVisits,
|
shortUrlVisits,
|
||||||
shortUrlDetail,
|
shortUrlDetail,
|
||||||
|
@ -26,9 +26,8 @@ const ShortUrlVisits = boundToMercureHub(({
|
||||||
getShortUrlDetail,
|
getShortUrlDetail,
|
||||||
cancelGetShortUrlVisits,
|
cancelGetShortUrlVisits,
|
||||||
}: ShortUrlVisitsProps) => {
|
}: ShortUrlVisitsProps) => {
|
||||||
const { params } = match;
|
|
||||||
const { shortCode } = params;
|
const { shortCode } = params;
|
||||||
const { domain } = qs.parse(search, { ignoreQueryPrefix: true }) as { domain?: string };
|
const { domain } = parseQuery<{ domain?: string }>(search);
|
||||||
|
|
||||||
const loadVisits = (params: Partial<ShlinkVisitsParams>) => getShortUrlVisits(shortCode, { ...params, domain });
|
const loadVisits = (params: Partial<ShlinkVisitsParams>) => getShortUrlVisits(shortCode, { ...params, domain });
|
||||||
|
|
||||||
|
@ -37,7 +36,13 @@ const ShortUrlVisits = boundToMercureHub(({
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<VisitsStats getVisits={loadVisits} cancelGetVisits={cancelGetShortUrlVisits} visitsInfo={shortUrlVisits}>
|
<VisitsStats
|
||||||
|
getVisits={loadVisits}
|
||||||
|
cancelGetVisits={cancelGetShortUrlVisits}
|
||||||
|
visitsInfo={shortUrlVisits}
|
||||||
|
baseUrl={url}
|
||||||
|
domain={domain}
|
||||||
|
>
|
||||||
<ShortUrlVisitsHeader shortUrlDetail={shortUrlDetail} shortUrlVisits={shortUrlVisits} goBack={goBack} />
|
<ShortUrlVisitsHeader shortUrlDetail={shortUrlDetail} shortUrlVisits={shortUrlVisits} goBack={goBack} />
|
||||||
</VisitsStats>
|
</VisitsStats>
|
||||||
);
|
);
|
||||||
|
|
|
@ -14,17 +14,16 @@ export interface TagVisitsProps extends RouteComponentProps<{ tag: string }> {
|
||||||
|
|
||||||
const TagVisits = (colorGenerator: ColorGenerator) => boundToMercureHub(({
|
const TagVisits = (colorGenerator: ColorGenerator) => boundToMercureHub(({
|
||||||
history: { goBack },
|
history: { goBack },
|
||||||
match,
|
match: { params, url },
|
||||||
getTagVisits,
|
getTagVisits,
|
||||||
tagVisits,
|
tagVisits,
|
||||||
cancelGetTagVisits,
|
cancelGetTagVisits,
|
||||||
}: TagVisitsProps) => {
|
}: TagVisitsProps) => {
|
||||||
const { params } = match;
|
|
||||||
const { tag } = params;
|
const { tag } = params;
|
||||||
const loadVisits = (params: ShlinkVisitsParams) => getTagVisits(tag, params);
|
const loadVisits = (params: ShlinkVisitsParams) => getTagVisits(tag, params);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<VisitsStats getVisits={loadVisits} cancelGetVisits={cancelGetTagVisits} visitsInfo={tagVisits}>
|
<VisitsStats getVisits={loadVisits} cancelGetVisits={cancelGetTagVisits} visitsInfo={tagVisits} baseUrl={url}>
|
||||||
<TagVisitsHeader tagVisits={tagVisits} goBack={goBack} colorGenerator={colorGenerator} />
|
<TagVisitsHeader tagVisits={tagVisits} goBack={goBack} colorGenerator={colorGenerator} />
|
||||||
</VisitsStats>
|
</VisitsStats>
|
||||||
);
|
);
|
||||||
|
|
|
@ -4,6 +4,8 @@ import { Button, Card, Nav, NavLink, Progress } from 'reactstrap';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import { faCalendarAlt, faMapMarkedAlt, faList, faChartPie } from '@fortawesome/free-solid-svg-icons';
|
import { faCalendarAlt, faMapMarkedAlt, faList, faChartPie } from '@fortawesome/free-solid-svg-icons';
|
||||||
import { IconDefinition } from '@fortawesome/fontawesome-common-types';
|
import { IconDefinition } from '@fortawesome/fontawesome-common-types';
|
||||||
|
import { Route, Switch, NavLink as RouterNavLink, Redirect } from 'react-router-dom';
|
||||||
|
import { Location } from 'history';
|
||||||
import { DateRangeSelector } from '../utils/dates/DateRangeSelector';
|
import { DateRangeSelector } from '../utils/dates/DateRangeSelector';
|
||||||
import Message from '../utils/Message';
|
import Message from '../utils/Message';
|
||||||
import { formatIsoDate } from '../utils/helpers/date';
|
import { formatIsoDate } from '../utils/helpers/date';
|
||||||
|
@ -22,16 +24,18 @@ export interface VisitsStatsProps {
|
||||||
getVisits: (params: Partial<ShlinkVisitsParams>) => void;
|
getVisits: (params: Partial<ShlinkVisitsParams>) => void;
|
||||||
visitsInfo: VisitsInfo;
|
visitsInfo: VisitsInfo;
|
||||||
cancelGetVisits: () => void;
|
cancelGetVisits: () => void;
|
||||||
|
baseUrl: string;
|
||||||
|
domain?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
type HighlightableProps = 'referer' | 'country' | 'city';
|
type HighlightableProps = 'referer' | 'country' | 'city';
|
||||||
type Section = 'byTime' | 'byContext' | 'byLocation' | 'list';
|
type Section = 'byTime' | 'byContext' | 'byLocation' | 'list';
|
||||||
|
|
||||||
const sections: Record<Section, { title: string; icon: IconDefinition }> = {
|
const sections: Record<Section, { title: string; subPath?: string; icon: IconDefinition }> = {
|
||||||
byTime: { title: 'By time', icon: faCalendarAlt },
|
byTime: { title: 'By time', icon: faCalendarAlt },
|
||||||
byContext: { title: 'By context', icon: faChartPie },
|
byContext: { title: 'By context', subPath: 'by-context', icon: faChartPie },
|
||||||
byLocation: { title: 'By location', icon: faMapMarkedAlt },
|
byLocation: { title: 'By location', subPath: 'by-location', icon: faMapMarkedAlt },
|
||||||
list: { title: 'List', icon: faList },
|
list: { title: 'List', subPath: 'list', icon: faList },
|
||||||
};
|
};
|
||||||
|
|
||||||
const highlightedVisitsToStats = (
|
const highlightedVisitsToStats = (
|
||||||
|
@ -49,13 +53,16 @@ const highlightedVisitsToStats = (
|
||||||
let selectedBar: string | undefined;
|
let selectedBar: string | undefined;
|
||||||
const initialInterval: DateInterval = 'last30Days';
|
const initialInterval: DateInterval = 'last30Days';
|
||||||
|
|
||||||
const VisitsStats: FC<VisitsStatsProps> = ({ children, visitsInfo, getVisits, cancelGetVisits }) => {
|
const VisitsStats: FC<VisitsStatsProps> = ({ children, visitsInfo, getVisits, cancelGetVisits, baseUrl, domain }) => {
|
||||||
const [ dateRange, setDateRange ] = useState<DateRange>(intervalToDateRange(initialInterval));
|
const [ dateRange, setDateRange ] = useState<DateRange>(intervalToDateRange(initialInterval));
|
||||||
const [ highlightedVisits, setHighlightedVisits ] = useState<NormalizedVisit[]>([]);
|
const [ highlightedVisits, setHighlightedVisits ] = useState<NormalizedVisit[]>([]);
|
||||||
const [ highlightedLabel, setHighlightedLabel ] = useState<string | undefined>();
|
const [ highlightedLabel, setHighlightedLabel ] = useState<string | undefined>();
|
||||||
const [ activeSection, setActiveSection ] = useState<Section>('byTime');
|
|
||||||
const onSectionChange = (section: Section) => () => setActiveSection(section);
|
|
||||||
|
|
||||||
|
const buildSectionUrl = (subPath?: string) => {
|
||||||
|
const query = domain ? `?domain=${domain}` : '';
|
||||||
|
|
||||||
|
return !subPath ? `${baseUrl}${query}` : `${baseUrl}/${subPath}${query}`;
|
||||||
|
};
|
||||||
const { visits, loading, loadingLarge, error, progress } = visitsInfo;
|
const { visits, loading, loadingLarge, error, progress } = visitsInfo;
|
||||||
const normalizedVisits = useMemo(() => normalizeVisits(visits), [ visits ]);
|
const normalizedVisits = useMemo(() => normalizeVisits(visits), [ visits ]);
|
||||||
const { os, browsers, referrers, countries, cities, citiesForMap } = useMemo(
|
const { os, browsers, referrers, countries, cities, citiesForMap } = useMemo(
|
||||||
|
@ -120,12 +127,15 @@ const VisitsStats: FC<VisitsStatsProps> = ({ children, visitsInfo, getVisits, ca
|
||||||
<Card className="visits-stats__nav p-0 mt-4 overflow-hidden" body>
|
<Card className="visits-stats__nav p-0 mt-4 overflow-hidden" body>
|
||||||
<Nav pills justified>
|
<Nav pills justified>
|
||||||
{Object.entries(sections).map(
|
{Object.entries(sections).map(
|
||||||
([ section, { title, icon }]) => (
|
([ section, { title, icon, subPath }]) => (
|
||||||
<NavLink
|
<NavLink
|
||||||
key={section}
|
key={section}
|
||||||
active={activeSection === section}
|
tag={RouterNavLink}
|
||||||
className="visits-stats__nav-link"
|
className="visits-stats__nav-link"
|
||||||
onClick={onSectionChange(section as Section)}
|
to={buildSectionUrl(subPath)}
|
||||||
|
isActive={(_: null, { pathname }: Location) =>
|
||||||
|
(!subPath && pathname.endsWith('/visits')) || (subPath && pathname.endsWith(subPath))
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon icon={icon} />
|
<FontAwesomeIcon icon={icon} />
|
||||||
<span className="ml-2 d-none d-sm-inline">{title}</span>
|
<span className="ml-2 d-none d-sm-inline">{title}</span>
|
||||||
|
@ -135,19 +145,20 @@ const VisitsStats: FC<VisitsStatsProps> = ({ children, visitsInfo, getVisits, ca
|
||||||
</Nav>
|
</Nav>
|
||||||
</Card>
|
</Card>
|
||||||
<div className="row">
|
<div className="row">
|
||||||
{activeSection === 'byTime' && (
|
<Switch>
|
||||||
<div className="col-12 mt-4">
|
<Route exact path={baseUrl}>
|
||||||
<LineChartCard
|
<div className="col-12 mt-4">
|
||||||
title="Visits during time"
|
<LineChartCard
|
||||||
visits={normalizedVisits}
|
title="Visits during time"
|
||||||
highlightedVisits={highlightedVisits}
|
visits={normalizedVisits}
|
||||||
highlightedLabel={highlightedLabel}
|
highlightedVisits={highlightedVisits}
|
||||||
setSelectedVisits={setSelectedVisits}
|
highlightedLabel={highlightedLabel}
|
||||||
/>
|
setSelectedVisits={setSelectedVisits}
|
||||||
</div>
|
/>
|
||||||
)}
|
</div>
|
||||||
{activeSection === 'byContext' && (
|
</Route>
|
||||||
<>
|
|
||||||
|
<Route exact path={`${baseUrl}/by-context`}>
|
||||||
<div className="col-xl-4 col-lg-6 mt-4">
|
<div className="col-xl-4 col-lg-6 mt-4">
|
||||||
<GraphCard title="Operating systems" stats={os} />
|
<GraphCard title="Operating systems" stats={os} />
|
||||||
</div>
|
</div>
|
||||||
|
@ -168,10 +179,9 @@ const VisitsStats: FC<VisitsStatsProps> = ({ children, visitsInfo, getVisits, ca
|
||||||
onClick={highlightVisitsForProp('referer')}
|
onClick={highlightVisitsForProp('referer')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</Route>
|
||||||
)}
|
|
||||||
{activeSection === 'byLocation' && (
|
<Route exact path={`${baseUrl}/by-location`}>
|
||||||
<>
|
|
||||||
<div className="col-lg-6 mt-4">
|
<div className="col-lg-6 mt-4">
|
||||||
<SortableBarGraph
|
<SortableBarGraph
|
||||||
title="Countries"
|
title="Countries"
|
||||||
|
@ -202,18 +212,20 @@ const VisitsStats: FC<VisitsStatsProps> = ({ children, visitsInfo, getVisits, ca
|
||||||
onClick={highlightVisitsForProp('city')}
|
onClick={highlightVisitsForProp('city')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</Route>
|
||||||
)}
|
|
||||||
{activeSection === 'list' && (
|
<Route exact path={`${baseUrl}/list`}>
|
||||||
<div className="col-12">
|
<div className="col-12">
|
||||||
<VisitsTable
|
<VisitsTable
|
||||||
visits={normalizedVisits}
|
visits={normalizedVisits}
|
||||||
selectedVisits={highlightedVisits}
|
selectedVisits={highlightedVisits}
|
||||||
setSelectedVisits={setSelectedVisits}
|
setSelectedVisits={setSelectedVisits}
|
||||||
isSticky
|
/>
|
||||||
/>
|
</div>
|
||||||
</div>
|
</Route>
|
||||||
)}
|
|
||||||
|
<Redirect to={baseUrl} />
|
||||||
|
</Switch>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -19,7 +19,6 @@ interface VisitsTableProps {
|
||||||
visits: NormalizedVisit[];
|
visits: NormalizedVisit[];
|
||||||
selectedVisits?: NormalizedVisit[];
|
selectedVisits?: NormalizedVisit[];
|
||||||
setSelectedVisits: (visits: NormalizedVisit[]) => void;
|
setSelectedVisits: (visits: NormalizedVisit[]) => void;
|
||||||
isSticky?: boolean;
|
|
||||||
matchMedia?: (query: string) => MediaQueryList;
|
matchMedia?: (query: string) => MediaQueryList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,12 +55,9 @@ const VisitsTable = ({
|
||||||
visits,
|
visits,
|
||||||
selectedVisits = [],
|
selectedVisits = [],
|
||||||
setSelectedVisits,
|
setSelectedVisits,
|
||||||
isSticky = false,
|
|
||||||
matchMedia = window.matchMedia,
|
matchMedia = window.matchMedia,
|
||||||
}: VisitsTableProps) => {
|
}: VisitsTableProps) => {
|
||||||
const headerCellsClass = classNames('visits-table__header-cell', {
|
const headerCellsClass = 'visits-table__header-cell visits-table__sticky';
|
||||||
'visits-table__sticky': isSticky,
|
|
||||||
});
|
|
||||||
const matchMobile = () => matchMedia('(max-width: 767px)').matches;
|
const matchMobile = () => matchMedia('(max-width: 767px)').matches;
|
||||||
|
|
||||||
const [ isMobileDevice, setIsMobileDevice ] = useState(matchMobile());
|
const [ isMobileDevice, setIsMobileDevice ] = useState(matchMobile());
|
||||||
|
@ -105,9 +101,7 @@ const VisitsTable = ({
|
||||||
<thead className="visits-table__header">
|
<thead className="visits-table__header">
|
||||||
<tr>
|
<tr>
|
||||||
<th
|
<th
|
||||||
className={classNames('visits-table__header-cell text-center', {
|
className="visits-table__header-cell visits-table__sticky text-center"
|
||||||
'visits-table__sticky': isSticky,
|
|
||||||
})}
|
|
||||||
onClick={() => setSelectedVisits(
|
onClick={() => setSelectedVisits(
|
||||||
selectedVisits.length < resultSet.total ? resultSet.visitsGroups.flat() : [],
|
selectedVisits.length < resultSet.total ? resultSet.visitsGroups.flat() : [],
|
||||||
)}
|
)}
|
||||||
|
@ -183,7 +177,7 @@ const VisitsTable = ({
|
||||||
{resultSet.total > PAGE_SIZE && (
|
{resultSet.total > PAGE_SIZE && (
|
||||||
<tfoot>
|
<tfoot>
|
||||||
<tr>
|
<tr>
|
||||||
<td colSpan={7} className={classNames('visits-table__footer-cell', { 'visits-table__sticky': isSticky })}>
|
<td colSpan={7} className="visits-table__footer-cell visits-table__sticky">
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col-md-6">
|
<div className="col-md-6">
|
||||||
<SimplePaginator
|
<SimplePaginator
|
||||||
|
|
|
@ -21,6 +21,7 @@ describe('<VisitStats />', () => {
|
||||||
getVisits={getVisitsMock}
|
getVisits={getVisitsMock}
|
||||||
visitsInfo={Mock.of<VisitsInfo>(visitsInfo)}
|
visitsInfo={Mock.of<VisitsInfo>(visitsInfo)}
|
||||||
cancelGetVisits={() => {}}
|
cancelGetVisits={() => {}}
|
||||||
|
baseUrl={''}
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -66,24 +67,15 @@ describe('<VisitStats />', () => {
|
||||||
expect(message.html()).toContain('There are no visits matching current filter :(');
|
expect(message.html()).toContain('There are no visits matching current filter :(');
|
||||||
});
|
});
|
||||||
|
|
||||||
it.each([
|
it('renders expected amount of graphics', () => {
|
||||||
[ 0, 1, 0 ],
|
|
||||||
[ 1, 3, 0 ],
|
|
||||||
[ 2, 2, 0 ],
|
|
||||||
[ 3, 0, 1 ],
|
|
||||||
])('renders expected amount of graphics based on active section', (navIndex, expectedGraphics, expectedTables) => {
|
|
||||||
const wrapper = createComponent({ loading: false, error: false, visits });
|
const wrapper = createComponent({ loading: false, error: false, visits });
|
||||||
const nav = wrapper.find(NavLink).at(navIndex);
|
|
||||||
|
|
||||||
nav.simulate('click');
|
|
||||||
|
|
||||||
const graphs = wrapper.find(GraphCard);
|
const graphs = wrapper.find(GraphCard);
|
||||||
const sortableBarGraphs = wrapper.find(SortableBarGraph);
|
const sortableBarGraphs = wrapper.find(SortableBarGraph);
|
||||||
const lineChart = wrapper.find(LineChartCard);
|
const lineChart = wrapper.find(LineChartCard);
|
||||||
const table = wrapper.find(VisitsTable);
|
const table = wrapper.find(VisitsTable);
|
||||||
|
|
||||||
expect(graphs.length + sortableBarGraphs.length + lineChart.length).toEqual(expectedGraphics);
|
expect(graphs.length + sortableBarGraphs.length + lineChart.length).toEqual(6);
|
||||||
expect(table).toHaveLength(expectedTables);
|
expect(table).toHaveLength(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('holds the map button content generator on cities graph extraHeaderContent', () => {
|
it('holds the map button content generator on cities graph extraHeaderContent', () => {
|
||||||
|
|
Loading…
Reference in a new issue