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
9 changes: 9 additions & 0 deletions src/renderer/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
import { BaseStyles, ThemeProvider } from '@primer/react';

import { AppProvider } from './context/App';
import { AccountScopesRoute } from './routes/AccountScopes';
import { AccountsRoute } from './routes/Accounts';
import { FiltersRoute } from './routes/Filters';
import { LoginRoute } from './routes/Login';
Expand Down Expand Up @@ -78,6 +79,14 @@ export const App = () => {
}
path="/accounts"
/>
<Route
element={
<RequireAuth>
<AccountScopesRoute />
</RequireAuth>
}
path="/account-scopes"
/>
<Route element={<LoginRoute />} path="/login" />
<Route
element={<LoginWithDeviceFlowRoute />}
Expand Down
9 changes: 4 additions & 5 deletions src/renderer/__mocks__/account-mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type {
Token,
} from '../types';

import { getRecommendedScopeNames } from '../utils/auth/utils';
import { mockGitifyUser } from './user-mocks';

export const mockGitHubAppAccount: Account = {
Expand All @@ -16,7 +17,7 @@ export const mockGitHubAppAccount: Account = {
token: 'token-987654321' as Token,
hostname: Constants.GITHUB_HOSTNAME,
user: mockGitifyUser,
hasRequiredScopes: true,
scopes: getRecommendedScopeNames(),
};

export const mockPersonalAccessTokenAccount: Account = {
Expand All @@ -25,7 +26,7 @@ export const mockPersonalAccessTokenAccount: Account = {
token: 'token-123-456' as Token,
hostname: Constants.GITHUB_HOSTNAME,
user: mockGitifyUser,
hasRequiredScopes: true,
scopes: getRecommendedScopeNames(),
};

export const mockOAuthAccount: Account = {
Expand All @@ -34,7 +35,7 @@ export const mockOAuthAccount: Account = {
token: 'token-1234568790' as Token,
hostname: 'github.gitify.io' as Hostname,
user: mockGitifyUser,
hasRequiredScopes: true,
scopes: getRecommendedScopeNames(),
};

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

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

export function mockAccountWithError(error: GitifyError): AccountNotifications {
Expand Down
107 changes: 107 additions & 0 deletions src/renderer/components/icons/ScopeStatusIcon.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { screen } from '@testing-library/react';

import { describe, expect, it } from 'vitest';

import { renderWithAppContext } from '../../__helpers__/test-utils';

import { ScopeStatusIcon } from './ScopeStatusIcon';

describe('renderer/components/icons/ScopeStatusIcon.tsx', () => {
describe('granted state', () => {
it('renders a success icon when granted', () => {
const tree = renderWithAppContext(<ScopeStatusIcon granted={true} />);
expect(tree.container).toMatchSnapshot();
});

it('renders a success icon with test id when withTestId is true', () => {
renderWithAppContext(
<ScopeStatusIcon granted={true} withTestId={true} />,
);
expect(
screen.getByTestId('account-scopes-scope-granted'),
).toBeInTheDocument();
});

it('does not render a test id by default', () => {
renderWithAppContext(<ScopeStatusIcon granted={true} />);
expect(
screen.queryByTestId('account-scopes-scope-granted'),
).not.toBeInTheDocument();
});
});

describe('missing state', () => {
it('renders a danger icon when not granted', () => {
const tree = renderWithAppContext(<ScopeStatusIcon granted={false} />);
expect(tree.container).toMatchSnapshot();
});

it('renders a danger icon with test id when withTestId is true', () => {
renderWithAppContext(
<ScopeStatusIcon granted={false} withTestId={true} />,
);
expect(
screen.getByTestId('account-scopes-scope-missing'),
).toBeInTheDocument();
});

it('does not render a test id by default', () => {
renderWithAppContext(<ScopeStatusIcon granted={false} />);
expect(
screen.queryByTestId('account-scopes-scope-missing'),
).not.toBeInTheDocument();
});
});

describe('notApplicable state', () => {
it('renders a dash icon when notApplicable', () => {
const tree = renderWithAppContext(
<ScopeStatusIcon granted={false} notApplicable={true} />,
);
expect(tree.container).toMatchSnapshot();
});

it('renders a dash icon with test id when withTestId is true', () => {
renderWithAppContext(
<ScopeStatusIcon
granted={false}
notApplicable={true}
withTestId={true}
/>,
);
expect(screen.getByTestId('account-scopes-scope-na')).toBeInTheDocument();
});

it('does not render a test id by default', () => {
renderWithAppContext(
<ScopeStatusIcon granted={false} notApplicable={true} />,
);
expect(
screen.queryByTestId('account-scopes-scope-na'),
).not.toBeInTheDocument();
});

it('ignores granted=true when notApplicable is true', () => {
renderWithAppContext(
<ScopeStatusIcon
granted={true}
notApplicable={true}
withTestId={true}
/>,
);
expect(screen.getByTestId('account-scopes-scope-na')).toBeInTheDocument();
expect(
screen.queryByTestId('account-scopes-scope-granted'),
).not.toBeInTheDocument();
});
});

describe('size prop', () => {
it('renders correctly with a custom size', () => {
const tree = renderWithAppContext(
<ScopeStatusIcon granted={true} size={20} />,
);
expect(tree.container).toMatchSnapshot();
});
});
});
45 changes: 45 additions & 0 deletions src/renderer/components/icons/ScopeStatusIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import type { FC } from 'react';

import {
CheckCircleFillIcon,
DashIcon,
XCircleFillIcon,
} from '@primer/octicons-react';

export interface ScopeStatusIconProps {
granted: boolean;
notApplicable?: boolean;
size?: number;
withTestId?: boolean;
}

export const ScopeStatusIcon: FC<ScopeStatusIconProps> = ({
granted,
notApplicable = false,
size = 14,
withTestId = false,
}) => {
if (notApplicable) {
return (
<DashIcon
className="opacity-30"
data-testid={withTestId ? 'account-scopes-scope-na' : undefined}
size={size}
/>
);
}

return granted ? (
<CheckCircleFillIcon
className="text-gitify-success"
data-testid={withTestId ? 'account-scopes-scope-granted' : undefined}
size={size}
/>
) : (
<XCircleFillIcon
className="text-gitify-danger"
data-testid={withTestId ? 'account-scopes-scope-missing' : undefined}
size={size}
/>
);
};

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 @@ -17,6 +17,12 @@ import {
type AccountNotificationsProps,
} from './AccountNotifications';

const navigateMock = vi.fn();
vi.mock('react-router-dom', async () => ({
...(await vi.importActual('react-router-dom')),
useNavigate: () => navigateMock,
}));

vi.mock('./RepositoryNotifications', () => ({
RepositoryNotifications: () => <div>RepositoryNotifications</div>,
}));
Expand Down
Loading