From e837ee52254e3e313933bd25e414e79f229b86d5 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 2 May 2022 18:47:18 +0200 Subject: [PATCH 1/7] Migrated EditServer test from enzyme to react testing library --- src/servers/helpers/ServerForm.tsx | 2 +- test/servers/EditServer.test.tsx | 55 ++++++++++++++++++++---------- 2 files changed, 38 insertions(+), 19 deletions(-) diff --git a/src/servers/helpers/ServerForm.tsx b/src/servers/helpers/ServerForm.tsx index f13b5b4a..5c781069 100644 --- a/src/servers/helpers/ServerForm.tsx +++ b/src/servers/helpers/ServerForm.tsx @@ -23,7 +23,7 @@ export const ServerForm: FC = ({ onSubmit, initialValues, child }, [initialValues]); return ( -
+ Name URL diff --git a/test/servers/EditServer.test.tsx b/test/servers/EditServer.test.tsx index abd60589..818578bc 100644 --- a/test/servers/EditServer.test.tsx +++ b/test/servers/EditServer.test.tsx @@ -1,46 +1,65 @@ -import { mount, ReactWrapper } from 'enzyme'; +import { fireEvent, render, screen } from '@testing-library/react'; import { Mock } from 'ts-mockery'; import { useNavigate } from 'react-router-dom'; import { EditServer as editServerConstruct } from '../../src/servers/EditServer'; -import { ServerForm } from '../../src/servers/helpers/ServerForm'; -import { ReachableServer } from '../../src/servers/data'; +import { ReachableServer, SelectedServer } from '../../src/servers/data'; jest.mock('react-router-dom', () => ({ ...jest.requireActual('react-router-dom'), useNavigate: jest.fn() })); describe('', () => { - let wrapper: ReactWrapper; const ServerError = jest.fn(); const editServerMock = jest.fn(); const navigate = jest.fn(); - const selectedServer = Mock.of({ + const defaultSelectedServer = Mock.of({ id: 'abc123', - name: 'name', - url: 'url', - apiKey: 'apiKey', + name: 'the_name', + url: 'the_url', + apiKey: 'the_api_key', }); const EditServer = editServerConstruct(ServerError); + const setUp = (selectedServer: SelectedServer = defaultSelectedServer) => render( + , + ); beforeEach(() => { (useNavigate as any).mockReturnValue(navigate); - - wrapper = mount( - , - ); }); afterEach(jest.clearAllMocks); - afterEach(() => wrapper?.unmount()); - it('renders components', () => { - expect(wrapper.find(ServerForm)).toHaveLength(1); + it('renders nothing if selected server is not reachable', () => { + setUp(Mock.all()); + + expect(screen.queryByText('Edit')).not.toBeInTheDocument(); + expect(screen.queryByText('Cancel')).not.toBeInTheDocument(); + expect(screen.queryByText('Save')).not.toBeInTheDocument(); + }); + + it('renders server title', () => { + setUp(); + expect(screen.getByText(`Edit "${defaultSelectedServer.name}"`)).toBeInTheDocument(); + }); + + it('display the server info in the form components', () => { + setUp(); + + expect(screen.getByDisplayValue('the_name')).toBeInTheDocument(); + expect(screen.getByDisplayValue('the_url')).toBeInTheDocument(); + expect(screen.getByDisplayValue('the_api_key')).toBeInTheDocument(); }); it('edits server and redirects to it when form is submitted', () => { - const form = wrapper.find(ServerForm); + setUp(); - form.simulate('submit', {}); + fireEvent.change(screen.getByDisplayValue('the_name'), { target: { value: 'the_new_name' } }); + fireEvent.change(screen.getByDisplayValue('the_url'), { target: { value: 'the_new_url' } }); + fireEvent.submit(screen.getByRole('form')); - expect(editServerMock).toHaveBeenCalledTimes(1); + expect(editServerMock).toHaveBeenCalledWith('abc123', { + name: 'the_new_name', + url: 'the_new_url', + apiKey: 'the_api_key', + }); expect(navigate).toHaveBeenCalledWith(-1); }); }); From c00aaa90180154b0e8c8ddc3644f725fd41b7215 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 2 May 2022 18:54:19 +0200 Subject: [PATCH 2/7] Migrated UseExistingIfFoundInfoIcon test from enzyme to react testing library --- src/short-urls/ShortUrlForm.tsx | 2 +- src/short-urls/UseExistingIfFoundInfoIcon.tsx | 4 +--- .../UseExistingIfFoundInfoIcon.test.tsx | 24 ++++++------------- 3 files changed, 9 insertions(+), 21 deletions(-) diff --git a/src/short-urls/ShortUrlForm.tsx b/src/short-urls/ShortUrlForm.tsx index c6b9800d..23406833 100644 --- a/src/short-urls/ShortUrlForm.tsx +++ b/src/short-urls/ShortUrlForm.tsx @@ -12,7 +12,7 @@ import { SelectedServer } from '../servers/data'; import { TagsSelectorProps } from '../tags/helpers/TagsSelector'; import { DomainSelectorProps } from '../domains/DomainSelector'; import { formatIsoDate } from '../utils/helpers/date'; -import UseExistingIfFoundInfoIcon from './UseExistingIfFoundInfoIcon'; +import { UseExistingIfFoundInfoIcon } from './UseExistingIfFoundInfoIcon'; import { ShortUrlData } from './data'; import { ShortUrlFormCheckboxGroup } from './helpers/ShortUrlFormCheckboxGroup'; import './ShortUrlForm.scss'; diff --git a/src/short-urls/UseExistingIfFoundInfoIcon.tsx b/src/short-urls/UseExistingIfFoundInfoIcon.tsx index 6024c810..2732247e 100644 --- a/src/short-urls/UseExistingIfFoundInfoIcon.tsx +++ b/src/short-urls/UseExistingIfFoundInfoIcon.tsx @@ -36,7 +36,7 @@ const InfoModal = ({ isOpen, toggle }: { isOpen: boolean; toggle: () => void }) ); -const UseExistingIfFoundInfoIcon = () => { +export const UseExistingIfFoundInfoIcon = () => { const [isModalOpen, toggleModal] = useToggle(); return ( @@ -48,5 +48,3 @@ const UseExistingIfFoundInfoIcon = () => { ); }; - -export default UseExistingIfFoundInfoIcon; diff --git a/test/short-urls/UseExistingIfFoundInfoIcon.test.tsx b/test/short-urls/UseExistingIfFoundInfoIcon.test.tsx index 354bd4d9..86a792c7 100644 --- a/test/short-urls/UseExistingIfFoundInfoIcon.test.tsx +++ b/test/short-urls/UseExistingIfFoundInfoIcon.test.tsx @@ -1,22 +1,12 @@ -import { mount, ReactWrapper } from 'enzyme'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { Modal } from 'reactstrap'; -import UseExistingIfFoundInfoIcon from '../../src/short-urls/UseExistingIfFoundInfoIcon'; +import { fireEvent, render, screen } from '@testing-library/react'; +import { UseExistingIfFoundInfoIcon } from '../../src/short-urls/UseExistingIfFoundInfoIcon'; describe('', () => { - let wrapped: ReactWrapper; + it('shows modal when icon is clicked', async () => { + render(); - beforeEach(() => { - wrapped = mount(); - }); - - afterEach(() => wrapped.unmount()); - - it('shows modal when icon is clicked', () => { - const icon = wrapped.find(FontAwesomeIcon); - - expect(wrapped.find(Modal).prop('isOpen')).toEqual(false); - icon.simulate('click'); - expect(wrapped.find(Modal).prop('isOpen')).toEqual(true); + expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); + fireEvent.click(screen.getByTitle('What does this mean?').firstChild as Node); + expect(await screen.findByRole('dialog')).toBeInTheDocument(); }); }); From 0de8eb1568fd789b4ab78cb0425de92743fed059 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 2 May 2022 18:58:57 +0200 Subject: [PATCH 3/7] Migrated ForServerVersion test from enzyme to react testing library --- src/servers/helpers/ForServerVersion.tsx | 6 +-- src/servers/services/provideServices.ts | 2 +- .../servers/helpers/ForServerVersion.test.tsx | 37 +++++++------------ 3 files changed, 17 insertions(+), 28 deletions(-) diff --git a/src/servers/helpers/ForServerVersion.tsx b/src/servers/helpers/ForServerVersion.tsx index abda57f2..f7c8534c 100644 --- a/src/servers/helpers/ForServerVersion.tsx +++ b/src/servers/helpers/ForServerVersion.tsx @@ -8,7 +8,9 @@ interface ForServerVersionConnectProps extends ForServerVersionProps { selectedServer: SelectedServer; } -const ForServerVersion: FC = ({ minVersion, maxVersion, selectedServer, children }) => { +export const ForServerVersion: FC = ( + { minVersion, maxVersion, selectedServer, children }, +) => { if (!isReachableServer(selectedServer)) { return null; } @@ -22,5 +24,3 @@ const ForServerVersion: FC = ({ minVersion, maxVer return <>{children}; }; - -export default ForServerVersion; diff --git a/src/servers/services/provideServices.ts b/src/servers/services/provideServices.ts index a4cbd9db..52c114ec 100644 --- a/src/servers/services/provideServices.ts +++ b/src/servers/services/provideServices.ts @@ -8,7 +8,7 @@ import ImportServersBtn from '../helpers/ImportServersBtn'; import { resetSelectedServer, selectServer } from '../reducers/selectedServer'; import { createServer, createServers, deleteServer, editServer, setAutoConnect } from '../reducers/servers'; import { fetchServers } from '../reducers/remoteServers'; -import ForServerVersion from '../helpers/ForServerVersion'; +import { ForServerVersion } from '../helpers/ForServerVersion'; import { ServerError } from '../helpers/ServerError'; import { ConnectDecorator } from '../../container/types'; import { withoutSelectedServer } from '../helpers/withoutSelectedServer'; diff --git a/test/servers/helpers/ForServerVersion.test.tsx b/test/servers/helpers/ForServerVersion.test.tsx index 2df01db2..d8865544 100644 --- a/test/servers/helpers/ForServerVersion.test.tsx +++ b/test/servers/helpers/ForServerVersion.test.tsx @@ -1,28 +1,19 @@ -import { mount, ReactWrapper } from 'enzyme'; +import { render, screen } from '@testing-library/react'; import { Mock } from 'ts-mockery'; -import ForServerVersion from '../../../src/servers/helpers/ForServerVersion'; +import { ForServerVersion } from '../../../src/servers/helpers/ForServerVersion'; import { ReachableServer, SelectedServer } from '../../../src/servers/data'; import { SemVer, SemVerPattern } from '../../../src/utils/helpers/version'; describe('', () => { - let wrapped: ReactWrapper; - - const renderComponent = (selectedServer: SelectedServer, minVersion?: SemVerPattern, maxVersion?: SemVerPattern) => { - wrapped = mount( - - Hello - , - ); - - return wrapped; - }; - - afterEach(() => wrapped?.unmount()); + const setUp = (selectedServer: SelectedServer, minVersion?: SemVerPattern, maxVersion?: SemVerPattern) => render( + + Hello + , + ); it('does not render children when current server is empty', () => { - const wrapped = renderComponent(null, '1.*.*'); - - expect(wrapped.html()).toBeNull(); + setUp(null, '1.*.*'); + expect(screen.queryByText('Hello')).not.toBeInTheDocument(); }); it.each([ @@ -30,9 +21,8 @@ describe('', () => { [undefined, '1.8.0' as SemVerPattern, '1.8.3' as SemVer], ['1.7.0' as SemVerPattern, '1.8.0' as SemVerPattern, '1.8.3' as SemVer], ])('does not render children when current version does not match requirements', (min, max, version) => { - const wrapped = renderComponent(Mock.of({ version, printableVersion: version }), min, max); - - expect(wrapped.html()).toBeNull(); + setUp(Mock.of({ version, printableVersion: version }), min, max); + expect(screen.queryByText('Hello')).not.toBeInTheDocument(); }); it.each([ @@ -42,8 +32,7 @@ describe('', () => { [undefined, '1.8.0' as SemVerPattern, '1.7.1' as SemVer], ['1.7.0' as SemVerPattern, '1.8.0' as SemVerPattern, '1.7.3' as SemVer], ])('renders children when current version matches requirements', (min, max, version) => { - const wrapped = renderComponent(Mock.of({ version, printableVersion: version }), min, max); - - expect(wrapped.html()).toContain('Hello'); + setUp(Mock.of({ version, printableVersion: version }), min, max); + expect(screen.queryByText('Hello')).toBeInTheDocument(); }); }); From 8918b1ac963c547d185cb73f9831760012abf474 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 2 May 2022 19:19:47 +0200 Subject: [PATCH 4/7] Migrated Overview test from enzyme to react testing library --- src/servers/Overview.tsx | 9 +-- src/servers/services/provideServices.ts | 2 +- test/servers/Overview.test.tsx | 101 +++++++++++------------- 3 files changed, 49 insertions(+), 63 deletions(-) diff --git a/src/servers/Overview.tsx b/src/servers/Overview.tsx index e9ddec8b..6b22a17c 100644 --- a/src/servers/Overview.tsx +++ b/src/servers/Overview.tsx @@ -13,7 +13,6 @@ import { ShlinkShortUrlsListParams } from '../api/types'; import { supportsNonOrphanVisits } from '../utils/helpers/features'; import { getServerId, SelectedServer } from './data'; import { HighlightCard } from './helpers/HighlightCard'; -import { ForServerVersionProps } from './helpers/ForServerVersion'; interface OverviewConnectProps { shortUrlsList: ShortUrlsListState; @@ -28,7 +27,6 @@ interface OverviewConnectProps { export const Overview = ( ShortUrlsTable: FC, CreateShortUrl: FC, - ForServerVersion: FC, ) => boundToMercureHub(({ shortUrlsList, listShortUrls, @@ -61,12 +59,7 @@ export const Overview = (
- - {loadingVisits ? 'Loading...' : prettify(orphanVisitsCount ?? 0)} - - - Shlink 2.6 is needed - + {loadingVisits ? 'Loading...' : prettify(orphanVisitsCount ?? 0)}
diff --git a/src/servers/services/provideServices.ts b/src/servers/services/provideServices.ts index 52c114ec..61c37cb1 100644 --- a/src/servers/services/provideServices.ts +++ b/src/servers/services/provideServices.ts @@ -61,7 +61,7 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator) => { bottle.serviceFactory('ServerError', ServerError, 'DeleteServerButton'); bottle.decorator('ServerError', connect(['servers', 'selectedServer'])); - bottle.serviceFactory('Overview', Overview, 'ShortUrlsTable', 'CreateShortUrl', 'ForServerVersion'); + bottle.serviceFactory('Overview', Overview, 'ShortUrlsTable', 'CreateShortUrl'); bottle.decorator('Overview', connect( ['shortUrlsList', 'tagsList', 'selectedServer', 'mercureInfo', 'visitsOverview'], ['listShortUrls', 'listTags', 'createNewVisits', 'loadMercureInfo', 'loadVisitsOverview'], diff --git a/test/servers/Overview.test.tsx b/test/servers/Overview.test.tsx index 28008507..2cf05347 100644 --- a/test/servers/Overview.test.tsx +++ b/test/servers/Overview.test.tsx @@ -1,7 +1,6 @@ -import { FC, PropsWithChildren } from 'react'; -import { mount, ReactWrapper } from 'enzyme'; +import { render, screen } from '@testing-library/react'; import { Mock } from 'ts-mockery'; -import { Link, MemoryRouter } from 'react-router-dom'; +import { MemoryRouter } from 'react-router-dom'; import { ShortUrlsList as ShortUrlsListState } from '../../src/short-urls/reducers/shortUrlsList'; import { Overview as overviewCreator } from '../../src/servers/Overview'; import { TagsList } from '../../src/tags/reducers/tagsList'; @@ -9,79 +8,73 @@ import { VisitsOverview } from '../../src/visits/reducers/visitsOverview'; import { MercureInfo } from '../../src/mercure/reducers/mercureInfo'; import { ReachableServer } from '../../src/servers/data'; import { prettify } from '../../src/utils/helpers/numbers'; -import { HighlightCard } from '../../src/servers/helpers/HighlightCard'; describe('', () => { - let wrapper: ReactWrapper; - const ShortUrlsTable = () => null; - const CreateShortUrl = () => null; - const ForServerVersion: FC> = ({ children }) => <>{children}; + const ShortUrlsTable = () => <>ShortUrlsTable; + const CreateShortUrl = () => <>CreateShortUrl; const listShortUrls = jest.fn(); const listTags = jest.fn(); const loadVisitsOverview = jest.fn(); - const Overview = overviewCreator(ShortUrlsTable, CreateShortUrl, ForServerVersion); + const Overview = overviewCreator(ShortUrlsTable, CreateShortUrl); const shortUrls = { pagination: { totalItems: 83710 }, }; const serverId = '123'; - const createWrapper = (loading = false) => { - wrapper = mount( - - ({ loading, shortUrls })} - tagsList={Mock.of({ loading, tags: ['foo', 'bar', 'baz'] })} - visitsOverview={Mock.of({ loading, visitsCount: 3456, orphanVisitsCount: 28 })} - selectedServer={Mock.of({ id: serverId })} - createNewVisits={jest.fn()} - loadMercureInfo={jest.fn()} - mercureInfo={Mock.all()} - /> - , - ); - - return wrapper; - }; - - afterEach(() => wrapper?.unmount()); + const setUp = (loading = false) => render( + + ({ loading, shortUrls })} + tagsList={Mock.of({ loading, tags: ['foo', 'bar', 'baz'] })} + visitsOverview={Mock.of({ loading, visitsCount: 3456, orphanVisitsCount: 28 })} + selectedServer={Mock.of({ id: serverId })} + createNewVisits={jest.fn()} + loadMercureInfo={jest.fn()} + mercureInfo={Mock.all()} + /> + , + ); it('displays loading messages when still loading', () => { - const wrapper = createWrapper(true); - const cards = wrapper.find(HighlightCard); - - expect(cards).toHaveLength(4); - cards.forEach((card) => expect(card.html()).toContain('Loading...')); + setUp(true); + expect(screen.getAllByText('Loading...')).toHaveLength(4); }); it('displays amounts in cards after finishing loading', () => { - const wrapper = createWrapper(); - const cards = wrapper.find(HighlightCard); + setUp(); - expect(cards).toHaveLength(4); - expect(cards.at(0).html()).toContain(prettify(3456)); - expect(cards.at(1).html()).toContain(prettify(28)); - expect(cards.at(2).html()).toContain(prettify(83710)); - expect(cards.at(3).html()).toContain(prettify(3)); + const headingElements = screen.getAllByRole('heading'); + + expect(screen.queryByText('Loading...')).not.toBeInTheDocument(); + expect(headingElements[0]).toHaveTextContent('Visits'); + expect(headingElements[1]).toHaveTextContent(prettify(3456)); + expect(headingElements[2]).toHaveTextContent('Orphan visits'); + expect(headingElements[3]).toHaveTextContent(prettify(28)); + expect(headingElements[4]).toHaveTextContent('Short URLs'); + expect(headingElements[5]).toHaveTextContent(prettify(83710)); + expect(headingElements[6]).toHaveTextContent('Tags'); + expect(headingElements[7]).toHaveTextContent(prettify(3)); }); - it('nests complex components', () => { - const wrapper = createWrapper(); + it('nests injected components', () => { + setUp(); - expect(wrapper.find(CreateShortUrl)).toHaveLength(1); - expect(wrapper.find(ShortUrlsTable)).toHaveLength(1); + expect(screen.queryByText('ShortUrlsTable')).toBeInTheDocument(); + expect(screen.queryByText('CreateShortUrl')).toBeInTheDocument(); }); it('displays links to other sections', () => { - const wrapper = createWrapper(); - const links = wrapper.find(Link); + setUp(); + + const links = screen.getAllByRole('link'); expect(links).toHaveLength(5); - expect(links.at(0).prop('to')).toEqual(`/server/${serverId}/orphan-visits`); - expect(links.at(1).prop('to')).toEqual(`/server/${serverId}/list-short-urls/1`); - expect(links.at(2).prop('to')).toEqual(`/server/${serverId}/manage-tags`); - expect(links.at(3).prop('to')).toEqual(`/server/${serverId}/create-short-url`); - expect(links.at(4).prop('to')).toEqual(`/server/${serverId}/list-short-urls/1`); + expect(links[0]).toHaveAttribute('href', `/server/${serverId}/orphan-visits`); + expect(links[1]).toHaveAttribute('href', `/server/${serverId}/list-short-urls/1`); + expect(links[2]).toHaveAttribute('href', `/server/${serverId}/manage-tags`); + expect(links[3]).toHaveAttribute('href', `/server/${serverId}/create-short-url`); + expect(links[4]).toHaveAttribute('href', `/server/${serverId}/list-short-urls/1`); }); }); From 677f1da8df6f765d3bb667e159806c733fa23e22 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 2 May 2022 19:24:57 +0200 Subject: [PATCH 5/7] Removed last occurrence of ForServerVersion component, replaced by feature check --- src/short-urls/helpers/QrCodeModal.tsx | 12 ++++++------ src/short-urls/services/provideServices.ts | 2 +- src/utils/helpers/features.ts | 1 + test/short-urls/helpers/QrCodeModal.test.tsx | 4 ++-- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/short-urls/helpers/QrCodeModal.tsx b/src/short-urls/helpers/QrCodeModal.tsx index a16d6654..8e07279c 100644 --- a/src/short-urls/helpers/QrCodeModal.tsx +++ b/src/short-urls/helpers/QrCodeModal.tsx @@ -1,4 +1,4 @@ -import { FC, useMemo, useState } from 'react'; +import { useMemo, useState } from 'react'; import { Modal, FormGroup, ModalBody, ModalHeader, Row, Button } from 'reactstrap'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faFileDownload as downloadIcon } from '@fortawesome/free-solid-svg-icons'; @@ -7,9 +7,8 @@ import { ShortUrlModalProps } from '../data'; import { SelectedServer } from '../../servers/data'; import { CopyToClipboardIcon } from '../../utils/CopyToClipboardIcon'; import { buildQrCodeUrl, QrCodeCapabilities, QrCodeFormat, QrErrorCorrection } from '../../utils/helpers/qrCodes'; -import { supportsQrErrorCorrection } from '../../utils/helpers/features'; +import { supportsNonRestCors, supportsQrErrorCorrection } from '../../utils/helpers/features'; import { ImageDownloader } from '../../common/services/ImageDownloader'; -import { ForServerVersionProps } from '../../servers/helpers/ForServerVersion'; import { QrFormatDropdown } from './qr-codes/QrFormatDropdown'; import { QrErrorCorrectionDropdown } from './qr-codes/QrErrorCorrectionDropdown'; import './QrCodeModal.scss'; @@ -18,7 +17,7 @@ interface QrCodeModalConnectProps extends ShortUrlModalProps { selectedServer: SelectedServer; } -const QrCodeModal = (imageDownloader: ImageDownloader, ForServerVersion: FC) => ( +const QrCodeModal = (imageDownloader: ImageDownloader) => ( { shortUrl: { shortUrl, shortCode }, toggle, isOpen, selectedServer }: QrCodeModalConnectProps, ) => { const [size, setSize] = useState(300); @@ -28,6 +27,7 @@ const QrCodeModal = (imageDownloader: ImageDownloader, ForServerVersion: FC ({ errorCorrectionIsSupported: supportsQrErrorCorrection(selectedServer), }), [selectedServer]); + const displayDownloadBtn = supportsNonRestCors(selectedServer); const willRenderThreeControls = !capabilities.errorCorrectionIsSupported; const qrCodeUrl = useMemo( () => buildQrCodeUrl(shortUrl, { size, format, margin, errorCorrection }, capabilities), @@ -89,7 +89,7 @@ const QrCodeModal = (imageDownloader: ImageDownloader, ForServerVersion: FC
QR code - + {displayDownloadBtn && (
-
+ )} diff --git a/src/short-urls/services/provideServices.ts b/src/short-urls/services/provideServices.ts index 052cb165..f895bb72 100644 --- a/src/short-urls/services/provideServices.ts +++ b/src/short-urls/services/provideServices.ts @@ -47,7 +47,7 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator) => { bottle.serviceFactory('DeleteShortUrlModal', () => DeleteShortUrlModal); bottle.decorator('DeleteShortUrlModal', connect(['shortUrlDeletion'], ['deleteShortUrl', 'resetDeleteShortUrl'])); - bottle.serviceFactory('QrCodeModal', QrCodeModal, 'ImageDownloader', 'ForServerVersion'); + bottle.serviceFactory('QrCodeModal', QrCodeModal, 'ImageDownloader'); bottle.decorator('QrCodeModal', connect(['selectedServer'])); bottle.serviceFactory('ShortUrlsFilteringBar', ShortUrlsFilteringBar, 'ColorGenerator', 'ExportShortUrlsBtn'); diff --git a/src/utils/helpers/features.ts b/src/utils/helpers/features.ts index 20b3d589..e3fbbd12 100644 --- a/src/utils/helpers/features.ts +++ b/src/utils/helpers/features.ts @@ -9,6 +9,7 @@ export const supportsCrawlableVisits = supportsBotVisits; export const supportsQrErrorCorrection = serverMatchesMinVersion('2.8.0'); export const supportsDomainRedirects = supportsQrErrorCorrection; export const supportsForwardQuery = serverMatchesMinVersion('2.9.0'); +export const supportsNonRestCors = supportsForwardQuery; export const supportsDefaultDomainRedirectsEdition = serverMatchesMinVersion('2.10.0'); export const supportsNonOrphanVisits = serverMatchesMinVersion('3.0.0'); export const supportsAllTagsFiltering = supportsNonOrphanVisits; diff --git a/test/short-urls/helpers/QrCodeModal.test.tsx b/test/short-urls/helpers/QrCodeModal.test.tsx index 44c7cd57..fad659dc 100644 --- a/test/short-urls/helpers/QrCodeModal.test.tsx +++ b/test/short-urls/helpers/QrCodeModal.test.tsx @@ -14,7 +14,7 @@ import { QrErrorCorrectionDropdown } from '../../../src/short-urls/helpers/qr-co describe('', () => { let wrapper: ShallowWrapper; const saveImage = jest.fn().mockReturnValue(Promise.resolve()); - const QrCodeModal = createQrCodeModal(Mock.of({ saveImage }), () => null); + const QrCodeModal = createQrCodeModal(Mock.of({ saveImage })); const shortUrl = 'https://doma.in/abc123'; const createWrapper = (version: SemVer = '2.6.0') => { const selectedServer = Mock.of({ version }); @@ -99,7 +99,7 @@ describe('', () => { }); it('saves the QR code image when clicking the Download button', () => { - const wrapper = createWrapper(); + const wrapper = createWrapper('2.9.0'); const downloadBtn = wrapper.find(Button); expect(saveImage).not.toHaveBeenCalled(); From 337bfc47c19282f11e7b9abe0d6c72510db275f8 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 2 May 2022 19:28:07 +0200 Subject: [PATCH 6/7] Deleted ForServerVersion component, which is no longer used --- src/api/types/index.ts | 2 +- src/servers/Overview.tsx | 2 +- src/servers/helpers/ForServerVersion.tsx | 26 ------------- src/servers/services/provideServices.ts | 4 -- src/visits/reducers/visitsOverview.ts | 2 +- .../servers/helpers/ForServerVersion.test.tsx | 38 ------------------- 6 files changed, 3 insertions(+), 71 deletions(-) delete mode 100644 src/servers/helpers/ForServerVersion.tsx delete mode 100644 test/servers/helpers/ForServerVersion.test.tsx diff --git a/src/api/types/index.ts b/src/api/types/index.ts index ce94d65d..126750bd 100644 --- a/src/api/types/index.ts +++ b/src/api/types/index.ts @@ -46,7 +46,7 @@ export interface ShlinkVisits { export interface ShlinkVisitsOverview { visitsCount: number; - orphanVisitsCount?: number; // Optional only for versions older than 2.6.0 + orphanVisitsCount: number; } export interface ShlinkVisitsParams { diff --git a/src/servers/Overview.tsx b/src/servers/Overview.tsx index 6b22a17c..8903709d 100644 --- a/src/servers/Overview.tsx +++ b/src/servers/Overview.tsx @@ -59,7 +59,7 @@ export const Overview = (
- {loadingVisits ? 'Loading...' : prettify(orphanVisitsCount ?? 0)} + {loadingVisits ? 'Loading...' : prettify(orphanVisitsCount)}
diff --git a/src/servers/helpers/ForServerVersion.tsx b/src/servers/helpers/ForServerVersion.tsx deleted file mode 100644 index f7c8534c..00000000 --- a/src/servers/helpers/ForServerVersion.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { FC, PropsWithChildren } from 'react'; -import { versionMatch, Versions } from '../../utils/helpers/version'; -import { isReachableServer, SelectedServer } from '../data'; - -export type ForServerVersionProps = PropsWithChildren; - -interface ForServerVersionConnectProps extends ForServerVersionProps { - selectedServer: SelectedServer; -} - -export const ForServerVersion: FC = ( - { minVersion, maxVersion, selectedServer, children }, -) => { - if (!isReachableServer(selectedServer)) { - return null; - } - - const { version } = selectedServer; - const matchesVersion = versionMatch(version, { maxVersion, minVersion }); - - if (!matchesVersion) { - return null; - } - - return <>{children}; -}; diff --git a/src/servers/services/provideServices.ts b/src/servers/services/provideServices.ts index 61c37cb1..fcb04a46 100644 --- a/src/servers/services/provideServices.ts +++ b/src/servers/services/provideServices.ts @@ -8,7 +8,6 @@ import ImportServersBtn from '../helpers/ImportServersBtn'; import { resetSelectedServer, selectServer } from '../reducers/selectedServer'; import { createServer, createServers, deleteServer, editServer, setAutoConnect } from '../reducers/servers'; import { fetchServers } from '../reducers/remoteServers'; -import { ForServerVersion } from '../helpers/ForServerVersion'; import { ServerError } from '../helpers/ServerError'; import { ConnectDecorator } from '../../container/types'; import { withoutSelectedServer } from '../helpers/withoutSelectedServer'; @@ -55,9 +54,6 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator) => { bottle.serviceFactory('ImportServersBtn', ImportServersBtn, 'ServersImporter'); bottle.decorator('ImportServersBtn', connect(['servers'], ['createServers'])); - bottle.serviceFactory('ForServerVersion', () => ForServerVersion); - bottle.decorator('ForServerVersion', connect(['selectedServer'])); - bottle.serviceFactory('ServerError', ServerError, 'DeleteServerButton'); bottle.decorator('ServerError', connect(['servers', 'selectedServer'])); diff --git a/src/visits/reducers/visitsOverview.ts b/src/visits/reducers/visitsOverview.ts index 9c1609b7..ec5d8fec 100644 --- a/src/visits/reducers/visitsOverview.ts +++ b/src/visits/reducers/visitsOverview.ts @@ -12,7 +12,7 @@ export const GET_OVERVIEW = 'shlink/visitsOverview/GET_OVERVIEW'; export interface VisitsOverview { visitsCount: number; - orphanVisitsCount?: number; + orphanVisitsCount: number; loading: boolean; error: boolean; } diff --git a/test/servers/helpers/ForServerVersion.test.tsx b/test/servers/helpers/ForServerVersion.test.tsx deleted file mode 100644 index d8865544..00000000 --- a/test/servers/helpers/ForServerVersion.test.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { render, screen } from '@testing-library/react'; -import { Mock } from 'ts-mockery'; -import { ForServerVersion } from '../../../src/servers/helpers/ForServerVersion'; -import { ReachableServer, SelectedServer } from '../../../src/servers/data'; -import { SemVer, SemVerPattern } from '../../../src/utils/helpers/version'; - -describe('', () => { - const setUp = (selectedServer: SelectedServer, minVersion?: SemVerPattern, maxVersion?: SemVerPattern) => render( - - Hello - , - ); - - it('does not render children when current server is empty', () => { - setUp(null, '1.*.*'); - expect(screen.queryByText('Hello')).not.toBeInTheDocument(); - }); - - it.each([ - ['2.0.0' as SemVerPattern, undefined, '1.8.3' as SemVer], - [undefined, '1.8.0' as SemVerPattern, '1.8.3' as SemVer], - ['1.7.0' as SemVerPattern, '1.8.0' as SemVerPattern, '1.8.3' as SemVer], - ])('does not render children when current version does not match requirements', (min, max, version) => { - setUp(Mock.of({ version, printableVersion: version }), min, max); - expect(screen.queryByText('Hello')).not.toBeInTheDocument(); - }); - - it.each([ - ['2.0.0' as SemVerPattern, undefined, '2.8.3' as SemVer], - ['2.0.0' as SemVerPattern, undefined, '2.0.0' as SemVer], - [undefined, '1.8.0' as SemVerPattern, '1.8.0' as SemVer], - [undefined, '1.8.0' as SemVerPattern, '1.7.1' as SemVer], - ['1.7.0' as SemVerPattern, '1.8.0' as SemVerPattern, '1.7.3' as SemVer], - ])('renders children when current version matches requirements', (min, max, version) => { - setUp(Mock.of({ version, printableVersion: version }), min, max); - expect(screen.queryByText('Hello')).toBeInTheDocument(); - }); -}); From e8a5eadd2beb4ca5aac2c3cfd4c2539da8c9ff54 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 2 May 2022 19:34:33 +0200 Subject: [PATCH 7/7] Migrated ShlinkApiError test from enzyme to react testing library --- test/api/ShlinkApiError.test.tsx | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/test/api/ShlinkApiError.test.tsx b/test/api/ShlinkApiError.test.tsx index aad32f72..fe04fe47 100644 --- a/test/api/ShlinkApiError.test.tsx +++ b/test/api/ShlinkApiError.test.tsx @@ -1,26 +1,20 @@ -import { shallow, ShallowWrapper } from 'enzyme'; +import { render, screen } from '@testing-library/react'; import { Mock } from 'ts-mockery'; import { ShlinkApiError, ShlinkApiErrorProps } from '../../src/api/ShlinkApiError'; import { InvalidArgumentError, ProblemDetailsError } from '../../src/api/types'; describe('', () => { - let commonWrapper: ShallowWrapper; - const createWrapper = (props: ShlinkApiErrorProps) => { - commonWrapper = shallow(); - - return commonWrapper; - }; - - afterEach(() => commonWrapper?.unmount()); + const setUp = (props: ShlinkApiErrorProps) => render(); it.each([ [undefined, 'the fallback', 'the fallback'], [Mock.all(), 'the fallback', 'the fallback'], [Mock.of({ detail: 'the detail' }), 'the fallback', 'the detail'], ])('renders proper message', (errorData, fallbackMessage, expectedMessage) => { - const wrapper = createWrapper({ errorData, fallbackMessage }); + const { container } = setUp({ errorData, fallbackMessage }); - expect(wrapper.text()).toContain(expectedMessage); + expect(container.firstChild).toHaveTextContent(expectedMessage); + expect(screen.queryByRole('paragraph')).not.toBeInTheDocument(); }); it.each([ @@ -28,9 +22,7 @@ describe('', () => { [Mock.all(), 0], [Mock.of({ type: 'INVALID_ARGUMENT', invalidElements: [] }), 1], ])('renders list of invalid elements when provided error is an InvalidError', (errorData, expectedElementsCount) => { - const wrapper = createWrapper({ errorData }); - const p = wrapper.find('p'); - - expect(p).toHaveLength(expectedElementsCount); + setUp({ errorData }); + expect(screen.queryAllByText(/^Invalid elements/)).toHaveLength(expectedElementsCount); }); });