diff --git a/CHANGELOG.md b/CHANGELOG.md index ff545e4a..3fd5ce97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,23 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org). +## [3.3.2] - 2021-10-17 +### Added +* *Nothing* + +### Changed +* *Nothing* + +### Deprecated +* *Nothing* + +### Removed +* *Nothing* + +### Fixed +* [#503](https://github.com/shlinkio/shlink-web-client/issues/503) Fixed short URLs title not being resettable after creation. + + ## [3.3.1] - 2021-09-27 ### Added * *Nothing* diff --git a/src/short-urls/ShortUrlForm.tsx b/src/short-urls/ShortUrlForm.tsx index b09a70cb..03a3b51d 100644 --- a/src/short-urls/ShortUrlForm.tsx +++ b/src/short-urls/ShortUrlForm.tsx @@ -1,13 +1,13 @@ import { FC, useEffect, useState } from 'react'; import { InputType } from 'reactstrap/lib/Input'; import { Button, FormGroup, Input, Row } from 'reactstrap'; -import { isEmpty, pipe, replace, trim } from 'ramda'; +import { cond, isEmpty, pipe, replace, trim, T } from 'ramda'; import classNames from 'classnames'; import { parseISO } from 'date-fns'; import DateInput, { DateInputProps } from '../utils/DateInput'; import { supportsCrawlableVisits, supportsShortUrlTitle } from '../utils/helpers/features'; import { SimpleCard } from '../utils/SimpleCard'; -import { handleEventPreventingDefault, hasValue } from '../utils/utils'; +import { handleEventPreventingDefault, hasValue, OptionalString } from '../utils/utils'; import Checkbox from '../utils/Checkbox'; import { SelectedServer } from '../servers/data'; import { TagsSelectorProps } from '../tags/helpers/TagsSelector'; @@ -40,14 +40,25 @@ export const ShortUrlForm = ( ): FC => ({ mode, saving, onSave, initialState, selectedServer }) => { const [ shortUrlData, setShortUrlData ] = useState(initialState); const isEdit = mode === 'edit'; + const hadTitleOriginally = hasValue(initialState.title); const changeTags = (tags: string[]) => setShortUrlData({ ...shortUrlData, tags: tags.map(normalizeTag) }); const reset = () => setShortUrlData(initialState); + const resolveNewTitle = (): OptionalString => { + const hasNewTitle = hasValue(shortUrlData.title); + const matcher = cond([ + [ () => !hasNewTitle && !hadTitleOriginally, () => undefined ], + [ () => !hasNewTitle && hadTitleOriginally, () => null ], + [ T, () => shortUrlData.title ], + ]); + + return matcher(); + }; const submit = handleEventPreventingDefault(async () => onSave({ ...shortUrlData, validSince: formatIsoDate(shortUrlData.validSince) ?? null, validUntil: formatIsoDate(shortUrlData.validUntil) ?? null, maxVisits: !hasValue(shortUrlData.maxVisits) ? null : Number(shortUrlData.maxVisits), - title: !hasValue(shortUrlData.title) ? undefined : shortUrlData.title, + title: resolveNewTitle(), }).then(() => !isEdit && reset()).catch(() => {})); useEffect(() => { diff --git a/src/short-urls/data/index.ts b/src/short-urls/data/index.ts index c0fded8b..c7a1899a 100644 --- a/src/short-urls/data/index.ts +++ b/src/short-urls/data/index.ts @@ -3,7 +3,7 @@ import { Nullable, OptionalString } from '../../utils/utils'; export interface EditShortUrlData { longUrl?: string; tags?: string[]; - title?: string; + title?: string | null; validSince?: Date | string | null; validUntil?: Date | string | null; maxVisits?: number | null; diff --git a/test/short-urls/ShortUrlForm.test.tsx b/test/short-urls/ShortUrlForm.test.tsx index 66ae1fc1..9f125216 100644 --- a/test/short-urls/ShortUrlForm.test.tsx +++ b/test/short-urls/ShortUrlForm.test.tsx @@ -9,13 +9,14 @@ import { ShortUrlData } from '../../src/short-urls/data'; import { ReachableServer, SelectedServer } from '../../src/servers/data'; import { SimpleCard } from '../../src/utils/SimpleCard'; import { parseDate } from '../../src/utils/helpers/date'; +import { OptionalString } from '../../src/utils/utils'; describe('', () => { let wrapper: ShallowWrapper; const TagsSelector = () => null; const DomainSelector = () => null; const createShortUrl = jest.fn(async () => Promise.resolve()); - const createWrapper = (selectedServer: SelectedServer = null, mode: Mode = 'create') => { + const createWrapper = (selectedServer: SelectedServer = null, mode: Mode = 'create', title?: OptionalString) => { const ShortUrlForm = createShortUrlForm(TagsSelector, DomainSelector); wrapper = shallow( @@ -23,7 +24,7 @@ describe('', () => { selectedServer={selectedServer} mode={mode} saving={false} - initialState={Mock.of({ validateUrl: true, findIfExists: false })} + initialState={Mock.of({ validateUrl: true, findIfExists: false, title })} onSave={createShortUrl} />, ); @@ -80,4 +81,26 @@ describe('', () => { expect(cards).toHaveLength(expectedAmountOfCards); }, ); + + it.each([ + [ null, 'new title', 'new title' ], + [ undefined, 'new title', 'new title' ], + [ '', 'new title', 'new title' ], + [ null, '', undefined ], + [ null, null, undefined ], + [ '', '', undefined ], + [ undefined, undefined, undefined ], + [ 'old title', null, null ], + [ 'old title', undefined, null ], + [ 'old title', '', null ], + ])('sends expected title based on original and new values', (originalTitle, newTitle, expectedSentTitle) => { + const wrapper = createWrapper(Mock.of({ version: '2.6.0' }), 'create', originalTitle); + + wrapper.find('#title').simulate('change', { target: { value: newTitle } }); + wrapper.find('form').simulate('submit', { preventDefault: identity }); + + expect(createShortUrl).toHaveBeenCalledWith(expect.objectContaining({ + title: expectedSentTitle, + })); + }); });