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
2 changes: 1 addition & 1 deletion src/renderer/components/filters/FilterCheckbox.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ describe('renderer/components/filters/FilterCheckbox.tsx', () => {

expect(screen.getByRole('checkbox', { name: 'Issues' })).toBeDisabled();

await userEvent.click(screen.getByTestId('tooltip-icon-tooltip-issues'));
await userEvent.click(screen.getByRole('button', { name: 'Help: Issues' }));

expect(screen.getByText('Only issues assigned to you.')).toBeInTheDocument();
});
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/components/filters/FilterCheckbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export function FilterCheckbox({
{label}
</label>

{tooltip && <HelpPopover name={`tooltip-${name}`} content={tooltip} />}
{tooltip && <HelpPopover content={tooltip} label={label} name={`tooltip-${name}`} />}

{counter !== undefined && (
<CounterLabel
Expand Down
4 changes: 2 additions & 2 deletions src/renderer/components/filters/SearchFilter.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ describe('renderer/components/filters/SearchFilter.tsx', () => {
it('lists the supported search qualifiers including commenter', async () => {
renderWithProviders(<SearchFilter />);

const tooltipIconElement = screen.getByTestId('tooltip-icon-tooltip-search');
const tooltipIconElement = screen.getByRole('button', { name: 'Help: Search' });
await userEvent.click(tooltipIconElement);

expect(screen.getByText('Author (author:handle)')).toBeInTheDocument();
Expand All @@ -30,7 +30,7 @@ describe('renderer/components/filters/SearchFilter.tsx', () => {
it('marks qualifiers that require detailed notifications as disabled', async () => {
renderWithProviders(<SearchFilter />, { settings: { detailedNotifications: false } });

const tooltipIconElement = screen.getByTestId('tooltip-icon-tooltip-search');
const tooltipIconElement = screen.getByRole('button', { name: 'Help: Search' });
await userEvent.click(tooltipIconElement);

expect(screen.getByText('Author (author:handle)')).toHaveAttribute('aria-disabled', 'true');
Expand Down
9 changes: 5 additions & 4 deletions src/renderer/components/primitives/HelpPopover.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,21 @@ import { HelpPopover, type HelpPopoverProps } from './HelpPopover';
describe('renderer/components/primitives/HelpPopover.tsx', () => {
const props: HelpPopoverProps = {
name: 'test',
label: 'Test setting',
content: 'This is some help text',
};

it('renders', () => {
renderWithProviders(<HelpPopover {...props} />);

expect(screen.getByTestId('tooltip-icon-test')).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'Help: Test setting' })).toBeInTheDocument();
expect(screen.queryByText(props.content as string)).not.toBeInTheDocument();
});

it('toggles (show/hide) help on clicking the icon', async () => {
renderWithProviders(<HelpPopover {...props} />);

const icon = screen.getByTestId('tooltip-icon-test');
const icon = screen.getByRole('button', { name: 'Help: Test setting' });

await userEvent.click(icon);
expect(screen.getByText(props.content as string)).toBeInTheDocument();
Expand All @@ -43,7 +44,7 @@ describe('renderer/components/primitives/HelpPopover.tsx', () => {
Object.defineProperty(scrollContainer, 'clientHeight', { configurable: true, value: 100 });
Object.defineProperty(scrollContainer, 'scrollHeight', { configurable: true, value: 400 });

await userEvent.click(screen.getByTestId('tooltip-icon-test'));
await userEvent.click(screen.getByRole('button', { name: 'Help: Test setting' }));
expect(screen.getByText(props.content as string)).toBeInTheDocument();

fireEvent.scroll(scrollContainer);
Expand All @@ -56,7 +57,7 @@ describe('renderer/components/primitives/HelpPopover.tsx', () => {
it('hides help when clicking outside', async () => {
renderWithProviders(<HelpPopover {...props} />);

const icon = screen.getByTestId('tooltip-icon-test');
const icon = screen.getByRole('button', { name: 'Help: Test setting' });

await userEvent.click(icon);
expect(screen.getByText(props.content as string)).toBeInTheDocument();
Expand Down
4 changes: 3 additions & 1 deletion src/renderer/components/primitives/HelpPopover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { cn } from 'cn';

export interface HelpPopoverProps {
name: string;
/** Accessible name for the help button, e.g. the label of the control it explains. */
label: string;
content: ReactNode | string;
}

Expand Down Expand Up @@ -74,7 +76,7 @@ export function HelpPopover(props: Readonly<HelpPopoverProps>) {
renderAnchor={(anchorProps) => (
<IconButton
{...anchorProps}
aria-label={props.name}
aria-label={`Help: ${props.label}`}
className="text-gitify-tooltip-icon!"
data-testid={`tooltip-icon-${props.name}`}
icon={QuestionIcon}
Expand Down
1 change: 1 addition & 0 deletions src/renderer/components/primitives/SectionHeading.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export function SectionHeading({ as = 'h4', ...props }: Readonly<SectionHeadingP
{props.tooltip}
</Stack>
}
label={props.children}
name={`tooltip-${name}`}
/>
);
Expand Down
4 changes: 2 additions & 2 deletions src/renderer/components/settings/AlertSettings.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ describe('renderer/components/settings/AlertSettings.tsx', () => {
it('shows detailed help in tooltips', async () => {
renderWithProviders(<AlertSettings />);

await userEvent.click(screen.getByTestId('tooltip-icon-tooltip-playSound'));
await userEvent.click(screen.getByRole('button', { name: 'Help: Play notification sound' }));

expect(
screen.getByText(
Expand All @@ -50,7 +50,7 @@ describe('renderer/components/settings/AlertSettings.tsx', () => {
expect(screen.getByText('Notification volume')).toBeInTheDocument();
expect(screen.getByTestId('reveal-volume')).toHaveAttribute('data-state', 'open');

await userEvent.click(screen.getByTestId('tooltip-icon-tooltip-notificationVolume'));
await userEvent.click(screen.getByRole('button', { name: 'Help: Notification volume' }));

expect(screen.getByText('Adjust the volume of the notification sound.')).toBeInTheDocument();
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,21 +127,21 @@ describe('renderer/components/settings/AppearanceSettings.tsx', () => {
renderWithProviders(<AppearanceSettings />);
});

await userEvent.click(screen.getByTestId('tooltip-icon-tooltip-designLanguage'));
await userEvent.click(screen.getByRole('button', { name: 'Help: Design' }));
expect(
screen.getByText(
'Choose Classic for the original Gitify layout, or Glass for the translucent, layered design language.',
),
).toBeInTheDocument();

await userEvent.click(screen.getByTestId('tooltip-icon-tooltip-theme'));
await userEvent.click(screen.getByRole('button', { name: 'Help: Theme' }));
expect(
screen.getByText(
'Choose a light, dark, or system color mode. Available options depend on the selected design language.',
),
).toBeInTheDocument();

await userEvent.click(screen.getByTestId('tooltip-icon-tooltip-zoom'));
await userEvent.click(screen.getByRole('button', { name: 'Help: Zoom' }));
expect(
screen.getByText(
'Change the size of the Gitify interface. The setting applies immediately and is remembered for the next launch.',
Expand Down
18 changes: 9 additions & 9 deletions src/renderer/components/settings/InboxSettings.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ describe('renderer/components/settings/InboxSettings.tsx', () => {
});
});

await userEvent.click(screen.getByTestId('tooltip-icon-tooltip-showOnlyParticipating'));
await userEvent.click(screen.getByRole('button', { name: 'Help: Fetch only participating' }));

await userEvent.click(
screen.getByTitle('Open GitHub documentation for participating and watching notifications'),
Expand All @@ -272,27 +272,27 @@ describe('renderer/components/settings/InboxSettings.tsx', () => {
});

it.each([
['groupBy', 'tooltip-groupBy'],
['showAccountHeader', 'tooltip-showAccountHeader'],
['wrapNotificationTitle', 'tooltip-wrapNotificationTitle'],
['showNumber', 'tooltip-showNumber'],
['delayNotificationState', 'tooltip-delayNotificationState'],
] as const)('renders the %s tooltip', async (_name, tooltipName) => {
['groupBy', 'Group by'],
['showAccountHeader', 'Show account header'],
['wrapNotificationTitle', 'Show full notification title'],
['showNumber', 'Show reference'],
['delayNotificationState', 'Delay notification removal'],
] as const)('renders the %s tooltip', async (_name, label) => {
await act(async () => {
renderWithProviders(<InboxSettings />, {
accounts: [mockGitHubCloudAccount],
});
});

expect(screen.getByTestId(`tooltip-icon-${tooltipName}`)).toBeInTheDocument();
expect(screen.getByRole('button', { name: `Help: ${label}` })).toBeInTheDocument();
});

it('shows the fetch interval details in a tooltip', async () => {
await act(async () => {
renderWithProviders(<InboxSettings />);
});

await userEvent.click(screen.getByTestId('tooltip-icon-tooltip-fetchInterval'));
await userEvent.click(screen.getByRole('button', { name: 'Help: Fetch interval' }));

expect(
screen.getByText(
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/components/settings/SystemSettings.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ describe('renderer/components/settings/SystemSettings.tsx', () => {

expect(screen.getByText('Global shortcut')).toBeInTheDocument();

await userEvent.click(screen.getByTestId('tooltip-icon-tooltip-shortcut'));
await userEvent.click(screen.getByRole('button', { name: 'Help: Global shortcut' }));

expect(screen.getByText('Shortcuts must include:')).toBeInTheDocument();
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ export function SettingsChoice({
>
{label}
</button>
{tooltip && <HelpPopover name={`tooltip-${name}`} content={tooltip} />}
{tooltip && <HelpPopover content={tooltip} label={label} name={`tooltip-${name}`} />}
</div>
<div className="shrink-0">{control}</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export function SettingsRow({
<span className="text-sm font-medium text-gitify-font" id={labelId}>
{label}
</span>
{tooltip && <HelpPopover name={`tooltip-${name}`} content={tooltip} />}
{tooltip && <HelpPopover content={tooltip} label={label} name={`tooltip-${name}`} />}
</div>
<div className="shrink-0">{children}</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export function SettingsToggle({
>
{label}
</button>
{tooltip && <HelpPopover name={`tooltip-${name}`} content={tooltip} />}
{tooltip && <HelpPopover content={tooltip} label={label} name={`tooltip-${name}`} />}
</div>

{caption && (
Expand Down
43 changes: 13 additions & 30 deletions src/renderer/icons/ScopeStatusIcon.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,9 @@ describe('renderer/icons/ScopeStatusIcon.tsx', () => {
expect(tree.container).toMatchSnapshot();
});

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

it('does not render a test id by default', () => {
it('announces the granted status', () => {
renderWithProviders(<ScopeStatusIcon granted={true} />);
expect(screen.queryByTestId('account-scopes-scope-granted')).not.toBeInTheDocument();
expect(screen.getByRole('img', { name: 'granted' })).toBeInTheDocument();
});
});

Expand All @@ -28,14 +23,9 @@ describe('renderer/icons/ScopeStatusIcon.tsx', () => {
expect(tree.container).toMatchSnapshot();
});

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

it('does not render a test id by default', () => {
it('announces the missing status', () => {
renderWithProviders(<ScopeStatusIcon granted={false} />);
expect(screen.queryByTestId('account-scopes-scope-missing')).not.toBeInTheDocument();
expect(screen.getByRole('img', { name: 'missing' })).toBeInTheDocument();
});
});

Expand All @@ -45,24 +35,17 @@ describe('renderer/icons/ScopeStatusIcon.tsx', () => {
expect(tree.container).toMatchSnapshot();
});

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

it('does not render a test id by default', () => {
renderWithProviders(<ScopeStatusIcon granted={false} notApplicable={true} />);
expect(screen.queryByTestId('account-scopes-scope-na')).not.toBeInTheDocument();
it('ignores granted=true when notApplicable is true', () => {
renderWithProviders(<ScopeStatusIcon granted={true} notApplicable={true} />);
expect(screen.getByRole('img', { name: 'not applicable' })).toBeInTheDocument();
expect(screen.queryByRole('img', { name: 'granted' })).not.toBeInTheDocument();
});
});

it('ignores granted=true when notApplicable is true', () => {
renderWithProviders(
<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('subject prop', () => {
it('prefixes the subject to the accessible name', () => {
renderWithProviders(<ScopeStatusIcon granted={false} subject="repo" />);
expect(screen.getByRole('img', { name: 'repo missing' })).toBeInTheDocument();
});
});

Expand Down
31 changes: 12 additions & 19 deletions src/renderer/icons/ScopeStatusIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,36 +4,29 @@ export interface ScopeStatusIconProps {
granted: boolean;
notApplicable?: boolean;
size?: number;
withTestId?: boolean;
/** What the status describes (e.g. a scope name), prefixed to the accessible name. */
subject?: string;
}

export function ScopeStatusIcon({
granted,
notApplicable = false,
size = 14,
withTestId = false,
subject,
}: Readonly<ScopeStatusIconProps>) {
let status = granted ? 'granted' : 'missing';
if (notApplicable) {
return (
<DashIcon
className="opacity-30"
data-testid={withTestId ? 'account-scopes-scope-na' : undefined}
size={size}
/>
);
status = 'not applicable';
}
const ariaLabel = subject ? `${subject} ${status}` : status;

if (notApplicable) {
return <DashIcon aria-label={ariaLabel} className="opacity-30" size={size} />;
}

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