Skip to content
1 change: 1 addition & 0 deletions src/renderer/__mocks__/partial-mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export function partialMockNotification(
hostname: Constants.GITHUB_API_BASE_URL as Hostname,
token: mockToken,
user: mockGitifyUser,
hasRequiredScopes: true,
},
subject: subject as Subject,
};
Expand Down
5 changes: 5 additions & 0 deletions src/renderer/__mocks__/state-mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export const mockPersonalAccessTokenAccount: Account = {
token: 'token-123-456' as Token,
hostname: Constants.DEFAULT_AUTH_OPTIONS.hostname,
user: mockGitifyUser,
hasRequiredScopes: true,
};

export const mockOAuthAccount: Account = {
Expand All @@ -42,6 +43,7 @@ export const mockOAuthAccount: Account = {
token: '1234568790' as Token,
hostname: 'github.gitify.io' as Hostname,
user: mockGitifyUser,
hasRequiredScopes: true,
};

export const mockGitHubCloudAccount: Account = {
Expand All @@ -51,6 +53,7 @@ export const mockGitHubCloudAccount: Account = {
hostname: Constants.DEFAULT_AUTH_OPTIONS.hostname,
user: mockGitifyUser,
version: 'latest',
hasRequiredScopes: true,
};

export const mockGitHubEnterpriseServerAccount: Account = {
Expand All @@ -59,6 +62,7 @@ export const mockGitHubEnterpriseServerAccount: Account = {
token: '1234568790' as Token,
hostname: 'github.gitify.io' as Hostname,
user: mockGitifyUser,
hasRequiredScopes: true,
};

export const mockGitHubAppAccount: Account = {
Expand All @@ -67,6 +71,7 @@ export const mockGitHubAppAccount: Account = {
token: '987654321' as Token,
hostname: Constants.DEFAULT_AUTH_OPTIONS.hostname,
user: mockGitifyUser,
hasRequiredScopes: true,
};

export const mockAuth: AuthState = {
Expand Down
39 changes: 39 additions & 0 deletions src/renderer/routes/Accounts.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,45 @@ describe('renderer/routes/Accounts.tsx', () => {
expect(screen.getByTestId('accounts')).toMatchSnapshot();
});

it('should render with PAT scopes warning', async () => {
const openExternalLinkMock = jest
.spyOn(comms, 'openExternalLink')
.mockImplementation();

await act(async () => {
render(
<AppContext.Provider
value={{
auth: {
accounts: [
{
...mockPersonalAccessTokenAccount,
hasRequiredScopes: false,
},
mockOAuthAccount,
mockGitHubAppAccount,
],
},
settings: mockSettings,
}}
>
<MemoryRouter>
<AccountsRoute />
</MemoryRouter>
</AppContext.Provider>,
);
});

expect(screen.getByTestId('accounts')).toMatchSnapshot();

fireEvent.click(screen.getByLabelText('missing-scopes'));

expect(openExternalLinkMock).toHaveBeenCalledTimes(1);
expect(openExternalLinkMock).toHaveBeenCalledWith(
'https://github.com/settings/tokens',
);
});

it('should go back by pressing the icon', async () => {
await act(async () => {
render(
Expand Down
21 changes: 20 additions & 1 deletion src/renderer/routes/Accounts.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
AlertFillIcon,
FeedPersonIcon,
KeyIcon,
MarkGithubIcon,
Expand All @@ -23,6 +24,7 @@ import { type Account, IconColor, Size } from '../types';
import { getAccountUUID, refreshAccount } from '../utils/auth/utils';
import { cn } from '../utils/cn';
import { updateTrayIcon, updateTrayTitle } from '../utils/comms';
import { Constants } from '../utils/constants';
import {
openAccountProfile,
openDeveloperSettings,
Expand Down Expand Up @@ -78,7 +80,7 @@ export const AccountsRoute: FC = () => {
className="mb-4 flex items-center justify-between rounded-md bg-gray-100 p-2 dark:bg-gray-sidebar"
>
<div className="ml-2 text-xs">
<div>
<div className="flex flex-1 items-center gap-2">
<button
type="button"
className="flex flex-1 gap-2 items-center justify-center mb-1 cursor-pointer text-sm font-semibold"
Expand All @@ -99,6 +101,23 @@ export const AccountsRoute: FC = () => {
({account.user?.name})
</span>
</button>

{account.hasRequiredScopes === false && (
<span className="text-xs font-medium italic">
<button
type="button"
className="cursor-pointer"
title={`This account is missing one or more required scopes: \n - ${Constants.AUTH_SCOPE.join('\n - ')}`}
aria-label="missing-scopes"
onClick={() => openDeveloperSettings(account)}
>
<AlertFillIcon
size={Size.XSMALL}
className={IconColor.RED}
/>
</button>
</span>
)}
</div>
<div>
<button
Expand Down
Loading
Loading