Skip to content
Merged
10 changes: 9 additions & 1 deletion src/renderer/__mocks__/notifications-mocks.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { AccountNotifications } from '../types';
import type { AccountNotifications, GitifyError } from '../types';
import type {
Notification,
StateType,
Expand Down Expand Up @@ -50,6 +50,14 @@ export function createSubjectMock(mocks: {
};
}

export function mockAccountWithError(error: GitifyError): AccountNotifications {
return {
account: mockGitHubCloudAccount,
notifications: [],
error,
};
}

export function createMockNotificationForRepoName(
id: string,
repoFullName: string | null,
Expand Down
14 changes: 5 additions & 9 deletions src/renderer/components/Sidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type FC, useContext, useMemo } from 'react';
import { type FC, useContext } from 'react';
import { useLocation, useNavigate } from 'react-router-dom';

import {
Expand All @@ -23,20 +23,20 @@ import {
openGitHubPulls,
} from '../utils/links';
import { hasActiveFilters } from '../utils/notifications/filters/filter';
import { getNotificationCount } from '../utils/notifications/notifications';
import { LogoIcon } from './icons/LogoIcon';

export const Sidebar: FC = () => {
const navigate = useNavigate();
const location = useLocation();

const {
notifications,
fetchNotifications,
isLoggedIn,
status,
settings,
auth,
unreadCount,
hasNotifications,
} = useContext(AppContext);

// We naively assume that the first account is the primary account for the purposes of our sidebar quick links
Expand Down Expand Up @@ -65,10 +65,6 @@ export const Sidebar: FC = () => {
fetchNotifications();
};

const notificationsCount = useMemo(() => {
return getNotificationCount(notifications);
}, [notifications]);

const sidebarButtonStyle = { color: 'white' };

return (
Expand Down Expand Up @@ -98,14 +94,14 @@ export const Sidebar: FC = () => {
<IconButton
aria-label="Notifications"
data-testid="sidebar-notifications"
description={`${notificationsCount} unread notifications ↗`}
description={`${unreadCount} unread notifications ↗`}
icon={BellIcon}
onClick={() => openGitHubNotifications(primaryAccountHostname)}
size="small"
sx={sidebarButtonStyle}
tooltipDirection="e"
unsafeDisableTooltip={false}
variant={notificationsCount > 0 ? 'primary' : 'invisible'}
variant={hasNotifications ? 'primary' : 'invisible'}
/>

{isLoggedIn && (
Expand Down
16 changes: 8 additions & 8 deletions src/renderer/components/__snapshots__/Sidebar.test.tsx.snap

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,9 @@ export const AccountNotifications: FC<IAccountNotifications> = (
{showAccountNotifications && (
<>
{props.error && <Oops error={props.error} fullHeight={false} />}

{!hasNotifications && !props.error && <AllRead fullHeight={false} />}

{isGroupByRepository(settings)
? groupedNotifications.map(([repoSlug, repoNotifications]) => (
<RepositoryNotifications
Expand Down
21 changes: 11 additions & 10 deletions src/renderer/context/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import * as apiRequests from '../utils/api/request';
import * as comms from '../utils/comms';
import * as notifications from '../utils/notifications/notifications';
import * as storage from '../utils/storage';
import * as tray from '../utils/tray';
import { AppContext, AppProvider } from './App';
import { defaultSettings } from './defaults';

Expand Down Expand Up @@ -38,11 +39,13 @@ describe('renderer/context/App.tsx', () => {
});

describe('notification methods', () => {
const getNotificationCountMock = jest.spyOn(
notifications,
'getNotificationCount',
);
getNotificationCountMock.mockReturnValue(1);
const setTrayIconColorAndTitleMock = jest
.spyOn(tray, 'setTrayIconColorAndTitle')
.mockImplementation(jest.fn());

jest
.spyOn(notifications, 'getUnreadNotificationCount')
.mockImplementation(jest.fn());

const fetchNotificationsMock = jest.fn();
const markNotificationsAsReadMock = jest.fn();
Expand Down Expand Up @@ -70,8 +73,6 @@ describe('renderer/context/App.tsx', () => {
it('fetch notifications every minute', async () => {
customRender(null);

// Wait for the useEffects, for settings.participating and accounts, to run.
// Those aren't what we're testing
await waitFor(() =>
expect(fetchNotificationsMock).toHaveBeenCalledTimes(1),
);
Expand All @@ -80,23 +81,20 @@ describe('renderer/context/App.tsx', () => {
jest.advanceTimersByTime(
Constants.DEFAULT_FETCH_NOTIFICATIONS_INTERVAL_MS,
);
return;
});
expect(fetchNotificationsMock).toHaveBeenCalledTimes(2);

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

act(() => {
jest.advanceTimersByTime(
Constants.DEFAULT_FETCH_NOTIFICATIONS_INTERVAL_MS,
);
return;
});
expect(fetchNotificationsMock).toHaveBeenCalledTimes(4);
});
Expand Down Expand Up @@ -144,6 +142,7 @@ describe('renderer/context/App.tsx', () => {
mockDefaultState,
[mockSingleNotification],
);
expect(setTrayIconColorAndTitleMock).toHaveBeenCalledTimes(1);
});

it('should call markNotificationsAsDone', async () => {
Expand All @@ -169,6 +168,7 @@ describe('renderer/context/App.tsx', () => {
mockDefaultState,
[mockSingleNotification],
);
expect(setTrayIconColorAndTitleMock).toHaveBeenCalledTimes(1);
});

it('should call unsubscribeNotification', async () => {
Expand All @@ -194,6 +194,7 @@ describe('renderer/context/App.tsx', () => {
mockDefaultState,
mockSingleNotification,
);
expect(setTrayIconColorAndTitleMock).toHaveBeenCalledTimes(1);
});
});

Expand Down
29 changes: 15 additions & 14 deletions src/renderer/context/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,17 +47,16 @@ import {
setKeyboardShortcut,
setUseAlternateIdleIcon,
setUseUnreadActiveIcon,
updateTrayColor,
updateTrayTitle,
} from '../utils/comms';
import { getNotificationCount } from '../utils/notifications/notifications';
import { getUnreadNotificationCount } from '../utils/notifications/notifications';
import { clearState, loadState, saveState } from '../utils/storage';
import {
DEFAULT_DAY_COLOR_SCHEME,
DEFAULT_NIGHT_COLOR_SCHEME,
mapThemeModeToColorMode,
mapThemeModeToColorScheme,
} from '../utils/theme';
import { setTrayIconColorAndTitle } from '../utils/tray';
import { zoomPercentageToLevel } from '../utils/zoom';
import { defaultAuth, defaultFilters, defaultSettings } from './defaults';

Expand All @@ -75,6 +74,8 @@ interface AppContextState {
globalError: GitifyError;

notifications: AccountNotifications[];
unreadCount: number;
hasNotifications: boolean;
fetchNotifications: () => Promise<void>;
removeAccountNotifications: (account: Account) => Promise<void>;

Expand Down Expand Up @@ -110,6 +111,10 @@ export const AppProvider = ({ children }: { children: ReactNode }) => {
unsubscribeNotification,
} = useNotifications();

const unreadCount = getUnreadNotificationCount(notifications);

const hasNotifications = useMemo(() => unreadCount > 0, [unreadCount]);

// biome-ignore lint/correctness/useExhaustiveDependencies: restoreSettings is stable and should run only once
useEffect(() => {
restoreSettings();
Expand Down Expand Up @@ -166,24 +171,16 @@ export const AppProvider = ({ children }: { children: ReactNode }) => {
}
}, Constants.REFRESH_ACCOUNTS_INTERVAL_MS);

// biome-ignore lint/correctness/useExhaustiveDependencies: We also want to update the tray on setting changes
useEffect(() => {
const count = getNotificationCount(notifications);

let title = '';
if (settings.showNotificationsCountInTray && count > 0) {
title = count.toString();
}

setUseUnreadActiveIcon(settings.useUnreadActiveIcon);
setUseAlternateIdleIcon(settings.useAlternateIdleIcon);

updateTrayColor(count);
updateTrayTitle(title);
setTrayIconColorAndTitle(unreadCount, settings);
}, [
settings.showNotificationsCountInTray,
settings.useUnreadActiveIcon,
settings.useAlternateIdleIcon,
notifications,
unreadCount,
]);

useEffect(() => {
Expand Down Expand Up @@ -362,6 +359,8 @@ export const AppProvider = ({ children }: { children: ReactNode }) => {
globalError,

notifications,
unreadCount,
hasNotifications,
fetchNotifications: fetchNotificationsWithAccounts,

markNotificationsAsRead: markNotificationsAsReadWithAccounts,
Expand All @@ -386,6 +385,8 @@ export const AppProvider = ({ children }: { children: ReactNode }) => {
globalError,

notifications,
unreadCount,
hasNotifications,
fetchNotificationsWithAccounts,
markNotificationsAsReadWithAccounts,
markNotificationsAsDoneWithAccounts,
Expand Down
Loading