diff --git a/src/settings/RealTimeUpdates.tsx b/src/settings/RealTimeUpdates.tsx index 46ac074e..8284eeaf 100644 --- a/src/settings/RealTimeUpdates.tsx +++ b/src/settings/RealTimeUpdates.tsx @@ -34,7 +34,7 @@ const RealTimeUpdates = ( placeholder="Immediate" disabled={!realTimeUpdates.enabled} value={intervalValue(realTimeUpdates.interval)} - onChange={(e) => setRealTimeUpdatesInterval(Number(e.target.value))} + onChange={({ target }) => setRealTimeUpdatesInterval(Number(target.value))} /> {realTimeUpdates.enabled && ( diff --git a/src/settings/reducers/settings.ts b/src/settings/reducers/settings.ts index a798697b..f0505001 100644 --- a/src/settings/reducers/settings.ts +++ b/src/settings/reducers/settings.ts @@ -12,7 +12,7 @@ export const SET_SETTINGS = 'shlink/realTimeUpdates/SET_SETTINGS'; * optional, as old instances of the app will load partial objects from local storage until it is saved again. */ -interface RealTimeUpdatesSettings { +export interface RealTimeUpdatesSettings { enabled: boolean; interval?: number; } diff --git a/test/settings/RealTimeUpdates.test.tsx b/test/settings/RealTimeUpdates.test.tsx new file mode 100644 index 00000000..89e3ccb5 --- /dev/null +++ b/test/settings/RealTimeUpdates.test.tsx @@ -0,0 +1,104 @@ +import { shallow, ShallowWrapper } from 'enzyme'; +import { Mock } from 'ts-mockery'; +import { Input } from 'reactstrap'; +import { RealTimeUpdatesSettings, Settings } from '../../src/settings/reducers/settings'; +import RealTimeUpdates from '../../src/settings/RealTimeUpdates'; +import ToggleSwitch from '../../src/utils/ToggleSwitch'; + +describe('', () => { + const toggleRealTimeUpdates = jest.fn(); + const setRealTimeUpdatesInterval = jest.fn(); + let wrapper: ShallowWrapper; + const createWrapper = (realTimeUpdates: Partial = {}) => { + const settings = Mock.of({ realTimeUpdates }); + + wrapper = shallow( + , + ); + + return wrapper; + }; + + afterEach(jest.clearAllMocks); + afterEach(() => wrapper?.unmount()); + + test('enabled real time updates are rendered as expected', () => { + const wrapper = createWrapper({ enabled: true }); + const toggle = wrapper.find(ToggleSwitch); + const label = wrapper.find('label'); + const input = wrapper.find(Input); + const small = wrapper.find('small'); + + expect(toggle.prop('checked')).toEqual(true); + expect(toggle.html()).toContain('processed'); + expect(toggle.html()).not.toContain('ignored'); + expect(label.prop('className')).toEqual(''); + expect(input.prop('disabled')).toEqual(false); + expect(small).toHaveLength(2); + }); + + test('disabled real time updates are rendered as expected', () => { + const wrapper = createWrapper({ enabled: false }); + const toggle = wrapper.find(ToggleSwitch); + const label = wrapper.find('label'); + const input = wrapper.find(Input); + const small = wrapper.find('small'); + + expect(toggle.prop('checked')).toEqual(false); + expect(toggle.html()).not.toContain('processed'); + expect(toggle.html()).toContain('ignored'); + expect(label.prop('className')).toEqual('text-muted'); + expect(input.prop('disabled')).toEqual(true); + expect(small).toHaveLength(1); + }); + + test.each([ + [ 1, 'minute' ], + [ 2, 'minutes' ], + [ 10, 'minutes' ], + [ 100, 'minutes' ], + ])('expected children are shown when interval is greater than 0', (interval, minutesWord) => { + const wrapper = createWrapper({ enabled: true, interval }); + const span = wrapper.find('span'); + const input = wrapper.find(Input); + + expect(span).toHaveLength(1); + expect(span.html()).toEqual( + `Updates will be reflected in the UI every ${interval} ${minutesWord}.`, + ); + expect(input.prop('value')).toEqual(`${interval}`); + }); + + test.each([[ undefined ], [ 0 ]])('expected children are shown when interval is 0 or undefined', (interval) => { + const wrapper = createWrapper({ enabled: true, interval }); + const span = wrapper.find('span'); + const small = wrapper.find('small').at(1); + const input = wrapper.find(Input); + + expect(span).toHaveLength(0); + expect(small.html()).toContain('Updates will be reflected in the UI as soon as they happen.'); + expect(input.prop('value')).toEqual(''); + }); + + test('real time updates are updated on input change', () => { + const wrapper = createWrapper(); + const input = wrapper.find(Input); + + expect(setRealTimeUpdatesInterval).not.toHaveBeenCalled(); + input.simulate('change', { target: { value: '10' } }); + expect(setRealTimeUpdatesInterval).toHaveBeenCalledWith(10); + }); + + test('real time updates are toggled on switch change', () => { + const wrapper = createWrapper(); + const toggle = wrapper.find(ToggleSwitch); + + expect(toggleRealTimeUpdates).not.toHaveBeenCalled(); + toggle.simulate('change'); + expect(toggleRealTimeUpdates).toHaveBeenCalled(); + }); +}); diff --git a/test/settings/Settings.test.tsx b/test/settings/Settings.test.tsx new file mode 100644 index 00000000..e947fb7d --- /dev/null +++ b/test/settings/Settings.test.tsx @@ -0,0 +1,18 @@ +import { shallow } from 'enzyme'; +import createSettings from '../../src/settings/Settings'; +import NoMenuLayout from '../../src/common/NoMenuLayout'; + +describe('', () => { + const Component = () => null; + const Settings = createSettings(Component, Component, Component, Component); + + test('a no-menu layout is renders with the four settings sections', () => { + const wrapper = shallow(); + const layout = wrapper.find(NoMenuLayout); + const sections = wrapper.find('SettingsSections'); + + expect(layout).toHaveLength(1); + expect(sections).toHaveLength(1); + expect((sections.prop('items') as any[]).flat()).toHaveLength(4); // eslint-disable-line @typescript-eslint/no-unnecessary-type-assertion + }); +});