Updated ShortUrlForm to ensure it does not render empty cards

This commit is contained in:
Alejandro Celaya 2021-03-27 18:39:55 +01:00
parent 6628a4059e
commit 56aab349db
4 changed files with 118 additions and 78 deletions

View file

@ -3,25 +3,27 @@ import { InputType } from 'reactstrap/lib/Input';
import { Button, FormGroup, Input, Row } from 'reactstrap'; import { Button, FormGroup, Input, Row } from 'reactstrap';
import { isEmpty, pipe, replace, trim } from 'ramda'; import { isEmpty, pipe, replace, trim } from 'ramda';
import m from 'moment'; import m from 'moment';
import classNames from 'classnames';
import DateInput, { DateInputProps } from '../utils/DateInput'; import DateInput, { DateInputProps } from '../utils/DateInput';
import { import {
supportsListingDomains, supportsListingDomains,
supportsSettingShortCodeLength, supportsSettingShortCodeLength,
supportsShortUrlTitle, supportsShortUrlTitle,
supportsValidateUrl,
} from '../utils/helpers/features'; } from '../utils/helpers/features';
import { SimpleCard } from '../utils/SimpleCard'; import { SimpleCard } from '../utils/SimpleCard';
import { handleEventPreventingDefault, hasValue } from '../utils/utils'; import { handleEventPreventingDefault, hasValue } from '../utils/utils';
import Checkbox from '../utils/Checkbox'; import Checkbox from '../utils/Checkbox';
import { SelectedServer } from '../servers/data'; import { SelectedServer } from '../servers/data';
import { TagsSelectorProps } from '../tags/helpers/TagsSelector'; import { TagsSelectorProps } from '../tags/helpers/TagsSelector';
import { Versions } from '../utils/helpers/version';
import { DomainSelectorProps } from '../domains/DomainSelector'; import { DomainSelectorProps } from '../domains/DomainSelector';
import { formatIsoDate } from '../utils/helpers/date'; import { formatIsoDate } from '../utils/helpers/date';
import UseExistingIfFoundInfoIcon from './UseExistingIfFoundInfoIcon'; import UseExistingIfFoundInfoIcon from './UseExistingIfFoundInfoIcon';
import { ShortUrlData } from './data'; import { ShortUrlData } from './data';
import './ShortUrlForm.scss'; import './ShortUrlForm.scss';
type Mode = 'create' | 'create-basic' | 'edit'; export type Mode = 'create' | 'create-basic' | 'edit';
type DateFields = 'validSince' | 'validUntil'; type DateFields = 'validSince' | 'validUntil';
type NonDateFields = 'longUrl' | 'customSlug' | 'shortCodeLength' | 'domain' | 'maxVisits' | 'title'; type NonDateFields = 'longUrl' | 'customSlug' | 'shortCodeLength' | 'domain' | 'maxVisits' | 'title';
@ -37,7 +39,6 @@ const normalizeTag = pipe(trim, replace(/ /g, '-'));
export const ShortUrlForm = ( export const ShortUrlForm = (
TagsSelector: FC<TagsSelectorProps>, TagsSelector: FC<TagsSelectorProps>,
ForServerVersion: FC<Versions>,
DomainSelector: FC<DomainSelectorProps>, DomainSelector: FC<DomainSelectorProps>,
): FC<ShortUrlFormProps> => ({ mode, saving, onSave, initialState, selectedServer }) => { // eslint-disable-line complexity ): FC<ShortUrlFormProps> => ({ mode, saving, onSave, initialState, selectedServer }) => { // eslint-disable-line complexity
const [ shortUrlData, setShortUrlData ] = useState(initialState); const [ shortUrlData, setShortUrlData ] = useState(initialState);
@ -101,6 +102,13 @@ export const ShortUrlForm = (
const showDomainSelector = supportsListingDomains(selectedServer); const showDomainSelector = supportsListingDomains(selectedServer);
const disableShortCodeLength = !supportsSettingShortCodeLength(selectedServer); const disableShortCodeLength = !supportsSettingShortCodeLength(selectedServer);
const supportsTitle = supportsShortUrlTitle(selectedServer); const supportsTitle = supportsShortUrlTitle(selectedServer);
const showCustomizeCard = supportsTitle || !isEdit;
const limitAccessCardClasses = classNames('mb-3', {
'col-sm-6': showCustomizeCard,
'col-sm-12': !showCustomizeCard,
});
const showValidateUrl = supportsValidateUrl(selectedServer);
const showExtraValidationsCard = showValidateUrl || !isEdit;
return ( return (
<form className="short-url-form" onSubmit={submit}> <form className="short-url-form" onSubmit={submit}>
@ -112,6 +120,7 @@ export const ShortUrlForm = (
</SimpleCard> </SimpleCard>
<Row> <Row>
{showCustomizeCard && (
<div className="col-sm-6 mb-3"> <div className="col-sm-6 mb-3">
<SimpleCard title="Customize the short URL"> <SimpleCard title="Customize the short URL">
{supportsTitle && renderOptionalInput('title', 'Title')} {supportsTitle && renderOptionalInput('title', 'Title')}
@ -146,8 +155,9 @@ export const ShortUrlForm = (
)} )}
</SimpleCard> </SimpleCard>
</div> </div>
)}
<div className="col-sm-6 mb-3"> <div className={limitAccessCardClasses}>
<SimpleCard title="Limit access to the short URL"> <SimpleCard title="Limit access to the short URL">
{renderOptionalInput('maxVisits', 'Maximum number of visits allowed', 'number', { min: 1 })} {renderOptionalInput('maxVisits', 'Maximum number of visits allowed', 'number', { min: 1 })}
{renderDateInput('validSince', 'Enabled since...', { maxDate: shortUrlData.validUntil ? m(shortUrlData.validUntil) : undefined })} {renderDateInput('validSince', 'Enabled since...', { maxDate: shortUrlData.validUntil ? m(shortUrlData.validUntil) : undefined })}
@ -156,6 +166,7 @@ export const ShortUrlForm = (
</div> </div>
</Row> </Row>
{showExtraValidationsCard && (
<SimpleCard title="Extra validations" className="mb-3"> <SimpleCard title="Extra validations" className="mb-3">
{!isEdit && ( {!isEdit && (
<p> <p>
@ -163,7 +174,7 @@ export const ShortUrlForm = (
provided data. provided data.
</p> </p>
)} )}
<ForServerVersion minVersion="2.4.0"> {showValidateUrl && (
<p> <p>
<Checkbox <Checkbox
inline inline
@ -173,7 +184,7 @@ export const ShortUrlForm = (
Validate URL Validate URL
</Checkbox> </Checkbox>
</p> </p>
</ForServerVersion> )}
{!isEdit && ( {!isEdit && (
<p> <p>
<Checkbox <Checkbox
@ -188,6 +199,7 @@ export const ShortUrlForm = (
</p> </p>
)} )}
</SimpleCard> </SimpleCard>
)}
</> </>
)} )}

View file

@ -34,7 +34,7 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
bottle.serviceFactory('ShortUrlsRow', ShortUrlsRow, 'ShortUrlsRowMenu', 'ColorGenerator', 'useStateFlagTimeout'); bottle.serviceFactory('ShortUrlsRow', ShortUrlsRow, 'ShortUrlsRowMenu', 'ColorGenerator', 'useStateFlagTimeout');
bottle.serviceFactory('ShortUrlsRowMenu', ShortUrlsRowMenu, 'DeleteShortUrlModal', 'QrCodeModal'); bottle.serviceFactory('ShortUrlsRowMenu', ShortUrlsRowMenu, 'DeleteShortUrlModal', 'QrCodeModal');
bottle.serviceFactory('CreateShortUrlResult', CreateShortUrlResult, 'useStateFlagTimeout'); bottle.serviceFactory('CreateShortUrlResult', CreateShortUrlResult, 'useStateFlagTimeout');
bottle.serviceFactory('ShortUrlForm', ShortUrlForm, 'TagsSelector', 'ForServerVersion', 'DomainSelector'); bottle.serviceFactory('ShortUrlForm', ShortUrlForm, 'TagsSelector', 'DomainSelector');
bottle.serviceFactory('CreateShortUrl', CreateShortUrl, 'ShortUrlForm', 'CreateShortUrlResult'); bottle.serviceFactory('CreateShortUrl', CreateShortUrl, 'ShortUrlForm', 'CreateShortUrlResult');
bottle.decorator( bottle.decorator(

View file

@ -12,6 +12,8 @@ export const supportsListingDomains = serverMatchesVersions({ minVersion: '2.4.0
export const supportsQrCodeSvgFormat = supportsListingDomains; export const supportsQrCodeSvgFormat = supportsListingDomains;
export const supportsValidateUrl = supportsListingDomains;
export const supportsQrCodeSizeInQuery = serverMatchesVersions({ minVersion: '2.5.0' }); export const supportsQrCodeSizeInQuery = serverMatchesVersions({ minVersion: '2.5.0' });
export const supportsShortUrlTitle = serverMatchesVersions({ minVersion: '2.6.0' }); export const supportsShortUrlTitle = serverMatchesVersions({ minVersion: '2.6.0' });

View file

@ -3,32 +3,37 @@ import moment from 'moment';
import { identity } from 'ramda'; import { identity } from 'ramda';
import { Mock } from 'ts-mockery'; import { Mock } from 'ts-mockery';
import { Input } from 'reactstrap'; import { Input } from 'reactstrap';
import { ShortUrlForm as createShortUrlForm } from '../../src/short-urls/ShortUrlForm'; import { ShortUrlForm as createShortUrlForm, Mode } from '../../src/short-urls/ShortUrlForm';
import DateInput from '../../src/utils/DateInput'; import DateInput from '../../src/utils/DateInput';
import { ShortUrlData } from '../../src/short-urls/data'; import { ShortUrlData } from '../../src/short-urls/data';
import { ReachableServer, SelectedServer } from '../../src/servers/data';
import { SimpleCard } from '../../src/utils/SimpleCard';
describe('<ShortUrlForm />', () => { describe('<ShortUrlForm />', () => {
let wrapper: ShallowWrapper; let wrapper: ShallowWrapper;
const TagsSelector = () => null; const TagsSelector = () => null;
const createShortUrl = jest.fn(); const createShortUrl = jest.fn();
const createWrapper = (selectedServer: SelectedServer = null, mode: Mode = 'create') => {
beforeEach(() => { const ShortUrlForm = createShortUrlForm(TagsSelector, () => null);
const ShortUrlForm = createShortUrlForm(TagsSelector, () => null, () => null);
wrapper = shallow( wrapper = shallow(
<ShortUrlForm <ShortUrlForm
selectedServer={null} selectedServer={selectedServer}
mode="create" mode={mode}
saving={false} saving={false}
initialState={Mock.of<ShortUrlData>({ validateUrl: true, findIfExists: false })} initialState={Mock.of<ShortUrlData>({ validateUrl: true, findIfExists: false })}
onSave={createShortUrl} onSave={createShortUrl}
/>, />,
); );
});
return wrapper;
};
afterEach(() => wrapper.unmount()); afterEach(() => wrapper.unmount());
afterEach(jest.clearAllMocks); afterEach(jest.clearAllMocks);
it('saves short URL with data set in form controls', () => { it('saves short URL with data set in form controls', () => {
const wrapper = createWrapper();
const validSince = moment('2017-01-01'); const validSince = moment('2017-01-01');
const validUntil = moment('2017-01-06'); const validUntil = moment('2017-01-06');
@ -56,4 +61,25 @@ describe('<ShortUrlForm />', () => {
validateUrl: true, validateUrl: true,
}); });
}); });
it.each([
[ null, 'create' as Mode, 4 ],
[ null, 'create-basic' as Mode, 0 ],
[ Mock.of<ReachableServer>({ version: '2.6.0' }), 'create' as Mode, 4 ],
[ Mock.of<ReachableServer>({ version: '2.5.0' }), 'create' as Mode, 4 ],
[ Mock.of<ReachableServer>({ version: '2.4.0' }), 'create' as Mode, 4 ],
[ Mock.of<ReachableServer>({ version: '2.3.0' }), 'create' as Mode, 4 ],
[ Mock.of<ReachableServer>({ version: '2.6.0' }), 'edit' as Mode, 4 ],
[ Mock.of<ReachableServer>({ version: '2.5.0' }), 'edit' as Mode, 3 ],
[ Mock.of<ReachableServer>({ version: '2.4.0' }), 'edit' as Mode, 3 ],
[ Mock.of<ReachableServer>({ version: '2.3.0' }), 'edit' as Mode, 2 ],
])(
'renders expected amount of cards based on server capabilities and mode',
(selectedServer, mode, expectedAmountOfCards) => {
const wrapper = createWrapper(selectedServer, mode);
const cards = wrapper.find(SimpleCard);
expect(cards).toHaveLength(expectedAmountOfCards);
},
);
}); });