Compare commits

..

5 Commits

Author SHA1 Message Date
Alejandro Celaya
f19746cd58 Merge pull request #505 from acelaya-forks/feature/resettable-title
Ensured short URL title can be resetted after creation
2021-10-17 12:39:45 +02:00
Alejandro Celaya
85161915b1 Ensured short URL title can be resetted after creation 2021-10-17 12:35:11 +02:00
Alejandro Celaya
a295734c13 Merge pull request #499 from shlinkio/develop
Release 3.3.1
2021-09-27 22:57:46 +02:00
Alejandro Celaya
d00b6165b3 Merge pull request #498 from acelaya-forks/feature/fix-multi-dots
Ensured all dots are replaced from domain when generating its domain ID
2021-09-27 22:54:06 +02:00
Alejandro Celaya
0cbba1182f Ensured all dots are replaced from domain when generating its domain ID 2021-09-27 22:50:12 +02:00
6 changed files with 89 additions and 11 deletions

View File

@@ -4,6 +4,40 @@ 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*
### Changed
* *Nothing*
### Deprecated
* *Nothing*
### Removed
* *Nothing*
### Fixed
* [#497](https://github.com/shlinkio/shlink-web-client/issues/497) Fixed crash in domains section when one of the domains have more than one dot.
## [3.3.0] - 2021-09-25
### Added
* [#465](https://github.com/shlinkio/shlink-web-client/issues/465) Added new page to manage domains and their redirects, when consuming Shlink 2.8 or higher.

View File

@@ -33,7 +33,7 @@ const DefaultDomain: FC = () => (
export const DomainRow: FC<DomainRowProps> = ({ domain, editDomainRedirects, defaultRedirects }) => {
const [ isOpen, toggle ] = useToggle();
const { domain: authority, isDefault, redirects } = domain;
const domainId = `domainEdit${authority.replace('.', '')}`;
const domainId = `domainEdit${authority.replace(/\./g, '')}`;
return (
<tr className="responsive-table__row">

View File

@@ -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<ShortUrlFormProps> => ({ 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<never, OptionalString>([
[ () => !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(() => {

View File

@@ -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;

View File

@@ -17,9 +17,15 @@ describe('<DomainRow />', () => {
afterEach(() => wrapper?.unmount());
it.each([
[ Mock.of<ShlinkDomain>({ domain: '', isDefault: true }), 1 ],
[ Mock.of<ShlinkDomain>({ domain: '', isDefault: false }), 0 ],
])('shows proper components based on the fact that provided domain is default or not', (domain, expectedComps) => {
[ Mock.of<ShlinkDomain>({ domain: '', isDefault: true }), 1, 'domainEdit' ],
[ Mock.of<ShlinkDomain>({ domain: '', isDefault: false }), 0, '' ],
[ Mock.of<ShlinkDomain>({ domain: 'foo.com', isDefault: true }), 1, 'domainEditfoocom' ],
[ Mock.of<ShlinkDomain>({ domain: 'foo.bar.com', isDefault: true }), 1, 'domainEditfoobarcom' ],
])('shows proper components based on the fact that provided domain is default or not', (
domain,
expectedComps,
expectedDomainId,
) => {
const wrapper = createWrapper(domain);
const defaultDomainComp = wrapper.find('td').first().find('DefaultDomain');
const tooltip = wrapper.find(UncontrolledTooltip);
@@ -27,9 +33,13 @@ describe('<DomainRow />', () => {
const icon = wrapper.find(FontAwesomeIcon);
expect(defaultDomainComp).toHaveLength(expectedComps);
expect(tooltip).toHaveLength(expectedComps);
expect(button.prop('disabled')).toEqual(domain.isDefault);
expect(icon.prop('icon')).toEqual(domain.isDefault ? forbiddenIcon : editIcon);
expect(tooltip).toHaveLength(expectedComps);
if (expectedComps > 0) {
expect(tooltip.prop('target')).toEqual(expectedDomainId);
}
});
it.each([

View File

@@ -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('<ShortUrlForm />', () => {
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('<ShortUrlForm />', () => {
selectedServer={selectedServer}
mode={mode}
saving={false}
initialState={Mock.of<ShortUrlData>({ validateUrl: true, findIfExists: false })}
initialState={Mock.of<ShortUrlData>({ validateUrl: true, findIfExists: false, title })}
onSave={createShortUrl}
/>,
);
@@ -80,4 +81,26 @@ describe('<ShortUrlForm />', () => {
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<ReachableServer>({ 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,
}));
});
});