mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2025-12-11 00:33:47 -06:00
Added some tests for new tags components
This commit is contained in:
parent
304a7431ad
commit
2f76c5381f
33
src/tags/TagsCards.tsx
Normal file
33
src/tags/TagsCards.tsx
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
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> => ({ tagsList, selectedServer }) => {
|
||||||
|
const [ displayedTag, setDisplayedTag ] = useState<string | undefined>();
|
||||||
|
const tagsCount = tagsList.filteredTags.length;
|
||||||
|
const tagsGroups = splitEvery(ceil(tagsCount / TAGS_GROUPS_AMOUNT), tagsList.filteredTags);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Row>
|
||||||
|
{tagsGroups.map((group, index) => (
|
||||||
|
<div key={index} className="col-md-6 col-xl-3">
|
||||||
|
{group.map((tag) => (
|
||||||
|
<TagCard
|
||||||
|
key={tag}
|
||||||
|
tag={tag}
|
||||||
|
tagStats={tagsList.stats[tag]}
|
||||||
|
selectedServer={selectedServer}
|
||||||
|
displayed={displayedTag === tag}
|
||||||
|
toggle={() => setDisplayedTag(displayedTag !== tag ? tag : undefined)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</Row>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -1,5 +1,4 @@
|
|||||||
import { FC, useEffect, useState } from 'react';
|
import { FC, useEffect, useState } from 'react';
|
||||||
import { splitEvery } from 'ramda';
|
|
||||||
import { Row } from 'reactstrap';
|
import { Row } from 'reactstrap';
|
||||||
import Message from '../utils/Message';
|
import Message from '../utils/Message';
|
||||||
import SearchField from '../utils/SearchField';
|
import SearchField from '../utils/SearchField';
|
||||||
@ -9,10 +8,8 @@ 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 { TagsList as TagsListState } from './reducers/tagsList';
|
import { TagsList as TagsListState } from './reducers/tagsList';
|
||||||
import { TagCardProps } from './TagCard';
|
import { TagsListChildrenProps } from './data/TagsListChildrenProps';
|
||||||
|
import { TagsMode, TagsModeDropdown } from './TagsModeDropdown';
|
||||||
const { ceil } = Math;
|
|
||||||
const TAGS_GROUPS_AMOUNT = 4;
|
|
||||||
|
|
||||||
export interface TagsListProps {
|
export interface TagsListProps {
|
||||||
filterTags: (searchTerm: string) => void;
|
filterTags: (searchTerm: string) => void;
|
||||||
@ -21,10 +18,10 @@ export interface TagsListProps {
|
|||||||
selectedServer: SelectedServer;
|
selectedServer: SelectedServer;
|
||||||
}
|
}
|
||||||
|
|
||||||
const TagsList = (TagCard: FC<TagCardProps>) => boundToMercureHub((
|
const TagsList = (TagsCards: FC<TagsListChildrenProps>, TagsTable: FC<TagsListChildrenProps>) => boundToMercureHub((
|
||||||
{ filterTags, forceListTags, tagsList, selectedServer }: TagsListProps,
|
{ filterTags, forceListTags, tagsList, selectedServer }: TagsListProps,
|
||||||
) => {
|
) => {
|
||||||
const [ displayedTag, setDisplayedTag ] = useState<string | undefined>();
|
const [ mode, setMode ] = useState<TagsMode>('cards');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
forceListTags();
|
forceListTags();
|
||||||
@ -43,37 +40,23 @@ const TagsList = (TagCard: FC<TagCardProps>) => boundToMercureHub((
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const tagsCount = tagsList.filteredTags.length;
|
if (tagsList.filteredTags.length < 1) {
|
||||||
|
|
||||||
if (tagsCount < 1) {
|
|
||||||
return <Message>No tags found</Message>;
|
return <Message>No tags found</Message>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const tagsGroups = splitEvery(ceil(tagsCount / TAGS_GROUPS_AMOUNT), tagsList.filteredTags);
|
return mode === 'cards'
|
||||||
|
? <TagsCards tagsList={tagsList} selectedServer={selectedServer} />
|
||||||
return (
|
: <TagsTable tagsList={tagsList} selectedServer={selectedServer} />;
|
||||||
<Row>
|
|
||||||
{tagsGroups.map((group, index) => (
|
|
||||||
<div key={index} className="col-md-6 col-xl-3">
|
|
||||||
{group.map((tag) => (
|
|
||||||
<TagCard
|
|
||||||
key={tag}
|
|
||||||
tag={tag}
|
|
||||||
tagStats={tagsList.stats[tag]}
|
|
||||||
selectedServer={selectedServer}
|
|
||||||
displayed={displayedTag === tag}
|
|
||||||
toggle={() => setDisplayedTag(displayedTag !== tag ? tag : undefined)}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</Row>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<SearchField className="mb-3" onChange={filterTags} />
|
<SearchField className="mb-3" onChange={filterTags} />
|
||||||
|
<Row className="mb-3">
|
||||||
|
<div className="col-lg-6 offset-lg-6">
|
||||||
|
<TagsModeDropdown mode={mode} onChange={setMode} />
|
||||||
|
</div>
|
||||||
|
</Row>
|
||||||
{renderContent()}
|
{renderContent()}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
7
src/tags/data/TagsListChildrenProps.ts
Normal file
7
src/tags/data/TagsListChildrenProps.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { TagsList as TagsListState } from '../reducers/tagsList';
|
||||||
|
import { SelectedServer } from '../../servers/data';
|
||||||
|
|
||||||
|
export interface TagsListChildrenProps {
|
||||||
|
tagsList: TagsListState;
|
||||||
|
selectedServer: SelectedServer;
|
||||||
|
}
|
||||||
@ -8,6 +8,9 @@ import { filterTags, listTags } from '../reducers/tagsList';
|
|||||||
import { deleteTag, tagDeleted } from '../reducers/tagDelete';
|
import { deleteTag, tagDeleted } from '../reducers/tagDelete';
|
||||||
import { editTag, tagEdited } from '../reducers/tagEdit';
|
import { editTag, tagEdited } from '../reducers/tagEdit';
|
||||||
import { ConnectDecorator } from '../../container/types';
|
import { ConnectDecorator } from '../../container/types';
|
||||||
|
import { TagsCards } from '../TagsCards';
|
||||||
|
import { TagsTable } from '../TagsTable';
|
||||||
|
import { TagsTableRow } from '../TagsTableRow';
|
||||||
|
|
||||||
const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
|
const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
|
||||||
// Components
|
// Components
|
||||||
@ -29,7 +32,11 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
|
|||||||
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('TagsList', TagsList, 'TagCard');
|
bottle.serviceFactory('TagsCards', TagsCards, 'TagCard');
|
||||||
|
bottle.serviceFactory('TagsTableRow', TagsTableRow, 'DeleteTagConfirmModal', 'EditTagModal');
|
||||||
|
bottle.serviceFactory('TagsTable', TagsTable, 'ColorGenerator', 'TagsTableRow');
|
||||||
|
|
||||||
|
bottle.serviceFactory('TagsList', TagsList, 'TagsCards', 'TagsTable');
|
||||||
bottle.decorator('TagsList', connect(
|
bottle.decorator('TagsList', connect(
|
||||||
[ 'tagsList', 'selectedServer', 'mercureInfo' ],
|
[ 'tagsList', 'selectedServer', 'mercureInfo' ],
|
||||||
[ 'forceListTags', 'filterTags', 'createNewVisits', 'loadMercureInfo' ],
|
[ 'forceListTags', 'filterTags', 'createNewVisits', 'loadMercureInfo' ],
|
||||||
|
|||||||
37
test/tags/TagsCards.test.tsx
Normal file
37
test/tags/TagsCards.test.tsx
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { shallow, ShallowWrapper } from 'enzyme';
|
||||||
|
import { Mock } from 'ts-mockery';
|
||||||
|
import { TagsCards as createTagsCards } from '../../src/tags/TagsCards';
|
||||||
|
import { TagsList } from '../../src/tags/reducers/tagsList';
|
||||||
|
import { SelectedServer } from '../../src/servers/data';
|
||||||
|
import { rangeOf } from '../../src/utils/utils';
|
||||||
|
|
||||||
|
describe('<TagsCards />', () => {
|
||||||
|
const amountOfTags = 10;
|
||||||
|
const tagsList = Mock.of<TagsList>({ filteredTags: rangeOf(amountOfTags, (i) => `tag_${i}`), stats: {} });
|
||||||
|
const TagCard = () => null;
|
||||||
|
const TagsCards = createTagsCards(TagCard);
|
||||||
|
let wrapper: ShallowWrapper;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
wrapper = shallow(<TagsCards tagsList={tagsList} selectedServer={Mock.all<SelectedServer>()} />);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => wrapper?.unmount());
|
||||||
|
|
||||||
|
it('renders the proper amount of groups and cards based on the amount of tags', () => {
|
||||||
|
const amountOfGroups = 4;
|
||||||
|
const cards = wrapper.find(TagCard);
|
||||||
|
const groups = wrapper.find('.col-md-6');
|
||||||
|
|
||||||
|
expect(cards).toHaveLength(amountOfTags);
|
||||||
|
expect(groups).toHaveLength(amountOfGroups);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('displays card on toggle', () => {
|
||||||
|
const card = () => wrapper.find(TagCard).at(5);
|
||||||
|
|
||||||
|
expect(card().prop('displayed')).toEqual(false);
|
||||||
|
(card().prop('toggle') as Function)(); // eslint-disable-line @typescript-eslint/no-unnecessary-type-assertion
|
||||||
|
expect(card().prop('displayed')).toEqual(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -3,19 +3,19 @@ import { identity } from 'ramda';
|
|||||||
import { Mock } from 'ts-mockery';
|
import { Mock } from 'ts-mockery';
|
||||||
import createTagsList, { TagsListProps } from '../../src/tags/TagsList';
|
import createTagsList, { TagsListProps } from '../../src/tags/TagsList';
|
||||||
import Message from '../../src/utils/Message';
|
import Message from '../../src/utils/Message';
|
||||||
import SearchField from '../../src/utils/SearchField';
|
|
||||||
import { rangeOf } from '../../src/utils/utils';
|
|
||||||
import { TagsList } from '../../src/tags/reducers/tagsList';
|
import { TagsList } from '../../src/tags/reducers/tagsList';
|
||||||
import { MercureBoundProps } from '../../src/mercure/helpers/boundToMercureHub';
|
import { MercureBoundProps } from '../../src/mercure/helpers/boundToMercureHub';
|
||||||
import { Result } from '../../src/utils/Result';
|
import { Result } from '../../src/utils/Result';
|
||||||
|
import { TagsModeDropdown } from '../../src/tags/TagsModeDropdown';
|
||||||
|
import SearchField from '../../src/utils/SearchField';
|
||||||
|
|
||||||
describe('<TagsList />', () => {
|
describe('<TagsList />', () => {
|
||||||
let wrapper: ShallowWrapper;
|
let wrapper: ShallowWrapper;
|
||||||
const filterTags = jest.fn();
|
const filterTags = jest.fn();
|
||||||
const TagCard = () => null;
|
const TagsCards = () => null;
|
||||||
|
const TagsTable = () => null;
|
||||||
|
const TagsListComp = createTagsList(TagsCards, TagsTable);
|
||||||
const createWrapper = (tagsList: Partial<TagsList>) => {
|
const createWrapper = (tagsList: Partial<TagsList>) => {
|
||||||
const TagsListComp = createTagsList(TagCard);
|
|
||||||
|
|
||||||
wrapper = shallow(
|
wrapper = shallow(
|
||||||
<TagsListComp
|
<TagsListComp
|
||||||
{...Mock.all<TagsListProps>()}
|
{...Mock.all<TagsListProps>()}
|
||||||
@ -56,28 +56,23 @@ describe('<TagsList />', () => {
|
|||||||
expect(msg.html()).toContain('No tags found');
|
expect(msg.html()).toContain('No tags found');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders the proper amount of groups and cards based on the amount of tags', () => {
|
it('renders proper component based on the display mode', () => {
|
||||||
const amountOfTags = 10;
|
const wrapper = createWrapper({ filteredTags: [ 'foo', 'bar' ], stats: {} });
|
||||||
const amountOfGroups = 4;
|
|
||||||
const wrapper = createWrapper({ filteredTags: rangeOf(amountOfTags, (i) => `tag_${i}`), stats: {} });
|
|
||||||
const cards = wrapper.find(TagCard);
|
|
||||||
const groups = wrapper.find('.col-md-6');
|
|
||||||
|
|
||||||
expect(cards).toHaveLength(amountOfTags);
|
expect(wrapper.find(TagsCards)).toHaveLength(1);
|
||||||
expect(groups).toHaveLength(amountOfGroups);
|
expect(wrapper.find(TagsTable)).toHaveLength(0);
|
||||||
|
|
||||||
|
wrapper.find(TagsModeDropdown).simulate('change');
|
||||||
|
|
||||||
|
expect(wrapper.find(TagsCards)).toHaveLength(0);
|
||||||
|
expect(wrapper.find(TagsTable)).toHaveLength(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('triggers tags filtering when search field changes', (done) => {
|
it('triggers tags filtering when search field changes', () => {
|
||||||
const wrapper = createWrapper({ filteredTags: [] });
|
const wrapper = createWrapper({ filteredTags: [] });
|
||||||
const searchField = wrapper.find(SearchField);
|
|
||||||
|
|
||||||
expect(searchField).toHaveLength(1);
|
|
||||||
expect(filterTags).not.toHaveBeenCalled();
|
expect(filterTags).not.toHaveBeenCalled();
|
||||||
searchField.simulate('change');
|
wrapper.find(SearchField).simulate('change');
|
||||||
|
expect(filterTags).toHaveBeenCalledTimes(1);
|
||||||
setImmediate(() => {
|
|
||||||
expect(filterTags).toHaveBeenCalledTimes(1);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user