mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2026-05-07 02:32:28 -05:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f19746cd58 | ||
|
|
85161915b1 | ||
|
|
a295734c13 | ||
|
|
d00b6165b3 | ||
|
|
0cbba1182f |
34
CHANGELOG.md
34
CHANGELOG.md
@@ -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.
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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([
|
||||
|
||||
@@ -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,
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user