mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2025-01-09 17:57:26 +03:00
Removed references to tags cards
This commit is contained in:
parent
0312a0911c
commit
37caa1ad19
12 changed files with 15 additions and 381 deletions
|
@ -1,10 +1,7 @@
|
||||||
import { FC } from 'react';
|
import { FC } from 'react';
|
||||||
import { SimpleCard } from '../utils/SimpleCard';
|
import { SimpleCard } from '../utils/SimpleCard';
|
||||||
import { TagsModeDropdown } from '../tags/TagsModeDropdown';
|
|
||||||
import { capitalize } from '../utils/utils';
|
|
||||||
import { OrderingDropdown } from '../utils/OrderingDropdown';
|
import { OrderingDropdown } from '../utils/OrderingDropdown';
|
||||||
import { TAGS_ORDERABLE_FIELDS } from '../tags/data/TagsListChildrenProps';
|
import { TAGS_ORDERABLE_FIELDS } from '../tags/data/TagsListChildrenProps';
|
||||||
import { FormText } from '../utils/forms/FormText';
|
|
||||||
import { LabeledFormGroup } from '../utils/forms/LabeledFormGroup';
|
import { LabeledFormGroup } from '../utils/forms/LabeledFormGroup';
|
||||||
import { Settings, TagsSettings as TagsSettingsOptions } from './reducers/settings';
|
import { Settings, TagsSettings as TagsSettingsOptions } from './reducers/settings';
|
||||||
|
|
||||||
|
@ -15,14 +12,6 @@ interface TagsProps {
|
||||||
|
|
||||||
export const TagsSettings: 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">
|
||||||
<LabeledFormGroup label="Default display mode when managing tags:">
|
|
||||||
<TagsModeDropdown
|
|
||||||
mode={tags?.defaultMode ?? 'cards'}
|
|
||||||
renderTitle={(tagsMode) => capitalize(tagsMode)}
|
|
||||||
onChange={(defaultMode) => setTagsSettings({ ...tags, defaultMode })}
|
|
||||||
/>
|
|
||||||
<FormText>Tags will be displayed as <b>{tags?.defaultMode ?? 'cards'}</b>.</FormText>
|
|
||||||
</LabeledFormGroup>
|
|
||||||
<LabeledFormGroup noMargin label="Default ordering for tags list:">
|
<LabeledFormGroup noMargin label="Default ordering for tags list:">
|
||||||
<OrderingDropdown
|
<OrderingDropdown
|
||||||
items={TAGS_ORDERABLE_FIELDS}
|
items={TAGS_ORDERABLE_FIELDS}
|
||||||
|
|
|
@ -1,38 +0,0 @@
|
||||||
@import '../utils/base';
|
|
||||||
|
|
||||||
.tag-card.tag-card {
|
|
||||||
margin-bottom: .5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tag-card__header.tag-card__header,
|
|
||||||
.tag-card__body.tag-card__body {
|
|
||||||
padding: .75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tag-card__tag-title {
|
|
||||||
margin: 0;
|
|
||||||
line-height: 31px;
|
|
||||||
padding-right: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tag-card__btn {
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tag-card__btn--last {
|
|
||||||
margin-left: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tag-card__table-cell.tag-card__table-cell {
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tag-card__tag-name {
|
|
||||||
color: $mainColor;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tag-card__tag-name:hover {
|
|
||||||
color: darken($mainColor, 15%);
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
|
@ -1,89 +0,0 @@
|
||||||
import { Card, CardHeader, CardBody, Button, Collapse } from 'reactstrap';
|
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
||||||
import { faTrash as deleteIcon, faPencilAlt as editIcon, faLink, faEye } from '@fortawesome/free-solid-svg-icons';
|
|
||||||
import { FC, useEffect, useRef } from 'react';
|
|
||||||
import { Link } from 'react-router-dom';
|
|
||||||
import { prettify } from '../utils/helpers/numbers';
|
|
||||||
import { useToggle } from '../utils/helpers/hooks';
|
|
||||||
import { ColorGenerator } from '../utils/services/ColorGenerator';
|
|
||||||
import { getServerId, SelectedServer } from '../servers/data';
|
|
||||||
import { TagBullet } from './helpers/TagBullet';
|
|
||||||
import { NormalizedTag, TagModalProps } from './data';
|
|
||||||
import './TagCard.scss';
|
|
||||||
import { mutableRefToElementRef } from '../utils/helpers/components';
|
|
||||||
|
|
||||||
export interface TagCardProps {
|
|
||||||
tag: NormalizedTag;
|
|
||||||
selectedServer: SelectedServer;
|
|
||||||
displayed: boolean;
|
|
||||||
toggle: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const isTruncated = (el: HTMLElement | undefined): boolean => !!el && el.scrollWidth > el.clientWidth;
|
|
||||||
|
|
||||||
export const TagCard = (
|
|
||||||
DeleteTagConfirmModal: FC<TagModalProps>,
|
|
||||||
EditTagModal: FC<TagModalProps>,
|
|
||||||
colorGenerator: ColorGenerator,
|
|
||||||
) => ({ tag, selectedServer, displayed, toggle }: TagCardProps) => {
|
|
||||||
const [isDeleteModalOpen, toggleDelete] = useToggle();
|
|
||||||
const [isEditModalOpen, toggleEdit] = useToggle();
|
|
||||||
const [hasTitle,, displayTitle] = useToggle();
|
|
||||||
const titleRef = useRef<HTMLHeadingElement | undefined>();
|
|
||||||
const serverId = getServerId(selectedServer);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (isTruncated(titleRef.current)) {
|
|
||||||
displayTitle();
|
|
||||||
}
|
|
||||||
}, [titleRef.current]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card className="tag-card">
|
|
||||||
<CardHeader className="tag-card__header">
|
|
||||||
<Button
|
|
||||||
aria-label="Delete tag"
|
|
||||||
color="link"
|
|
||||||
size="sm"
|
|
||||||
className="tag-card__btn tag-card__btn--last"
|
|
||||||
onClick={toggleDelete}
|
|
||||||
>
|
|
||||||
<FontAwesomeIcon icon={deleteIcon} />
|
|
||||||
</Button>
|
|
||||||
<Button aria-label="Edit tag" color="link" size="sm" className="tag-card__btn" onClick={toggleEdit}>
|
|
||||||
<FontAwesomeIcon icon={editIcon} />
|
|
||||||
</Button>
|
|
||||||
<h5
|
|
||||||
className="tag-card__tag-title text-ellipsis"
|
|
||||||
title={hasTitle ? tag.tag : undefined}
|
|
||||||
ref={mutableRefToElementRef(titleRef)}
|
|
||||||
>
|
|
||||||
<TagBullet tag={tag.tag} colorGenerator={colorGenerator} />
|
|
||||||
<span className="tag-card__tag-name" onClick={toggle}>{tag.tag}</span>
|
|
||||||
</h5>
|
|
||||||
</CardHeader>
|
|
||||||
|
|
||||||
<Collapse isOpen={displayed}>
|
|
||||||
<CardBody className="tag-card__body">
|
|
||||||
<Link
|
|
||||||
to={`/server/${serverId}/list-short-urls/1?tags=${encodeURIComponent(tag.tag)}`}
|
|
||||||
className="btn btn-outline-secondary btn-block d-flex justify-content-between align-items-center mb-1"
|
|
||||||
>
|
|
||||||
<span className="text-ellipsis"><FontAwesomeIcon icon={faLink} className="me-2" />Short URLs</span>
|
|
||||||
<b>{prettify(tag.shortUrls)}</b>
|
|
||||||
</Link>
|
|
||||||
<Link
|
|
||||||
to={`/server/${serverId}/tag/${tag.tag}/visits`}
|
|
||||||
className="btn btn-outline-secondary btn-block d-flex justify-content-between align-items-center"
|
|
||||||
>
|
|
||||||
<span className="text-ellipsis"><FontAwesomeIcon icon={faEye} className="me-2" />Visits</span>
|
|
||||||
<b>{prettify(tag.visits)}</b>
|
|
||||||
</Link>
|
|
||||||
</CardBody>
|
|
||||||
</Collapse>
|
|
||||||
|
|
||||||
<DeleteTagConfirmModal tag={tag.tag} toggle={toggleDelete} isOpen={isDeleteModalOpen} />
|
|
||||||
<EditTagModal tag={tag.tag} toggle={toggleEdit} isOpen={isEditModalOpen} />
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,32 +0,0 @@
|
||||||
import { FC, useState } from 'react';
|
|
||||||
import { splitEvery } from 'ramda';
|
|
||||||
import { Row } from 'reactstrap';
|
|
||||||
import { TagCardProps } from './TagCard';
|
|
||||||
import { TagsListChildrenProps } from './data/TagsListChildrenProps';
|
|
||||||
|
|
||||||
const { ceil } = Math;
|
|
||||||
const TAGS_GROUPS_AMOUNT = 4;
|
|
||||||
|
|
||||||
export const TagsCards = (TagCard: FC<TagCardProps>): FC<TagsListChildrenProps> => ({ sortedTags, selectedServer }) => {
|
|
||||||
const [displayedTag, setDisplayedTag] = useState<string | undefined>();
|
|
||||||
const tagsCount = sortedTags.length;
|
|
||||||
const tagsGroups = splitEvery(ceil(tagsCount / TAGS_GROUPS_AMOUNT), sortedTags);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Row>
|
|
||||||
{tagsGroups.map((group, index) => (
|
|
||||||
<div key={index} className="col-md-6 col-xl-3">
|
|
||||||
{group.map((tag) => (
|
|
||||||
<TagCard
|
|
||||||
key={tag.tag}
|
|
||||||
tag={tag}
|
|
||||||
selectedServer={selectedServer}
|
|
||||||
displayed={displayedTag === tag.tag}
|
|
||||||
toggle={() => setDisplayedTag(displayedTag !== tag.tag ? tag.tag : undefined)}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</Row>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -8,17 +8,11 @@ import { boundToMercureHub } from '../mercure/helpers/boundToMercureHub';
|
||||||
import { Result } from '../utils/Result';
|
import { Result } from '../utils/Result';
|
||||||
import { ShlinkApiError } from '../api/ShlinkApiError';
|
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 } from '../settings/reducers/settings';
|
||||||
import { determineOrderDir, sortList } from '../utils/helpers/ordering';
|
import { determineOrderDir, sortList } from '../utils/helpers/ordering';
|
||||||
import { OrderingDropdown } from '../utils/OrderingDropdown';
|
import { OrderingDropdown } from '../utils/OrderingDropdown';
|
||||||
import { TagsList as TagsListState } from './reducers/tagsList';
|
import { TagsList as TagsListState } from './reducers/tagsList';
|
||||||
import {
|
import { TagsOrderableFields, TAGS_ORDERABLE_FIELDS, TagsOrder } from './data/TagsListChildrenProps';
|
||||||
TagsOrderableFields,
|
|
||||||
TAGS_ORDERABLE_FIELDS,
|
|
||||||
TagsListChildrenProps,
|
|
||||||
TagsOrder,
|
|
||||||
} from './data/TagsListChildrenProps';
|
|
||||||
import { TagsModeDropdown } from './TagsModeDropdown';
|
|
||||||
import { NormalizedTag } from './data';
|
import { NormalizedTag } from './data';
|
||||||
import { TagsTableProps } from './TagsTable';
|
import { TagsTableProps } from './TagsTable';
|
||||||
|
|
||||||
|
@ -30,10 +24,9 @@ export interface TagsListProps {
|
||||||
settings: Settings;
|
settings: Settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TagsList = (TagsCards: FC<TagsListChildrenProps>, TagsTable: FC<TagsTableProps>) => boundToMercureHub((
|
export const TagsList = (TagsTable: FC<TagsTableProps>) => boundToMercureHub((
|
||||||
{ filterTags, forceListTags, tagsList, selectedServer, settings }: TagsListProps,
|
{ filterTags, forceListTags, tagsList, selectedServer, settings }: TagsListProps,
|
||||||
) => {
|
) => {
|
||||||
const [mode, setMode] = useState<TagsMode>(settings.tags?.defaultMode ?? 'cards');
|
|
||||||
const [order, setOrder] = useState<TagsOrder>(settings.tags?.defaultOrdering ?? {});
|
const [order, setOrder] = useState<TagsOrder>(settings.tags?.defaultOrdering ?? {});
|
||||||
const resolveSortedTags = pipe(
|
const resolveSortedTags = pipe(
|
||||||
() => tagsList.filteredTags.map((tag): NormalizedTag => ({
|
() => tagsList.filteredTags.map((tag): NormalizedTag => ({
|
||||||
|
@ -73,26 +66,21 @@ export const TagsList = (TagsCards: FC<TagsListChildrenProps>, TagsTable: FC<Tag
|
||||||
|
|
||||||
const sortedTags = resolveSortedTags();
|
const sortedTags = resolveSortedTags();
|
||||||
|
|
||||||
return mode === 'cards'
|
return (
|
||||||
? <TagsCards sortedTags={sortedTags} selectedServer={selectedServer} />
|
<TagsTable
|
||||||
: (
|
sortedTags={sortedTags}
|
||||||
<TagsTable
|
selectedServer={selectedServer}
|
||||||
sortedTags={sortedTags}
|
currentOrder={order}
|
||||||
selectedServer={selectedServer}
|
orderByColumn={orderByColumn}
|
||||||
currentOrder={order}
|
/>
|
||||||
orderByColumn={orderByColumn}
|
);
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<SearchField className="mb-3" onChange={filterTags} />
|
<SearchField className="mb-3" onChange={filterTags} />
|
||||||
<Row className="mb-3">
|
<Row className="mb-3">
|
||||||
<div className="col-lg-6">
|
<div className="col-lg-6 offset-lg-6">
|
||||||
<TagsModeDropdown mode={mode} onChange={setMode} />
|
|
||||||
</div>
|
|
||||||
<div className="col-lg-6 mt-3 mt-lg-0">
|
|
||||||
<OrderingDropdown
|
<OrderingDropdown
|
||||||
items={TAGS_ORDERABLE_FIELDS}
|
items={TAGS_ORDERABLE_FIELDS}
|
||||||
order={order}
|
order={order}
|
||||||
|
|
|
@ -1,23 +0,0 @@
|
||||||
import { FC } from 'react';
|
|
||||||
import { DropdownItem } from 'reactstrap';
|
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
||||||
import { faBars as listIcon, faThLarge as cardsIcon } from '@fortawesome/free-solid-svg-icons';
|
|
||||||
import { DropdownBtn } from '../utils/DropdownBtn';
|
|
||||||
import { TagsMode } from '../settings/reducers/settings';
|
|
||||||
|
|
||||||
interface TagsModeDropdownProps {
|
|
||||||
mode: TagsMode;
|
|
||||||
onChange: (newMode: TagsMode) => void;
|
|
||||||
renderTitle?: (mode: TagsMode) => string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const TagsModeDropdown: FC<TagsModeDropdownProps> = ({ mode, onChange, renderTitle }) => (
|
|
||||||
<DropdownBtn text={renderTitle?.(mode) ?? `Display mode: ${mode}`}>
|
|
||||||
<DropdownItem active={mode === 'cards'} onClick={() => onChange('cards')}>
|
|
||||||
<FontAwesomeIcon icon={cardsIcon} fixedWidth className="me-1" /> Cards
|
|
||||||
</DropdownItem>
|
|
||||||
<DropdownItem active={mode === 'list'} onClick={() => onChange('list')}>
|
|
||||||
<FontAwesomeIcon icon={listIcon} fixedWidth className="me-1" /> List
|
|
||||||
</DropdownItem>
|
|
||||||
</DropdownBtn>
|
|
||||||
);
|
|
|
@ -1,7 +1,6 @@
|
||||||
import { prop } from 'ramda';
|
import { prop } from 'ramda';
|
||||||
import Bottle, { IContainer } from 'bottlejs';
|
import Bottle, { IContainer } from 'bottlejs';
|
||||||
import { TagsSelector } from '../helpers/TagsSelector';
|
import { TagsSelector } from '../helpers/TagsSelector';
|
||||||
import { TagCard } from '../TagCard';
|
|
||||||
import { DeleteTagConfirmModal } from '../helpers/DeleteTagConfirmModal';
|
import { DeleteTagConfirmModal } from '../helpers/DeleteTagConfirmModal';
|
||||||
import { EditTagModal } from '../helpers/EditTagModal';
|
import { EditTagModal } from '../helpers/EditTagModal';
|
||||||
import { TagsList } from '../TagsList';
|
import { TagsList } from '../TagsList';
|
||||||
|
@ -9,7 +8,6 @@ import { filterTags, listTags, tagsListReducerCreator } from '../reducers/tagsLi
|
||||||
import { tagDeleted, tagDeleteReducerCreator } from '../reducers/tagDelete';
|
import { tagDeleted, tagDeleteReducerCreator } from '../reducers/tagDelete';
|
||||||
import { editTag, tagEdited, tagEditReducerCreator } from '../reducers/tagEdit';
|
import { editTag, tagEdited, tagEditReducerCreator } from '../reducers/tagEdit';
|
||||||
import { ConnectDecorator } from '../../container/types';
|
import { ConnectDecorator } from '../../container/types';
|
||||||
import { TagsCards } from '../TagsCards';
|
|
||||||
import { TagsTable } from '../TagsTable';
|
import { TagsTable } from '../TagsTable';
|
||||||
import { TagsTableRow } from '../TagsTableRow';
|
import { TagsTableRow } from '../TagsTableRow';
|
||||||
|
|
||||||
|
@ -18,20 +16,17 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
|
||||||
bottle.serviceFactory('TagsSelector', TagsSelector, 'ColorGenerator');
|
bottle.serviceFactory('TagsSelector', TagsSelector, 'ColorGenerator');
|
||||||
bottle.decorator('TagsSelector', connect(['tagsList', 'settings'], ['listTags']));
|
bottle.decorator('TagsSelector', connect(['tagsList', 'settings'], ['listTags']));
|
||||||
|
|
||||||
bottle.serviceFactory('TagCard', TagCard, 'DeleteTagConfirmModal', 'EditTagModal', 'ColorGenerator');
|
|
||||||
|
|
||||||
bottle.serviceFactory('DeleteTagConfirmModal', () => DeleteTagConfirmModal);
|
bottle.serviceFactory('DeleteTagConfirmModal', () => DeleteTagConfirmModal);
|
||||||
bottle.decorator('DeleteTagConfirmModal', connect(['tagDelete'], ['deleteTag', 'tagDeleted']));
|
bottle.decorator('DeleteTagConfirmModal', connect(['tagDelete'], ['deleteTag', 'tagDeleted']));
|
||||||
|
|
||||||
bottle.serviceFactory('EditTagModal', EditTagModal, 'ColorGenerator');
|
bottle.serviceFactory('EditTagModal', EditTagModal, 'ColorGenerator');
|
||||||
bottle.decorator('EditTagModal', connect(['tagEdit'], ['editTag', 'tagEdited']));
|
bottle.decorator('EditTagModal', connect(['tagEdit'], ['editTag', 'tagEdited']));
|
||||||
|
|
||||||
bottle.serviceFactory('TagsCards', TagsCards, 'TagCard');
|
|
||||||
bottle.serviceFactory('TagsTableRow', TagsTableRow, 'DeleteTagConfirmModal', 'EditTagModal', 'ColorGenerator');
|
bottle.serviceFactory('TagsTableRow', TagsTableRow, 'DeleteTagConfirmModal', 'EditTagModal', 'ColorGenerator');
|
||||||
|
|
||||||
bottle.serviceFactory('TagsTable', TagsTable, 'TagsTableRow');
|
bottle.serviceFactory('TagsTable', TagsTable, 'TagsTableRow');
|
||||||
|
|
||||||
bottle.serviceFactory('TagsList', TagsList, 'TagsCards', 'TagsTable');
|
bottle.serviceFactory('TagsList', TagsList, 'TagsTable');
|
||||||
bottle.decorator('TagsList', connect(
|
bottle.decorator('TagsList', connect(
|
||||||
['tagsList', 'selectedServer', 'mercureInfo', 'settings'],
|
['tagsList', 'selectedServer', 'mercureInfo', 'settings'],
|
||||||
['forceListTags', 'filterTags', 'createNewVisits', 'loadMercureInfo'],
|
['forceListTags', 'filterTags', 'createNewVisits', 'loadMercureInfo'],
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
import { screen } from '@testing-library/react';
|
import { screen } from '@testing-library/react';
|
||||||
import { Mock } from 'ts-mockery';
|
import { Mock } from 'ts-mockery';
|
||||||
import { Settings, TagsMode, TagsSettings as TagsSettingsOptions } from '../../src/settings/reducers/settings';
|
import { Settings, TagsSettings as TagsSettingsOptions } from '../../src/settings/reducers/settings';
|
||||||
import { TagsSettings } from '../../src/settings/TagsSettings';
|
import { TagsSettings } from '../../src/settings/TagsSettings';
|
||||||
import { TagsOrder } from '../../src/tags/data/TagsListChildrenProps';
|
import { TagsOrder } from '../../src/tags/data/TagsListChildrenProps';
|
||||||
import { capitalize } from '../../src/utils/utils';
|
|
||||||
import { renderWithEvents } from '../__helpers__/setUpTest';
|
import { renderWithEvents } from '../__helpers__/setUpTest';
|
||||||
|
|
||||||
describe('<TagsSettings />', () => {
|
describe('<TagsSettings />', () => {
|
||||||
|
@ -17,35 +16,10 @@ describe('<TagsSettings />', () => {
|
||||||
it('renders expected amount of groups', () => {
|
it('renders expected amount of groups', () => {
|
||||||
setUp();
|
setUp();
|
||||||
|
|
||||||
expect(screen.getByText('Default display mode when managing tags:')).toBeInTheDocument();
|
|
||||||
expect(screen.getByText('Default ordering for tags list:')).toBeInTheDocument();
|
expect(screen.getByText('Default ordering for tags list:')).toBeInTheDocument();
|
||||||
expect(screen.getByRole('button', { name: 'Order by...' })).toBeInTheDocument();
|
expect(screen.getByRole('button', { name: 'Order by...' })).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it.each([
|
|
||||||
[undefined, 'cards'],
|
|
||||||
[{}, 'cards'],
|
|
||||||
[{ defaultMode: 'cards' as TagsMode }, 'cards'],
|
|
||||||
[{ defaultMode: 'list' as TagsMode }, 'list'],
|
|
||||||
])('shows expected tags displaying mode', (tags, expectedMode) => {
|
|
||||||
const { container } = setUp(tags);
|
|
||||||
|
|
||||||
expect(screen.getByRole('button', { name: capitalize(expectedMode) })).toBeInTheDocument();
|
|
||||||
expect(container.querySelector('.form-text')).toHaveTextContent(`Tags will be displayed as ${expectedMode}.`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it.each([
|
|
||||||
['cards' as TagsMode],
|
|
||||||
['list' as TagsMode],
|
|
||||||
])('invokes setTagsSettings when tags mode changes', async (defaultMode) => {
|
|
||||||
const { user } = setUp();
|
|
||||||
|
|
||||||
expect(setTagsSettings).not.toHaveBeenCalled();
|
|
||||||
await user.click(screen.getByText('List'));
|
|
||||||
await user.click(screen.getByRole('menuitem', { name: capitalize(defaultMode) }));
|
|
||||||
expect(setTagsSettings).toHaveBeenCalledWith({ defaultMode });
|
|
||||||
});
|
|
||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
[undefined, 'Order by...'],
|
[undefined, 'Order by...'],
|
||||||
[{}, 'Order by...'],
|
[{}, 'Order by...'],
|
||||||
|
|
|
@ -1,57 +0,0 @@
|
||||||
import { screen } from '@testing-library/react';
|
|
||||||
import { MemoryRouter } from 'react-router-dom';
|
|
||||||
import { Mock } from 'ts-mockery';
|
|
||||||
import { TagCard as createTagCard } from '../../src/tags/TagCard';
|
|
||||||
import { ReachableServer } from '../../src/servers/data';
|
|
||||||
import { renderWithEvents } from '../__helpers__/setUpTest';
|
|
||||||
import { colorGeneratorMock } from '../utils/services/__mocks__/ColorGenerator.mock';
|
|
||||||
|
|
||||||
describe('<TagCard />', () => {
|
|
||||||
const TagCard = createTagCard(
|
|
||||||
({ isOpen }) => <span>DeleteTagConfirmModal {isOpen ? '[Open]' : '[Closed]'}</span>,
|
|
||||||
({ isOpen }) => <span>EditTagModal {isOpen ? '[Open]' : '[Closed]'}</span>,
|
|
||||||
colorGeneratorMock,
|
|
||||||
);
|
|
||||||
const setUp = (tag = 'ssr') => renderWithEvents(
|
|
||||||
<MemoryRouter>
|
|
||||||
<TagCard
|
|
||||||
tag={{ tag, visits: 23257, shortUrls: 48 }}
|
|
||||||
selectedServer={Mock.of<ReachableServer>({ id: '1' })}
|
|
||||||
displayed
|
|
||||||
toggle={() => {}}
|
|
||||||
/>
|
|
||||||
</MemoryRouter>,
|
|
||||||
);
|
|
||||||
|
|
||||||
afterEach(jest.resetAllMocks);
|
|
||||||
|
|
||||||
it.each([
|
|
||||||
['ssr', '/server/1/list-short-urls/1?tags=ssr', '/server/1/tag/ssr/visits'],
|
|
||||||
['ssr-&-foo', '/server/1/list-short-urls/1?tags=ssr-%26-foo', '/server/1/tag/ssr-&-foo/visits'],
|
|
||||||
])('shows expected links for provided tags', (tag, shortUrlsLink, visitsLink) => {
|
|
||||||
setUp(tag);
|
|
||||||
|
|
||||||
expect(screen.getByText('48').parentNode).toHaveAttribute('href', shortUrlsLink);
|
|
||||||
expect(screen.getByText('23,257').parentNode).toHaveAttribute('href', visitsLink);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('displays delete modal when delete btn is clicked', async () => {
|
|
||||||
const { user } = setUp();
|
|
||||||
|
|
||||||
expect(screen.getByText(/^DeleteTagConfirmModal/)).not.toHaveTextContent('[Open]');
|
|
||||||
expect(screen.getByText(/^DeleteTagConfirmModal/)).toHaveTextContent('[Closed]');
|
|
||||||
await user.click(screen.getByLabelText('Delete tag'));
|
|
||||||
expect(screen.getByText(/^DeleteTagConfirmModal/)).toHaveTextContent('[Open]');
|
|
||||||
expect(screen.getByText(/^DeleteTagConfirmModal/)).not.toHaveTextContent('[Closed]');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('displays edit modal when edit btn is clicked', async () => {
|
|
||||||
const { user } = setUp();
|
|
||||||
|
|
||||||
expect(screen.getByText(/^EditTagModal/)).not.toHaveTextContent('[Open]');
|
|
||||||
expect(screen.getByText(/^EditTagModal/)).toHaveTextContent('[Closed]');
|
|
||||||
await user.click(screen.getByLabelText('Edit tag'));
|
|
||||||
expect(screen.getByText(/^EditTagModal/)).toHaveTextContent('[Open]');
|
|
||||||
expect(screen.getByText(/^EditTagModal/)).not.toHaveTextContent('[Closed]');
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,26 +0,0 @@
|
||||||
import { screen } from '@testing-library/react';
|
|
||||||
import { Mock } from 'ts-mockery';
|
|
||||||
import { TagsCards as createTagsCards } from '../../src/tags/TagsCards';
|
|
||||||
import { SelectedServer } from '../../src/servers/data';
|
|
||||||
import { rangeOf } from '../../src/utils/utils';
|
|
||||||
import { NormalizedTag } from '../../src/tags/data';
|
|
||||||
import { renderWithEvents } from '../__helpers__/setUpTest';
|
|
||||||
|
|
||||||
describe('<TagsCards />', () => {
|
|
||||||
const amountOfTags = 10;
|
|
||||||
const sortedTags = rangeOf(amountOfTags, (i) => Mock.of<NormalizedTag>({ tag: `tag_${i}` }));
|
|
||||||
const TagsCards = createTagsCards(() => <span>TagCard</span>);
|
|
||||||
const setUp = () => renderWithEvents(
|
|
||||||
<TagsCards sortedTags={sortedTags} selectedServer={Mock.all<SelectedServer>()} />,
|
|
||||||
);
|
|
||||||
|
|
||||||
it('renders the proper amount of groups and cards based on the amount of tags', () => {
|
|
||||||
const { container } = setUp();
|
|
||||||
const amountOfGroups = 4;
|
|
||||||
const cards = screen.getAllByText('TagCard');
|
|
||||||
const groups = container.querySelectorAll('.col-md-6');
|
|
||||||
|
|
||||||
expect(cards).toHaveLength(amountOfTags);
|
|
||||||
expect(groups).toHaveLength(amountOfGroups);
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -9,7 +9,7 @@ import { renderWithEvents } from '../__helpers__/setUpTest';
|
||||||
|
|
||||||
describe('<TagsList />', () => {
|
describe('<TagsList />', () => {
|
||||||
const filterTags = jest.fn();
|
const filterTags = jest.fn();
|
||||||
const TagsListComp = createTagsList(() => <>TagsCards</>, () => <>TagsTable</>);
|
const TagsListComp = createTagsList(() => <>TagsTable</>);
|
||||||
const setUp = (tagsList: Partial<TagsList>) => renderWithEvents(
|
const setUp = (tagsList: Partial<TagsList>) => renderWithEvents(
|
||||||
<TagsListComp
|
<TagsListComp
|
||||||
{...Mock.all<TagsListProps>()}
|
{...Mock.all<TagsListProps>()}
|
||||||
|
@ -45,19 +45,6 @@ describe('<TagsList />', () => {
|
||||||
expect(screen.queryByText('Loading')).not.toBeInTheDocument();
|
expect(screen.queryByText('Loading')).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders proper component based on the display mode', async () => {
|
|
||||||
const { user } = setUp({ filteredTags: ['foo', 'bar'], stats: {} });
|
|
||||||
|
|
||||||
expect(screen.getByText('TagsCards')).toBeInTheDocument();
|
|
||||||
expect(screen.queryByText('TagsTable')).not.toBeInTheDocument();
|
|
||||||
|
|
||||||
await user.click(screen.getByRole('button', { name: /^Display mode/ }));
|
|
||||||
await user.click(screen.getByRole('menuitem', { name: /List/ }));
|
|
||||||
|
|
||||||
expect(screen.queryByText('TagsCards')).not.toBeInTheDocument();
|
|
||||||
expect(screen.getByText('TagsTable')).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('triggers tags filtering when search field changes', async () => {
|
it('triggers tags filtering when search field changes', async () => {
|
||||||
const { user } = setUp({ filteredTags: [] });
|
const { user } = setUp({ filteredTags: [] });
|
||||||
|
|
||||||
|
|
|
@ -1,34 +0,0 @@
|
||||||
import { screen } from '@testing-library/react';
|
|
||||||
import { TagsModeDropdown } from '../../src/tags/TagsModeDropdown';
|
|
||||||
import { TagsMode } from '../../src/settings/reducers/settings';
|
|
||||||
import { renderWithEvents } from '../__helpers__/setUpTest';
|
|
||||||
|
|
||||||
describe('<TagsModeDropdown />', () => {
|
|
||||||
const onChange = jest.fn();
|
|
||||||
const setUp = (mode: TagsMode) => renderWithEvents(<TagsModeDropdown mode={mode} onChange={onChange} />);
|
|
||||||
|
|
||||||
afterEach(jest.clearAllMocks);
|
|
||||||
|
|
||||||
it.each([
|
|
||||||
['cards' as TagsMode],
|
|
||||||
['list' as TagsMode],
|
|
||||||
])('renders expected initial value', (mode) => {
|
|
||||||
setUp(mode);
|
|
||||||
expect(screen.getByRole('button')).toHaveTextContent(`Display mode: ${mode}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('changes active element on click', async () => {
|
|
||||||
const { user } = setUp('list');
|
|
||||||
const clickItem = async (index: 0 | 1) => {
|
|
||||||
await user.click(screen.getByRole('button'));
|
|
||||||
await user.click(screen.getAllByRole('menuitem')[index]);
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(onChange).not.toHaveBeenCalled();
|
|
||||||
await clickItem(0);
|
|
||||||
expect(onChange).toHaveBeenCalledWith('cards');
|
|
||||||
|
|
||||||
await clickItem(1);
|
|
||||||
expect(onChange).toHaveBeenCalledWith('list');
|
|
||||||
});
|
|
||||||
});
|
|
Loading…
Reference in a new issue