diff --git a/CHANGELOG.md b/CHANGELOG.md index 25c52727..1e72bcf5 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). +## [Unreleased] +### Added +* [#671](https://github.com/shlinkio/shlink-web-client/pull/671) Added proper color-scheme in root element based on selected theme. + +### Changed +* *Nothing* + +### Deprecated +* *Nothing* + +### Removed +* *Nothing* + +### Fixed +* *Nothing* + + ## [3.7.1] - 2022-05-25 ### Added * *Nothing* diff --git a/src/index.scss b/src/index.scss index f6f09ed3..f9cb0643 100644 --- a/src/index.scss +++ b/src/index.scss @@ -13,6 +13,7 @@ :root { scroll-behavior: auto; + color-scheme: var(--color-scheme); } html, diff --git a/src/short-urls/EditShortUrl.tsx b/src/short-urls/EditShortUrl.tsx index 1eec0bf5..72ad44cc 100644 --- a/src/short-urls/EditShortUrl.tsx +++ b/src/short-urls/EditShortUrl.tsx @@ -5,7 +5,7 @@ import { faArrowLeft } from '@fortawesome/free-solid-svg-icons'; import { ExternalLink } from 'react-external-link'; import { useLocation, useParams } from 'react-router-dom'; import { SelectedServer } from '../servers/data'; -import { Settings, ShortUrlCreationSettings } from '../settings/reducers/settings'; +import { Settings } from '../settings/reducers/settings'; import { OptionalString } from '../utils/utils'; import { parseQuery } from '../utils/helpers/query'; import { Message } from '../utils/Message'; @@ -14,8 +14,9 @@ import { ShlinkApiError } from '../api/ShlinkApiError'; import { useGoBack, useToggle } from '../utils/helpers/hooks'; import { ShortUrlFormProps } from './ShortUrlForm'; import { ShortUrlDetail } from './reducers/shortUrlDetail'; -import { EditShortUrlData, ShortUrl, ShortUrlData } from './data'; +import { EditShortUrlData } from './data'; import { ShortUrlEdition } from './reducers/shortUrlEdition'; +import { shortUrlDataFromShortUrl } from './helpers'; interface EditShortUrlConnectProps { settings: Settings; @@ -26,27 +27,6 @@ interface EditShortUrlConnectProps { editShortUrl: (shortUrl: string, domain: OptionalString, data: EditShortUrlData) => Promise; } -const getInitialState = (shortUrl?: ShortUrl, settings?: ShortUrlCreationSettings): ShortUrlData => { - const validateUrl = settings?.validateUrls ?? false; - - if (!shortUrl) { - return { longUrl: '', validateUrl }; - } - - return { - longUrl: shortUrl.longUrl, - tags: shortUrl.tags, - title: shortUrl.title ?? undefined, - domain: shortUrl.domain ?? undefined, - validSince: shortUrl.meta.validSince ?? undefined, - validUntil: shortUrl.meta.validUntil ?? undefined, - maxVisits: shortUrl.meta.maxVisits ?? undefined, - crawlable: shortUrl.crawlable, - forwardQuery: shortUrl.forwardQuery, - validateUrl, - }; -}; - export const EditShortUrl = (ShortUrlForm: FC) => ({ settings: { shortUrlCreation: shortUrlCreationSettings }, selectedServer, @@ -62,7 +42,7 @@ export const EditShortUrl = (ShortUrlForm: FC) => ({ const { saving, error: savingError, errorData: savingErrorData } = shortUrlEdition; const { domain } = parseQuery<{ domain?: string }>(search); const initialState = useMemo( - () => getInitialState(shortUrl, shortUrlCreationSettings), + () => shortUrlDataFromShortUrl(shortUrl, shortUrlCreationSettings), [shortUrl, shortUrlCreationSettings], ); const [savingSucceeded,, isSuccessful, isNotSuccessful] = useToggle(); diff --git a/src/short-urls/helpers/index.ts b/src/short-urls/helpers/index.ts index e5994edb..c8074507 100644 --- a/src/short-urls/helpers/index.ts +++ b/src/short-urls/helpers/index.ts @@ -1,7 +1,8 @@ import { isNil } from 'ramda'; -import { ShortUrl } from '../data'; +import { ShortUrl, ShortUrlData } from '../data'; import { OptionalString } from '../../utils/utils'; import { DEFAULT_DOMAIN } from '../../visits/reducers/domainVisits'; +import { ShortUrlCreationSettings } from '../../settings/reducers/settings'; export const shortUrlMatches = (shortUrl: ShortUrl, shortCode: string, domain: OptionalString): boolean => { if (isNil(domain)) { @@ -18,3 +19,24 @@ export const domainMatches = (shortUrl: ShortUrl, domain: string): boolean => { return shortUrl.domain === domain; }; + +export const shortUrlDataFromShortUrl = (shortUrl?: ShortUrl, settings?: ShortUrlCreationSettings): ShortUrlData => { + const validateUrl = settings?.validateUrls ?? false; + + if (!shortUrl) { + return { longUrl: '', validateUrl }; + } + + return { + longUrl: shortUrl.longUrl, + tags: shortUrl.tags, + title: shortUrl.title ?? undefined, + domain: shortUrl.domain ?? undefined, + validSince: shortUrl.meta.validSince ?? undefined, + validUntil: shortUrl.meta.validUntil ?? undefined, + maxVisits: shortUrl.meta.maxVisits ?? undefined, + crawlable: shortUrl.crawlable, + forwardQuery: shortUrl.forwardQuery, + validateUrl, + }; +}; diff --git a/src/theme/theme.scss b/src/theme/theme.scss index aecf5243..7d797202 100644 --- a/src/theme/theme.scss +++ b/src/theme/theme.scss @@ -31,6 +31,7 @@ $darkBorderInputColor: $darkBorderColor; $darkTableHighlightColor: $darkBorderColor; html:not([data-theme='dark']) { + --color-scheme: initial; --primary-color: #{$lightPrimaryColor}; --primary-color-alfa: #{$lightPrimaryColorAlfa}; --secondary-color: #{$lightSecondaryColor}; @@ -48,6 +49,7 @@ html:not([data-theme='dark']) { } html[data-theme='dark'] { + --color-scheme: dark; --primary-color: #{$darkPrimaryColor}; --primary-color-alfa: #{$darkPrimaryColorAlfa}; --secondary-color: #{$darkSecondaryColor}; diff --git a/test/short-urls/EditShortUrl.test.tsx b/test/short-urls/EditShortUrl.test.tsx index 71fcb80e..e97152fd 100644 --- a/test/short-urls/EditShortUrl.test.tsx +++ b/test/short-urls/EditShortUrl.test.tsx @@ -1,107 +1,54 @@ -import { shallow, ShallowWrapper } from 'enzyme'; +import { render, screen } from '@testing-library/react'; +import { MemoryRouter } from 'react-router-dom'; import { Mock } from 'ts-mockery'; -import { useLocation, useParams } from 'react-router-dom'; import { EditShortUrl as createEditShortUrl } from '../../src/short-urls/EditShortUrl'; import { Settings } from '../../src/settings/reducers/settings'; import { ShortUrlDetail } from '../../src/short-urls/reducers/shortUrlDetail'; import { ShortUrlEdition } from '../../src/short-urls/reducers/shortUrlEdition'; -import { ShlinkApiError } from '../../src/api/ShlinkApiError'; import { ShortUrl } from '../../src/short-urls/data'; -jest.mock('react-router-dom', () => ({ - ...jest.requireActual('react-router-dom'), - useNavigate: jest.fn().mockReturnValue(jest.fn()), - useParams: jest.fn().mockReturnValue({}), - useLocation: jest.fn().mockReturnValue({}), -})); - describe('', () => { - let wrapper: ShallowWrapper; - const ShortUrlForm = () => null; - const getShortUrlDetail = jest.fn(); - const editShortUrl = jest.fn(async () => Promise.resolve()); const shortUrlCreation = { validateUrls: true }; - const EditShortUrl = createEditShortUrl(ShortUrlForm); - const createWrapper = (detail: Partial = {}, edition: Partial = {}) => { - (useParams as any).mockReturnValue({ shortCode: 'the_base_url' }); - (useLocation as any).mockReturnValue({ search: '' }); - - wrapper = shallow( + const EditShortUrl = createEditShortUrl(() => ShortUrlForm); + const setUp = (detail: Partial = {}, edition: Partial = {}) => render( + ({ shortUrlCreation })} selectedServer={null} shortUrlDetail={Mock.of(detail)} shortUrlEdition={Mock.of(edition)} - getShortUrlDetail={getShortUrlDetail} - editShortUrl={editShortUrl} - />, - ); - - return wrapper; - }; - - beforeEach(jest.clearAllMocks); - afterEach(() => wrapper?.unmount()); + getShortUrlDetail={jest.fn()} + editShortUrl={jest.fn(async () => Promise.resolve())} + /> + , + ); it('renders loading message while loading detail', () => { - const wrapper = createWrapper({ loading: true }); + setUp({ loading: true }); - expect(wrapper.prop('loading')).toEqual(true); + expect(screen.getByText('Loading...')).toBeInTheDocument(); + expect(screen.queryByText('ShortUrlForm')).not.toBeInTheDocument(); }); it('renders error when loading detail fails', () => { - const wrapper = createWrapper({ error: true }); - const form = wrapper.find(ShortUrlForm); - const apiError = wrapper.find(ShlinkApiError); + setUp({ error: true }); - expect(form).toHaveLength(0); - expect(apiError).toHaveLength(1); - expect(apiError.prop('fallbackMessage')).toEqual('An error occurred while loading short URL detail :('); + expect(screen.getByText('An error occurred while loading short URL detail :(')).toBeInTheDocument(); + expect(screen.queryByText('ShortUrlForm')).not.toBeInTheDocument(); }); - it.each([ - [undefined, { longUrl: '', validateUrl: true }, true], - [ - Mock.of({ meta: {} }), - { - longUrl: undefined, - tags: undefined, - title: undefined, - domain: undefined, - validSince: undefined, - validUntil: undefined, - maxVisits: undefined, - validateUrl: true, - }, - false, - ], - ])('renders form when detail properly loads', (shortUrl, expectedInitialState, saving) => { - const wrapper = createWrapper({ shortUrl }, { saving }); - const form = wrapper.find(ShortUrlForm); - const apiError = wrapper.find(ShlinkApiError); + it('renders form when detail properly loads', () => { + setUp({ shortUrl: Mock.of({ meta: {} }) }); - expect(form).toHaveLength(1); - expect(apiError).toHaveLength(0); - expect(form.prop('initialState')).toEqual(expectedInitialState); - expect(form.prop('saving')).toEqual(saving); - expect(editShortUrl).not.toHaveBeenCalled(); - - form.simulate('save', {}); - - if (shortUrl) { - expect(editShortUrl).toHaveBeenCalledWith(shortUrl.shortCode, shortUrl.domain, {}); - } else { - expect(editShortUrl).not.toHaveBeenCalled(); - } + expect(screen.getByText('ShortUrlForm')).toBeInTheDocument(); + expect(screen.queryByText('Loading...')).not.toBeInTheDocument(); + expect(screen.queryByText('An error occurred while loading short URL detail :(')).not.toBeInTheDocument(); }); it('shows error when saving data has failed', () => { - const wrapper = createWrapper({}, { error: true }); - const form = wrapper.find(ShortUrlForm); - const apiError = wrapper.find(ShlinkApiError); + setUp({}, { error: true }); - expect(form).toHaveLength(1); - expect(apiError).toHaveLength(1); - expect(apiError.prop('fallbackMessage')).toEqual('An error occurred while updating short URL :('); + expect(screen.getByText('An error occurred while updating short URL :(')).toBeInTheDocument(); + expect(screen.getByText('ShortUrlForm')).toBeInTheDocument(); }); }); diff --git a/test/short-urls/helpers/index.test.ts b/test/short-urls/helpers/index.test.ts new file mode 100644 index 00000000..29fcf80d --- /dev/null +++ b/test/short-urls/helpers/index.test.ts @@ -0,0 +1,28 @@ +import { Mock } from 'ts-mockery'; +import { ShortUrl } from '../../../src/short-urls/data'; +import { shortUrlDataFromShortUrl } from '../../../src/short-urls/helpers'; + +describe('helpers', () => { + describe('shortUrlDataFromShortUrl', () => { + it.each([ + [undefined, { validateUrls: true }, { longUrl: '', validateUrl: true }], + [undefined, undefined, { longUrl: '', validateUrl: false }], + [ + Mock.of({ meta: {} }), + { validateUrls: false }, + { + longUrl: undefined, + tags: undefined, + title: undefined, + domain: undefined, + validSince: undefined, + validUntil: undefined, + maxVisits: undefined, + validateUrl: false, + }, + ], + ])('returns expected data', (shortUrl, settings, expectedInitialState) => { + expect(shortUrlDataFromShortUrl(shortUrl, settings)).toEqual(expectedInitialState); + }); + }); +});