Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/renderer/__mocks__/state-mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ const mockAppearanceSettings: AppearanceSettingsState = {
const mockNotificationSettings: NotificationSettingsState = {
groupBy: GroupBy.REPOSITORY,
fetchType: FetchType.INTERVAL,
fetchInterval: Constants.DEFAULT_FETCH_NOTIFICATIONS_INTERVAL_MS,
fetchAllNotifications: true,
detailedNotifications: true,
showPills: true,
Expand Down
156 changes: 156 additions & 0 deletions src/renderer/components/settings/NotificationSettings.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { act, render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

import { mockAuth, mockSettings } from '../../__mocks__/state-mocks';
import { Constants } from '../../constants';
import { AppContext } from '../../context/App';
import * as comms from '../../utils/comms';
import { NotificationSettings } from './NotificationSettings';
Expand Down Expand Up @@ -55,6 +56,161 @@ describe('renderer/components/settings/NotificationSettings.tsx', () => {
expect(updateSetting).toHaveBeenCalledWith('fetchType', 'INACTIVITY');
});

describe('fetch interval settings', () => {
it('should update the fetch interval values when using the buttons', async () => {
await act(async () => {
render(
<AppContext.Provider
value={{
auth: mockAuth,
settings: mockSettings,
updateSetting,
}}
>
<NotificationSettings />
</AppContext.Provider>,
);
});

// Increase fetch interval
await act(async () => {
await userEvent.click(
screen.getByTestId('settings-fetch-interval-increase'),
);

expect(updateSetting).toHaveBeenCalledTimes(1);
expect(updateSetting).toHaveBeenCalledWith('fetchInterval', 120000);
});

await act(async () => {
await userEvent.click(
screen.getByTestId('settings-fetch-interval-increase'),
);

expect(updateSetting).toHaveBeenCalledTimes(2);
expect(updateSetting).toHaveBeenNthCalledWith(
2,
'fetchInterval',
180000,
);
});

// Decrease fetch interval
await act(async () => {
await userEvent.click(
screen.getByTestId('settings-fetch-interval-decrease'),
);

expect(updateSetting).toHaveBeenCalledTimes(3);
expect(updateSetting).toHaveBeenNthCalledWith(
3,
'fetchInterval',
120000,
);
});

// Fetch interval reset
await act(async () => {
await userEvent.click(
screen.getByTestId('settings-fetch-interval-reset'),
);

expect(updateSetting).toHaveBeenCalledTimes(4);
expect(updateSetting).toHaveBeenNthCalledWith(
4,
'fetchInterval',
60000,
);
});
});

it('should prevent going lower than minimum interval', async () => {
await act(async () => {
render(
<AppContext.Provider
value={{
auth: mockAuth,
settings: {
...mockSettings,
fetchInterval:
Constants.MIN_FETCH_NOTIFICATIONS_INTERVAL_MS +
Constants.FETCH_NOTIFICATIONS_INTERVAL_STEP_MS,
},
updateSetting,
}}
>
<NotificationSettings />
</AppContext.Provider>,
);
});

await act(async () => {
await userEvent.click(
screen.getByTestId('settings-fetch-interval-decrease'),
);

expect(updateSetting).toHaveBeenCalledTimes(1);
expect(updateSetting).toHaveBeenNthCalledWith(
1,
'fetchInterval',
60000,
);
});

// Attempt to go below the minimum interval, update settings should not be called
await act(async () => {
await userEvent.click(
screen.getByTestId('settings-fetch-interval-decrease'),
);

expect(updateSetting).toHaveBeenCalledTimes(1);
});
});

it('should prevent going above maximum interval', async () => {
await act(async () => {
render(
<AppContext.Provider
value={{
auth: mockAuth,
settings: {
...mockSettings,
fetchInterval:
Constants.MAX_FETCH_NOTIFICATIONS_INTERVAL_MS -
Constants.FETCH_NOTIFICATIONS_INTERVAL_STEP_MS,
},
updateSetting,
}}
>
<NotificationSettings />
</AppContext.Provider>,
);
});

await act(async () => {
await userEvent.click(
screen.getByTestId('settings-fetch-interval-increase'),
);

expect(updateSetting).toHaveBeenCalledTimes(1);
expect(updateSetting).toHaveBeenNthCalledWith(
1,
'fetchInterval',
3600000,
);
});

// Attempt to go above the maximum interval, update settings should not be called
await act(async () => {
await userEvent.click(
screen.getByTestId('settings-fetch-interval-increase'),
);

expect(updateSetting).toHaveBeenCalledTimes(1);
});
});
});

it('should toggle the fetchAllNotifications checkbox', async () => {
await act(async () => {
render(
Expand Down
99 changes: 97 additions & 2 deletions src/renderer/components/settings/NotificationSettings.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,47 @@
import { type FC, type MouseEvent, useContext } from 'react';
import {
type FC,
type MouseEvent,
useContext,
useEffect,
useState,
} from 'react';

import {
BellIcon,
CheckIcon,
CommentIcon,
DashIcon,
GitPullRequestIcon,
IssueOpenedIcon,
MilestoneIcon,
PlusIcon,
SyncIcon,
TagIcon,
} from '@primer/octicons-react';
import { Stack, Text } from '@primer/react';
import { Button, ButtonGroup, IconButton, Stack, Text } from '@primer/react';

import { formatDuration, millisecondsToMinutes } from 'date-fns';

import { APPLICATION } from '../../../shared/constants';

import { Constants } from '../../constants';
import { AppContext } from '../../context/App';
import { FetchType, GroupBy, Size } from '../../types';
import { openGitHubParticipatingDocs } from '../../utils/links';
import { Checkbox } from '../fields/Checkbox';
import { FieldLabel } from '../fields/FieldLabel';
import { RadioGroup } from '../fields/RadioGroup';
import { Title } from '../primitives/Title';

export const NotificationSettings: FC = () => {
const { settings, updateSetting } = useContext(AppContext);
const [fetchInterval, setFetchInterval] = useState<number>(
settings.fetchInterval,
);

useEffect(() => {
setFetchInterval(settings.fetchInterval);
}, [settings.fetchInterval]);

return (
<fieldset>
Expand Down Expand Up @@ -68,6 +88,81 @@ export const NotificationSettings: FC = () => {
value={settings.fetchType}
/>

<Stack
align="center"
className="text-sm"
direction="horizontal"
gap="condensed"
>
<FieldLabel label="Fetch interval:" name="fetchInterval" />

<ButtonGroup className="ml-2">
<IconButton
aria-label="Decrease fetch interval"
data-testid="settings-fetch-interval-decrease"
icon={DashIcon}
onClick={() => {
const newInterval = Math.max(
fetchInterval -
Constants.FETCH_NOTIFICATIONS_INTERVAL_STEP_MS,
Constants.MIN_FETCH_NOTIFICATIONS_INTERVAL_MS,
);

if (newInterval !== fetchInterval) {
setFetchInterval(newInterval);
updateSetting('fetchInterval', newInterval);
}
}}
size="small"
unsafeDisableTooltip={true}
/>

<Button aria-label="Fetch interval" disabled size="small">
{formatDuration({
minutes: millisecondsToMinutes(fetchInterval),
})}
</Button>

<IconButton
aria-label="Increase fetch interval"
data-testid="settings-fetch-interval-increase"
icon={PlusIcon}
onClick={() => {
const newInterval = Math.min(
fetchInterval +
Constants.FETCH_NOTIFICATIONS_INTERVAL_STEP_MS,
Constants.MAX_FETCH_NOTIFICATIONS_INTERVAL_MS,
);

if (newInterval !== fetchInterval) {
setFetchInterval(newInterval);
updateSetting('fetchInterval', newInterval);
}
}}
size="small"
unsafeDisableTooltip={true}
/>

<IconButton
aria-label="Reset fetch interval"
data-testid="settings-fetch-interval-reset"
icon={SyncIcon}
onClick={() => {
setFetchInterval(
Constants.DEFAULT_FETCH_NOTIFICATIONS_INTERVAL_MS,
);
updateSetting(
'fetchInterval',
Constants.DEFAULT_FETCH_NOTIFICATIONS_INTERVAL_MS,
);
}}
size="small"
unsafeDisableTooltip={true}
variant="danger"
/>
</ButtonGroup>
</Stack>

<Checkbox
checked={settings.fetchAllNotifications}
label="Fetch all notifications"
Expand Down
5 changes: 4 additions & 1 deletion src/renderer/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ export const Constants = {

ALL_READ_EMOJIS: ['🎉', '🎊', '🥳', '👏', '🙌', '😎', '🏖️', '🚀', '✨', '🏆'],

FETCH_NOTIFICATIONS_INTERVAL_MS: 60 * 1000, // 1 minute
DEFAULT_FETCH_NOTIFICATIONS_INTERVAL_MS: 60 * 1000, // 1 minute
MIN_FETCH_NOTIFICATIONS_INTERVAL_MS: 60 * 1000, // 1 minute
MAX_FETCH_NOTIFICATIONS_INTERVAL_MS: 60 * 60 * 1000, // 1 hour
FETCH_NOTIFICATIONS_INTERVAL_STEP_MS: 60 * 1000, // 1 minute

REFRESH_ACCOUNTS_INTERVAL_MS: 60 * 60 * 1000, // 1 hour

Expand Down
12 changes: 9 additions & 3 deletions src/renderer/context/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,19 +77,25 @@ describe('renderer/context/App.tsx', () => {
);

act(() => {
jest.advanceTimersByTime(Constants.FETCH_NOTIFICATIONS_INTERVAL_MS);
jest.advanceTimersByTime(
Constants.DEFAULT_FETCH_NOTIFICATIONS_INTERVAL_MS,
);
return;
});
expect(fetchNotificationsMock).toHaveBeenCalledTimes(2);

act(() => {
jest.advanceTimersByTime(Constants.FETCH_NOTIFICATIONS_INTERVAL_MS);
jest.advanceTimersByTime(
Constants.DEFAULT_FETCH_NOTIFICATIONS_INTERVAL_MS,
);
return;
});
expect(fetchNotificationsMock).toHaveBeenCalledTimes(3);

act(() => {
jest.advanceTimersByTime(Constants.FETCH_NOTIFICATIONS_INTERVAL_MS);
jest.advanceTimersByTime(
Constants.DEFAULT_FETCH_NOTIFICATIONS_INTERVAL_MS,
);
return;
});
expect(fetchNotificationsMock).toHaveBeenCalledTimes(4);
Expand Down
8 changes: 2 additions & 6 deletions src/renderer/context/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -148,18 +148,14 @@ export const AppProvider = ({ children }: { children: ReactNode }) => {
() => {
fetchNotifications({ auth, settings });
},
settings.fetchType === FetchType.INTERVAL
? Constants.FETCH_NOTIFICATIONS_INTERVAL_MS
: null,
settings.fetchType === FetchType.INTERVAL ? settings.fetchInterval : null,
);

useInactivityTimer(
() => {
fetchNotifications({ auth, settings });
},
settings.fetchType === FetchType.INACTIVITY
? Constants.FETCH_NOTIFICATIONS_INTERVAL_MS
: null,
settings.fetchType === FetchType.INACTIVITY ? settings.fetchInterval : null,
);

useIntervalTimer(() => {
Expand Down
2 changes: 2 additions & 0 deletions src/renderer/context/defaults.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Constants } from '../constants';
import {
type AppearanceSettingsState,
type AuthState,
Expand Down Expand Up @@ -27,6 +28,7 @@ const defaultAppearanceSettings: AppearanceSettingsState = {
const defaultNotificationSettings: NotificationSettingsState = {
groupBy: GroupBy.REPOSITORY,
fetchType: FetchType.INTERVAL,
fetchInterval: Constants.DEFAULT_FETCH_NOTIFICATIONS_INTERVAL_MS,
fetchAllNotifications: true,
detailedNotifications: true,
showPills: true,
Expand Down
Loading