From 0ecb771b2330b17f906058e2a2e1472d21246f42 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 16 Aug 2021 13:21:53 +0200 Subject: [PATCH 1/6] Created lint:fix global command --- package.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index efe43b57..d2376a65 100644 --- a/package.json +++ b/package.json @@ -7,10 +7,11 @@ "license": "MIT", "scripts": { "lint": "npm run lint:css && npm run lint:js", - "lint:js": "eslint --ext .js,.ts,.tsx src test", - "lint:js:fix": "npm run lint:js -- --fix", "lint:css": "stylelint src/*.scss src/**/*.scss", + "lint:js": "eslint --ext .js,.ts,.tsx src test", + "lint:fix": "npm run lint:css:fix && npm run lint:js:fix", "lint:css:fix": "npm run lint:css -- --fix", + "lint:js:fix": "npm run lint:js -- --fix", "start": "node scripts/start.js", "serve:build": "serve ./build", "build": "node scripts/build.js", From 461c0e0bc9fcc66965dd09a89989d947a7f3d515 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 16 Aug 2021 17:13:31 +0200 Subject: [PATCH 2/6] Added new component for QR codes error correction when consuming Shlink 2.8 --- src/short-urls/helpers/QrCodeModal.tsx | 93 ++++++++++++-------- src/utils/helpers/features.ts | 2 + src/utils/helpers/qrCodes.ts | 9 +- test/short-urls/helpers/QrCodeModal.test.tsx | 6 +- 4 files changed, 70 insertions(+), 40 deletions(-) diff --git a/src/short-urls/helpers/QrCodeModal.tsx b/src/short-urls/helpers/QrCodeModal.tsx index 9b43b58c..fd20672a 100644 --- a/src/short-urls/helpers/QrCodeModal.tsx +++ b/src/short-urls/helpers/QrCodeModal.tsx @@ -8,8 +8,13 @@ import { ShortUrlModalProps } from '../data'; import { SelectedServer } from '../../servers/data'; import { DropdownBtn } from '../../utils/DropdownBtn'; import { CopyToClipboardIcon } from '../../utils/CopyToClipboardIcon'; -import { buildQrCodeUrl, QrCodeCapabilities, QrCodeFormat } from '../../utils/helpers/qrCodes'; -import { supportsQrCodeSizeInQuery, supportsQrCodeSvgFormat, supportsQrCodeMargin } from '../../utils/helpers/features'; +import { buildQrCodeUrl, QrCodeCapabilities, QrCodeFormat, QrErrorCorrection } from '../../utils/helpers/qrCodes'; +import { + supportsQrCodeSizeInQuery, + supportsQrCodeSvgFormat, + supportsQrCodeMargin, + supportsQrErrorCorrection, +} from '../../utils/helpers/features'; import { ImageDownloader } from '../../common/services/ImageDownloader'; import { Versions } from '../../utils/helpers/version'; import './QrCodeModal.scss'; @@ -18,20 +23,22 @@ interface QrCodeModalConnectProps extends ShortUrlModalProps { selectedServer: SelectedServer; } -const QrCodeModal = (imageDownloader: ImageDownloader, ForServerVersion: FC) => ( +const QrCodeModal = (imageDownloader: ImageDownloader, ForServerVersion: FC) => ( // eslint-disable-line { shortUrl: { shortUrl, shortCode }, toggle, isOpen, selectedServer }: QrCodeModalConnectProps, ) => { const [ size, setSize ] = useState(300); const [ margin, setMargin ] = useState(0); const [ format, setFormat ] = useState('png'); + const [ errorCorrection, setErrorCorrection ] = useState('L'); const capabilities: QrCodeCapabilities = useMemo(() => ({ useSizeInPath: !supportsQrCodeSizeInQuery(selectedServer), svgIsSupported: supportsQrCodeSvgFormat(selectedServer), marginIsSupported: supportsQrCodeMargin(selectedServer), - }), [ selectedServer ]); + errorCorrectionIsSupported: supportsQrErrorCorrection(selectedServer), + }) as QrCodeCapabilities, [ selectedServer ]); const qrCodeUrl = useMemo( - () => buildQrCodeUrl(shortUrl, { size, format, margin }, capabilities), - [ shortUrl, size, format, margin, capabilities ], + () => buildQrCodeUrl(shortUrl, { size, format, margin, errorCorrection }, capabilities), + [ shortUrl, size, format, margin, errorCorrection, capabilities ], ); const totalSize = useMemo(() => size + margin, [ size, margin ]); const modalSize = useMemo(() => { @@ -48,50 +55,64 @@ const QrCodeModal = (imageDownloader: ImageDownloader, ForServerVersion: FC{shortUrl} - -
+ - - + + setSize(Number(e.target.value))} + /> + + {capabilities.marginIsSupported && ( + + setSize(Number(e.target.value))} + value={margin} + step={1} + min={0} + max={100} + onChange={(e) => setMargin(Number(e.target.value))} /> -
- {capabilities.marginIsSupported && ( -
- - - setMargin(Number(e.target.value))} - /> - -
)} {capabilities.svgIsSupported && ( -
+ setFormat('png')}>PNG setFormat('svg')}>SVG -
+ + )} + {capabilities.errorCorrectionIsSupported && ( + + + setErrorCorrection('L')}> + Low + + setErrorCorrection('M')}> + Medium + + setErrorCorrection('Q')}> + Quartile + + setErrorCorrection('H')}> + High + + + )}
diff --git a/src/utils/helpers/features.ts b/src/utils/helpers/features.ts index 9eb314f2..609c9e95 100644 --- a/src/utils/helpers/features.ts +++ b/src/utils/helpers/features.ts @@ -27,3 +27,5 @@ export const supportsTagsInPatch = supportsShortUrlTitle; export const supportsBotVisits = serverMatchesVersions({ minVersion: '2.7.0' }); export const supportsCrawlableVisits = supportsBotVisits; + +export const supportsQrErrorCorrection = serverMatchesVersions({ minVersion: '2.8.0' }); diff --git a/src/utils/helpers/qrCodes.ts b/src/utils/helpers/qrCodes.ts index 60342bd4..fb94350a 100644 --- a/src/utils/helpers/qrCodes.ts +++ b/src/utils/helpers/qrCodes.ts @@ -5,26 +5,31 @@ export interface QrCodeCapabilities { useSizeInPath: boolean; svgIsSupported: boolean; marginIsSupported: boolean; + errorCorrectionIsSupported: boolean; } export type QrCodeFormat = 'svg' | 'png'; +export type QrErrorCorrection = 'L' | 'M' | 'Q' | 'H'; + export interface QrCodeOptions { size: number; format: QrCodeFormat; margin: number; + errorCorrection: QrErrorCorrection; } export const buildQrCodeUrl = ( shortUrl: string, - { size, format, margin }: QrCodeOptions, - { useSizeInPath, svgIsSupported, marginIsSupported }: QrCodeCapabilities, + { size, format, margin, errorCorrection }: QrCodeOptions, + { useSizeInPath, svgIsSupported, marginIsSupported, errorCorrectionIsSupported }: QrCodeCapabilities, ): string => { const baseUrl = `${shortUrl}/qr-code${useSizeInPath ? `/${size}` : ''}`; const query = stringifyQuery({ size: useSizeInPath ? undefined : size, format: svgIsSupported ? format : undefined, margin: marginIsSupported && margin > 0 ? margin : undefined, + errorCorrection: errorCorrectionIsSupported ? errorCorrection : undefined, }); return `${baseUrl}${isEmpty(query) ? '' : `?${query}`}`; diff --git a/test/short-urls/helpers/QrCodeModal.test.tsx b/test/short-urls/helpers/QrCodeModal.test.tsx index 9cdf1bb7..2daeafce 100644 --- a/test/short-urls/helpers/QrCodeModal.test.tsx +++ b/test/short-urls/helpers/QrCodeModal.test.tsx @@ -1,6 +1,6 @@ import { shallow, ShallowWrapper } from 'enzyme'; import { ExternalLink } from 'react-external-link'; -import { Button, Modal, ModalBody, ModalHeader, Row } from 'reactstrap'; +import { Button, FormGroup, Modal, ModalBody, ModalHeader, Row } from 'reactstrap'; import { Mock } from 'ts-mockery'; import createQrCodeModal from '../../../src/short-urls/helpers/QrCodeModal'; import { ShortUrl } from '../../../src/short-urls/data'; @@ -48,6 +48,7 @@ describe('', () => { [ '2.5.0' as SemVer, 0, '/qr-code?size=300&format=png' ], [ '2.6.0' as SemVer, 0, '/qr-code?size=300&format=png' ], [ '2.6.0' as SemVer, 10, '/qr-code?size=300&format=png&margin=10' ], + [ '2.8.0' as SemVer, 0, '/qr-code?size=300&format=png&errorCorrection=L' ], ])('displays an image with the QR code of the URL', (version, margin, expectedUrl) => { const wrapper = createWrapper(version); const formControls = wrapper.find('.form-control-range'); @@ -91,10 +92,11 @@ describe('', () => { [ '2.3.0' as SemVer, 0, 'col-12' ], [ '2.4.0' as SemVer, 1, 'col-md-6' ], [ '2.6.0' as SemVer, 1, 'col-md-4' ], + [ '2.8.0' as SemVer, 2, 'col-md-6' ], ])('shows expected components based on server version', (version, expectedAmountOfDropdowns, expectedRangeClass) => { const wrapper = createWrapper(version); const dropdown = wrapper.find(DropdownBtn); - const firstCol = wrapper.find(Row).find('div').first(); + const firstCol = wrapper.find(Row).find(FormGroup).first(); expect(dropdown).toHaveLength(expectedAmountOfDropdowns); expect(firstCol.prop('className')).toEqual(expectedRangeClass); From 520e52595f22c94196ee9129d403c139d9344597 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 16 Aug 2021 17:14:57 +0200 Subject: [PATCH 3/6] Updated changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c4b3d18..76565154 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), * `includes`: Suggests tags that contain the input. * [#464](https://github.com/shlinkio/shlink-web-client/pull/464) Added support to download QR codes. This feature requires an unreleased version of Shlink, so it comes disabled, and will get enabled as soon as Shlink v2.9 is released. +* [#469](https://github.com/shlinkio/shlink-web-client/pull/469) Added support `errorCorrection` in QR codes, when consuming Shlink 2.8 or higher. ### Changed * *Nothing* From 51663407793b9d7ff0c770130d92f7d40ea30eb4 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 16 Aug 2021 17:26:54 +0200 Subject: [PATCH 4/6] Extracted some QR code modal components to external components --- src/short-urls/helpers/QrCodeModal.tsx | 25 ++++------------- .../qr-codes/QrErrorCorrectionDropdown.tsx | 28 +++++++++++++++++++ .../helpers/qr-codes/QrFormatDropdown.tsx | 16 +++++++++++ test/short-urls/helpers/QrCodeModal.test.tsx | 7 +++-- 4 files changed, 53 insertions(+), 23 deletions(-) create mode 100644 src/short-urls/helpers/qr-codes/QrErrorCorrectionDropdown.tsx create mode 100644 src/short-urls/helpers/qr-codes/QrFormatDropdown.tsx diff --git a/src/short-urls/helpers/QrCodeModal.tsx b/src/short-urls/helpers/QrCodeModal.tsx index fd20672a..ad4fc209 100644 --- a/src/short-urls/helpers/QrCodeModal.tsx +++ b/src/short-urls/helpers/QrCodeModal.tsx @@ -1,12 +1,11 @@ import { FC, useMemo, useState } from 'react'; -import { Modal, DropdownItem, FormGroup, ModalBody, ModalHeader, Row, Button } from 'reactstrap'; +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'; import { ExternalLink } from 'react-external-link'; import classNames from 'classnames'; import { ShortUrlModalProps } from '../data'; import { SelectedServer } from '../../servers/data'; -import { DropdownBtn } from '../../utils/DropdownBtn'; import { CopyToClipboardIcon } from '../../utils/CopyToClipboardIcon'; import { buildQrCodeUrl, QrCodeCapabilities, QrCodeFormat, QrErrorCorrection } from '../../utils/helpers/qrCodes'; import { @@ -17,7 +16,9 @@ import { } from '../../utils/helpers/features'; import { ImageDownloader } from '../../common/services/ImageDownloader'; import { Versions } from '../../utils/helpers/version'; +import { QrFormatDropdown } from './qr-codes/QrFormatDropdown'; import './QrCodeModal.scss'; +import { QrErrorCorrectionDropdown } from './qr-codes/QrErrorCorrectionDropdown'; interface QrCodeModalConnectProps extends ShortUrlModalProps { selectedServer: SelectedServer; @@ -90,28 +91,12 @@ const QrCodeModal = (imageDownloader: ImageDownloader, ForServerVersion: FC - - setFormat('png')}>PNG - setFormat('svg')}>SVG - + )} {capabilities.errorCorrectionIsSupported && ( - - setErrorCorrection('L')}> - Low - - setErrorCorrection('M')}> - Medium - - setErrorCorrection('Q')}> - Quartile - - setErrorCorrection('H')}> - High - - + )} diff --git a/src/short-urls/helpers/qr-codes/QrErrorCorrectionDropdown.tsx b/src/short-urls/helpers/qr-codes/QrErrorCorrectionDropdown.tsx new file mode 100644 index 00000000..b6fd68b7 --- /dev/null +++ b/src/short-urls/helpers/qr-codes/QrErrorCorrectionDropdown.tsx @@ -0,0 +1,28 @@ +import { FC } from 'react'; +import { DropdownItem } from 'reactstrap'; +import { DropdownBtn } from '../../../utils/DropdownBtn'; +import { QrErrorCorrection } from '../../../utils/helpers/qrCodes'; + +interface QrErrorCorrectionDropdownProps { + errorCorrection: QrErrorCorrection; + setErrorCorrection: (errorCorrection: QrErrorCorrection) => void; +} + +export const QrErrorCorrectionDropdown: FC = ( + { errorCorrection, setErrorCorrection }, +) => ( + + setErrorCorrection('L')}> + Low + + setErrorCorrection('M')}> + Medium + + setErrorCorrection('Q')}> + Quartile + + setErrorCorrection('H')}> + High + + +); diff --git a/src/short-urls/helpers/qr-codes/QrFormatDropdown.tsx b/src/short-urls/helpers/qr-codes/QrFormatDropdown.tsx new file mode 100644 index 00000000..3c9ca705 --- /dev/null +++ b/src/short-urls/helpers/qr-codes/QrFormatDropdown.tsx @@ -0,0 +1,16 @@ +import { FC } from 'react'; +import { DropdownItem } from 'reactstrap'; +import { DropdownBtn } from '../../../utils/DropdownBtn'; +import { QrCodeFormat } from '../../../utils/helpers/qrCodes'; + +interface QrFormatDropdownProps { + format: QrCodeFormat; + setFormat: (format: QrCodeFormat) => void; +} + +export const QrFormatDropdown: FC = ({ format, setFormat }) => ( + + setFormat('png')}>PNG + setFormat('svg')}>SVG + +); diff --git a/test/short-urls/helpers/QrCodeModal.test.tsx b/test/short-urls/helpers/QrCodeModal.test.tsx index 2daeafce..aa8b5160 100644 --- a/test/short-urls/helpers/QrCodeModal.test.tsx +++ b/test/short-urls/helpers/QrCodeModal.test.tsx @@ -6,9 +6,10 @@ import createQrCodeModal from '../../../src/short-urls/helpers/QrCodeModal'; import { ShortUrl } from '../../../src/short-urls/data'; import { ReachableServer } from '../../../src/servers/data'; import { CopyToClipboardIcon } from '../../../src/utils/CopyToClipboardIcon'; -import { DropdownBtn } from '../../../src/utils/DropdownBtn'; import { SemVer } from '../../../src/utils/helpers/version'; import { ImageDownloader } from '../../../src/common/services/ImageDownloader'; +import { QrFormatDropdown } from '../../../src/short-urls/helpers/qr-codes/QrFormatDropdown'; +import { QrErrorCorrectionDropdown } from '../../../src/short-urls/helpers/qr-codes/QrErrorCorrectionDropdown'; describe('', () => { let wrapper: ShallowWrapper; @@ -95,10 +96,10 @@ describe('', () => { [ '2.8.0' as SemVer, 2, 'col-md-6' ], ])('shows expected components based on server version', (version, expectedAmountOfDropdowns, expectedRangeClass) => { const wrapper = createWrapper(version); - const dropdown = wrapper.find(DropdownBtn); + const dropdownsLength = wrapper.find(QrFormatDropdown).length + wrapper.find(QrErrorCorrectionDropdown).length; const firstCol = wrapper.find(Row).find(FormGroup).first(); - expect(dropdown).toHaveLength(expectedAmountOfDropdowns); + expect(dropdownsLength).toEqual(expectedAmountOfDropdowns); expect(firstCol.prop('className')).toEqual(expectedRangeClass); }); From c6be8bd96f631a1646c7c6708292a15528f881e0 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 16 Aug 2021 17:38:25 +0200 Subject: [PATCH 5/6] Created tests for new QR code dropdowns --- .../QrErrorCorrectionDropdown.test.tsx | 47 +++++++++++++++++++ .../qr-codes/QrFormatDropdown.test.tsx | 37 +++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 test/short-urls/helpers/qr-codes/QrErrorCorrectionDropdown.test.tsx create mode 100644 test/short-urls/helpers/qr-codes/QrFormatDropdown.test.tsx diff --git a/test/short-urls/helpers/qr-codes/QrErrorCorrectionDropdown.test.tsx b/test/short-urls/helpers/qr-codes/QrErrorCorrectionDropdown.test.tsx new file mode 100644 index 00000000..32db92f8 --- /dev/null +++ b/test/short-urls/helpers/qr-codes/QrErrorCorrectionDropdown.test.tsx @@ -0,0 +1,47 @@ +import { shallow, ShallowWrapper } from 'enzyme'; +import { DropdownItem } from 'reactstrap'; +import { QrErrorCorrection } from '../../../../src/utils/helpers/qrCodes'; +import { QrErrorCorrectionDropdown } from '../../../../src/short-urls/helpers/qr-codes/QrErrorCorrectionDropdown'; + +describe('', () => { + const initialErrorCorrection: QrErrorCorrection = 'Q'; + const setErrorCorrection = jest.fn(); + let wrapper: ShallowWrapper; + + beforeEach(() => { + wrapper = shallow( + , + ); + }); + + afterEach(() => wrapper?.unmount()); + afterEach(jest.clearAllMocks); + + it('renders initial state', () => { + const items = wrapper.find(DropdownItem); + + expect(wrapper.prop('text')).toEqual('Error correction (Q)'); + expect(items.at(0).prop('active')).toEqual(false); + expect(items.at(1).prop('active')).toEqual(false); + expect(items.at(2).prop('active')).toEqual(true); + expect(items.at(3).prop('active')).toEqual(false); + }); + + it('invokes callback when items are clicked', () => { + const items = wrapper.find(DropdownItem); + + expect(setErrorCorrection).not.toHaveBeenCalled(); + + items.at(0).simulate('click'); + expect(setErrorCorrection).toHaveBeenCalledWith('L'); + + items.at(1).simulate('click'); + expect(setErrorCorrection).toHaveBeenCalledWith('M'); + + items.at(2).simulate('click'); + expect(setErrorCorrection).toHaveBeenCalledWith('Q'); + + items.at(3).simulate('click'); + expect(setErrorCorrection).toHaveBeenCalledWith('H'); + }); +}); diff --git a/test/short-urls/helpers/qr-codes/QrFormatDropdown.test.tsx b/test/short-urls/helpers/qr-codes/QrFormatDropdown.test.tsx new file mode 100644 index 00000000..c40e05d0 --- /dev/null +++ b/test/short-urls/helpers/qr-codes/QrFormatDropdown.test.tsx @@ -0,0 +1,37 @@ +import { shallow, ShallowWrapper } from 'enzyme'; +import { DropdownItem } from 'reactstrap'; +import { QrCodeFormat } from '../../../../src/utils/helpers/qrCodes'; +import { QrFormatDropdown } from '../../../../src/short-urls/helpers/qr-codes/QrFormatDropdown'; + +describe('', () => { + const initialFormat: QrCodeFormat = 'svg'; + const setFormat = jest.fn(); + let wrapper: ShallowWrapper; + + beforeEach(() => { + wrapper = shallow(); + }); + + afterEach(() => wrapper?.unmount()); + afterEach(jest.clearAllMocks); + + it('renders initial state', () => { + const items = wrapper.find(DropdownItem); + + expect(wrapper.prop('text')).toEqual('Format (svg)'); + expect(items.at(0).prop('active')).toEqual(false); + expect(items.at(1).prop('active')).toEqual(true); + }); + + it('invokes callback when items are clicked', () => { + const items = wrapper.find(DropdownItem); + + expect(setFormat).not.toHaveBeenCalled(); + + items.at(0).simulate('click'); + expect(setFormat).toHaveBeenCalledWith('png'); + + items.at(1).simulate('click'); + expect(setFormat).toHaveBeenCalledWith('svg'); + }); +}); From 37a3a2022bb3c744588c64f80305d1f23a0939a3 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 16 Aug 2021 17:44:11 +0200 Subject: [PATCH 6/6] Added missing props on qrCodes test --- src/short-urls/helpers/QrCodeModal.tsx | 2 +- test/utils/helpers/qrCodes.test.ts | 48 +++++++++++++++----------- 2 files changed, 28 insertions(+), 22 deletions(-) diff --git a/src/short-urls/helpers/QrCodeModal.tsx b/src/short-urls/helpers/QrCodeModal.tsx index ad4fc209..6521e5e1 100644 --- a/src/short-urls/helpers/QrCodeModal.tsx +++ b/src/short-urls/helpers/QrCodeModal.tsx @@ -36,7 +36,7 @@ const QrCodeModal = (imageDownloader: ImageDownloader, ForServerVersion: FC buildQrCodeUrl(shortUrl, { size, format, margin, errorCorrection }, capabilities), [ shortUrl, size, format, margin, errorCorrection, capabilities ], diff --git a/test/utils/helpers/qrCodes.test.ts b/test/utils/helpers/qrCodes.test.ts index 12f1afdd..cf77e34f 100644 --- a/test/utils/helpers/qrCodes.test.ts +++ b/test/utils/helpers/qrCodes.test.ts @@ -1,68 +1,74 @@ -import { buildQrCodeUrl, QrCodeFormat } from '../../../src/utils/helpers/qrCodes'; +import { buildQrCodeUrl, QrCodeFormat, QrErrorCorrection } from '../../../src/utils/helpers/qrCodes'; describe('qrCodes', () => { describe('buildQrCodeUrl', () => { test.each([ [ 'foo.com', - { size: 530, format: 'svg' as QrCodeFormat, margin: 0 }, - { useSizeInPath: true, svgIsSupported: true, marginIsSupported: false }, + { size: 530, format: 'svg' as QrCodeFormat, margin: 0, errorCorrection: 'L' as QrErrorCorrection }, + { useSizeInPath: true, svgIsSupported: true, marginIsSupported: false, errorCorrectionIsSupported: false }, 'foo.com/qr-code/530?format=svg', ], [ 'foo.com', - { size: 530, format: 'png' as QrCodeFormat, margin: 0 }, - { useSizeInPath: true, svgIsSupported: true, marginIsSupported: false }, + { size: 530, format: 'png' as QrCodeFormat, margin: 0, errorCorrection: 'L' as QrErrorCorrection }, + { useSizeInPath: true, svgIsSupported: true, marginIsSupported: false, errorCorrectionIsSupported: false }, 'foo.com/qr-code/530?format=png', ], [ 'bar.io', - { size: 870, format: 'svg' as QrCodeFormat, margin: 0 }, - { useSizeInPath: false, svgIsSupported: false, marginIsSupported: false }, + { size: 870, format: 'svg' as QrCodeFormat, margin: 0, errorCorrection: 'L' as QrErrorCorrection }, + { useSizeInPath: false, svgIsSupported: false, marginIsSupported: false, errorCorrectionIsSupported: false }, 'bar.io/qr-code?size=870', ], [ 'bar.io', - { size: 200, format: 'png' as QrCodeFormat, margin: 0 }, - { useSizeInPath: false, svgIsSupported: true, marginIsSupported: false }, + { size: 200, format: 'png' as QrCodeFormat, margin: 0, errorCorrection: 'L' as QrErrorCorrection }, + { useSizeInPath: false, svgIsSupported: true, marginIsSupported: false, errorCorrectionIsSupported: false }, 'bar.io/qr-code?size=200&format=png', ], [ 'bar.io', - { size: 200, format: 'svg' as QrCodeFormat, margin: 0 }, - { useSizeInPath: false, svgIsSupported: true, marginIsSupported: false }, + { size: 200, format: 'svg' as QrCodeFormat, margin: 0, errorCorrection: 'L' as QrErrorCorrection }, + { useSizeInPath: false, svgIsSupported: true, marginIsSupported: false, errorCorrectionIsSupported: false }, 'bar.io/qr-code?size=200&format=svg', ], [ 'foo.net', - { size: 480, format: 'png' as QrCodeFormat, margin: 0 }, - { useSizeInPath: true, svgIsSupported: false, marginIsSupported: false }, + { size: 480, format: 'png' as QrCodeFormat, margin: 0, errorCorrection: 'L' as QrErrorCorrection }, + { useSizeInPath: true, svgIsSupported: false, marginIsSupported: false, errorCorrectionIsSupported: false }, 'foo.net/qr-code/480', ], [ 'foo.net', - { size: 480, format: 'svg' as QrCodeFormat, margin: 0 }, - { useSizeInPath: true, svgIsSupported: false, marginIsSupported: false }, + { size: 480, format: 'svg' as QrCodeFormat, margin: 0, errorCorrection: 'L' as QrErrorCorrection }, + { useSizeInPath: true, svgIsSupported: false, marginIsSupported: false, errorCorrectionIsSupported: false }, 'foo.net/qr-code/480', ], [ 'shlink.io', - { size: 123, format: 'svg' as QrCodeFormat, margin: 10 }, - { useSizeInPath: true, svgIsSupported: false, marginIsSupported: false }, + { size: 123, format: 'svg' as QrCodeFormat, margin: 10, errorCorrection: 'L' as QrErrorCorrection }, + { useSizeInPath: true, svgIsSupported: false, marginIsSupported: false, errorCorrectionIsSupported: false }, 'shlink.io/qr-code/123', ], [ 'shlink.io', - { size: 456, format: 'png' as QrCodeFormat, margin: 10 }, - { useSizeInPath: true, svgIsSupported: true, marginIsSupported: true }, + { size: 456, format: 'png' as QrCodeFormat, margin: 10, errorCorrection: 'L' as QrErrorCorrection }, + { useSizeInPath: true, svgIsSupported: true, marginIsSupported: true, errorCorrectionIsSupported: false }, 'shlink.io/qr-code/456?format=png&margin=10', ], [ 'shlink.io', - { size: 456, format: 'png' as QrCodeFormat, margin: 0 }, - { useSizeInPath: true, svgIsSupported: true, marginIsSupported: true }, + { size: 456, format: 'png' as QrCodeFormat, margin: 0, errorCorrection: 'L' as QrErrorCorrection }, + { useSizeInPath: true, svgIsSupported: true, marginIsSupported: true, errorCorrectionIsSupported: false }, 'shlink.io/qr-code/456?format=png', ], + [ + 'shlink.io', + { size: 456, format: 'png' as QrCodeFormat, margin: 0, errorCorrection: 'H' as QrErrorCorrection }, + { useSizeInPath: true, svgIsSupported: true, marginIsSupported: true, errorCorrectionIsSupported: true }, + 'shlink.io/qr-code/456?format=png&errorCorrection=H', + ], ])('builds expected URL based in params', (shortUrl, options, capabilities, expectedUrl) => { expect(buildQrCodeUrl(shortUrl, options, capabilities)).toEqual(expectedUrl); });