mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2025-01-09 09:47:28 +03:00
Finished migrating visits helpers to TS
This commit is contained in:
parent
8a146021dd
commit
260ed3041a
7 changed files with 130 additions and 118 deletions
|
@ -47,15 +47,15 @@ const generateGraphData = (
|
|||
borderColor: isBarChart ? 'rgba(70, 150, 229, 1)' : 'white',
|
||||
borderWidth: 2,
|
||||
},
|
||||
(highlightedData && {
|
||||
highlightedData && {
|
||||
title,
|
||||
label: highlightedLabel ?? 'Selected',
|
||||
data: highlightedData,
|
||||
backgroundColor: 'rgba(247, 127, 40, 0.4)',
|
||||
borderColor: '#F77F28',
|
||||
borderWidth: 2,
|
||||
}) as unknown as ChartDataSets,
|
||||
].filter(Boolean),
|
||||
},
|
||||
].filter(Boolean) as ChartDataSets[],
|
||||
});
|
||||
|
||||
const dropLabelIfHidden = (label: string) => label.startsWith('hidden') ? '' : label;
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import React, { useState, useMemo } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
Card,
|
||||
CardHeader,
|
||||
|
@ -12,35 +11,38 @@ import {
|
|||
import { Line } from 'react-chartjs-2';
|
||||
import { always, cond, reverse } from 'ramda';
|
||||
import moment from 'moment';
|
||||
import { VisitType } from '../types';
|
||||
import { ChartData, ChartDataSets } from 'chart.js';
|
||||
import { Stats, Visit } from '../types';
|
||||
import { fillTheGaps } from '../../utils/helpers/visits';
|
||||
import './LineChartCard.scss';
|
||||
import { useToggle } from '../../utils/helpers/hooks';
|
||||
import { rangeOf } from '../../utils/utils';
|
||||
import ToggleSwitch from '../../utils/ToggleSwitch';
|
||||
import './LineChartCard.scss';
|
||||
|
||||
const propTypes = {
|
||||
title: PropTypes.string,
|
||||
highlightedLabel: PropTypes.string,
|
||||
visits: PropTypes.arrayOf(VisitType),
|
||||
highlightedVisits: PropTypes.arrayOf(VisitType),
|
||||
};
|
||||
interface LineChartCardProps {
|
||||
title: string;
|
||||
highlightedLabel?: string;
|
||||
visits: Visit[];
|
||||
highlightedVisits: Visit[];
|
||||
}
|
||||
|
||||
const STEPS_MAP = {
|
||||
type Step = 'monthly' | 'weekly' | 'daily' | 'hourly';
|
||||
|
||||
const STEPS_MAP: Record<Step, string> = {
|
||||
monthly: 'Month',
|
||||
weekly: 'Week',
|
||||
daily: 'Day',
|
||||
hourly: 'Hour',
|
||||
};
|
||||
|
||||
const STEP_TO_DATE_UNIT_MAP = {
|
||||
const STEP_TO_DATE_UNIT_MAP: Record<Step, moment.unitOfTime.Diff> = {
|
||||
hourly: 'hour',
|
||||
daily: 'day',
|
||||
weekly: 'week',
|
||||
monthly: 'month',
|
||||
};
|
||||
|
||||
const STEP_TO_DATE_FORMAT = {
|
||||
const STEP_TO_DATE_FORMAT: Record<Step, (date: moment.Moment | string) => string> = {
|
||||
hourly: (date) => moment(date).format('YYYY-MM-DD HH:00'),
|
||||
daily: (date) => moment(date).format('YYYY-MM-DD'),
|
||||
weekly(date) {
|
||||
|
@ -52,19 +54,19 @@ const STEP_TO_DATE_FORMAT = {
|
|||
monthly: (date) => moment(date).format('YYYY-MM'),
|
||||
};
|
||||
|
||||
const determineInitialStep = (oldestVisitDate) => {
|
||||
const determineInitialStep = (oldestVisitDate: string): Step => {
|
||||
const now = moment();
|
||||
const oldestDate = moment(oldestVisitDate);
|
||||
const matcher = cond([
|
||||
[ () => now.diff(oldestDate, 'day') <= 2, always('hourly') ], // Less than 2 days
|
||||
[ () => now.diff(oldestDate, 'month') <= 1, always('daily') ], // Between 2 days and 1 month
|
||||
[ () => now.diff(oldestDate, 'month') <= 6, always('weekly') ], // Between 1 and 6 months
|
||||
const matcher = cond<never, Step | undefined>([
|
||||
[ () => now.diff(oldestDate, 'day') <= 2, always<Step>('hourly') ], // Less than 2 days
|
||||
[ () => now.diff(oldestDate, 'month') <= 1, always<Step>('daily') ], // Between 2 days and 1 month
|
||||
[ () => now.diff(oldestDate, 'month') <= 6, always<Step>('weekly') ], // Between 1 and 6 months
|
||||
]);
|
||||
|
||||
return matcher() || 'monthly';
|
||||
return matcher() ?? 'monthly';
|
||||
};
|
||||
|
||||
const groupVisitsByStep = (step, visits) => visits.reduce((acc, visit) => {
|
||||
const groupVisitsByStep = (step: Step, visits: Visit[]): Stats => visits.reduce<Stats>((acc, visit) => {
|
||||
const key = STEP_TO_DATE_FORMAT[step](visit.date);
|
||||
|
||||
acc[key] = acc[key] ? acc[key] + 1 : 1;
|
||||
|
@ -72,7 +74,7 @@ const groupVisitsByStep = (step, visits) => visits.reduce((acc, visit) => {
|
|||
return acc;
|
||||
}, {});
|
||||
|
||||
const generateLabels = (step, visits) => {
|
||||
const generateLabels = (step: Step, visits: Visit[]): string[] => {
|
||||
const unit = STEP_TO_DATE_UNIT_MAP[step];
|
||||
const formatter = STEP_TO_DATE_FORMAT[step];
|
||||
const newerDate = moment(visits[0].date);
|
||||
|
@ -85,9 +87,14 @@ const generateLabels = (step, visits) => {
|
|||
];
|
||||
};
|
||||
|
||||
const generateLabelsAndGroupedVisits = (visits, groupedVisitsWithGaps, step, skipNoElements) => {
|
||||
const generateLabelsAndGroupedVisits = (
|
||||
visits: Visit[],
|
||||
groupedVisitsWithGaps: Stats,
|
||||
step: Step,
|
||||
skipNoElements: boolean,
|
||||
): [string[], number[]] => {
|
||||
if (skipNoElements) {
|
||||
return [ Object.keys(groupedVisitsWithGaps), groupedVisitsWithGaps ];
|
||||
return [ Object.keys(groupedVisitsWithGaps), Object.values(groupedVisitsWithGaps) ];
|
||||
}
|
||||
|
||||
const labels = generateLabels(step, visits);
|
||||
|
@ -95,17 +102,17 @@ const generateLabelsAndGroupedVisits = (visits, groupedVisitsWithGaps, step, ski
|
|||
return [ labels, fillTheGaps(groupedVisitsWithGaps, labels) ];
|
||||
};
|
||||
|
||||
const generateDataset = (stats, label, color) => ({
|
||||
const generateDataset = (data: number[], label: string, color: string): ChartDataSets => ({
|
||||
label,
|
||||
data: Object.values(stats),
|
||||
data,
|
||||
fill: false,
|
||||
lineTension: 0.2,
|
||||
borderColor: color,
|
||||
backgroundColor: color,
|
||||
});
|
||||
|
||||
const LineChartCard = ({ title, visits, highlightedVisits, highlightedLabel = 'Selected' }) => {
|
||||
const [ step, setStep ] = useState(
|
||||
const LineChartCard = ({ title, visits, highlightedVisits, highlightedLabel = 'Selected' }: LineChartCardProps) => {
|
||||
const [ step, setStep ] = useState<Step>(
|
||||
visits.length > 0 ? determineInitialStep(visits[visits.length - 1].date) : 'monthly',
|
||||
);
|
||||
const [ skipNoVisits, toggleSkipNoVisits ] = useToggle(true);
|
||||
|
@ -120,12 +127,12 @@ const LineChartCard = ({ title, visits, highlightedVisits, highlightedLabel = 'S
|
|||
[ highlightedVisits, step, labels ],
|
||||
);
|
||||
|
||||
const data = {
|
||||
const data: ChartData = {
|
||||
labels,
|
||||
datasets: [
|
||||
generateDataset(groupedVisits, 'Visits', '#4696e5'),
|
||||
highlightedVisits.length > 0 && generateDataset(groupedHighlighted, highlightedLabel, '#F77F28'),
|
||||
].filter(Boolean),
|
||||
].filter(Boolean) as ChartDataSets[],
|
||||
};
|
||||
const options = {
|
||||
maintainAspectRatio: false,
|
||||
|
@ -159,7 +166,7 @@ const LineChartCard = ({ title, visits, highlightedVisits, highlightedLabel = 'S
|
|||
</DropdownToggle>
|
||||
<DropdownMenu right>
|
||||
{Object.entries(STEPS_MAP).map(([ value, menuText ]) => (
|
||||
<DropdownItem key={value} active={step === value} onClick={() => setStep(value)}>
|
||||
<DropdownItem key={value} active={step === value} onClick={() => setStep(value as Step)}>
|
||||
{menuText}
|
||||
</DropdownItem>
|
||||
))}
|
||||
|
@ -179,6 +186,4 @@ const LineChartCard = ({ title, visits, highlightedVisits, highlightedLabel = 'S
|
|||
);
|
||||
};
|
||||
|
||||
LineChartCard.propTypes = propTypes;
|
||||
|
||||
export default LineChartCard;
|
|
@ -1,27 +1,23 @@
|
|||
import React, { useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { fromPairs, head, keys, pipe, prop, reverse, sortBy, splitEvery, toLower, toPairs, type, zipObj } from 'ramda';
|
||||
import { fromPairs, pipe, reverse, sortBy, splitEvery, toLower, toPairs, type, zipObj } from 'ramda';
|
||||
import SortingDropdown from '../../utils/SortingDropdown';
|
||||
import PaginationDropdown from '../../utils/PaginationDropdown';
|
||||
import { rangeOf } from '../../utils/utils';
|
||||
import { OrderDir, rangeOf } from '../../utils/utils';
|
||||
import { roundTen } from '../../utils/helpers/numbers';
|
||||
import SimplePaginator from '../../common/SimplePaginator';
|
||||
import { Stats, StatsRow } from '../types';
|
||||
import GraphCard from './GraphCard';
|
||||
import { DefaultChartProps } from './DefaultChart';
|
||||
|
||||
const propTypes = {
|
||||
stats: PropTypes.object.isRequired,
|
||||
highlightedStats: PropTypes.object,
|
||||
highlightedLabel: PropTypes.string,
|
||||
title: PropTypes.string.isRequired,
|
||||
sortingItems: PropTypes.object.isRequired,
|
||||
extraHeaderContent: PropTypes.func,
|
||||
withPagination: PropTypes.bool,
|
||||
onClick: PropTypes.func,
|
||||
};
|
||||
const toLowerIfString = (value: any) => type(value) === 'String' ? toLower(value) : value;
|
||||
const pickKeyFromPair = ([ key ]: StatsRow) => key;
|
||||
const pickValueFromPair = ([ , value ]: StatsRow) => value;
|
||||
|
||||
const toLowerIfString = (value) => type(value) === 'String' ? toLower(value) : value;
|
||||
const pickKeyFromPair = ([ key ]) => key;
|
||||
const pickValueFromPair = ([ , value ]) => value;
|
||||
interface SortableBarGraphProps extends DefaultChartProps {
|
||||
sortingItems: Record<string, string>;
|
||||
withPagination?: boolean;
|
||||
extraHeaderContent?: Function;
|
||||
}
|
||||
|
||||
const SortableBarGraph = ({
|
||||
stats,
|
||||
|
@ -31,19 +27,19 @@ const SortableBarGraph = ({
|
|||
extraHeaderContent,
|
||||
withPagination = true,
|
||||
...rest
|
||||
}) => {
|
||||
const [ order, setOrder ] = useState({
|
||||
}: SortableBarGraphProps) => {
|
||||
const [ order, setOrder ] = useState<{ orderField?: string; orderDir?: OrderDir }>({
|
||||
orderField: undefined,
|
||||
orderDir: undefined,
|
||||
});
|
||||
const [ currentPage, setCurrentPage ] = useState(1);
|
||||
const [ itemsPerPage, setItemsPerPage ] = useState(50);
|
||||
|
||||
const getSortedPairsForStats = (stats, sortingItems) => {
|
||||
const getSortedPairsForStats = (stats: Stats, sortingItems: Record<string, string>) => {
|
||||
const pairs = toPairs(stats);
|
||||
const sortedPairs = !order.orderField ? pairs : sortBy(
|
||||
pipe(
|
||||
prop(order.orderField === head(keys(sortingItems)) ? 0 : 1),
|
||||
pipe<StatsRow, string | number, string | number>(
|
||||
order.orderField === Object.keys(sortingItems)[0] ? pickKeyFromPair : pickValueFromPair,
|
||||
toLowerIfString,
|
||||
),
|
||||
pairs,
|
||||
|
@ -51,7 +47,21 @@ const SortableBarGraph = ({
|
|||
|
||||
return !order.orderDir || order.orderDir === 'ASC' ? sortedPairs : reverse(sortedPairs);
|
||||
};
|
||||
const determineStats = (stats, highlightedStats, sortingItems) => {
|
||||
const determineCurrentPagePairs = (pages: StatsRow[][]): StatsRow[] => {
|
||||
const page = pages[currentPage - 1];
|
||||
|
||||
if (currentPage < pages.length) {
|
||||
return page;
|
||||
}
|
||||
|
||||
const firstPageLength = pages[0].length;
|
||||
|
||||
// Using the "hidden" key, the chart will just replace the label by an empty string
|
||||
return [ ...page, ...rangeOf(firstPageLength - page.length, (i): StatsRow => [ `hidden_${i}`, 0 ]) ];
|
||||
};
|
||||
const renderPagination = (pagesCount: number) =>
|
||||
<SimplePaginator currentPage={currentPage} pagesCount={pagesCount} setCurrentPage={setCurrentPage} />;
|
||||
const determineStats = (stats: Stats, highlightedStats: Stats | undefined, sortingItems: Record<string, string>) => {
|
||||
const sortedPairs = getSortedPairsForStats(stats, sortingItems);
|
||||
const sortedKeys = sortedPairs.map(pickKeyFromPair);
|
||||
// The highlighted stats have to be ordered based on the regular stats, not on its own values
|
||||
|
@ -76,27 +86,13 @@ const SortableBarGraph = ({
|
|||
max: roundTen(Math.max(...sortedPairs.map(pickValueFromPair))),
|
||||
};
|
||||
};
|
||||
const determineCurrentPagePairs = (pages) => {
|
||||
const page = pages[currentPage - 1];
|
||||
|
||||
if (currentPage < pages.length) {
|
||||
return page;
|
||||
}
|
||||
|
||||
const firstPageLength = pages[0].length;
|
||||
|
||||
// Using the "hidden" key, the chart will just replace the label by an empty string
|
||||
return [ ...page, ...rangeOf(firstPageLength - page.length, (i) => [ `hidden_${i}`, 0 ]) ];
|
||||
};
|
||||
const renderPagination = (pagesCount) =>
|
||||
<SimplePaginator currentPage={currentPage} pagesCount={pagesCount} setCurrentPage={setCurrentPage} />;
|
||||
|
||||
const { currentPageStats, currentPageHighlightedStats, pagination, max } = determineStats(
|
||||
stats,
|
||||
highlightedStats && keys(highlightedStats).length > 0 ? highlightedStats : undefined,
|
||||
highlightedStats && Object.keys(highlightedStats).length > 0 ? highlightedStats : undefined,
|
||||
sortingItems,
|
||||
);
|
||||
const activeCities = keys(currentPageStats);
|
||||
const activeCities = Object.keys(currentPageStats);
|
||||
const computeTitle = () => (
|
||||
<React.Fragment>
|
||||
{title}
|
||||
|
@ -107,16 +103,22 @@ const SortableBarGraph = ({
|
|||
items={sortingItems}
|
||||
orderField={order.orderField}
|
||||
orderDir={order.orderDir}
|
||||
onChange={(orderField, orderDir) => setOrder({ orderField, orderDir }) || setCurrentPage(1)}
|
||||
onChange={(orderField, orderDir) => {
|
||||
setOrder({ orderField, orderDir });
|
||||
setCurrentPage(1);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{withPagination && keys(stats).length > 50 && (
|
||||
{withPagination && Object.keys(stats).length > 50 && (
|
||||
<div className="float-right">
|
||||
<PaginationDropdown
|
||||
toggleClassName="btn-sm p-0 mr-3"
|
||||
ranges={[ 50, 100, 200, 500 ]}
|
||||
value={itemsPerPage}
|
||||
setValue={(itemsPerPage) => setItemsPerPage(itemsPerPage) || setCurrentPage(1)}
|
||||
setValue={(itemsPerPage) => {
|
||||
setItemsPerPage(itemsPerPage);
|
||||
setCurrentPage(1);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
@ -141,6 +143,4 @@ const SortableBarGraph = ({
|
|||
);
|
||||
};
|
||||
|
||||
SortableBarGraph.propTypes = propTypes;
|
||||
|
||||
export default SortableBarGraph;
|
|
@ -80,6 +80,8 @@ export interface CreateVisit {
|
|||
|
||||
export type Stats = Record<string, number>;
|
||||
|
||||
export type StatsRow = [string, number];
|
||||
|
||||
export interface CityStats {
|
||||
cityName: string;
|
||||
count: number;
|
||||
|
|
|
@ -1,20 +1,22 @@
|
|||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { shallow, ShallowWrapper } from 'enzyme';
|
||||
import { CardHeader, DropdownItem } from 'reactstrap';
|
||||
import { Line } from 'react-chartjs-2';
|
||||
import moment from 'moment';
|
||||
import { Mock } from 'ts-mockery';
|
||||
import LineChartCard from '../../../src/visits/helpers/LineChartCard';
|
||||
import ToggleSwitch from '../../../src/utils/ToggleSwitch';
|
||||
import { Visit } from '../../../src/visits/types';
|
||||
|
||||
describe('<LineChartCard />', () => {
|
||||
let wrapper;
|
||||
const createWrapper = (visits = [], highlightedVisits = []) => {
|
||||
let wrapper: ShallowWrapper;
|
||||
const createWrapper = (visits: Visit[] = [], highlightedVisits: Visit[] = []) => {
|
||||
wrapper = shallow(<LineChartCard title="Cool title" visits={visits} highlightedVisits={highlightedVisits} />);
|
||||
|
||||
return wrapper;
|
||||
};
|
||||
|
||||
afterEach(() => wrapper && wrapper.unmount());
|
||||
afterEach(() => wrapper?.unmount());
|
||||
|
||||
it('renders provided title', () => {
|
||||
const wrapper = createWrapper();
|
||||
|
@ -32,7 +34,7 @@ describe('<LineChartCard />', () => {
|
|||
[[{ date: moment().subtract(7, 'month').format() }], 'monthly' ],
|
||||
[[{ date: moment().subtract(1, 'year').format() }], 'monthly' ],
|
||||
])('renders group menu and selects proper grouping item based on visits dates', (visits, expectedActiveItem) => {
|
||||
const wrapper = createWrapper(visits);
|
||||
const wrapper = createWrapper(visits.map((visit) => Mock.of<Visit>(visit)));
|
||||
const items = wrapper.find(DropdownItem);
|
||||
|
||||
expect(items).toHaveLength(4);
|
||||
|
@ -73,24 +75,24 @@ describe('<LineChartCard />', () => {
|
|||
});
|
||||
|
||||
it.each([
|
||||
[[{}], [], 1 ],
|
||||
[[{}], [{}], 2 ],
|
||||
[[ Mock.of<Visit>({}) ], [], 1 ],
|
||||
[[ Mock.of<Visit>({}) ], [ Mock.of<Visit>({}) ], 2 ],
|
||||
])('renders chart with expected data', (visits, highlightedVisits, expectedLines) => {
|
||||
const wrapper = createWrapper(visits, highlightedVisits);
|
||||
const chart = wrapper.find(Line);
|
||||
const { datasets } = chart.prop('data');
|
||||
const { datasets } = chart.prop('data') as any;
|
||||
|
||||
expect(datasets).toHaveLength(expectedLines);
|
||||
});
|
||||
|
||||
it('includes stats for visits with no dates if selected', () => {
|
||||
const wrapper = createWrapper([
|
||||
{ date: '2016-04-01' },
|
||||
{ date: '2016-01-01' },
|
||||
Mock.of<Visit>({ date: '2016-04-01' }),
|
||||
Mock.of<Visit>({ date: '2016-01-01' }),
|
||||
]);
|
||||
|
||||
expect(wrapper.find(Line).prop('data').labels).toHaveLength(2);
|
||||
expect((wrapper.find(Line).prop('data') as any).labels).toHaveLength(2);
|
||||
wrapper.find(ToggleSwitch).simulate('change');
|
||||
expect(wrapper.find(Line).prop('data').labels).toHaveLength(4);
|
||||
expect((wrapper.find(Line).prop('data') as any).labels).toHaveLength(4);
|
||||
});
|
||||
});
|
|
@ -1,14 +1,15 @@
|
|||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { keys, range, values } from 'ramda';
|
||||
import { shallow, ShallowWrapper } from 'enzyme';
|
||||
import { range } from 'ramda';
|
||||
import SortableBarGraph from '../../../src/visits/helpers/SortableBarGraph';
|
||||
import GraphCard from '../../../src/visits/helpers/GraphCard';
|
||||
import SortingDropdown from '../../../src/utils/SortingDropdown';
|
||||
import PaginationDropdown from '../../../src/utils/PaginationDropdown';
|
||||
import { rangeOf } from '../../../src/utils/utils';
|
||||
import { OrderDir, rangeOf } from '../../../src/utils/utils';
|
||||
import { Stats } from '../../../src/visits/types';
|
||||
|
||||
describe('<SortableBarGraph />', () => {
|
||||
let wrapper;
|
||||
let wrapper: ShallowWrapper;
|
||||
const sortingItems = {
|
||||
name: 'Name',
|
||||
amount: 'Amount',
|
||||
|
@ -30,7 +31,7 @@ describe('<SortableBarGraph />', () => {
|
|||
return wrapper;
|
||||
};
|
||||
|
||||
afterEach(() => wrapper && wrapper.unmount());
|
||||
afterEach(() => wrapper?.unmount());
|
||||
|
||||
it('renders stats unchanged when no ordering is set', () => {
|
||||
const wrapper = createWrapper();
|
||||
|
@ -40,19 +41,19 @@ describe('<SortableBarGraph />', () => {
|
|||
});
|
||||
|
||||
describe('renders properly ordered stats when ordering is set', () => {
|
||||
let assert;
|
||||
let assert: (sortName: string, sortDir: OrderDir, keys: string[], values: number[], done: Function) => void;
|
||||
|
||||
beforeEach(() => {
|
||||
const wrapper = createWrapper();
|
||||
const dropdown = wrapper.renderProp('title')().find(SortingDropdown);
|
||||
const dropdown = wrapper.renderProp('title' as never)().find(SortingDropdown);
|
||||
|
||||
assert = (sortName, sortDir, expectedKeys, expectedValues, done) => {
|
||||
assert = (sortName: string, sortDir: OrderDir, keys: string[], values: number[], done: Function) => {
|
||||
dropdown.prop('onChange')(sortName, sortDir);
|
||||
setImmediate(() => {
|
||||
const stats = wrapper.find(GraphCard).prop('stats');
|
||||
|
||||
expect(keys(stats)).toEqual(expectedKeys);
|
||||
expect(values(stats)).toEqual(expectedValues);
|
||||
expect(Object.keys(stats)).toEqual(keys);
|
||||
expect(Object.values(stats)).toEqual(values);
|
||||
done();
|
||||
});
|
||||
};
|
||||
|
@ -65,28 +66,28 @@ describe('<SortableBarGraph />', () => {
|
|||
});
|
||||
|
||||
describe('renders properly paginated stats when pagination is set', () => {
|
||||
let assert;
|
||||
let assert: (itemsPerPage: number, expectedStats: string[], done: Function) => void;
|
||||
|
||||
beforeEach(() => {
|
||||
const wrapper = createWrapper(true, range(1, 159).reduce((accum, value) => {
|
||||
const wrapper = createWrapper(true, range(1, 159).reduce<Stats>((accum, value) => {
|
||||
accum[`key_${value}`] = value;
|
||||
|
||||
return accum;
|
||||
}, {}));
|
||||
const dropdown = wrapper.renderProp('title')().find(PaginationDropdown);
|
||||
const dropdown = wrapper.renderProp('title' as never)().find(PaginationDropdown);
|
||||
|
||||
assert = (itemsPerPage, expectedStats, done) => {
|
||||
assert = (itemsPerPage: number, expectedStats: string[], done: Function) => {
|
||||
dropdown.prop('setValue')(itemsPerPage);
|
||||
setImmediate(() => {
|
||||
const stats = wrapper.find(GraphCard).prop('stats');
|
||||
|
||||
expect(keys(stats)).toEqual(expectedStats);
|
||||
expect(Object.keys(stats)).toEqual(expectedStats);
|
||||
done();
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
const buildExpected = (size) => [ 'Foo', 'Bar', ...rangeOf(size - 2, (i) => `key_${i}`) ];
|
||||
const buildExpected = (size: number): string[] => [ 'Foo', 'Bar', ...rangeOf(size - 2, (i) => `key_${i}`) ];
|
||||
|
||||
it('50 items per page', (done) => assert(50, buildExpected(50), done));
|
||||
it('100 items per page', (done) => assert(100, buildExpected(100), done));
|
||||
|
@ -95,7 +96,7 @@ describe('<SortableBarGraph />', () => {
|
|||
});
|
||||
|
||||
it('renders extra header content', () => {
|
||||
wrapper = shallow(
|
||||
const wrapper = shallow(
|
||||
<span>
|
||||
<SortableBarGraph
|
||||
title="Foo"
|
|
@ -1,8 +1,10 @@
|
|||
import { Mock } from 'ts-mockery';
|
||||
import { processStatsFromVisits, normalizeVisits } from '../../../src/visits/services/VisitsParser';
|
||||
import { Visit, VisitsStats } from '../../../src/visits/types';
|
||||
|
||||
describe('VisitsParser', () => {
|
||||
const visits = [
|
||||
{
|
||||
const visits: Visit[] = [
|
||||
Mock.of<Visit>({
|
||||
userAgent: 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0',
|
||||
referer: 'https://google.com',
|
||||
visitLocation: {
|
||||
|
@ -11,8 +13,8 @@ describe('VisitsParser', () => {
|
|||
latitude: 123.45,
|
||||
longitude: -543.21,
|
||||
},
|
||||
},
|
||||
{
|
||||
}),
|
||||
Mock.of<Visit>({
|
||||
userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X x.y; rv:42.0) Gecko/20100101 Firefox/42.0',
|
||||
referer: 'https://google.com',
|
||||
visitLocation: {
|
||||
|
@ -21,14 +23,14 @@ describe('VisitsParser', () => {
|
|||
latitude: 1029,
|
||||
longitude: 6758,
|
||||
},
|
||||
},
|
||||
{
|
||||
}),
|
||||
Mock.of<Visit>({
|
||||
userAgent: 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36',
|
||||
visitLocation: {
|
||||
countryName: 'Spain',
|
||||
},
|
||||
},
|
||||
{
|
||||
}),
|
||||
Mock.of<Visit>({
|
||||
userAgent: 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36',
|
||||
referer: 'https://m.facebook.com',
|
||||
visitLocation: {
|
||||
|
@ -37,14 +39,14 @@ describe('VisitsParser', () => {
|
|||
latitude: 123.45,
|
||||
longitude: -543.21,
|
||||
},
|
||||
},
|
||||
{
|
||||
}),
|
||||
Mock.of<Visit>({
|
||||
userAgent: 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36 OPR/38.0.2220.41',
|
||||
},
|
||||
}),
|
||||
];
|
||||
|
||||
describe('processStatsFromVisits', () => {
|
||||
let stats;
|
||||
let stats: VisitsStats;
|
||||
|
||||
beforeAll(() => {
|
||||
stats = processStatsFromVisits(normalizeVisits(visits));
|
Loading…
Reference in a new issue