mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2024-12-23 09:30:31 +03:00
Merge pull request #545 from acelaya-forks/feature/refactorings
Feature/refactorings
This commit is contained in:
commit
91f319df65
29 changed files with 172 additions and 154 deletions
|
@ -12,7 +12,7 @@ interface RealTimeUpdatesProps {
|
||||||
|
|
||||||
const intervalValue = (interval?: number) => !interval ? '' : `${interval}`;
|
const intervalValue = (interval?: number) => !interval ? '' : `${interval}`;
|
||||||
|
|
||||||
const RealTimeUpdates = (
|
const RealTimeUpdatesSettings = (
|
||||||
{ settings: { realTimeUpdates }, toggleRealTimeUpdates, setRealTimeUpdatesInterval }: RealTimeUpdatesProps,
|
{ settings: { realTimeUpdates }, toggleRealTimeUpdates, setRealTimeUpdatesInterval }: RealTimeUpdatesProps,
|
||||||
) => (
|
) => (
|
||||||
<SimpleCard title="Real-time updates" className="h-100">
|
<SimpleCard title="Real-time updates" className="h-100">
|
||||||
|
@ -50,4 +50,4 @@ const RealTimeUpdates = (
|
||||||
</SimpleCard>
|
</SimpleCard>
|
||||||
);
|
);
|
||||||
|
|
||||||
export default RealTimeUpdates;
|
export default RealTimeUpdatesSettings;
|
|
@ -3,11 +3,11 @@ import { DropdownItem, FormGroup } from 'reactstrap';
|
||||||
import { SimpleCard } from '../utils/SimpleCard';
|
import { SimpleCard } from '../utils/SimpleCard';
|
||||||
import ToggleSwitch from '../utils/ToggleSwitch';
|
import ToggleSwitch from '../utils/ToggleSwitch';
|
||||||
import { DropdownBtn } from '../utils/DropdownBtn';
|
import { DropdownBtn } from '../utils/DropdownBtn';
|
||||||
import { Settings, ShortUrlCreationSettings, TagFilteringMode } from './reducers/settings';
|
import { Settings, ShortUrlCreationSettings as ShortUrlsSettings, TagFilteringMode } from './reducers/settings';
|
||||||
|
|
||||||
interface ShortUrlCreationProps {
|
interface ShortUrlCreationProps {
|
||||||
settings: Settings;
|
settings: Settings;
|
||||||
setShortUrlCreationSettings: (settings: ShortUrlCreationSettings) => void;
|
setShortUrlCreationSettings: (settings: ShortUrlsSettings) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const tagFilteringModeText = (tagFilteringMode: TagFilteringMode | undefined): string =>
|
const tagFilteringModeText = (tagFilteringMode: TagFilteringMode | undefined): string =>
|
||||||
|
@ -17,8 +17,8 @@ const tagFilteringModeHint = (tagFilteringMode: TagFilteringMode | undefined): R
|
||||||
? <>The list of suggested tags will contain those <b>including</b> provided input.</>
|
? <>The list of suggested tags will contain those <b>including</b> provided input.</>
|
||||||
: <>The list of suggested tags will contain those <b>starting with</b> provided input.</>;
|
: <>The list of suggested tags will contain those <b>starting with</b> provided input.</>;
|
||||||
|
|
||||||
export const ShortUrlCreation: FC<ShortUrlCreationProps> = ({ settings, setShortUrlCreationSettings }) => {
|
export const ShortUrlCreationSettings: FC<ShortUrlCreationProps> = ({ settings, setShortUrlCreationSettings }) => {
|
||||||
const shortUrlCreation: ShortUrlCreationSettings = settings.shortUrlCreation ?? { validateUrls: false };
|
const shortUrlCreation: ShortUrlsSettings = settings.shortUrlCreation ?? { validateUrls: false };
|
||||||
const changeTagsFilteringMode = (tagFilteringMode: TagFilteringMode) => () => setShortUrlCreationSettings(
|
const changeTagsFilteringMode = (tagFilteringMode: TagFilteringMode) => () => setShortUrlCreationSettings(
|
||||||
{ ...shortUrlCreation ?? { validateUrls: false }, tagFilteringMode },
|
{ ...shortUrlCreation ?? { validateUrls: false }, tagFilteringMode },
|
||||||
);
|
);
|
|
@ -1,21 +1,23 @@
|
||||||
import { FC } from 'react';
|
import { FC } from 'react';
|
||||||
import { FormGroup } from 'reactstrap';
|
import { FormGroup } from 'reactstrap';
|
||||||
import SortingDropdown from '../utils/SortingDropdown';
|
import { OrderingDropdown } from '../utils/OrderingDropdown';
|
||||||
import { SORTABLE_FIELDS } from '../short-urls/data';
|
import { SHORT_URLS_ORDERABLE_FIELDS } from '../short-urls/data';
|
||||||
import { SimpleCard } from '../utils/SimpleCard';
|
import { SimpleCard } from '../utils/SimpleCard';
|
||||||
import { DEFAULT_SHORT_URLS_ORDERING, Settings, ShortUrlsListSettings } from './reducers/settings';
|
import { DEFAULT_SHORT_URLS_ORDERING, Settings, ShortUrlsListSettings as ShortUrlsSettings } from './reducers/settings';
|
||||||
|
|
||||||
interface ShortUrlsListProps {
|
interface ShortUrlsListProps {
|
||||||
settings: Settings;
|
settings: Settings;
|
||||||
setShortUrlsListSettings: (settings: ShortUrlsListSettings) => void;
|
setShortUrlsListSettings: (settings: ShortUrlsSettings) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ShortUrlsList: FC<ShortUrlsListProps> = ({ settings: { shortUrlsList }, setShortUrlsListSettings }) => (
|
export const ShortUrlsListSettings: FC<ShortUrlsListProps> = (
|
||||||
|
{ settings: { shortUrlsList }, setShortUrlsListSettings },
|
||||||
|
) => (
|
||||||
<SimpleCard title="Short URLs list" className="h-100">
|
<SimpleCard title="Short URLs list" className="h-100">
|
||||||
<FormGroup className="mb-0">
|
<FormGroup className="mb-0">
|
||||||
<label>Default ordering for short URLs list:</label>
|
<label>Default ordering for short URLs list:</label>
|
||||||
<SortingDropdown
|
<OrderingDropdown
|
||||||
items={SORTABLE_FIELDS}
|
items={SHORT_URLS_ORDERABLE_FIELDS}
|
||||||
order={shortUrlsList?.defaultOrdering ?? DEFAULT_SHORT_URLS_ORDERING}
|
order={shortUrlsList?.defaultOrdering ?? DEFAULT_SHORT_URLS_ORDERING}
|
||||||
onChange={(field, dir) => setShortUrlsListSettings({ defaultOrdering: { field, dir } })}
|
onChange={(field, dir) => setShortUrlsListSettings({ defaultOrdering: { field, dir } })}
|
||||||
/>
|
/>
|
|
@ -3,16 +3,16 @@ import { FormGroup } from 'reactstrap';
|
||||||
import { SimpleCard } from '../utils/SimpleCard';
|
import { SimpleCard } from '../utils/SimpleCard';
|
||||||
import { TagsModeDropdown } from '../tags/TagsModeDropdown';
|
import { TagsModeDropdown } from '../tags/TagsModeDropdown';
|
||||||
import { capitalize } from '../utils/utils';
|
import { capitalize } from '../utils/utils';
|
||||||
import SortingDropdown from '../utils/SortingDropdown';
|
import { OrderingDropdown } from '../utils/OrderingDropdown';
|
||||||
import { SORTABLE_FIELDS } from '../tags/data/TagsListChildrenProps';
|
import { TAGS_ORDERABLE_FIELDS } from '../tags/data/TagsListChildrenProps';
|
||||||
import { Settings, TagsSettings } from './reducers/settings';
|
import { Settings, TagsSettings as TagsSettingsOptions } from './reducers/settings';
|
||||||
|
|
||||||
interface TagsProps {
|
interface TagsProps {
|
||||||
settings: Settings;
|
settings: Settings;
|
||||||
setTagsSettings: (settings: TagsSettings) => void;
|
setTagsSettings: (settings: TagsSettingsOptions) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Tags: FC<TagsProps> = ({ settings: { tags }, setTagsSettings }) => (
|
export const TagsSettings: FC<TagsProps> = ({ settings: { tags }, setTagsSettings }) => (
|
||||||
<SimpleCard title="Tags" className="h-100">
|
<SimpleCard title="Tags" className="h-100">
|
||||||
<FormGroup>
|
<FormGroup>
|
||||||
<label>Default display mode when managing tags:</label>
|
<label>Default display mode when managing tags:</label>
|
||||||
|
@ -25,8 +25,8 @@ export const Tags: FC<TagsProps> = ({ settings: { tags }, setTagsSettings }) =>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
<FormGroup className="mb-0">
|
<FormGroup className="mb-0">
|
||||||
<label>Default ordering for tags list:</label>
|
<label>Default ordering for tags list:</label>
|
||||||
<SortingDropdown
|
<OrderingDropdown
|
||||||
items={SORTABLE_FIELDS}
|
items={TAGS_ORDERABLE_FIELDS}
|
||||||
order={tags?.defaultOrdering ?? {}}
|
order={tags?.defaultOrdering ?? {}}
|
||||||
onChange={(field, dir) => setTagsSettings({ ...tags, defaultOrdering: { field, dir } })}
|
onChange={(field, dir) => setTagsSettings({ ...tags, defaultOrdering: { field, dir } })}
|
||||||
/>
|
/>
|
|
@ -6,14 +6,14 @@ import { SimpleCard } from '../utils/SimpleCard';
|
||||||
import ToggleSwitch from '../utils/ToggleSwitch';
|
import ToggleSwitch from '../utils/ToggleSwitch';
|
||||||
import { changeThemeInMarkup, Theme } from '../utils/theme';
|
import { changeThemeInMarkup, Theme } from '../utils/theme';
|
||||||
import { Settings, UiSettings } from './reducers/settings';
|
import { Settings, UiSettings } from './reducers/settings';
|
||||||
import './UserInterface.scss';
|
import './UserInterfaceSettings.scss';
|
||||||
|
|
||||||
interface UserInterfaceProps {
|
interface UserInterfaceProps {
|
||||||
settings: Settings;
|
settings: Settings;
|
||||||
setUiSettings: (settings: UiSettings) => void;
|
setUiSettings: (settings: UiSettings) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const UserInterface: FC<UserInterfaceProps> = ({ settings: { ui }, setUiSettings }) => (
|
export const UserInterfaceSettings: FC<UserInterfaceProps> = ({ settings: { ui }, setUiSettings }) => (
|
||||||
<SimpleCard title="User interface" className="h-100">
|
<SimpleCard title="User interface" className="h-100">
|
||||||
<FormGroup>
|
<FormGroup>
|
||||||
<FontAwesomeIcon icon={ui?.theme === 'dark' ? faMoon : faSun} className="user-interface__theme-icon" />
|
<FontAwesomeIcon icon={ui?.theme === 'dark' ? faMoon : faSun} className="user-interface__theme-icon" />
|
|
@ -2,14 +2,14 @@ import { FormGroup } from 'reactstrap';
|
||||||
import { FC } from 'react';
|
import { FC } from 'react';
|
||||||
import { SimpleCard } from '../utils/SimpleCard';
|
import { SimpleCard } from '../utils/SimpleCard';
|
||||||
import { DateIntervalSelector } from '../utils/dates/DateIntervalSelector';
|
import { DateIntervalSelector } from '../utils/dates/DateIntervalSelector';
|
||||||
import { Settings, VisitsSettings } from './reducers/settings';
|
import { Settings, VisitsSettings as VisitsSettingsConfig } from './reducers/settings';
|
||||||
|
|
||||||
interface VisitsProps {
|
interface VisitsProps {
|
||||||
settings: Settings;
|
settings: Settings;
|
||||||
setVisitsSettings: (settings: VisitsSettings) => void;
|
setVisitsSettings: (settings: VisitsSettingsConfig) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Visits: FC<VisitsProps> = ({ settings, setVisitsSettings }) => (
|
export const VisitsSettings: FC<VisitsProps> = ({ settings, setVisitsSettings }) => (
|
||||||
<SimpleCard title="Visits" className="h-100">
|
<SimpleCard title="Visits" className="h-100">
|
||||||
<FormGroup className="mb-0">
|
<FormGroup className="mb-0">
|
||||||
<label>Default interval to load on visits sections:</label>
|
<label>Default interval to load on visits sections:</label>
|
|
@ -1,5 +1,5 @@
|
||||||
import Bottle from 'bottlejs';
|
import Bottle from 'bottlejs';
|
||||||
import RealTimeUpdates from '../RealTimeUpdates';
|
import RealTimeUpdatesSettings from '../RealTimeUpdatesSettings';
|
||||||
import Settings from '../Settings';
|
import Settings from '../Settings';
|
||||||
import {
|
import {
|
||||||
setRealTimeUpdatesInterval,
|
setRealTimeUpdatesInterval,
|
||||||
|
@ -12,46 +12,46 @@ import {
|
||||||
} from '../reducers/settings';
|
} from '../reducers/settings';
|
||||||
import { ConnectDecorator } from '../../container/types';
|
import { ConnectDecorator } from '../../container/types';
|
||||||
import { withoutSelectedServer } from '../../servers/helpers/withoutSelectedServer';
|
import { withoutSelectedServer } from '../../servers/helpers/withoutSelectedServer';
|
||||||
import { ShortUrlCreation } from '../ShortUrlCreation';
|
import { ShortUrlCreationSettings } from '../ShortUrlCreationSettings';
|
||||||
import { UserInterface } from '../UserInterface';
|
import { UserInterfaceSettings } from '../UserInterfaceSettings';
|
||||||
import { Visits } from '../Visits';
|
import { VisitsSettings } from '../VisitsSettings';
|
||||||
import { Tags } from '../Tags';
|
import { TagsSettings } from '../TagsSettings';
|
||||||
import { ShortUrlsList } from '../ShortUrlsList';
|
import { ShortUrlsListSettings } from '../ShortUrlsListSettings';
|
||||||
|
|
||||||
const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
|
const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
|
||||||
// Components
|
// Components
|
||||||
bottle.serviceFactory(
|
bottle.serviceFactory(
|
||||||
'Settings',
|
'Settings',
|
||||||
Settings,
|
Settings,
|
||||||
'RealTimeUpdates',
|
'RealTimeUpdatesSettings',
|
||||||
'ShortUrlCreation',
|
'ShortUrlCreationSettings',
|
||||||
'ShortUrlsListSettings',
|
'ShortUrlsListSettings',
|
||||||
'UserInterface',
|
'UserInterfaceSettings',
|
||||||
'Visits',
|
'VisitsSettings',
|
||||||
'Tags',
|
'TagsSettings',
|
||||||
);
|
);
|
||||||
bottle.decorator('Settings', withoutSelectedServer);
|
bottle.decorator('Settings', withoutSelectedServer);
|
||||||
bottle.decorator('Settings', connect(null, [ 'resetSelectedServer' ]));
|
bottle.decorator('Settings', connect(null, [ 'resetSelectedServer' ]));
|
||||||
|
|
||||||
bottle.serviceFactory('RealTimeUpdates', () => RealTimeUpdates);
|
bottle.serviceFactory('RealTimeUpdatesSettings', () => RealTimeUpdatesSettings);
|
||||||
bottle.decorator(
|
bottle.decorator(
|
||||||
'RealTimeUpdates',
|
'RealTimeUpdatesSettings',
|
||||||
connect([ 'settings' ], [ 'toggleRealTimeUpdates', 'setRealTimeUpdatesInterval' ]),
|
connect([ 'settings' ], [ 'toggleRealTimeUpdates', 'setRealTimeUpdatesInterval' ]),
|
||||||
);
|
);
|
||||||
|
|
||||||
bottle.serviceFactory('ShortUrlCreation', () => ShortUrlCreation);
|
bottle.serviceFactory('ShortUrlCreationSettings', () => ShortUrlCreationSettings);
|
||||||
bottle.decorator('ShortUrlCreation', connect([ 'settings' ], [ 'setShortUrlCreationSettings' ]));
|
bottle.decorator('ShortUrlCreationSettings', connect([ 'settings' ], [ 'setShortUrlCreationSettings' ]));
|
||||||
|
|
||||||
bottle.serviceFactory('UserInterface', () => UserInterface);
|
bottle.serviceFactory('UserInterfaceSettings', () => UserInterfaceSettings);
|
||||||
bottle.decorator('UserInterface', connect([ 'settings' ], [ 'setUiSettings' ]));
|
bottle.decorator('UserInterfaceSettings', connect([ 'settings' ], [ 'setUiSettings' ]));
|
||||||
|
|
||||||
bottle.serviceFactory('Visits', () => Visits);
|
bottle.serviceFactory('VisitsSettings', () => VisitsSettings);
|
||||||
bottle.decorator('Visits', connect([ 'settings' ], [ 'setVisitsSettings' ]));
|
bottle.decorator('VisitsSettings', connect([ 'settings' ], [ 'setVisitsSettings' ]));
|
||||||
|
|
||||||
bottle.serviceFactory('Tags', () => Tags);
|
bottle.serviceFactory('TagsSettings', () => TagsSettings);
|
||||||
bottle.decorator('Tags', connect([ 'settings' ], [ 'setTagsSettings' ]));
|
bottle.decorator('TagsSettings', connect([ 'settings' ], [ 'setTagsSettings' ]));
|
||||||
|
|
||||||
bottle.serviceFactory('ShortUrlsListSettings', () => ShortUrlsList);
|
bottle.serviceFactory('ShortUrlsListSettings', () => ShortUrlsListSettings);
|
||||||
bottle.decorator('ShortUrlsListSettings', connect([ 'settings' ], [ 'setShortUrlsListSettings' ]));
|
bottle.decorator('ShortUrlsListSettings', connect([ 'settings' ], [ 'setShortUrlsListSettings' ]));
|
||||||
|
|
||||||
// Actions
|
// Actions
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { pipe } from 'ramda';
|
||||||
import { FC, useEffect, useMemo, 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 { OrderingDropdown } from '../utils/OrderingDropdown';
|
||||||
import { determineOrderDir, OrderDir } from '../utils/helpers/ordering';
|
import { determineOrderDir, 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';
|
||||||
|
@ -14,7 +14,7 @@ import { ShortUrlsList as ShortUrlsListState } from './reducers/shortUrlsList';
|
||||||
import { ShortUrlsTableProps } from './ShortUrlsTable';
|
import { ShortUrlsTableProps } from './ShortUrlsTable';
|
||||||
import Paginator from './Paginator';
|
import Paginator from './Paginator';
|
||||||
import { ShortUrlListRouteParams, useShortUrlsQuery } from './helpers/hooks';
|
import { ShortUrlListRouteParams, useShortUrlsQuery } from './helpers/hooks';
|
||||||
import { OrderableFields, ShortUrlsOrder, SORTABLE_FIELDS } from './data';
|
import { ShortUrlsOrderableFields, ShortUrlsOrder, SHORT_URLS_ORDERABLE_FIELDS } from './data';
|
||||||
|
|
||||||
interface ShortUrlsListProps extends RouteComponentProps<ShortUrlListRouteParams> {
|
interface ShortUrlsListProps extends RouteComponentProps<ShortUrlListRouteParams> {
|
||||||
selectedServer: SelectedServer;
|
selectedServer: SelectedServer;
|
||||||
|
@ -39,10 +39,10 @@ const ShortUrlsList = (ShortUrlsTable: FC<ShortUrlsTableProps>, SearchBar: FC) =
|
||||||
const selectedTags = useMemo(() => tags?.split(',') ?? [], [ tags ]);
|
const selectedTags = useMemo(() => tags?.split(',') ?? [], [ tags ]);
|
||||||
const { pagination } = shortUrlsList?.shortUrls ?? {};
|
const { pagination } = shortUrlsList?.shortUrls ?? {};
|
||||||
|
|
||||||
const handleOrderBy = (field?: OrderableFields, dir?: OrderDir) => setOrder({ field, dir });
|
const handleOrderBy = (field?: ShortUrlsOrderableFields, dir?: OrderDir) => setOrder({ field, dir });
|
||||||
const orderByColumn = (field: OrderableFields) => () =>
|
const orderByColumn = (field: ShortUrlsOrderableFields) => () =>
|
||||||
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: ShortUrlsOrderableFields) => <TableOrderIcon currentOrder={order} field={field} />;
|
||||||
const addTag = pipe(
|
const addTag = pipe(
|
||||||
(newTag: string) => [ ...new Set([ ...selectedTags, newTag ]) ].join(','),
|
(newTag: string) => [ ...new Set([ ...selectedTags, newTag ]) ].join(','),
|
||||||
(tags) => toFirstPage({ tags }),
|
(tags) => toFirstPage({ tags }),
|
||||||
|
@ -64,7 +64,7 @@ const ShortUrlsList = (ShortUrlsTable: FC<ShortUrlsTableProps>, SearchBar: FC) =
|
||||||
<>
|
<>
|
||||||
<div className="mb-3"><SearchBar /></div>
|
<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} />
|
<OrderingDropdown items={SHORT_URLS_ORDERABLE_FIELDS} order={order} onChange={handleOrderBy} />
|
||||||
</div>
|
</div>
|
||||||
<Card body className="pb-1">
|
<Card body className="pb-1">
|
||||||
<ShortUrlsTable
|
<ShortUrlsTable
|
||||||
|
|
|
@ -5,12 +5,12 @@ import { SelectedServer } from '../servers/data';
|
||||||
import { supportsShortUrlTitle } from '../utils/helpers/features';
|
import { supportsShortUrlTitle } from '../utils/helpers/features';
|
||||||
import { ShortUrlsList as ShortUrlsListState } from './reducers/shortUrlsList';
|
import { ShortUrlsList as ShortUrlsListState } from './reducers/shortUrlsList';
|
||||||
import { ShortUrlsRowProps } from './helpers/ShortUrlsRow';
|
import { ShortUrlsRowProps } from './helpers/ShortUrlsRow';
|
||||||
import { OrderableFields } from './data';
|
import { ShortUrlsOrderableFields } from './data';
|
||||||
import './ShortUrlsTable.scss';
|
import './ShortUrlsTable.scss';
|
||||||
|
|
||||||
export interface ShortUrlsTableProps {
|
export interface ShortUrlsTableProps {
|
||||||
orderByColumn?: (column: OrderableFields) => () => void;
|
orderByColumn?: (column: ShortUrlsOrderableFields) => () => void;
|
||||||
renderOrderIcon?: (column: OrderableFields) => ReactNode;
|
renderOrderIcon?: (column: ShortUrlsOrderableFields) => ReactNode;
|
||||||
shortUrlsList: ShortUrlsListState;
|
shortUrlsList: ShortUrlsListState;
|
||||||
selectedServer: SelectedServer;
|
selectedServer: SelectedServer;
|
||||||
onTagClick?: (tag: string) => void;
|
onTagClick?: (tag: string) => void;
|
||||||
|
|
|
@ -52,7 +52,7 @@ export interface ShortUrlIdentifier {
|
||||||
domain: OptionalString;
|
domain: OptionalString;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SORTABLE_FIELDS = {
|
export const SHORT_URLS_ORDERABLE_FIELDS = {
|
||||||
dateCreated: 'Created at',
|
dateCreated: 'Created at',
|
||||||
shortCode: 'Short URL',
|
shortCode: 'Short URL',
|
||||||
longUrl: 'Long URL',
|
longUrl: 'Long URL',
|
||||||
|
@ -60,6 +60,6 @@ export const SORTABLE_FIELDS = {
|
||||||
visits: 'Visits',
|
visits: 'Visits',
|
||||||
};
|
};
|
||||||
|
|
||||||
export type OrderableFields = keyof typeof SORTABLE_FIELDS;
|
export type ShortUrlsOrderableFields = keyof typeof SHORT_URLS_ORDERABLE_FIELDS;
|
||||||
|
|
||||||
export type ShortUrlsOrder = Order<OrderableFields>;
|
export type ShortUrlsOrder = Order<ShortUrlsOrderableFields>;
|
||||||
|
|
|
@ -10,9 +10,14 @@ import { ShlinkApiError } from '../api/ShlinkApiError';
|
||||||
import { Topics } from '../mercure/helpers/Topics';
|
import { Topics } from '../mercure/helpers/Topics';
|
||||||
import { Settings, TagsMode } from '../settings/reducers/settings';
|
import { Settings, TagsMode } from '../settings/reducers/settings';
|
||||||
import { determineOrderDir, sortList } from '../utils/helpers/ordering';
|
import { determineOrderDir, sortList } from '../utils/helpers/ordering';
|
||||||
import SortingDropdown from '../utils/SortingDropdown';
|
import { OrderingDropdown } from '../utils/OrderingDropdown';
|
||||||
import { TagsList as TagsListState } from './reducers/tagsList';
|
import { TagsList as TagsListState } from './reducers/tagsList';
|
||||||
import { OrderableFields, SORTABLE_FIELDS, TagsListChildrenProps, TagsOrder } from './data/TagsListChildrenProps';
|
import {
|
||||||
|
TagsOrderableFields,
|
||||||
|
TAGS_ORDERABLE_FIELDS,
|
||||||
|
TagsListChildrenProps,
|
||||||
|
TagsOrder,
|
||||||
|
} from './data/TagsListChildrenProps';
|
||||||
import { TagsModeDropdown } from './TagsModeDropdown';
|
import { TagsModeDropdown } from './TagsModeDropdown';
|
||||||
import { NormalizedTag } from './data';
|
import { NormalizedTag } from './data';
|
||||||
import { TagsTableProps } from './TagsTable';
|
import { TagsTableProps } from './TagsTable';
|
||||||
|
@ -55,7 +60,7 @@ const TagsList = (TagsCards: FC<TagsListChildrenProps>, TagsTable: FC<TagsTableP
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const orderByColumn = (field: OrderableFields) => () => {
|
const orderByColumn = (field: TagsOrderableFields) => () => {
|
||||||
const dir = determineOrderDir(field, order.field, order.dir);
|
const dir = determineOrderDir(field, order.field, order.dir);
|
||||||
|
|
||||||
setOrder({ field: dir ? field : undefined, dir });
|
setOrder({ field: dir ? field : undefined, dir });
|
||||||
|
@ -88,7 +93,11 @@ const TagsList = (TagsCards: FC<TagsListChildrenProps>, TagsTable: FC<TagsTableP
|
||||||
<TagsModeDropdown mode={mode} onChange={setMode} />
|
<TagsModeDropdown mode={mode} onChange={setMode} />
|
||||||
</div>
|
</div>
|
||||||
<div className="col-lg-6 mt-3 mt-lg-0">
|
<div className="col-lg-6 mt-3 mt-lg-0">
|
||||||
<SortingDropdown items={SORTABLE_FIELDS} order={order} onChange={(field, dir) => setOrder({ field, dir })} />
|
<OrderingDropdown
|
||||||
|
items={TAGS_ORDERABLE_FIELDS}
|
||||||
|
order={order}
|
||||||
|
onChange={(field, dir) => setOrder({ field, dir })}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Row>
|
</Row>
|
||||||
{renderContent()}
|
{renderContent()}
|
||||||
|
|
|
@ -6,12 +6,12 @@ import SimplePaginator from '../common/SimplePaginator';
|
||||||
import { useQueryState } from '../utils/helpers/hooks';
|
import { useQueryState } from '../utils/helpers/hooks';
|
||||||
import { parseQuery } from '../utils/helpers/query';
|
import { parseQuery } from '../utils/helpers/query';
|
||||||
import { TableOrderIcon } from '../utils/table/TableOrderIcon';
|
import { TableOrderIcon } from '../utils/table/TableOrderIcon';
|
||||||
import { OrderableFields, TagsListChildrenProps, TagsOrder } from './data/TagsListChildrenProps';
|
import { TagsOrderableFields, TagsListChildrenProps, TagsOrder } from './data/TagsListChildrenProps';
|
||||||
import { TagsTableRowProps } from './TagsTableRow';
|
import { TagsTableRowProps } from './TagsTableRow';
|
||||||
import './TagsTable.scss';
|
import './TagsTable.scss';
|
||||||
|
|
||||||
export interface TagsTableProps extends TagsListChildrenProps {
|
export interface TagsTableProps extends TagsListChildrenProps {
|
||||||
orderByColumn: (field: OrderableFields) => () => void;
|
orderByColumn: (field: TagsOrderableFields) => () => void;
|
||||||
currentOrder: TagsOrder;
|
currentOrder: TagsOrder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,15 +2,15 @@ import { SelectedServer } from '../../servers/data';
|
||||||
import { Order } from '../../utils/helpers/ordering';
|
import { Order } from '../../utils/helpers/ordering';
|
||||||
import { NormalizedTag } from './index';
|
import { NormalizedTag } from './index';
|
||||||
|
|
||||||
export const SORTABLE_FIELDS = {
|
export const TAGS_ORDERABLE_FIELDS = {
|
||||||
tag: 'Tag',
|
tag: 'Tag',
|
||||||
shortUrls: 'Short URLs',
|
shortUrls: 'Short URLs',
|
||||||
visits: 'Visits',
|
visits: 'Visits',
|
||||||
};
|
};
|
||||||
|
|
||||||
export type OrderableFields = keyof typeof SORTABLE_FIELDS;
|
export type TagsOrderableFields = keyof typeof TAGS_ORDERABLE_FIELDS;
|
||||||
|
|
||||||
export type TagsOrder = Order<OrderableFields>;
|
export type TagsOrder = Order<TagsOrderableFields>;
|
||||||
|
|
||||||
export interface TagsListChildrenProps {
|
export interface TagsListChildrenProps {
|
||||||
sortedTags: NormalizedTag[];
|
sortedTags: NormalizedTag[];
|
||||||
|
|
8
src/utils/OrderingDropdown.scss
Normal file
8
src/utils/OrderingDropdown.scss
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
.ordering-dropdown__menu--link.ordering-dropdown__menu--link {
|
||||||
|
min-width: 11rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ordering-dropdown__sort-icon {
|
||||||
|
margin: 3.5px 0 0;
|
||||||
|
float: right;
|
||||||
|
}
|
|
@ -4,9 +4,9 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import { faSortAmountUp as sortAscIcon, faSortAmountDown as sortDescIcon } from '@fortawesome/free-solid-svg-icons';
|
import { faSortAmountUp as sortAscIcon, faSortAmountDown as sortDescIcon } from '@fortawesome/free-solid-svg-icons';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { determineOrderDir, Order, OrderDir } from './helpers/ordering';
|
import { determineOrderDir, Order, OrderDir } from './helpers/ordering';
|
||||||
import './SortingDropdown.scss';
|
import './OrderingDropdown.scss';
|
||||||
|
|
||||||
export interface SortingDropdownProps<T extends string = string> {
|
export interface OrderingDropdownProps<T extends string = string> {
|
||||||
items: Record<T, string>;
|
items: Record<T, string>;
|
||||||
order: Order<T>;
|
order: Order<T>;
|
||||||
onChange: (orderField?: T, orderDir?: OrderDir) => void;
|
onChange: (orderField?: T, orderDir?: OrderDir) => void;
|
||||||
|
@ -14,8 +14,8 @@ export interface SortingDropdownProps<T extends string = string> {
|
||||||
right?: boolean;
|
right?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function SortingDropdown<T extends string = string>(
|
export function OrderingDropdown<T extends string = string>(
|
||||||
{ items, order, onChange, isButton = true, right = false }: SortingDropdownProps<T>,
|
{ items, order, onChange, isButton = true, right = false }: OrderingDropdownProps<T>,
|
||||||
) {
|
) {
|
||||||
const handleItemClick = (fieldKey: T) => () => {
|
const handleItemClick = (fieldKey: T) => () => {
|
||||||
const newOrderDir = determineOrderDir(fieldKey, order.field, order.dir);
|
const newOrderDir = determineOrderDir(fieldKey, order.field, order.dir);
|
||||||
|
@ -36,7 +36,7 @@ export default function SortingDropdown<T extends string = string>(
|
||||||
</DropdownToggle>
|
</DropdownToggle>
|
||||||
<DropdownMenu
|
<DropdownMenu
|
||||||
right={right}
|
right={right}
|
||||||
className={classNames('w-100', { 'sorting-dropdown__menu--link': !isButton })}
|
className={classNames('w-100', { 'ordering-dropdown__menu--link': !isButton })}
|
||||||
>
|
>
|
||||||
{toPairs(items).map(([ fieldKey, fieldValue ]) => (
|
{toPairs(items).map(([ fieldKey, fieldValue ]) => (
|
||||||
<DropdownItem key={fieldKey} active={order.field === fieldKey} onClick={handleItemClick(fieldKey as T)}>
|
<DropdownItem key={fieldKey} active={order.field === fieldKey} onClick={handleItemClick(fieldKey as T)}>
|
||||||
|
@ -44,7 +44,7 @@ export default function SortingDropdown<T extends string = string>(
|
||||||
{order.field === fieldKey && (
|
{order.field === fieldKey && (
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
icon={order.dir === 'ASC' ? sortAscIcon : sortDescIcon}
|
icon={order.dir === 'ASC' ? sortAscIcon : sortDescIcon}
|
||||||
className="sorting-dropdown__sort-icon"
|
className="ordering-dropdown__sort-icon"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</DropdownItem>
|
</DropdownItem>
|
|
@ -1,8 +0,0 @@
|
||||||
.sorting-dropdown__menu--link.sorting-dropdown__menu--link {
|
|
||||||
min-width: 11rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sorting-dropdown__sort-icon {
|
|
||||||
margin: 3.5px 0 0;
|
|
||||||
float: right;
|
|
||||||
}
|
|
|
@ -4,7 +4,7 @@ import { rangeOf } from '../../utils/utils';
|
||||||
import { Order } from '../../utils/helpers/ordering';
|
import { Order } from '../../utils/helpers/ordering';
|
||||||
import SimplePaginator from '../../common/SimplePaginator';
|
import SimplePaginator from '../../common/SimplePaginator';
|
||||||
import { roundTen } from '../../utils/helpers/numbers';
|
import { roundTen } from '../../utils/helpers/numbers';
|
||||||
import SortingDropdown from '../../utils/SortingDropdown';
|
import { OrderingDropdown } from '../../utils/OrderingDropdown';
|
||||||
import PaginationDropdown from '../../utils/PaginationDropdown';
|
import PaginationDropdown from '../../utils/PaginationDropdown';
|
||||||
import { Stats, StatsRow } from '../types';
|
import { Stats, StatsRow } from '../types';
|
||||||
import { HorizontalBarChart, HorizontalBarChartProps } from './HorizontalBarChart';
|
import { HorizontalBarChart, HorizontalBarChartProps } from './HorizontalBarChart';
|
||||||
|
@ -96,7 +96,7 @@ export const SortableBarChartCard: FC<SortableBarChartCardProps> = ({
|
||||||
<>
|
<>
|
||||||
{title}
|
{title}
|
||||||
<div className="float-right">
|
<div className="float-right">
|
||||||
<SortingDropdown
|
<OrderingDropdown
|
||||||
isButton={false}
|
isButton={false}
|
||||||
right
|
right
|
||||||
items={sortingItems}
|
items={sortingItems}
|
||||||
|
|
|
@ -1,19 +1,22 @@
|
||||||
import { shallow, ShallowWrapper } from 'enzyme';
|
import { shallow, ShallowWrapper } from 'enzyme';
|
||||||
import { Mock } from 'ts-mockery';
|
import { Mock } from 'ts-mockery';
|
||||||
import { Input } from 'reactstrap';
|
import { Input } from 'reactstrap';
|
||||||
import { RealTimeUpdatesSettings, Settings } from '../../src/settings/reducers/settings';
|
import {
|
||||||
import RealTimeUpdates from '../../src/settings/RealTimeUpdates';
|
RealTimeUpdatesSettings as RealTimeUpdatesSettingsOptions,
|
||||||
|
Settings,
|
||||||
|
} from '../../src/settings/reducers/settings';
|
||||||
|
import RealTimeUpdatesSettings from '../../src/settings/RealTimeUpdatesSettings';
|
||||||
import ToggleSwitch from '../../src/utils/ToggleSwitch';
|
import ToggleSwitch from '../../src/utils/ToggleSwitch';
|
||||||
|
|
||||||
describe('<RealTimeUpdates />', () => {
|
describe('<RealTimeUpdatesSettings />', () => {
|
||||||
const toggleRealTimeUpdates = jest.fn();
|
const toggleRealTimeUpdates = jest.fn();
|
||||||
const setRealTimeUpdatesInterval = jest.fn();
|
const setRealTimeUpdatesInterval = jest.fn();
|
||||||
let wrapper: ShallowWrapper;
|
let wrapper: ShallowWrapper;
|
||||||
const createWrapper = (realTimeUpdates: Partial<RealTimeUpdatesSettings> = {}) => {
|
const createWrapper = (realTimeUpdates: Partial<RealTimeUpdatesSettingsOptions> = {}) => {
|
||||||
const settings = Mock.of<Settings>({ realTimeUpdates });
|
const settings = Mock.of<Settings>({ realTimeUpdates });
|
||||||
|
|
||||||
wrapper = shallow(
|
wrapper = shallow(
|
||||||
<RealTimeUpdates
|
<RealTimeUpdatesSettings
|
||||||
settings={settings}
|
settings={settings}
|
||||||
toggleRealTimeUpdates={toggleRealTimeUpdates}
|
toggleRealTimeUpdates={toggleRealTimeUpdates}
|
||||||
setRealTimeUpdatesInterval={setRealTimeUpdatesInterval}
|
setRealTimeUpdatesInterval={setRealTimeUpdatesInterval}
|
|
@ -1,17 +1,17 @@
|
||||||
import { shallow, ShallowWrapper } from 'enzyme';
|
import { shallow, ShallowWrapper } from 'enzyme';
|
||||||
import { Mock } from 'ts-mockery';
|
import { Mock } from 'ts-mockery';
|
||||||
import { DropdownItem } from 'reactstrap';
|
import { DropdownItem } from 'reactstrap';
|
||||||
import { ShortUrlCreationSettings, Settings } from '../../src/settings/reducers/settings';
|
import { ShortUrlCreationSettings as ShortUrlsSettings, Settings } from '../../src/settings/reducers/settings';
|
||||||
import { ShortUrlCreation } from '../../src/settings/ShortUrlCreation';
|
import { ShortUrlCreationSettings } from '../../src/settings/ShortUrlCreationSettings';
|
||||||
import ToggleSwitch from '../../src/utils/ToggleSwitch';
|
import ToggleSwitch from '../../src/utils/ToggleSwitch';
|
||||||
import { DropdownBtn } from '../../src/utils/DropdownBtn';
|
import { DropdownBtn } from '../../src/utils/DropdownBtn';
|
||||||
|
|
||||||
describe('<ShortUrlCreation />', () => {
|
describe('<ShortUrlCreationSettings />', () => {
|
||||||
let wrapper: ShallowWrapper;
|
let wrapper: ShallowWrapper;
|
||||||
const setShortUrlCreationSettings = jest.fn();
|
const setShortUrlCreationSettings = jest.fn();
|
||||||
const createWrapper = (shortUrlCreation?: ShortUrlCreationSettings) => {
|
const createWrapper = (shortUrlCreation?: ShortUrlsSettings) => {
|
||||||
wrapper = shallow(
|
wrapper = shallow(
|
||||||
<ShortUrlCreation
|
<ShortUrlCreationSettings
|
||||||
settings={Mock.of<Settings>({ shortUrlCreation })}
|
settings={Mock.of<Settings>({ shortUrlCreation })}
|
||||||
setShortUrlCreationSettings={setShortUrlCreationSettings}
|
setShortUrlCreationSettings={setShortUrlCreationSettings}
|
||||||
/>,
|
/>,
|
||||||
|
@ -68,9 +68,9 @@ describe('<ShortUrlCreation />', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
[ { tagFilteringMode: 'includes' } as ShortUrlCreationSettings, 'Suggest tags including input', 'including' ],
|
[ { tagFilteringMode: 'includes' } as ShortUrlsSettings, 'Suggest tags including input', 'including' ],
|
||||||
[
|
[
|
||||||
{ tagFilteringMode: 'startsWith' } as ShortUrlCreationSettings,
|
{ tagFilteringMode: 'startsWith' } as ShortUrlsSettings,
|
||||||
'Suggest tags starting with input',
|
'Suggest tags starting with input',
|
||||||
'starting with',
|
'starting with',
|
||||||
],
|
],
|
|
@ -1,16 +1,20 @@
|
||||||
import { shallow, ShallowWrapper } from 'enzyme';
|
import { shallow, ShallowWrapper } from 'enzyme';
|
||||||
import { Mock } from 'ts-mockery';
|
import { Mock } from 'ts-mockery';
|
||||||
import { DEFAULT_SHORT_URLS_ORDERING, Settings, ShortUrlsListSettings } from '../../src/settings/reducers/settings';
|
import {
|
||||||
import { ShortUrlsList } from '../../src/settings/ShortUrlsList';
|
DEFAULT_SHORT_URLS_ORDERING,
|
||||||
import SortingDropdown from '../../src/utils/SortingDropdown';
|
Settings,
|
||||||
|
ShortUrlsListSettings as ShortUrlsSettings,
|
||||||
|
} from '../../src/settings/reducers/settings';
|
||||||
|
import { ShortUrlsListSettings } from '../../src/settings/ShortUrlsListSettings';
|
||||||
|
import { OrderingDropdown } from '../../src/utils/OrderingDropdown';
|
||||||
import { ShortUrlsOrder } from '../../src/short-urls/data';
|
import { ShortUrlsOrder } from '../../src/short-urls/data';
|
||||||
|
|
||||||
describe('<ShortUrlsList />', () => {
|
describe('<ShortUrlsListSettings />', () => {
|
||||||
let wrapper: ShallowWrapper;
|
let wrapper: ShallowWrapper;
|
||||||
const setSettings = jest.fn();
|
const setSettings = jest.fn();
|
||||||
const createWrapper = (shortUrlsList?: ShortUrlsListSettings) => {
|
const createWrapper = (shortUrlsList?: ShortUrlsSettings) => {
|
||||||
wrapper = shallow(
|
wrapper = shallow(
|
||||||
<ShortUrlsList settings={Mock.of<Settings>({ shortUrlsList })} setShortUrlsListSettings={setSettings} />,
|
<ShortUrlsListSettings settings={Mock.of<Settings>({ shortUrlsList })} setShortUrlsListSettings={setSettings} />,
|
||||||
);
|
);
|
||||||
|
|
||||||
return wrapper;
|
return wrapper;
|
||||||
|
@ -27,7 +31,7 @@ describe('<ShortUrlsList />', () => {
|
||||||
[{ defaultOrdering: { field: 'visits', dir: 'ASC' } as ShortUrlsOrder }, { field: 'visits', dir: 'ASC' }],
|
[{ defaultOrdering: { field: 'visits', dir: 'ASC' } as ShortUrlsOrder }, { field: 'visits', dir: 'ASC' }],
|
||||||
])('shows expected ordering', (shortUrlsList, expectedOrder) => {
|
])('shows expected ordering', (shortUrlsList, expectedOrder) => {
|
||||||
const wrapper = createWrapper(shortUrlsList);
|
const wrapper = createWrapper(shortUrlsList);
|
||||||
const dropdown = wrapper.find(SortingDropdown);
|
const dropdown = wrapper.find(OrderingDropdown);
|
||||||
|
|
||||||
expect(dropdown.prop('order')).toEqual(expectedOrder);
|
expect(dropdown.prop('order')).toEqual(expectedOrder);
|
||||||
});
|
});
|
||||||
|
@ -39,7 +43,7 @@ describe('<ShortUrlsList />', () => {
|
||||||
[ 'title', 'DESC' ],
|
[ 'title', 'DESC' ],
|
||||||
])('invokes setSettings when ordering changes', (field, dir) => {
|
])('invokes setSettings when ordering changes', (field, dir) => {
|
||||||
const wrapper = createWrapper();
|
const wrapper = createWrapper();
|
||||||
const dropdown = wrapper.find(SortingDropdown);
|
const dropdown = wrapper.find(OrderingDropdown);
|
||||||
|
|
||||||
expect(setSettings).not.toHaveBeenCalled();
|
expect(setSettings).not.toHaveBeenCalled();
|
||||||
dropdown.simulate('change', field, dir);
|
dropdown.simulate('change', field, dir);
|
|
@ -1,17 +1,17 @@
|
||||||
import { shallow, ShallowWrapper } from 'enzyme';
|
import { shallow, ShallowWrapper } from 'enzyme';
|
||||||
import { Mock } from 'ts-mockery';
|
import { Mock } from 'ts-mockery';
|
||||||
import { FormGroup } from 'reactstrap';
|
import { FormGroup } from 'reactstrap';
|
||||||
import { Settings, TagsMode, TagsSettings } from '../../src/settings/reducers/settings';
|
import { Settings, TagsMode, TagsSettings as TagsSettingsOptions } from '../../src/settings/reducers/settings';
|
||||||
import { TagsModeDropdown } from '../../src/tags/TagsModeDropdown';
|
import { TagsModeDropdown } from '../../src/tags/TagsModeDropdown';
|
||||||
import { Tags } from '../../src/settings/Tags';
|
import { TagsSettings } from '../../src/settings/TagsSettings';
|
||||||
import SortingDropdown from '../../src/utils/SortingDropdown';
|
import { OrderingDropdown } from '../../src/utils/OrderingDropdown';
|
||||||
import { TagsOrder } from '../../src/tags/data/TagsListChildrenProps';
|
import { TagsOrder } from '../../src/tags/data/TagsListChildrenProps';
|
||||||
|
|
||||||
describe('<Tags />', () => {
|
describe('<TagsSettings />', () => {
|
||||||
let wrapper: ShallowWrapper;
|
let wrapper: ShallowWrapper;
|
||||||
const setTagsSettings = jest.fn();
|
const setTagsSettings = jest.fn();
|
||||||
const createWrapper = (tags?: TagsSettings) => {
|
const createWrapper = (tags?: TagsSettingsOptions) => {
|
||||||
wrapper = shallow(<Tags settings={Mock.of<Settings>({ tags })} setTagsSettings={setTagsSettings} />);
|
wrapper = shallow(<TagsSettings settings={Mock.of<Settings>({ tags })} setTagsSettings={setTagsSettings} />);
|
||||||
|
|
||||||
return wrapper;
|
return wrapper;
|
||||||
};
|
};
|
||||||
|
@ -60,7 +60,7 @@ describe('<Tags />', () => {
|
||||||
[{ defaultOrdering: { field: 'visits', dir: 'ASC' } as TagsOrder }, { field: 'visits', dir: 'ASC' }],
|
[{ defaultOrdering: { field: 'visits', dir: 'ASC' } as TagsOrder }, { field: 'visits', dir: 'ASC' }],
|
||||||
])('shows expected ordering', (tags, expectedOrder) => {
|
])('shows expected ordering', (tags, expectedOrder) => {
|
||||||
const wrapper = createWrapper(tags);
|
const wrapper = createWrapper(tags);
|
||||||
const dropdown = wrapper.find(SortingDropdown);
|
const dropdown = wrapper.find(OrderingDropdown);
|
||||||
|
|
||||||
expect(dropdown.prop('order')).toEqual(expectedOrder);
|
expect(dropdown.prop('order')).toEqual(expectedOrder);
|
||||||
});
|
});
|
||||||
|
@ -72,7 +72,7 @@ describe('<Tags />', () => {
|
||||||
[ 'shortUrls', 'DESC' ],
|
[ 'shortUrls', 'DESC' ],
|
||||||
])('invokes setTagsSettings when ordering changes', (field, dir) => {
|
])('invokes setTagsSettings when ordering changes', (field, dir) => {
|
||||||
const wrapper = createWrapper();
|
const wrapper = createWrapper();
|
||||||
const dropdown = wrapper.find(SortingDropdown);
|
const dropdown = wrapper.find(OrderingDropdown);
|
||||||
|
|
||||||
expect(setTagsSettings).not.toHaveBeenCalled();
|
expect(setTagsSettings).not.toHaveBeenCalled();
|
||||||
dropdown.simulate('change', field, dir);
|
dropdown.simulate('change', field, dir);
|
|
@ -3,15 +3,15 @@ import { Mock } from 'ts-mockery';
|
||||||
import { faMoon, faSun } from '@fortawesome/free-solid-svg-icons';
|
import { faMoon, faSun } from '@fortawesome/free-solid-svg-icons';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import { Settings, UiSettings } from '../../src/settings/reducers/settings';
|
import { Settings, UiSettings } from '../../src/settings/reducers/settings';
|
||||||
import { UserInterface } from '../../src/settings/UserInterface';
|
import { UserInterfaceSettings } from '../../src/settings/UserInterfaceSettings';
|
||||||
import ToggleSwitch from '../../src/utils/ToggleSwitch';
|
import ToggleSwitch from '../../src/utils/ToggleSwitch';
|
||||||
import { Theme } from '../../src/utils/theme';
|
import { Theme } from '../../src/utils/theme';
|
||||||
|
|
||||||
describe('<UserInterface />', () => {
|
describe('<UserInterfaceSettings />', () => {
|
||||||
let wrapper: ShallowWrapper;
|
let wrapper: ShallowWrapper;
|
||||||
const setUiSettings = jest.fn();
|
const setUiSettings = jest.fn();
|
||||||
const createWrapper = (ui?: UiSettings) => {
|
const createWrapper = (ui?: UiSettings) => {
|
||||||
wrapper = shallow(<UserInterface settings={Mock.of<Settings>({ ui })} setUiSettings={setUiSettings} />);
|
wrapper = shallow(<UserInterfaceSettings settings={Mock.of<Settings>({ ui })} setUiSettings={setUiSettings} />);
|
||||||
|
|
||||||
return wrapper;
|
return wrapper;
|
||||||
};
|
};
|
|
@ -1,15 +1,15 @@
|
||||||
import { shallow, ShallowWrapper } from 'enzyme';
|
import { shallow, ShallowWrapper } from 'enzyme';
|
||||||
import { Mock } from 'ts-mockery';
|
import { Mock } from 'ts-mockery';
|
||||||
import { Settings } from '../../src/settings/reducers/settings';
|
import { Settings } from '../../src/settings/reducers/settings';
|
||||||
import { Visits } from '../../src/settings/Visits';
|
import { VisitsSettings } from '../../src/settings/VisitsSettings';
|
||||||
import { SimpleCard } from '../../src/utils/SimpleCard';
|
import { SimpleCard } from '../../src/utils/SimpleCard';
|
||||||
import { DateIntervalSelector } from '../../src/utils/dates/DateIntervalSelector';
|
import { DateIntervalSelector } from '../../src/utils/dates/DateIntervalSelector';
|
||||||
|
|
||||||
describe('<Visits />', () => {
|
describe('<VisitsSettings />', () => {
|
||||||
let wrapper: ShallowWrapper;
|
let wrapper: ShallowWrapper;
|
||||||
const setVisitsSettings = jest.fn();
|
const setVisitsSettings = jest.fn();
|
||||||
const createWrapper = (settings: Partial<Settings> = {}) => {
|
const createWrapper = (settings: Partial<Settings> = {}) => {
|
||||||
wrapper = shallow(<Visits settings={Mock.of<Settings>(settings)} setVisitsSettings={setVisitsSettings} />);
|
wrapper = shallow(<VisitsSettings settings={Mock.of<Settings>(settings)} setVisitsSettings={setVisitsSettings} />);
|
||||||
|
|
||||||
return wrapper;
|
return wrapper;
|
||||||
};
|
};
|
|
@ -4,10 +4,10 @@ import { Mock } from 'ts-mockery';
|
||||||
import { History, Location } from 'history';
|
import { History, Location } from 'history';
|
||||||
import { match } from 'react-router';
|
import { match } from 'react-router';
|
||||||
import shortUrlsListCreator from '../../src/short-urls/ShortUrlsList';
|
import shortUrlsListCreator from '../../src/short-urls/ShortUrlsList';
|
||||||
import { OrderableFields, ShortUrl, ShortUrlsOrder } from '../../src/short-urls/data';
|
import { ShortUrlsOrderableFields, ShortUrl, ShortUrlsOrder } 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 { OrderingDropdown } from '../../src/utils/OrderingDropdown';
|
||||||
import Paginator from '../../src/short-urls/Paginator';
|
import Paginator from '../../src/short-urls/Paginator';
|
||||||
import { ReachableServer } from '../../src/servers/data';
|
import { ReachableServer } from '../../src/servers/data';
|
||||||
import { ShortUrlListRouteParams } from '../../src/short-urls/helpers/hooks';
|
import { ShortUrlListRouteParams } from '../../src/short-urls/helpers/hooks';
|
||||||
|
@ -54,7 +54,7 @@ describe('<ShortUrlsList />', () => {
|
||||||
|
|
||||||
it('wraps expected components', () => {
|
it('wraps expected components', () => {
|
||||||
expect(wrapper.find(ShortUrlsTable)).toHaveLength(1);
|
expect(wrapper.find(ShortUrlsTable)).toHaveLength(1);
|
||||||
expect(wrapper.find(SortingDropdown)).toHaveLength(1);
|
expect(wrapper.find(OrderingDropdown)).toHaveLength(1);
|
||||||
expect(wrapper.find(Paginator)).toHaveLength(1);
|
expect(wrapper.find(Paginator)).toHaveLength(1);
|
||||||
expect(wrapper.find(SearchBar)).toHaveLength(1);
|
expect(wrapper.find(SearchBar)).toHaveLength(1);
|
||||||
});
|
});
|
||||||
|
@ -75,44 +75,44 @@ describe('<ShortUrlsList />', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('invokes order icon rendering', () => {
|
it('invokes order icon rendering', () => {
|
||||||
const renderIcon = (field: OrderableFields) =>
|
const renderIcon = (field: ShortUrlsOrderableFields) =>
|
||||||
(wrapper.find(ShortUrlsTable).prop('renderOrderIcon') as (field: OrderableFields) => ReactElement)(field);
|
(wrapper.find(ShortUrlsTable).prop('renderOrderIcon') as (field: ShortUrlsOrderableFields) => ReactElement)(field);
|
||||||
|
|
||||||
expect(renderIcon('visits').props.currentOrder).toEqual({});
|
expect(renderIcon('visits').props.currentOrder).toEqual({});
|
||||||
|
|
||||||
wrapper.find(SortingDropdown).simulate('change', 'visits');
|
wrapper.find(OrderingDropdown).simulate('change', 'visits');
|
||||||
expect(renderIcon('visits').props.currentOrder).toEqual({ field: 'visits' });
|
expect(renderIcon('visits').props.currentOrder).toEqual({ field: 'visits' });
|
||||||
|
|
||||||
wrapper.find(SortingDropdown).simulate('change', 'visits', 'ASC');
|
wrapper.find(OrderingDropdown).simulate('change', 'visits', 'ASC');
|
||||||
expect(renderIcon('visits').props.currentOrder).toEqual({ field: 'visits', dir: 'ASC' });
|
expect(renderIcon('visits').props.currentOrder).toEqual({ field: 'visits', dir: 'ASC' });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handles order through table', () => {
|
it('handles order through table', () => {
|
||||||
const orderByColumn: (field: OrderableFields) => Function = wrapper.find(ShortUrlsTable).prop('orderByColumn');
|
const orderByColumn: (field: ShortUrlsOrderableFields) => Function = wrapper.find(ShortUrlsTable).prop('orderByColumn');
|
||||||
|
|
||||||
expect(wrapper.find(SortingDropdown).prop('order')).toEqual({});
|
expect(wrapper.find(OrderingDropdown).prop('order')).toEqual({});
|
||||||
|
|
||||||
orderByColumn('visits')();
|
orderByColumn('visits')();
|
||||||
expect(wrapper.find(SortingDropdown).prop('order')).toEqual({ field: 'visits', dir: 'ASC' });
|
expect(wrapper.find(OrderingDropdown).prop('order')).toEqual({ field: 'visits', dir: 'ASC' });
|
||||||
|
|
||||||
orderByColumn('title')();
|
orderByColumn('title')();
|
||||||
expect(wrapper.find(SortingDropdown).prop('order')).toEqual({ field: 'title', dir: 'ASC' });
|
expect(wrapper.find(OrderingDropdown).prop('order')).toEqual({ field: 'title', dir: 'ASC' });
|
||||||
|
|
||||||
orderByColumn('shortCode')();
|
orderByColumn('shortCode')();
|
||||||
expect(wrapper.find(SortingDropdown).prop('order')).toEqual({ field: 'shortCode', dir: 'ASC' });
|
expect(wrapper.find(OrderingDropdown).prop('order')).toEqual({ field: 'shortCode', dir: 'ASC' });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handles order through dropdown', () => {
|
it('handles order through dropdown', () => {
|
||||||
expect(wrapper.find(SortingDropdown).prop('order')).toEqual({});
|
expect(wrapper.find(OrderingDropdown).prop('order')).toEqual({});
|
||||||
|
|
||||||
wrapper.find(SortingDropdown).simulate('change', 'visits', 'ASC');
|
wrapper.find(OrderingDropdown).simulate('change', 'visits', 'ASC');
|
||||||
expect(wrapper.find(SortingDropdown).prop('order')).toEqual({ field: 'visits', dir: 'ASC' });
|
expect(wrapper.find(OrderingDropdown).prop('order')).toEqual({ field: 'visits', dir: 'ASC' });
|
||||||
|
|
||||||
wrapper.find(SortingDropdown).simulate('change', 'shortCode', 'DESC');
|
wrapper.find(OrderingDropdown).simulate('change', 'shortCode', 'DESC');
|
||||||
expect(wrapper.find(SortingDropdown).prop('order')).toEqual({ field: 'shortCode', dir: 'DESC' });
|
expect(wrapper.find(OrderingDropdown).prop('order')).toEqual({ field: 'shortCode', dir: 'DESC' });
|
||||||
|
|
||||||
wrapper.find(SortingDropdown).simulate('change', undefined, undefined);
|
wrapper.find(OrderingDropdown).simulate('change', undefined, undefined);
|
||||||
expect(wrapper.find(SortingDropdown).prop('order')).toEqual({});
|
expect(wrapper.find(OrderingDropdown).prop('order')).toEqual({});
|
||||||
});
|
});
|
||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
|
@ -122,6 +122,6 @@ describe('<ShortUrlsList />', () => {
|
||||||
])('has expected initial ordering', (initialOrderBy, field, dir) => {
|
])('has expected initial ordering', (initialOrderBy, field, dir) => {
|
||||||
const wrapper = createWrapper(initialOrderBy);
|
const wrapper = createWrapper(initialOrderBy);
|
||||||
|
|
||||||
expect(wrapper.find(SortingDropdown).prop('order')).toEqual({ field, dir });
|
expect(wrapper.find(OrderingDropdown).prop('order')).toEqual({ field, dir });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { ShortUrlsTable as shortUrlsTableCreator } from '../../src/short-urls/Sh
|
||||||
import { ShortUrlsList } from '../../src/short-urls/reducers/shortUrlsList';
|
import { ShortUrlsList } from '../../src/short-urls/reducers/shortUrlsList';
|
||||||
import { ReachableServer, SelectedServer } from '../../src/servers/data';
|
import { ReachableServer, SelectedServer } from '../../src/servers/data';
|
||||||
import { SemVer } from '../../src/utils/helpers/version';
|
import { SemVer } from '../../src/utils/helpers/version';
|
||||||
import { OrderableFields, SORTABLE_FIELDS } from '../../src/short-urls/data';
|
import { ShortUrlsOrderableFields, SHORT_URLS_ORDERABLE_FIELDS } from '../../src/short-urls/data';
|
||||||
|
|
||||||
describe('<ShortUrlsTable />', () => {
|
describe('<ShortUrlsTable />', () => {
|
||||||
let wrapper: ShallowWrapper;
|
let wrapper: ShallowWrapper;
|
||||||
|
@ -51,8 +51,8 @@ describe('<ShortUrlsTable />', () => {
|
||||||
.find('thead')
|
.find('thead')
|
||||||
.find('tr')
|
.find('tr')
|
||||||
.find('th')
|
.find('th')
|
||||||
.filterWhere((e) => e.text().includes(SORTABLE_FIELDS[orderableField as OrderableFields]));
|
.filterWhere((e) => e.text().includes(SHORT_URLS_ORDERABLE_FIELDS[orderableField as ShortUrlsOrderableFields]));
|
||||||
const sortableFields = Object.keys(SORTABLE_FIELDS).filter((sortableField) => sortableField !== 'title');
|
const sortableFields = Object.keys(SHORT_URLS_ORDERABLE_FIELDS).filter((sortableField) => sortableField !== 'title');
|
||||||
|
|
||||||
expect.assertions(sortableFields.length);
|
expect.assertions(sortableFields.length);
|
||||||
sortableFields.forEach((sortableField) => {
|
sortableFields.forEach((sortableField) => {
|
||||||
|
|
|
@ -9,8 +9,8 @@ import { Result } from '../../src/utils/Result';
|
||||||
import { TagsModeDropdown } from '../../src/tags/TagsModeDropdown';
|
import { TagsModeDropdown } from '../../src/tags/TagsModeDropdown';
|
||||||
import SearchField from '../../src/utils/SearchField';
|
import SearchField from '../../src/utils/SearchField';
|
||||||
import { Settings } from '../../src/settings/reducers/settings';
|
import { Settings } from '../../src/settings/reducers/settings';
|
||||||
import { OrderableFields } from '../../src/tags/data/TagsListChildrenProps';
|
import { TagsOrderableFields } from '../../src/tags/data/TagsListChildrenProps';
|
||||||
import SortingDropdown from '../../src/utils/SortingDropdown';
|
import { OrderingDropdown } from '../../src/utils/OrderingDropdown';
|
||||||
|
|
||||||
describe('<TagsList />', () => {
|
describe('<TagsList />', () => {
|
||||||
let wrapper: ShallowWrapper;
|
let wrapper: ShallowWrapper;
|
||||||
|
@ -89,16 +89,16 @@ describe('<TagsList />', () => {
|
||||||
it('triggers ordering when sorting dropdown changes', () => {
|
it('triggers ordering when sorting dropdown changes', () => {
|
||||||
const wrapper = createWrapper({ filteredTags: [] });
|
const wrapper = createWrapper({ filteredTags: [] });
|
||||||
|
|
||||||
expect(wrapper.find(SortingDropdown).prop('order')).toEqual({});
|
expect(wrapper.find(OrderingDropdown).prop('order')).toEqual({});
|
||||||
wrapper.find(SortingDropdown).simulate('change', 'tag', 'DESC');
|
wrapper.find(OrderingDropdown).simulate('change', 'tag', 'DESC');
|
||||||
expect(wrapper.find(SortingDropdown).prop('order')).toEqual({ field: 'tag', dir: 'DESC' });
|
expect(wrapper.find(OrderingDropdown).prop('order')).toEqual({ field: 'tag', dir: 'DESC' });
|
||||||
wrapper.find(SortingDropdown).simulate('change', 'visits', 'ASC');
|
wrapper.find(OrderingDropdown).simulate('change', 'visits', 'ASC');
|
||||||
expect(wrapper.find(SortingDropdown).prop('order')).toEqual({ field: 'visits', dir: 'ASC' });
|
expect(wrapper.find(OrderingDropdown).prop('order')).toEqual({ field: 'visits', dir: 'ASC' });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can update current order via orderByColumn from table component', () => {
|
it('can update current order via orderByColumn from table component', () => {
|
||||||
const wrapper = createWrapper({ filteredTags: [ 'foo', 'bar' ], stats: {} });
|
const wrapper = createWrapper({ filteredTags: [ 'foo', 'bar' ], stats: {} });
|
||||||
const callOrderBy = (field: OrderableFields) => {
|
const callOrderBy = (field: TagsOrderableFields) => {
|
||||||
((wrapper.find(TagsTable).prop('orderByColumn') as Function)(field) as Function)();
|
((wrapper.find(TagsTable).prop('orderByColumn') as Function)(field) as Function)();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { DropdownItem, DropdownToggle } from 'reactstrap';
|
||||||
import { identity, values } from 'ramda';
|
import { identity, values } from 'ramda';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import { faSortAmountDown as caretDownIcon } from '@fortawesome/free-solid-svg-icons';
|
import { faSortAmountDown as caretDownIcon } from '@fortawesome/free-solid-svg-icons';
|
||||||
import SortingDropdown, { SortingDropdownProps } from '../../src/utils/SortingDropdown';
|
import { OrderingDropdown, OrderingDropdownProps } from '../../src/utils/OrderingDropdown';
|
||||||
import { OrderDir } from '../../src/utils/helpers/ordering';
|
import { OrderDir } from '../../src/utils/helpers/ordering';
|
||||||
|
|
||||||
describe('<SortingDropdown />', () => {
|
describe('<SortingDropdown />', () => {
|
||||||
|
@ -13,8 +13,8 @@ describe('<SortingDropdown />', () => {
|
||||||
bar: 'Bar',
|
bar: 'Bar',
|
||||||
baz: 'Hello World',
|
baz: 'Hello World',
|
||||||
};
|
};
|
||||||
const createWrapper = (props: Partial<SortingDropdownProps> = {}) => {
|
const createWrapper = (props: Partial<OrderingDropdownProps> = {}) => {
|
||||||
wrapper = shallow(<SortingDropdown items={items} order={{}} onChange={identity} {...props} />);
|
wrapper = shallow(<OrderingDropdown items={items} order={{}} onChange={identity} {...props} />);
|
||||||
|
|
||||||
return wrapper;
|
return wrapper;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { shallow, ShallowWrapper } from 'enzyme';
|
import { shallow, ShallowWrapper } from 'enzyme';
|
||||||
import { range } from 'ramda';
|
import { range } from 'ramda';
|
||||||
import SortingDropdown from '../../../src/utils/SortingDropdown';
|
import { OrderingDropdown } from '../../../src/utils/OrderingDropdown';
|
||||||
import PaginationDropdown from '../../../src/utils/PaginationDropdown';
|
import PaginationDropdown from '../../../src/utils/PaginationDropdown';
|
||||||
import { rangeOf } from '../../../src/utils/utils';
|
import { rangeOf } from '../../../src/utils/utils';
|
||||||
import { OrderDir } from '../../../src/utils/helpers/ordering';
|
import { OrderDir } from '../../../src/utils/helpers/ordering';
|
||||||
|
@ -45,7 +45,7 @@ describe('<SortableBarChartCard />', () => {
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
const wrapper = createWrapper();
|
const wrapper = createWrapper();
|
||||||
const dropdown = wrapper.renderProp('title' as never)().find(SortingDropdown);
|
const dropdown = wrapper.renderProp('title' as never)().find(OrderingDropdown);
|
||||||
|
|
||||||
assert = (sortName: string, sortDir: OrderDir, keys: string[], values: number[]) => {
|
assert = (sortName: string, sortDir: OrderDir, keys: string[], values: number[]) => {
|
||||||
dropdown.prop('onChange')(sortName, sortDir);
|
dropdown.prop('onChange')(sortName, sortDir);
|
||||||
|
|
Loading…
Reference in a new issue