diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx index e9d3369f..81d047ed 100644 --- a/src/settings/Settings.tsx +++ b/src/settings/Settings.tsx @@ -16,13 +16,20 @@ const SettingsSections: FC<{ items: ReactNode[][] }> = ({ items }) => ( ); -const Settings = (RealTimeUpdates: FC, ShortUrlCreation: FC, UserInterface: FC, Visits: FC, Tags: FC) => () => ( +const Settings = ( + RealTimeUpdates: FC, + ShortUrlCreation: FC, + ShortUrlsList: FC, + UserInterface: FC, + Visits: FC, + Tags: FC, +) => () => ( ], // eslint-disable-line react/jsx-key - [ , ], // eslint-disable-line react/jsx-key - [ , ], // eslint-disable-line react/jsx-key + [ , ], // eslint-disable-line react/jsx-key + [ , ], // eslint-disable-line react/jsx-key + [ , ], // eslint-disable-line react/jsx-key ]} /> diff --git a/src/settings/ShortUrlsList.tsx b/src/settings/ShortUrlsList.tsx new file mode 100644 index 00000000..aadbb502 --- /dev/null +++ b/src/settings/ShortUrlsList.tsx @@ -0,0 +1,24 @@ +import { FC } from 'react'; +import { FormGroup } from 'reactstrap'; +import SortingDropdown from '../utils/SortingDropdown'; +import { SORTABLE_FIELDS } from '../short-urls/data'; +import { SimpleCard } from '../utils/SimpleCard'; +import { DEFAULT_SHORT_URLS_ORDERING, Settings, ShortUrlsListSettings } from './reducers/settings'; + +interface ShortUrlsListProps { + settings: Settings; + setShortUrlsListSettings: (settings: ShortUrlsListSettings) => void; +} + +export const ShortUrlsList: FC = ({ settings: { shortUrlsList }, setShortUrlsListSettings }) => ( + + + + setShortUrlsListSettings({ defaultOrdering: { field, dir } })} + /> + + +); diff --git a/src/settings/Tags.tsx b/src/settings/Tags.tsx index 63bc4aab..cbbd2af8 100644 --- a/src/settings/Tags.tsx +++ b/src/settings/Tags.tsx @@ -13,7 +13,7 @@ interface TagsProps { } export const Tags: FC = ({ settings: { tags }, setTagsSettings }) => ( - + ({ + type: SET_SETTINGS, + shortUrlsList: settings, +}); + export const setUiSettings = (settings: UiSettings): PartialSettingsAction => ({ type: SET_SETTINGS, ui: settings, diff --git a/src/settings/services/provideServices.ts b/src/settings/services/provideServices.ts index 93d82584..c54d37aa 100644 --- a/src/settings/services/provideServices.ts +++ b/src/settings/services/provideServices.ts @@ -4,6 +4,7 @@ import Settings from '../Settings'; import { setRealTimeUpdatesInterval, setShortUrlCreationSettings, + setShortUrlsListSettings, setTagsSettings, setUiSettings, setVisitsSettings, @@ -15,10 +16,20 @@ import { ShortUrlCreation } from '../ShortUrlCreation'; import { UserInterface } from '../UserInterface'; import { Visits } from '../Visits'; import { Tags } from '../Tags'; +import { ShortUrlsList } from '../ShortUrlsList'; const provideServices = (bottle: Bottle, connect: ConnectDecorator) => { // Components - bottle.serviceFactory('Settings', Settings, 'RealTimeUpdates', 'ShortUrlCreation', 'UserInterface', 'Visits', 'Tags'); + bottle.serviceFactory( + 'Settings', + Settings, + 'RealTimeUpdates', + 'ShortUrlCreation', + 'ShortUrlsListSettings', + 'UserInterface', + 'Visits', + 'Tags', + ); bottle.decorator('Settings', withoutSelectedServer); bottle.decorator('Settings', connect(null, [ 'resetSelectedServer' ])); @@ -40,10 +51,14 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator) => { bottle.serviceFactory('Tags', () => Tags); bottle.decorator('Tags', connect([ 'settings' ], [ 'setTagsSettings' ])); + bottle.serviceFactory('ShortUrlsListSettings', () => ShortUrlsList); + bottle.decorator('ShortUrlsListSettings', connect([ 'settings' ], [ 'setShortUrlsListSettings' ])); + // Actions bottle.serviceFactory('toggleRealTimeUpdates', () => toggleRealTimeUpdates); bottle.serviceFactory('setRealTimeUpdatesInterval', () => setRealTimeUpdatesInterval); bottle.serviceFactory('setShortUrlCreationSettings', () => setShortUrlCreationSettings); + bottle.serviceFactory('setShortUrlsListSettings', () => setShortUrlsListSettings); bottle.serviceFactory('setUiSettings', () => setUiSettings); bottle.serviceFactory('setVisitsSettings', () => setVisitsSettings); bottle.serviceFactory('setTagsSettings', () => setTagsSettings); diff --git a/src/short-urls/ShortUrlsList.tsx b/src/short-urls/ShortUrlsList.tsx index b3e63943..09227fff 100644 --- a/src/short-urls/ShortUrlsList.tsx +++ b/src/short-urls/ShortUrlsList.tsx @@ -33,7 +33,7 @@ const ShortUrlsList = (ShortUrlsTable: FC, SearchBar: FC) = settings, }: ShortUrlsListProps) => { const serverId = getServerId(selectedServer); - const initialOrderBy = settings.shortUrlList?.defaultOrdering ?? DEFAULT_SHORT_URLS_ORDERING; + const initialOrderBy = settings.shortUrlsList?.defaultOrdering ?? DEFAULT_SHORT_URLS_ORDERING; const [ order, setOrder ] = useState(initialOrderBy); const [{ tags, search, startDate, endDate }, toFirstPage ] = useShortUrlsQuery({ history, match, location }); const selectedTags = useMemo(() => tags?.split(',') ?? [], [ tags ]); diff --git a/test/settings/Settings.test.tsx b/test/settings/Settings.test.tsx index e8f3560c..0810cf8f 100644 --- a/test/settings/Settings.test.tsx +++ b/test/settings/Settings.test.tsx @@ -4,7 +4,7 @@ import NoMenuLayout from '../../src/common/NoMenuLayout'; describe('', () => { const Component = () => null; - const Settings = createSettings(Component, Component, Component, Component, Component); + const Settings = createSettings(Component, Component, Component, Component, Component, Component); it('renders a no-menu layout with the expected settings sections', () => { const wrapper = shallow(); @@ -13,6 +13,6 @@ describe('', () => { expect(layout).toHaveLength(1); expect(sections).toHaveLength(1); - expect((sections.prop('items') as any[]).flat()).toHaveLength(5); + expect((sections.prop('items') as any[]).flat()).toHaveLength(6); }); }); diff --git a/test/settings/ShortUrlsList.test.tsx b/test/settings/ShortUrlsList.test.tsx new file mode 100644 index 00000000..3c1136de --- /dev/null +++ b/test/settings/ShortUrlsList.test.tsx @@ -0,0 +1,48 @@ +import { shallow, ShallowWrapper } from 'enzyme'; +import { Mock } from 'ts-mockery'; +import { DEFAULT_SHORT_URLS_ORDERING, Settings, ShortUrlsListSettings } from '../../src/settings/reducers/settings'; +import { ShortUrlsList } from '../../src/settings/ShortUrlsList'; +import SortingDropdown from '../../src/utils/SortingDropdown'; +import { ShortUrlsOrder } from '../../src/short-urls/data'; + +describe('', () => { + let wrapper: ShallowWrapper; + const setSettings = jest.fn(); + const createWrapper = (shortUrlsList?: ShortUrlsListSettings) => { + wrapper = shallow( + ({ shortUrlsList })} setShortUrlsListSettings={setSettings} />, + ); + + return wrapper; + }; + + afterEach(() => wrapper?.unmount()); + afterEach(jest.clearAllMocks); + + it.each([ + [ undefined, DEFAULT_SHORT_URLS_ORDERING ], + [{}, DEFAULT_SHORT_URLS_ORDERING ], + [{ defaultOrdering: {} }, {}], + [{ defaultOrdering: { field: 'longUrl', dir: 'DESC' } as ShortUrlsOrder }, { field: 'longUrl', dir: 'DESC' }], + [{ defaultOrdering: { field: 'visits', dir: 'ASC' } as ShortUrlsOrder }, { field: 'visits', dir: 'ASC' }], + ])('shows expected ordering', (shortUrlsList, expectedOrder) => { + const wrapper = createWrapper(shortUrlsList); + const dropdown = wrapper.find(SortingDropdown); + + expect(dropdown.prop('order')).toEqual(expectedOrder); + }); + + it.each([ + [ undefined, undefined ], + [ 'longUrl', 'ASC' ], + [ 'visits', undefined ], + [ 'title', 'DESC' ], + ])('invokes setSettings when ordering changes', (field, dir) => { + const wrapper = createWrapper(); + const dropdown = wrapper.find(SortingDropdown); + + expect(setSettings).not.toHaveBeenCalled(); + dropdown.simulate('change', field, dir); + expect(setSettings).toHaveBeenCalledWith({ defaultOrdering: { field, dir } }); + }); +}); diff --git a/test/settings/reducers/settings.test.ts b/test/settings/reducers/settings.test.ts index 272e8905..09a7b14c 100644 --- a/test/settings/reducers/settings.test.ts +++ b/test/settings/reducers/settings.test.ts @@ -7,6 +7,7 @@ import reducer, { setUiSettings, setVisitsSettings, setTagsSettings, + setShortUrlsListSettings, } from '../../../src/settings/reducers/settings'; describe('settingsReducer', () => { @@ -14,8 +15,8 @@ describe('settingsReducer', () => { const shortUrlCreation = { validateUrls: false }; const ui = { theme: 'light' }; const visits = { defaultInterval: 'last30Days' }; - const shortUrlList = { defaultOrdering: DEFAULT_SHORT_URLS_ORDERING }; - const settings = { realTimeUpdates, shortUrlCreation, ui, visits, shortUrlList }; + const shortUrlsList = { defaultOrdering: DEFAULT_SHORT_URLS_ORDERING }; + const settings = { realTimeUpdates, shortUrlCreation, ui, visits, shortUrlsList }; describe('reducer', () => { it('returns realTimeUpdates when action is SET_SETTINGS', () => { @@ -70,4 +71,12 @@ describe('settingsReducer', () => { expect(result).toEqual({ type: SET_SETTINGS, tags: { defaultMode: 'list' } }); }); }); + + describe('setShortUrlsListSettings', () => { + it('creates action to set short URLs list settings', () => { + const result = setShortUrlsListSettings({ defaultOrdering: DEFAULT_SHORT_URLS_ORDERING }); + + expect(result).toEqual({ type: SET_SETTINGS, shortUrlsList: { defaultOrdering: DEFAULT_SHORT_URLS_ORDERING } }); + }); + }); }); diff --git a/test/short-urls/ShortUrlsList.test.tsx b/test/short-urls/ShortUrlsList.test.tsx index e6379e14..3602667e 100644 --- a/test/short-urls/ShortUrlsList.test.tsx +++ b/test/short-urls/ShortUrlsList.test.tsx @@ -41,7 +41,7 @@ describe('', () => { shortUrlsList={shortUrlsList} history={Mock.of({ push })} selectedServer={Mock.of({ id: '1' })} - settings={Mock.of({ shortUrlList: { defaultOrdering } })} + settings={Mock.of({ shortUrlsList: { defaultOrdering } })} />, ).dive(); // Dive is needed as this component is wrapped in a HOC