Skip to content

Commit

Permalink
feat: add state filter (#1859)
Browse files Browse the repository at this point in the history
* feat: add state filter

Signed-off-by: Adam Setch <adam.setch@outlook.com>

* update coverage

Signed-off-by: Adam Setch <adam.setch@outlook.com>

* update coverage

Signed-off-by: Adam Setch <adam.setch@outlook.com>

* merge main

Signed-off-by: Adam Setch <adam.setch@outlook.com>

---------

Signed-off-by: Adam Setch <adam.setch@outlook.com>
  • Loading branch information
setchy authored Feb 25, 2025
1 parent da58488 commit 99a3dc6
Show file tree
Hide file tree
Showing 20 changed files with 2,564 additions and 84 deletions.
1 change: 1 addition & 0 deletions src/renderer/__mocks__/state-mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ const mockFilters: FilterSettingsState = {
filterUserTypes: [],
filterIncludeHandles: [],
filterExcludeHandles: [],
filterStates: [],
filterReasons: [],
};

Expand Down
3 changes: 2 additions & 1 deletion src/renderer/components/filters/ReasonFilter.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { act, fireEvent, render, screen } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import { mockAccountNotifications } from '../../__mocks__/notifications-mocks';
import { mockSettings } from '../../__mocks__/state-mocks';
import { AppContext } from '../../context/App';
import { ReasonFilter } from './ReasonFilter';
Expand All @@ -12,7 +13,7 @@ describe('renderer/components/filters/ReasonFilter.tsx', () => {
<AppContext.Provider
value={{
settings: mockSettings,
notifications: [],
notifications: mockAccountNotifications,
}}
>
<MemoryRouter>
Expand Down
111 changes: 111 additions & 0 deletions src/renderer/components/filters/StateFilter.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { act, fireEvent, render, screen } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import { mockAccountNotifications } from '../../__mocks__/notifications-mocks';
import { mockSettings } from '../../__mocks__/state-mocks';
import { AppContext } from '../../context/App';
import type { SettingsState } from '../../types';
import { StateFilter } from './StateFilter';

describe('renderer/components/filters/StateFilter.tsx', () => {
const updateFilter = jest.fn();

describe('should render itself & its children', () => {
it('with detailed notifications enabled', () => {
const tree = render(
<AppContext.Provider
value={{
settings: {
...mockSettings,
detailedNotifications: true,
} as SettingsState,
notifications: mockAccountNotifications,
}}
>
<MemoryRouter>
<StateFilter />
</MemoryRouter>
</AppContext.Provider>,
);

expect(tree).toMatchSnapshot();
});

it('with detailed notifications disabled', () => {
const tree = render(
<AppContext.Provider
value={{
settings: {
...mockSettings,
detailedNotifications: false,
} as SettingsState,
notifications: mockAccountNotifications,
}}
>
<MemoryRouter>
<StateFilter />
</MemoryRouter>
</AppContext.Provider>,
);

expect(tree).toMatchSnapshot();
});
});

it('should be able to toggle user type - none already set', async () => {
await act(async () => {
render(
<AppContext.Provider
value={{
settings: {
...mockSettings,
filterStates: [],
},
notifications: [],
updateFilter,
}}
>
<MemoryRouter>
<StateFilter />
</MemoryRouter>
</AppContext.Provider>,
);
});

fireEvent.click(screen.getByLabelText('Open'));

expect(updateFilter).toHaveBeenCalledWith('filterStates', 'open', true);

expect(
screen.getByLabelText('Open').parentNode.parentNode,
).toMatchSnapshot();
});

it('should be able to toggle user type - some filters already set', async () => {
await act(async () => {
render(
<AppContext.Provider
value={{
settings: {
...mockSettings,
filterStates: ['open'],
},
notifications: [],
updateFilter,
}}
>
<MemoryRouter>
<StateFilter />
</MemoryRouter>
</AppContext.Provider>,
);
});

fireEvent.click(screen.getByLabelText('Closed'));

expect(updateFilter).toHaveBeenCalledWith('filterStates', 'closed', true);

expect(
screen.getByLabelText('Closed').parentNode.parentNode,
).toMatchSnapshot();
});
});
68 changes: 68 additions & 0 deletions src/renderer/components/filters/StateFilter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { type FC, useContext } from 'react';

import { BellIcon } from '@primer/octicons-react';
import { Stack, Text } from '@primer/react';

import { AppContext } from '../../context/App';
import type { FilterStateType } from '../../types';
import {
FILTERS_STATE_TYPES,
getStateDetails,
getStateFilterCount,
isStateFilterSet,
} from '../../utils/notifications/filters/state';
import { Checkbox } from '../fields/Checkbox';
import { Tooltip } from '../fields/Tooltip';
import { Title } from '../primitives/Title';

export const StateFilter: FC = () => {
const { updateFilter, settings, notifications } = useContext(AppContext);

return (
<fieldset id="filter-state" className="mb-3">
<Stack direction="horizontal" gap="condensed" align="baseline">
<Title icon={BellIcon}>State</Title>
<Tooltip
name="tooltip-filter-state"
tooltip={
<Stack direction="vertical" gap="condensed">
<Text>Filter notifications by state.</Text>
<Text className="text-gitify-caution">
⚠️ This filter requires the{' '}
<Text as="strong">Detailed Notifications</Text> setting to be
enabled.
</Text>
</Stack>
}
/>
</Stack>

<Stack direction="vertical" gap="condensed">
{Object.keys(FILTERS_STATE_TYPES).map((stateType: FilterStateType) => {
const stateDetails = getStateDetails(stateType);
const stateTitle = stateDetails.title;
const stateDescription = stateDetails.description;
const isStateChecked = isStateFilterSet(settings, stateType);
const stateCount = getStateFilterCount(notifications, stateType);

return (
<Checkbox
key={stateType}
name={stateTitle}
label={stateTitle}
checked={isStateChecked}
onChange={(evt) =>
updateFilter('filterStates', stateType, evt.target.checked)
}
tooltip={
stateDescription ? <Text>{stateDescription}</Text> : null
}
disabled={!settings.detailedNotifications}
counter={stateCount}
/>
);
})}
</Stack>
</fieldset>
);
};
77 changes: 40 additions & 37 deletions src/renderer/components/filters/UserHandleFilter.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { act, fireEvent, render, screen } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import { mockAccountNotifications } from '../../__mocks__/notifications-mocks';
import { mockSettings } from '../../__mocks__/state-mocks';
import { AppContext } from '../../context/App';
import type { SettingsState } from '../../types';
Expand All @@ -12,44 +13,46 @@ describe('renderer/components/filters/UserHandleFilter.tsx', () => {
jest.clearAllMocks();
});

it('should render itself & its children - detailed notifications enabled', () => {
const tree = render(
<AppContext.Provider
value={{
settings: {
...mockSettings,
detailedNotifications: true,
} as SettingsState,
notifications: [],
}}
>
<MemoryRouter>
<UserHandleFilter />
</MemoryRouter>
</AppContext.Provider>,
);

expect(tree).toMatchSnapshot();
});
describe('should render itself & its children', () => {
it('with detailed notifications enabled', () => {
const tree = render(
<AppContext.Provider
value={{
settings: {
...mockSettings,
detailedNotifications: true,
} as SettingsState,
notifications: mockAccountNotifications,
}}
>
<MemoryRouter>
<UserHandleFilter />
</MemoryRouter>
</AppContext.Provider>,
);

expect(tree).toMatchSnapshot();
});

it('should render itself & its children - detailed notifications disabled', () => {
const tree = render(
<AppContext.Provider
value={{
settings: {
...mockSettings,
detailedNotifications: false,
} as SettingsState,
notifications: [],
}}
>
<MemoryRouter>
<UserHandleFilter />
</MemoryRouter>
</AppContext.Provider>,
);

expect(tree).toMatchSnapshot();
it('with detailed notifications disabled', () => {
const tree = render(
<AppContext.Provider
value={{
settings: {
...mockSettings,
detailedNotifications: false,
} as SettingsState,
notifications: mockAccountNotifications,
}}
>
<MemoryRouter>
<UserHandleFilter />
</MemoryRouter>
</AppContext.Provider>,
);

expect(tree).toMatchSnapshot();
});
});

describe('Include user handles', () => {
Expand Down
73 changes: 38 additions & 35 deletions src/renderer/components/filters/UserTypeFilter.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { act, fireEvent, render, screen } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import { mockAccountNotifications } from '../../__mocks__/notifications-mocks';
import { mockSettings } from '../../__mocks__/state-mocks';
import { AppContext } from '../../context/App';
import type { SettingsState } from '../../types';
Expand All @@ -8,44 +9,46 @@ import { UserTypeFilter } from './UserTypeFilter';
describe('renderer/components/filters/UserTypeFilter.tsx', () => {
const updateFilter = jest.fn();

it('should render itself & its children - detailed notifications enabled', () => {
const tree = render(
<AppContext.Provider
value={{
settings: {
...mockSettings,
detailedNotifications: true,
} as SettingsState,
notifications: [],
}}
>
<MemoryRouter>
<UserTypeFilter />
</MemoryRouter>
</AppContext.Provider>,
);
describe('should render itself & its children', () => {
it('with detailed notifications enabled', () => {
const tree = render(
<AppContext.Provider
value={{
settings: {
...mockSettings,
detailedNotifications: true,
} as SettingsState,
notifications: mockAccountNotifications,
}}
>
<MemoryRouter>
<UserTypeFilter />
</MemoryRouter>
</AppContext.Provider>,
);

expect(tree).toMatchSnapshot();
});
expect(tree).toMatchSnapshot();
});

it('should render itself & its children - detailed notifications disabled', () => {
const tree = render(
<AppContext.Provider
value={{
settings: {
...mockSettings,
detailedNotifications: false,
} as SettingsState,
notifications: [],
}}
>
<MemoryRouter>
<UserTypeFilter />
</MemoryRouter>
</AppContext.Provider>,
);
it('with detailed notifications disabled', () => {
const tree = render(
<AppContext.Provider
value={{
settings: {
...mockSettings,
detailedNotifications: false,
} as SettingsState,
notifications: mockAccountNotifications,
}}
>
<MemoryRouter>
<UserTypeFilter />
</MemoryRouter>
</AppContext.Provider>,
);

expect(tree).toMatchSnapshot();
expect(tree).toMatchSnapshot();
});
});

it('should be able to toggle user type - none already set', async () => {
Expand Down
Loading

0 comments on commit 99a3dc6

Please sign in to comment.