diff --git a/src/renderer/components/filters/FilterSection.test.tsx b/src/renderer/components/filters/FilterSection.test.tsx new file mode 100644 index 000000000..7df2b73b6 --- /dev/null +++ b/src/renderer/components/filters/FilterSection.test.tsx @@ -0,0 +1,148 @@ +import { MarkGithubIcon } from '@primer/octicons-react'; +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 '../../utils/notifications/filters'; +import { FilterSection } from './FilterSection'; + +describe('renderer/components/filters/FilterSection.tsx', () => { + const updateFilter = jest.fn(); + + const mockFilter = stateFilter; + const mockFilterSetting = 'filterStates'; + + describe('should render itself & its children', () => { + it('with detailed notifications enabled', () => { + const tree = render( + + + + + , + ); + + expect(tree).toMatchSnapshot(); + }); + + it('with detailed notifications disabled', () => { + const tree = render( + + + , + ); + + expect(tree).toMatchSnapshot(); + }); + }); + + it('should be able to toggle filter value - none already set', async () => { + await act(async () => { + render( + + + + + , + ); + }); + + fireEvent.click(screen.getByLabelText('Open')); + + expect(updateFilter).toHaveBeenCalledWith(mockFilterSetting, '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( + + + + + , + ); + }); + + fireEvent.click(screen.getByLabelText('Closed')); + + expect(updateFilter).toHaveBeenCalledWith( + mockFilterSetting, + 'closed', + true, + ); + + expect( + screen.getByLabelText('Closed').parentNode.parentNode, + ).toMatchSnapshot(); + }); +}); diff --git a/src/renderer/components/filters/FilterSection.tsx b/src/renderer/components/filters/FilterSection.tsx new file mode 100644 index 000000000..e4e39a385 --- /dev/null +++ b/src/renderer/components/filters/FilterSection.tsx @@ -0,0 +1,86 @@ +import { type ReactNode, useContext } from 'react'; + +import type { Icon } from '@primer/octicons-react'; +import { Stack, Text } from '@primer/react'; + +import { AppContext } from '../../context/App'; +import type { FilterSettingsState, FilterValue } from '../../types'; +import type { Filter } from '../../utils/notifications/filters'; +import { Checkbox } from '../fields/Checkbox'; +import { Tooltip } from '../fields/Tooltip'; +import { Title } from '../primitives/Title'; +import { RequiresDetailedNotificationWarning } from './RequiresDetailedNotificationsWarning'; + +export interface IFilterSection { + id: string; + title: string; + icon: Icon; + filter: Filter; + filterSetting: keyof FilterSettingsState; + tooltip?: ReactNode; + layout?: 'horizontal' | 'vertical'; +} + +export const FilterSection = ({ + id, + title, + icon, + filter, + filterSetting, + tooltip, + layout = 'vertical', +}: IFilterSection) => { + const { updateFilter, settings, notifications } = useContext(AppContext); + + return ( +
+ + {title} + {tooltip && ( + + {tooltip} + {filter.requiresDetailsNotifications && ( + + )} + + } + /> + )} + + + + {(Object.keys(filter.FILTER_TYPES) as T[]).map((type) => { + const typeDetails = filter.getTypeDetails(type); + const typeTitle = typeDetails.title; + const typeDescription = typeDetails.description; + const isChecked = filter.isFilterSet(settings, type); + const count = filter.getFilterCount(notifications, type); + + return ( + + updateFilter(filterSetting, type, evt.target.checked) + } + tooltip={typeDescription ? {typeDescription} : null} + disabled={ + filter.requiresDetailsNotifications && + !settings.detailedNotifications + } + counter={count} + /> + ); + })} + +
+ ); +}; diff --git a/src/renderer/components/filters/ReasonFilter.test.tsx b/src/renderer/components/filters/ReasonFilter.test.tsx index 2eee6490e..485cd2156 100644 --- a/src/renderer/components/filters/ReasonFilter.test.tsx +++ b/src/renderer/components/filters/ReasonFilter.test.tsx @@ -1,4 +1,4 @@ -import { act, fireEvent, render, screen } from '@testing-library/react'; +import { render } from '@testing-library/react'; import { MemoryRouter } from 'react-router-dom'; import { mockAccountNotifications } from '../../__mocks__/notifications-mocks'; import { mockSettings } from '../../__mocks__/state-mocks'; @@ -6,8 +6,6 @@ import { AppContext } from '../../context/App'; import { ReasonFilter } from './ReasonFilter'; describe('renderer/components/filters/ReasonFilter.tsx', () => { - const updateFilter = jest.fn(); - it('should render itself & its children', () => { const tree = render( { expect(tree).toMatchSnapshot(); }); - - it('should be able to toggle reason type - none already set', async () => { - await act(async () => { - render( - - - - - , - ); - }); - - fireEvent.click(screen.getByLabelText('Mentioned')); - - expect(updateFilter).toHaveBeenCalledWith('filterReasons', 'mention', true); - - expect( - screen.getByLabelText('Mentioned').parentNode.parentNode, - ).toMatchSnapshot(); - }); - - it('should be able to toggle reason type - some filters already set', async () => { - await act(async () => { - render( - - - - - , - ); - }); - - fireEvent.click(screen.getByLabelText('Mentioned')); - - expect(updateFilter).toHaveBeenCalledWith('filterReasons', 'mention', true); - - expect( - screen.getByLabelText('Mentioned').parentNode.parentNode, - ).toMatchSnapshot(); - }); }); diff --git a/src/renderer/components/filters/ReasonFilter.tsx b/src/renderer/components/filters/ReasonFilter.tsx index 96fd4d740..94c4ec505 100644 --- a/src/renderer/components/filters/ReasonFilter.tsx +++ b/src/renderer/components/filters/ReasonFilter.tsx @@ -1,48 +1,20 @@ -import { type FC, useContext } from 'react'; +import type { FC } from 'react'; import { NoteIcon } from '@primer/octicons-react'; -import { Stack, Text } from '@primer/react'; +import { Text } from '@primer/react'; -import { AppContext } from '../../context/App'; -import type { Reason } from '../../typesGitHub'; -import { - getReasonFilterCount, - isReasonFilterSet, -} from '../../utils/notifications/filters/reason'; -import { FORMATTED_REASONS, getReasonDetails } from '../../utils/reason'; -import { Checkbox } from '../fields/Checkbox'; -import { Title } from '../primitives/Title'; +import { reasonFilter } from '../../utils/notifications/filters'; +import { FilterSection } from './FilterSection'; export const ReasonFilter: FC = () => { - const { updateFilter, settings, notifications } = useContext(AppContext); - return ( -
- Reason - - - {Object.keys(FORMATTED_REASONS).map((reason: Reason) => { - const reasonDetails = getReasonDetails(reason); - const reasonTitle = reasonDetails.title; - const reasonDescription = reasonDetails.description; - const isReasonChecked = isReasonFilterSet(settings, reason); - const reasonCount = getReasonFilterCount(notifications, reason); - - return ( - - updateFilter('filterReasons', reason, evt.target.checked) - } - tooltip={{reasonDescription}} - counter={reasonCount} - /> - ); - })} - -
+ Filter notifications by reason.} + /> ); }; diff --git a/src/renderer/components/filters/RequiresDetailedNotificationsWarning.test.tsx b/src/renderer/components/filters/RequiresDetailedNotificationsWarning.test.tsx new file mode 100644 index 000000000..0ddc41dc8 --- /dev/null +++ b/src/renderer/components/filters/RequiresDetailedNotificationsWarning.test.tsx @@ -0,0 +1,25 @@ +import { render } 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 { RequiresDetailedNotificationWarning } from './RequiresDetailedNotificationsWarning'; + +describe('renderer/components/filters/RequiresDetailedNotificationsWarning.tsx', () => { + it('should render itself & its children', () => { + const tree = render( + + + + + , + ); + + expect(tree).toMatchSnapshot(); + }); +}); diff --git a/src/renderer/components/filters/RequiresDetailedNotificationsWarning.tsx b/src/renderer/components/filters/RequiresDetailedNotificationsWarning.tsx new file mode 100644 index 000000000..98dc3c1e7 --- /dev/null +++ b/src/renderer/components/filters/RequiresDetailedNotificationsWarning.tsx @@ -0,0 +1,9 @@ +import { Text } from '@primer/react'; +import type { FC } from 'react'; + +export const RequiresDetailedNotificationWarning: FC = () => ( + + ⚠️ This filter requires the Detailed Notifications{' '} + setting to be enabled. + +); diff --git a/src/renderer/components/filters/StateFilter.test.tsx b/src/renderer/components/filters/StateFilter.test.tsx index 8f3a06ad2..316a417f6 100644 --- a/src/renderer/components/filters/StateFilter.test.tsx +++ b/src/renderer/components/filters/StateFilter.test.tsx @@ -1,4 +1,4 @@ -import { act, fireEvent, render, screen } from '@testing-library/react'; +import { render } from '@testing-library/react'; import { MemoryRouter } from 'react-router-dom'; import { mockAccountNotifications } from '../../__mocks__/notifications-mocks'; import { mockSettings } from '../../__mocks__/state-mocks'; @@ -7,105 +7,23 @@ 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( - - - - - , - ); - - expect(tree).toMatchSnapshot(); - }); - - it('with detailed notifications disabled', () => { - const tree = render( - - - - - , - ); - - expect(tree).toMatchSnapshot(); - }); - }); - - it('should be able to toggle user type - none already set', async () => { - await act(async () => { - render( - - - - - , - ); - }); - - 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( - - - - - , - ); - }); - - fireEvent.click(screen.getByLabelText('Closed')); - - expect(updateFilter).toHaveBeenCalledWith('filterStates', 'closed', true); - - expect( - screen.getByLabelText('Closed').parentNode.parentNode, - ).toMatchSnapshot(); + it('should render itself & its children', () => { + const tree = render( + + + + + , + ); + + expect(tree).toMatchSnapshot(); }); }); diff --git a/src/renderer/components/filters/StateFilter.tsx b/src/renderer/components/filters/StateFilter.tsx index 2d0ec9a20..b6f41b87d 100644 --- a/src/renderer/components/filters/StateFilter.tsx +++ b/src/renderer/components/filters/StateFilter.tsx @@ -1,68 +1,20 @@ -import { type FC, useContext } from 'react'; +import type { FC } from 'react'; import { IssueOpenedIcon } from '@primer/octicons-react'; -import { Stack, Text } from '@primer/react'; +import { 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'; +import { stateFilter } from '../../utils/notifications/filters'; +import { FilterSection } from './FilterSection'; export const StateFilter: FC = () => { - const { updateFilter, settings, notifications } = useContext(AppContext); - return ( -
- - State - - Filter notifications by state. - - ⚠️ This filter requires the{' '} - Detailed Notifications setting to be - enabled. - - - } - /> - - - - {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 ( - - updateFilter('filterStates', stateType, evt.target.checked) - } - tooltip={ - stateDescription ? {stateDescription} : null - } - disabled={!settings.detailedNotifications} - counter={stateCount} - /> - ); - })} - -
+ Filter notifications by state.} + /> ); }; diff --git a/src/renderer/components/filters/SubjectTypeFilter.test.tsx b/src/renderer/components/filters/SubjectTypeFilter.test.tsx index e9ba6372c..b9f8cf3f2 100644 --- a/src/renderer/components/filters/SubjectTypeFilter.test.tsx +++ b/src/renderer/components/filters/SubjectTypeFilter.test.tsx @@ -1,4 +1,4 @@ -import { act, fireEvent, render, screen } from '@testing-library/react'; +import { render } from '@testing-library/react'; import { MemoryRouter } from 'react-router-dom'; import { mockAccountNotifications } from '../../__mocks__/notifications-mocks'; import { mockSettings } from '../../__mocks__/state-mocks'; @@ -7,8 +7,6 @@ import type { SettingsState } from '../../types'; import { SubjectTypeFilter } from './SubjectTypeFilter'; describe('renderer/components/filters/SubjectTypeFilter.tsx', () => { - const updateFilter = jest.fn(); - it('should render itself & its children', () => { const tree = render( { expect(tree).toMatchSnapshot(); }); - - it('should be able to toggle subject type - none already set', async () => { - await act(async () => { - render( - - - - - , - ); - }); - - fireEvent.click(screen.getByLabelText('Issue')); - - expect(updateFilter).toHaveBeenCalledWith( - 'filterSubjectTypes', - 'Issue', - true, - ); - - expect( - screen.getByLabelText('Issue').parentNode.parentNode, - ).toMatchSnapshot(); - }); - - it('should be able to toggle subject type - some filters already set', async () => { - await act(async () => { - render( - - - - - , - ); - }); - - fireEvent.click(screen.getByLabelText('Pull Request')); - - expect(updateFilter).toHaveBeenCalledWith( - 'filterSubjectTypes', - 'PullRequest', - true, - ); - - expect( - screen.getByLabelText('Pull Request').parentNode.parentNode, - ).toMatchSnapshot(); - }); }); diff --git a/src/renderer/components/filters/SubjectTypeFilter.tsx b/src/renderer/components/filters/SubjectTypeFilter.tsx index 7f0435cff..25ed975c3 100644 --- a/src/renderer/components/filters/SubjectTypeFilter.tsx +++ b/src/renderer/components/filters/SubjectTypeFilter.tsx @@ -1,75 +1,20 @@ -import { type FC, useContext } from 'react'; +import type { FC } from 'react'; import { BellIcon } from '@primer/octicons-react'; -import { Stack, Text } from '@primer/react'; +import { Text } from '@primer/react'; -import { AppContext } from '../../context/App'; -import type { SubjectType } from '../../typesGitHub'; -import { - FILTERS_SUBJECT_TYPES, - getSubjectTypeDetails, - getSubjectTypeFilterCount, - isSubjectTypeFilterSet, -} from '../../utils/notifications/filters/subjectType'; -import { Checkbox } from '../fields/Checkbox'; -import { Tooltip } from '../fields/Tooltip'; -import { Title } from '../primitives/Title'; +import { subjectTypeFilter } from '../../utils/notifications/filters'; +import { FilterSection } from './FilterSection'; export const SubjectTypeFilter: FC = () => { - const { updateFilter, settings, notifications } = useContext(AppContext); - return ( -
- - Type - - Filter notifications by type. - - } - /> - - - - {Object.keys(FILTERS_SUBJECT_TYPES).map((subjectType: SubjectType) => { - const subjectTypeDetails = getSubjectTypeDetails(subjectType); - const subjectTypeTitle = subjectTypeDetails.title; - const subjectTypeDescription = subjectTypeDetails.description; - const isSubjectTypeChecked = isSubjectTypeFilterSet( - settings, - subjectType, - ); - const subjectTypeCount = getSubjectTypeFilterCount( - notifications, - subjectType, - ); - - return ( - - updateFilter( - 'filterSubjectTypes', - subjectType, - evt.target.checked, - ) - } - tooltip={ - subjectTypeDescription ? ( - {subjectTypeDescription} - ) : null - } - disabled={!settings.detailedNotifications} - counter={subjectTypeCount} - /> - ); - })} - -
+ Filter notifications by type.} + /> ); }; diff --git a/src/renderer/components/filters/UserHandleFilter.tsx b/src/renderer/components/filters/UserHandleFilter.tsx index 7b788557f..76cf3c643 100644 --- a/src/renderer/components/filters/UserHandleFilter.tsx +++ b/src/renderer/components/filters/UserHandleFilter.tsx @@ -15,6 +15,7 @@ import { } from '../../utils/notifications/filters/handles'; import { Tooltip } from '../fields/Tooltip'; import { Title } from '../primitives/Title'; +import { RequiresDetailedNotificationWarning } from './RequiresDetailedNotificationsWarning'; type InputToken = { id: number; @@ -127,11 +128,7 @@ export const UserHandleFilter: FC = () => { tooltip={ Filter notifications by user handle. - - ⚠️ This filter requires the{' '} - Detailed Notifications setting to be - enabled. - + } /> diff --git a/src/renderer/components/filters/UserTypeFilter.test.tsx b/src/renderer/components/filters/UserTypeFilter.test.tsx index c56c22429..4ade31495 100644 --- a/src/renderer/components/filters/UserTypeFilter.test.tsx +++ b/src/renderer/components/filters/UserTypeFilter.test.tsx @@ -1,4 +1,4 @@ -import { act, fireEvent, render, screen } from '@testing-library/react'; +import { render } from '@testing-library/react'; import { MemoryRouter } from 'react-router-dom'; import { mockAccountNotifications } from '../../__mocks__/notifications-mocks'; import { mockSettings } from '../../__mocks__/state-mocks'; @@ -7,105 +7,23 @@ import type { SettingsState } from '../../types'; import { UserTypeFilter } from './UserTypeFilter'; describe('renderer/components/filters/UserTypeFilter.tsx', () => { - const updateFilter = jest.fn(); - - describe('should render itself & its children', () => { - it('with detailed notifications enabled', () => { - const tree = render( - - - - - , - ); - - expect(tree).toMatchSnapshot(); - }); - - it('with detailed notifications disabled', () => { - const tree = render( - - - - - , - ); - - expect(tree).toMatchSnapshot(); - }); - }); - - it('should be able to toggle user type - none already set', async () => { - await act(async () => { - render( - - - - - , - ); - }); - - fireEvent.click(screen.getByLabelText('User')); - - expect(updateFilter).toHaveBeenCalledWith('filterUserTypes', 'User', true); - - expect( - screen.getByLabelText('User').parentNode.parentNode, - ).toMatchSnapshot(); - }); - - it('should be able to toggle user type - some filters already set', async () => { - await act(async () => { - render( - - - - - , - ); - }); - - fireEvent.click(screen.getByLabelText('Bot')); - - expect(updateFilter).toHaveBeenCalledWith('filterUserTypes', 'Bot', true); - - expect( - screen.getByLabelText('Bot').parentNode.parentNode, - ).toMatchSnapshot(); + it('should render itself & its children', () => { + const tree = render( + + + + + , + ); + + expect(tree).toMatchSnapshot(); }); }); diff --git a/src/renderer/components/filters/UserTypeFilter.tsx b/src/renderer/components/filters/UserTypeFilter.tsx index 4671a9e32..565a91adb 100644 --- a/src/renderer/components/filters/UserTypeFilter.tsx +++ b/src/renderer/components/filters/UserTypeFilter.tsx @@ -1,4 +1,4 @@ -import { type FC, useContext } from 'react'; +import type { FC } from 'react'; import { DependabotIcon, @@ -8,83 +8,40 @@ import { } from '@primer/octicons-react'; import { Box, Stack, Text } from '@primer/react'; -import { AppContext } from '../../context/App'; import { Size } from '../../types'; -import type { UserType } from '../../typesGitHub'; -import { - FILTERS_USER_TYPES, - getUserTypeDetails, - getUserTypeFilterCount, - isUserTypeFilterSet, -} from '../../utils/notifications/filters/userType'; -import { Checkbox } from '../fields/Checkbox'; -import { Tooltip } from '../fields/Tooltip'; -import { Title } from '../primitives/Title'; +import { userTypeFilter } from '../../utils/notifications/filters'; +import { FilterSection } from './FilterSection'; export const UserTypeFilter: FC = () => { - const { updateFilter, settings, notifications } = useContext(AppContext); - return ( -
- - User Type - + Filter notifications by user type: + - Filter notifications by user type: - - - - - User - - - - Bot accounts such as @dependabot, @renovate, @netlify, etc - - - - Organization - - - - - ⚠️ This filter requires the{' '} - Detailed Notifications setting to be - enabled. - + + + User + + + + Bot accounts such as @dependabot, @renovate, @netlify, etc + + + + Organization + - } - /> - - - - {Object.keys(FILTERS_USER_TYPES).map((userType: UserType) => { - const userTypeDetails = getUserTypeDetails(userType); - const userTypeTitle = userTypeDetails.title; - const userTypeDescription = userTypeDetails.description; - const isUserTypeChecked = isUserTypeFilterSet(settings, userType); - const userTypeCount = getUserTypeFilterCount(notifications, userType); - - return ( - - updateFilter('filterUserTypes', userType, evt.target.checked) - } - tooltip={ - userTypeDescription ? {userTypeDescription} : null - } - disabled={!settings.detailedNotifications} - counter={userTypeCount} - /> - ); - })} - -
+ + + } + /> ); }; diff --git a/src/renderer/components/filters/__snapshots__/FilterSection.test.tsx.snap b/src/renderer/components/filters/__snapshots__/FilterSection.test.tsx.snap new file mode 100644 index 000000000..f11b2ee11 --- /dev/null +++ b/src/renderer/components/filters/__snapshots__/FilterSection.test.tsx.snap @@ -0,0 +1,1630 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renderer/components/filters/FilterSection.tsx should be able to toggle filter value - none already set 1`] = ` +
+
+ + + + 0 + +
+
+ + +
+ +
+ + 0 + +
+
+ + + + 0 + +
+
+ + +
+ +
+ + 0 + +
+
+ + +
+ +
+ + 0 + +
+
+`; + +exports[`renderer/components/filters/FilterSection.tsx should be able to toggle user type - some filters already set 1`] = ` +
+
+ + + + 0 + +
+
+ + +
+ +
+ + 0 + +
+
+ + + + 0 + +
+
+ + +
+ +
+ + 0 + +
+
+ + +
+ +
+ + 0 + +
+
+`; + +exports[`renderer/components/filters/FilterSection.tsx should render itself & its children with detailed notifications disabled 1`] = ` +{ + "asFragment": [Function], + "baseElement": +
+
+
+ +
+
+ +

+ FilterSectionTitle +

+
+
+
+
+
+
+ + + + 0 + +
+
+ + +
+ +
+ + 1 + +
+
+ + + + 0 + +
+
+ + +
+ +
+ + 0 + +
+
+ + +
+ +
+ + 3 + +
+
+
+
+ , + "container":
+
+
+ +
+
+ +

+ FilterSectionTitle +

+
+
+
+
+
+
+ + + + 0 + +
+
+ + +
+ +
+ + 1 + +
+
+ + + + 0 + +
+
+ + +
+ +
+ + 0 + +
+
+ + +
+ +
+ + 3 + +
+
+
+
, + "debug": [Function], + "findAllByAltText": [Function], + "findAllByDisplayValue": [Function], + "findAllByLabelText": [Function], + "findAllByPlaceholderText": [Function], + "findAllByRole": [Function], + "findAllByTestId": [Function], + "findAllByText": [Function], + "findAllByTitle": [Function], + "findByAltText": [Function], + "findByDisplayValue": [Function], + "findByLabelText": [Function], + "findByPlaceholderText": [Function], + "findByRole": [Function], + "findByTestId": [Function], + "findByText": [Function], + "findByTitle": [Function], + "getAllByAltText": [Function], + "getAllByDisplayValue": [Function], + "getAllByLabelText": [Function], + "getAllByPlaceholderText": [Function], + "getAllByRole": [Function], + "getAllByTestId": [Function], + "getAllByText": [Function], + "getAllByTitle": [Function], + "getByAltText": [Function], + "getByDisplayValue": [Function], + "getByLabelText": [Function], + "getByPlaceholderText": [Function], + "getByRole": [Function], + "getByTestId": [Function], + "getByText": [Function], + "getByTitle": [Function], + "queryAllByAltText": [Function], + "queryAllByDisplayValue": [Function], + "queryAllByLabelText": [Function], + "queryAllByPlaceholderText": [Function], + "queryAllByRole": [Function], + "queryAllByTestId": [Function], + "queryAllByText": [Function], + "queryAllByTitle": [Function], + "queryByAltText": [Function], + "queryByDisplayValue": [Function], + "queryByLabelText": [Function], + "queryByPlaceholderText": [Function], + "queryByRole": [Function], + "queryByTestId": [Function], + "queryByText": [Function], + "queryByTitle": [Function], + "rerender": [Function], + "unmount": [Function], +} +`; + +exports[`renderer/components/filters/FilterSection.tsx should render itself & its children with detailed notifications enabled 1`] = ` +{ + "asFragment": [Function], + "baseElement": +
+
+
+ +
+
+ +

+ FilterSectionTitle +

+
+
+
+
+
+
+ + + + 0 + +
+
+ + +
+ +
+ + 1 + +
+
+ + + + 0 + +
+
+ + +
+ +
+ + 0 + +
+
+ + +
+ +
+ + 3 + +
+
+
+
+ , + "container":
+
+
+ +
+
+ +

+ FilterSectionTitle +

+
+
+
+
+
+
+ + + + 0 + +
+
+ + +
+ +
+ + 1 + +
+
+ + + + 0 + +
+
+ + +
+ +
+ + 0 + +
+
+ + +
+ +
+ + 3 + +
+
+
+
, + "debug": [Function], + "findAllByAltText": [Function], + "findAllByDisplayValue": [Function], + "findAllByLabelText": [Function], + "findAllByPlaceholderText": [Function], + "findAllByRole": [Function], + "findAllByTestId": [Function], + "findAllByText": [Function], + "findAllByTitle": [Function], + "findByAltText": [Function], + "findByDisplayValue": [Function], + "findByLabelText": [Function], + "findByPlaceholderText": [Function], + "findByRole": [Function], + "findByTestId": [Function], + "findByText": [Function], + "findByTitle": [Function], + "getAllByAltText": [Function], + "getAllByDisplayValue": [Function], + "getAllByLabelText": [Function], + "getAllByPlaceholderText": [Function], + "getAllByRole": [Function], + "getAllByTestId": [Function], + "getAllByText": [Function], + "getAllByTitle": [Function], + "getByAltText": [Function], + "getByDisplayValue": [Function], + "getByLabelText": [Function], + "getByPlaceholderText": [Function], + "getByRole": [Function], + "getByTestId": [Function], + "getByText": [Function], + "getByTitle": [Function], + "queryAllByAltText": [Function], + "queryAllByDisplayValue": [Function], + "queryAllByLabelText": [Function], + "queryAllByPlaceholderText": [Function], + "queryAllByRole": [Function], + "queryAllByTestId": [Function], + "queryAllByText": [Function], + "queryAllByTitle": [Function], + "queryByAltText": [Function], + "queryByDisplayValue": [Function], + "queryByLabelText": [Function], + "queryByPlaceholderText": [Function], + "queryByRole": [Function], + "queryByTestId": [Function], + "queryByText": [Function], + "queryByTitle": [Function], + "rerender": [Function], + "unmount": [Function], +} +`; diff --git a/src/renderer/components/filters/__snapshots__/ReasonFilter.test.tsx.snap b/src/renderer/components/filters/__snapshots__/ReasonFilter.test.tsx.snap index 64caea7b4..77795a432 100644 --- a/src/renderer/components/filters/__snapshots__/ReasonFilter.test.tsx.snap +++ b/src/renderer/components/filters/__snapshots__/ReasonFilter.test.tsx.snap @@ -1,1532 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`renderer/components/filters/ReasonFilter.tsx should be able to toggle reason type - none already set 1`] = ` -
-
- - -
- -
- - 0 - -
-
- - -
- -
- - 0 - -
-
- - -
- -
- - 0 - -
-
- - -
- -
- - 0 - -
-
- - -
- -
- - 0 - -
-
- - -
- -
- - 0 - -
-
- - -
- -
- - 0 - -
-
- - -
- -
- - 0 - -
-
- - -
- -
- - 0 - -
-
- - -
- -
- - 0 - -
-
- - -
- -
- - 0 - -
-
- - -
- -
- - 0 - -
-
- - -
- -
- - 0 - -
-
- - -
- -
- - 0 - -
-
- - -
- -
- - 0 - -
-
-`; - -exports[`renderer/components/filters/ReasonFilter.tsx should be able to toggle reason type - some filters already set 1`] = ` -
-
- - -
- -
- - 0 - -
-
- - -
- -
- - 0 - -
-
- - -
- -
- - 0 - -
-
- - -
- -
- - 0 - -
-
- - -
- -
- - 0 - -
-
- - -
- -
- - 0 - -
-
- - -
- -
- - 0 - -
-
- - -
- -
- - 0 - -
-
- - -
- -
- - 0 - -
-
- - -
- -
- - 0 - -
-
- - -
- -
- - 0 - -
-
- - -
- -
- - 0 - -
-
- - -
- -
- - 0 - -
-
- - -
- -
- - 0 - -
-
- - -
- -
- - 0 - -
-
-`; - exports[`renderer/components/filters/ReasonFilter.tsx should render itself & its children 1`] = ` { "asFragment": [Function], @@ -1535,45 +8,78 @@ exports[`renderer/components/filters/ReasonFilter.tsx should render itself & its
- -
+
+
- -

- Reason -

+ +

+ Reason +

+
+ +
+
- +
- -
+
+
- -

- Reason -

+ +

+ Reason +

+
+ +
+
- +
+
+ + ⚠️ This filter requires the + + Detailed Notifications + + + setting to be enabled. + +
+ , + "container":
+ + ⚠️ This filter requires the + + Detailed Notifications + + + setting to be enabled. + +
, + "debug": [Function], + "findAllByAltText": [Function], + "findAllByDisplayValue": [Function], + "findAllByLabelText": [Function], + "findAllByPlaceholderText": [Function], + "findAllByRole": [Function], + "findAllByTestId": [Function], + "findAllByText": [Function], + "findAllByTitle": [Function], + "findByAltText": [Function], + "findByDisplayValue": [Function], + "findByLabelText": [Function], + "findByPlaceholderText": [Function], + "findByRole": [Function], + "findByTestId": [Function], + "findByText": [Function], + "findByTitle": [Function], + "getAllByAltText": [Function], + "getAllByDisplayValue": [Function], + "getAllByLabelText": [Function], + "getAllByPlaceholderText": [Function], + "getAllByRole": [Function], + "getAllByTestId": [Function], + "getAllByText": [Function], + "getAllByTitle": [Function], + "getByAltText": [Function], + "getByDisplayValue": [Function], + "getByLabelText": [Function], + "getByPlaceholderText": [Function], + "getByRole": [Function], + "getByTestId": [Function], + "getByText": [Function], + "getByTitle": [Function], + "queryAllByAltText": [Function], + "queryAllByDisplayValue": [Function], + "queryAllByLabelText": [Function], + "queryAllByPlaceholderText": [Function], + "queryAllByRole": [Function], + "queryAllByTestId": [Function], + "queryAllByText": [Function], + "queryAllByTitle": [Function], + "queryByAltText": [Function], + "queryByDisplayValue": [Function], + "queryByLabelText": [Function], + "queryByPlaceholderText": [Function], + "queryByRole": [Function], + "queryByTestId": [Function], + "queryByText": [Function], + "queryByTitle": [Function], + "rerender": [Function], + "unmount": [Function], +} +`; diff --git a/src/renderer/components/filters/__snapshots__/StateFilter.test.tsx.snap b/src/renderer/components/filters/__snapshots__/StateFilter.test.tsx.snap index 20482a468..fb7c325b4 100644 --- a/src/renderer/components/filters/__snapshots__/StateFilter.test.tsx.snap +++ b/src/renderer/components/filters/__snapshots__/StateFilter.test.tsx.snap @@ -1,1100 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`renderer/components/filters/StateFilter.tsx should be able to toggle user type - none already set 1`] = ` -
-
- - - - 0 - -
-
- - -
- -
- - 0 - -
-
- - - - 0 - -
-
- - -
- -
- - 0 - -
-
- - -
- -
- - 0 - -
-
-`; - -exports[`renderer/components/filters/StateFilter.tsx should be able to toggle user type - some filters already set 1`] = ` -
-
- - - - 0 - -
-
- - -
- -
- - 0 - -
-
- - - - 0 - -
-
- - -
- -
- - 0 - -
-
- - -
- -
- - 0 - -
-
-`; - -exports[`renderer/components/filters/StateFilter.tsx should render itself & its children with detailed notifications disabled 1`] = ` -{ - "asFragment": [Function], - "baseElement": -
-
-
- -
-
- -

- State -

-
-
-
-
- -
-
-
-
- - - - 0 - -
-
- - -
- -
- - 1 - -
-
- - - - 0 - -
-
- - -
- -
- - 0 - -
-
- - -
- -
- - 3 - -
-
-
-
- , - "container":
-
-
- -
-
- -

- State -

-
-
-
-
- -
-
-
-
- - - - 0 - -
-
- - -
- -
- - 1 - -
-
- - - - 0 - -
-
- - -
- -
- - 0 - -
-
- - -
- -
- - 3 - -
-
-
-
, - "debug": [Function], - "findAllByAltText": [Function], - "findAllByDisplayValue": [Function], - "findAllByLabelText": [Function], - "findAllByPlaceholderText": [Function], - "findAllByRole": [Function], - "findAllByTestId": [Function], - "findAllByText": [Function], - "findAllByTitle": [Function], - "findByAltText": [Function], - "findByDisplayValue": [Function], - "findByLabelText": [Function], - "findByPlaceholderText": [Function], - "findByRole": [Function], - "findByTestId": [Function], - "findByText": [Function], - "findByTitle": [Function], - "getAllByAltText": [Function], - "getAllByDisplayValue": [Function], - "getAllByLabelText": [Function], - "getAllByPlaceholderText": [Function], - "getAllByRole": [Function], - "getAllByTestId": [Function], - "getAllByText": [Function], - "getAllByTitle": [Function], - "getByAltText": [Function], - "getByDisplayValue": [Function], - "getByLabelText": [Function], - "getByPlaceholderText": [Function], - "getByRole": [Function], - "getByTestId": [Function], - "getByText": [Function], - "getByTitle": [Function], - "queryAllByAltText": [Function], - "queryAllByDisplayValue": [Function], - "queryAllByLabelText": [Function], - "queryAllByPlaceholderText": [Function], - "queryAllByRole": [Function], - "queryAllByTestId": [Function], - "queryAllByText": [Function], - "queryAllByTitle": [Function], - "queryByAltText": [Function], - "queryByDisplayValue": [Function], - "queryByLabelText": [Function], - "queryByPlaceholderText": [Function], - "queryByRole": [Function], - "queryByTestId": [Function], - "queryByText": [Function], - "queryByTitle": [Function], - "rerender": [Function], - "unmount": [Function], -} -`; - -exports[`renderer/components/filters/StateFilter.tsx should render itself & its children with detailed notifications enabled 1`] = ` +exports[`renderer/components/filters/StateFilter.tsx should render itself & its children 1`] = ` { "asFragment": [Function], "baseElement": @@ -1104,7 +10,7 @@ exports[`renderer/components/filters/StateFilter.tsx should render itself & its >
-
- - - - 0 - -
-
- - - - 0 - -
-
- - - - 0 - -
-
- - - - 0 - -
-
- - - - 0 - -
-
- - - - 0 - -
-
- - - - 0 - -
-
- - - - 0 - -
-
- - - - 0 - -
-
- - - - 0 - -
-
-`; - -exports[`renderer/components/filters/SubjectTypeFilter.tsx should be able to toggle subject type - some filters already set 1`] = ` -
-
- - - - 0 - -
-
- - - - 0 - -
-
- - - - 0 - -
-
- - - - 0 - -
-
- - - - 0 - -
-
- - - - 0 - -
-
- - - - 0 - -
-
- - - - 0 - -
-
- - - - 0 - -
-
- - - - 0 - -
-
-`; - exports[`renderer/components/filters/SubjectTypeFilter.tsx should render itself & its children 1`] = ` { "asFragment": [Function], diff --git a/src/renderer/components/filters/__snapshots__/UserTypeFilter.test.tsx.snap b/src/renderer/components/filters/__snapshots__/UserTypeFilter.test.tsx.snap index 16253278c..d185c6465 100644 --- a/src/renderer/components/filters/__snapshots__/UserTypeFilter.test.tsx.snap +++ b/src/renderer/components/filters/__snapshots__/UserTypeFilter.test.tsx.snap @@ -1,690 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`renderer/components/filters/UserTypeFilter.tsx should be able to toggle user type - none already set 1`] = ` -
-
- - - - 0 - -
-
- - -
- -
- - 0 - -
-
- - - - 0 - -
-
-`; - -exports[`renderer/components/filters/UserTypeFilter.tsx should be able to toggle user type - some filters already set 1`] = ` -
-
- - - - 0 - -
-
- - -
- -
- - 0 - -
-
- - - - 0 - -
-
-`; - -exports[`renderer/components/filters/UserTypeFilter.tsx should render itself & its children with detailed notifications disabled 1`] = ` -{ - "asFragment": [Function], - "baseElement": -
-
-
- -
-
- -

- User Type -

-
-
-
-
- -
-
-
-
- - - - 1 - -
-
- - -
- -
- - 0 - -
-
- - - - 0 - -
-
-
-
- , - "container":
-
-
- -
-
- -

- User Type -

-
-
-
-
- -
-
-
-
- - - - 1 - -
-
- - -
- -
- - 0 - -
-
- - - - 0 - -
-
-
-
, - "debug": [Function], - "findAllByAltText": [Function], - "findAllByDisplayValue": [Function], - "findAllByLabelText": [Function], - "findAllByPlaceholderText": [Function], - "findAllByRole": [Function], - "findAllByTestId": [Function], - "findAllByText": [Function], - "findAllByTitle": [Function], - "findByAltText": [Function], - "findByDisplayValue": [Function], - "findByLabelText": [Function], - "findByPlaceholderText": [Function], - "findByRole": [Function], - "findByTestId": [Function], - "findByText": [Function], - "findByTitle": [Function], - "getAllByAltText": [Function], - "getAllByDisplayValue": [Function], - "getAllByLabelText": [Function], - "getAllByPlaceholderText": [Function], - "getAllByRole": [Function], - "getAllByTestId": [Function], - "getAllByText": [Function], - "getAllByTitle": [Function], - "getByAltText": [Function], - "getByDisplayValue": [Function], - "getByLabelText": [Function], - "getByPlaceholderText": [Function], - "getByRole": [Function], - "getByTestId": [Function], - "getByText": [Function], - "getByTitle": [Function], - "queryAllByAltText": [Function], - "queryAllByDisplayValue": [Function], - "queryAllByLabelText": [Function], - "queryAllByPlaceholderText": [Function], - "queryAllByRole": [Function], - "queryAllByTestId": [Function], - "queryAllByText": [Function], - "queryAllByTitle": [Function], - "queryByAltText": [Function], - "queryByDisplayValue": [Function], - "queryByLabelText": [Function], - "queryByPlaceholderText": [Function], - "queryByRole": [Function], - "queryByTestId": [Function], - "queryByText": [Function], - "queryByTitle": [Function], - "rerender": [Function], - "unmount": [Function], -} -`; - -exports[`renderer/components/filters/UserTypeFilter.tsx should render itself & its children with detailed notifications enabled 1`] = ` +exports[`renderer/components/filters/UserTypeFilter.tsx should render itself & its children 1`] = ` { "asFragment": [Function], "baseElement": @@ -694,7 +10,7 @@ exports[`renderer/components/filters/UserTypeFilter.tsx should render itself & i >
- -
+
+
- -

- Reason -

+ +

+ Reason +

+
+ +
+
- +
- filterNotificationByUserType(notification, userType), + userTypeFilter.filterNotification(notification, userType), ); } @@ -45,28 +42,28 @@ export function filterNotifications( ); } - if (hasStateFilters(settings)) { + if (stateFilter.hasFilters(settings)) { passesFilters = passesFilters && settings.filterStates.some((state) => - filterNotificationByState(notification, state), + stateFilter.filterNotification(notification, state), ); } } - if (hasSubjectTypeFilters(settings)) { + if (subjectTypeFilter.hasFilters(settings)) { passesFilters = passesFilters && settings.filterSubjectTypes.some((subjectType) => - filterNotificationBySubjectType(notification, subjectType), + subjectTypeFilter.filterNotification(notification, subjectType), ); } - if (hasReasonFilters(settings)) { + if (reasonFilter.hasFilters(settings)) { passesFilters = passesFilters && settings.filterReasons.some((reason) => - filterNotificationByReason(notification, reason), + reasonFilter.filterNotification(notification, reason), ); } @@ -76,11 +73,11 @@ export function filterNotifications( export function hasAnyFiltersSet(settings: SettingsState): boolean { return ( - hasUserTypeFilters(settings) || + userTypeFilter.hasFilters(settings) || hasIncludeHandleFilters(settings) || hasExcludeHandleFilters(settings) || - hasSubjectTypeFilters(settings) || - hasStateFilters(settings) || - hasReasonFilters(settings) + subjectTypeFilter.hasFilters(settings) || + stateFilter.hasFilters(settings) || + reasonFilter.hasFilters(settings) ); } diff --git a/src/renderer/utils/notifications/filters/index.ts b/src/renderer/utils/notifications/filters/index.ts new file mode 100644 index 000000000..b8a4ce82f --- /dev/null +++ b/src/renderer/utils/notifications/filters/index.ts @@ -0,0 +1,6 @@ +export * from './types'; +export * from './userType'; +export * from './subjectType'; +export * from './state'; +export * from './reason'; +export * from './handles'; diff --git a/src/renderer/utils/notifications/filters/reason.ts b/src/renderer/utils/notifications/filters/reason.ts index 34a7fe6f3..fa3cbfe88 100644 --- a/src/renderer/utils/notifications/filters/reason.ts +++ b/src/renderer/utils/notifications/filters/reason.ts @@ -1,28 +1,40 @@ -import type { AccountNotifications, SettingsState } from '../../../types'; +import type { + AccountNotifications, + SettingsState, + TypeDetails, +} from '../../../types'; import type { Notification, Reason } from '../../../typesGitHub'; +import { REASON_TYPE_DETAILS } from '../../reason'; +import type { Filter } from './types'; -export function hasReasonFilters(settings: SettingsState) { - return settings.filterReasons.length > 0; -} +export const reasonFilter: Filter = { + FILTER_TYPES: REASON_TYPE_DETAILS, -export function isReasonFilterSet(settings: SettingsState, reason: Reason) { - return settings.filterReasons.includes(reason); -} + requiresDetailsNotifications: false, -export function getReasonFilterCount( - notifications: AccountNotifications[], - reason: Reason, -) { - return notifications.reduce( - (sum, account) => - sum + account.notifications.filter((n) => n.reason === reason).length, - 0, - ); -} + getTypeDetails(reason: Reason): TypeDetails { + return this.FILTER_TYPES[reason]; + }, -export function filterNotificationByReason( - notification: Notification, - reason: Reason, -): boolean { - return notification.reason === reason; -} + hasFilters(settings: SettingsState) { + return settings.filterReasons.length > 0; + }, + + isFilterSet(settings: SettingsState, reason: Reason) { + return settings.filterReasons.includes(reason); + }, + + getFilterCount(notifications: AccountNotifications[], reason: Reason) { + return notifications.reduce( + (sum, account) => + sum + + account.notifications.filter((n) => this.filterNotification(n, reason)) + .length, + 0, + ); + }, + + filterNotification(notification: Notification, reason: Reason): boolean { + return notification.reason === reason; + }, +}; diff --git a/src/renderer/utils/notifications/filters/state.test.ts b/src/renderer/utils/notifications/filters/state.test.ts index aaf738c1f..78fce244b 100644 --- a/src/renderer/utils/notifications/filters/state.test.ts +++ b/src/renderer/utils/notifications/filters/state.test.ts @@ -1,5 +1,5 @@ import type { Notification } from '../../../typesGitHub'; -import { filterNotificationByState } from './state'; +import { stateFilter } from './state'; describe('renderer/utils/notifications/filters/state.ts', () => { afterEach(() => { @@ -15,47 +15,47 @@ describe('renderer/utils/notifications/filters/state.ts', () => { // Open states mockPartialNotification.subject.state = 'open'; - expect(filterNotificationByState(mockPartialNotification, 'open')).toBe( - true, - ); + expect( + stateFilter.filterNotification(mockPartialNotification, 'open'), + ).toBe(true); mockPartialNotification.subject.state = 'reopened'; - expect(filterNotificationByState(mockPartialNotification, 'open')).toBe( - true, - ); + expect( + stateFilter.filterNotification(mockPartialNotification, 'open'), + ).toBe(true); // Closed states mockPartialNotification.subject.state = 'closed'; - expect(filterNotificationByState(mockPartialNotification, 'closed')).toBe( - true, - ); + expect( + stateFilter.filterNotification(mockPartialNotification, 'closed'), + ).toBe(true); mockPartialNotification.subject.state = 'completed'; - expect(filterNotificationByState(mockPartialNotification, 'closed')).toBe( - true, - ); + expect( + stateFilter.filterNotification(mockPartialNotification, 'closed'), + ).toBe(true); mockPartialNotification.subject.state = 'not_planned'; - expect(filterNotificationByState(mockPartialNotification, 'closed')).toBe( - true, - ); + expect( + stateFilter.filterNotification(mockPartialNotification, 'closed'), + ).toBe(true); // Merged states mockPartialNotification.subject.state = 'merged'; - expect(filterNotificationByState(mockPartialNotification, 'merged')).toBe( - true, - ); + expect( + stateFilter.filterNotification(mockPartialNotification, 'merged'), + ).toBe(true); // Draft states mockPartialNotification.subject.state = 'draft'; - expect(filterNotificationByState(mockPartialNotification, 'draft')).toBe( - true, - ); + expect( + stateFilter.filterNotification(mockPartialNotification, 'draft'), + ).toBe(true); // Other states mockPartialNotification.subject.state = 'OUTDATED'; - expect(filterNotificationByState(mockPartialNotification, 'other')).toBe( - true, - ); + expect( + stateFilter.filterNotification(mockPartialNotification, 'other'), + ).toBe(true); }); }); diff --git a/src/renderer/utils/notifications/filters/state.ts b/src/renderer/utils/notifications/filters/state.ts index f87709edc..aa775bb04 100644 --- a/src/renderer/utils/notifications/filters/state.ts +++ b/src/renderer/utils/notifications/filters/state.ts @@ -5,8 +5,9 @@ import type { TypeDetails, } from '../../../types'; import type { Notification } from '../../../typesGitHub'; +import type { Filter } from './types'; -export const FILTERS_STATE_TYPES: Record = { +const STATE_TYPE_DETAILS: Record = { draft: { title: 'Draft', }, @@ -27,60 +28,63 @@ export const FILTERS_STATE_TYPES: Record = { }, }; -export function getStateDetails(stateType: FilterStateType): TypeDetails { - return FILTERS_STATE_TYPES[stateType]; -} +export const stateFilter: Filter = { + FILTER_TYPES: STATE_TYPE_DETAILS, -export function hasStateFilters(settings: SettingsState) { - return settings.filterStates.length > 0; -} + requiresDetailsNotifications: true, -export function isStateFilterSet( - settings: SettingsState, - stateType: FilterStateType, -) { - return settings.filterStates.includes(stateType); -} + getTypeDetails(stateType: FilterStateType): TypeDetails { + return this.FILTER_TYPES[stateType]; + }, + + hasFilters(settings: SettingsState) { + return settings.filterStates.length > 0; + }, + + isFilterSet(settings: SettingsState, stateType: FilterStateType) { + return settings.filterStates.includes(stateType); + }, -export function getStateFilterCount( - notifications: AccountNotifications[], - stateType: FilterStateType, -) { - return notifications.reduce( - (sum, account) => - sum + - account.notifications.filter((n) => - filterNotificationByState(n, stateType), - ).length, - 0, - ); -} + getFilterCount( + notifications: AccountNotifications[], + stateType: FilterStateType, + ) { + return notifications.reduce( + (sum, account) => + sum + + account.notifications.filter((n) => + this.filterNotification(n, stateType), + ).length, + 0, + ); + }, -export function filterNotificationByState( - notification: Notification, - stateType: FilterStateType, -): boolean { - const allOpenStates = ['open', 'reopened']; - const allClosedStates = ['closed', 'completed', 'not_planned']; - const allMergedStates = ['merged']; - const allDraftStates = ['draft']; - const allFilterableStates = [ - ...allOpenStates, - ...allClosedStates, - ...allMergedStates, - ...allDraftStates, - ]; + filterNotification( + notification: Notification, + stateType: FilterStateType, + ): boolean { + const allOpenStates = ['open', 'reopened']; + const allClosedStates = ['closed', 'completed', 'not_planned']; + const allMergedStates = ['merged']; + const allDraftStates = ['draft']; + const allFilterableStates = [ + ...allOpenStates, + ...allClosedStates, + ...allMergedStates, + ...allDraftStates, + ]; - switch (stateType) { - case 'open': - return allOpenStates.includes(notification.subject?.state); - case 'closed': - return allClosedStates.includes(notification.subject?.state); - case 'merged': - return allMergedStates.includes(notification.subject?.state); - case 'draft': - return allDraftStates.includes(notification.subject?.state); - default: - return !allFilterableStates.includes(notification.subject?.state); - } -} + switch (stateType) { + case 'open': + return allOpenStates.includes(notification.subject?.state); + case 'closed': + return allClosedStates.includes(notification.subject?.state); + case 'merged': + return allMergedStates.includes(notification.subject?.state); + case 'draft': + return allDraftStates.includes(notification.subject?.state); + default: + return !allFilterableStates.includes(notification.subject?.state); + } + }, +}; diff --git a/src/renderer/utils/notifications/filters/subjectType.ts b/src/renderer/utils/notifications/filters/subjectType.ts index 4b25c18ac..2101b2140 100644 --- a/src/renderer/utils/notifications/filters/subjectType.ts +++ b/src/renderer/utils/notifications/filters/subjectType.ts @@ -4,8 +4,9 @@ import type { TypeDetails, } from '../../../types'; import type { Notification, SubjectType } from '../../../typesGitHub'; +import type { Filter } from './types'; -export const FILTERS_SUBJECT_TYPES: Record = { +const SUBJECT_TYPE_DETAILS: Record = { CheckSuite: { title: 'Check Suite', }, @@ -38,38 +39,41 @@ export const FILTERS_SUBJECT_TYPES: Record = { }, }; -export function getSubjectTypeDetails(subjectType: SubjectType): TypeDetails { - return FILTERS_SUBJECT_TYPES[subjectType]; -} +export const subjectTypeFilter: Filter = { + FILTER_TYPES: SUBJECT_TYPE_DETAILS, -export function hasSubjectTypeFilters(settings: SettingsState) { - return settings.filterSubjectTypes.length > 0; -} + requiresDetailsNotifications: false, -export function isSubjectTypeFilterSet( - settings: SettingsState, - subjectType: SubjectType, -) { - return settings.filterSubjectTypes.includes(subjectType); -} + getTypeDetails(subjectType: SubjectType): TypeDetails { + return this.FILTER_TYPES[subjectType]; + }, + + hasFilters(settings: SettingsState) { + return settings.filterSubjectTypes.length > 0; + }, + + isFilterSet(settings: SettingsState, subjectType: SubjectType) { + return settings.filterSubjectTypes.includes(subjectType); + }, -export function getSubjectTypeFilterCount( - notifications: AccountNotifications[], - subjectType: SubjectType, -) { - return notifications.reduce( - (sum, account) => - sum + - account.notifications.filter((n) => - filterNotificationBySubjectType(n, subjectType), - ).length, - 0, - ); -} + getFilterCount( + notifications: AccountNotifications[], + subjectType: SubjectType, + ) { + return notifications.reduce( + (sum, account) => + sum + + account.notifications.filter((n) => + this.filterNotification(n, subjectType), + ).length, + 0, + ); + }, -export function filterNotificationBySubjectType( - notification: Notification, - subjectType: SubjectType, -): boolean { - return notification.subject.type === subjectType; -} + filterNotification( + notification: Notification, + subjectType: SubjectType, + ): boolean { + return notification.subject.type === subjectType; + }, +}; diff --git a/src/renderer/utils/notifications/filters/types.ts b/src/renderer/utils/notifications/filters/types.ts new file mode 100644 index 000000000..ac7609d4b --- /dev/null +++ b/src/renderer/utils/notifications/filters/types.ts @@ -0,0 +1,22 @@ +import type { + AccountNotifications, + SettingsState, + TypeDetails, +} from '../../../types'; +import type { Notification } from '../../../typesGitHub'; + +export interface Filter { + FILTER_TYPES: Record; + + requiresDetailsNotifications: boolean; + + getTypeDetails(type: T): TypeDetails; + + hasFilters(settings: SettingsState): boolean; + + isFilterSet(settings: SettingsState, type: T): boolean; + + getFilterCount(notifications: AccountNotifications[], type: T): number; + + filterNotification(notification: Notification, type: T): boolean; +} diff --git a/src/renderer/utils/notifications/filters/userType.test.ts b/src/renderer/utils/notifications/filters/userType.test.ts index 4a07060b5..1055b6299 100644 --- a/src/renderer/utils/notifications/filters/userType.test.ts +++ b/src/renderer/utils/notifications/filters/userType.test.ts @@ -1,5 +1,5 @@ import type { Notification } from '../../../typesGitHub'; -import { filterNotificationByUserType, isNonHumanUser } from './userType'; +import { isNonHumanUser, userTypeFilter } from './userType'; describe('renderer/utils/notifications/filters/userType.ts', () => { afterEach(() => { @@ -24,23 +24,26 @@ describe('renderer/utils/notifications/filters/userType.ts', () => { } as Partial as Notification; mockPartialNotification.subject.user.type = 'User'; - expect(filterNotificationByUserType(mockPartialNotification, 'User')).toBe( - true, - ); + expect( + userTypeFilter.filterNotification(mockPartialNotification, 'User'), + ).toBe(true); mockPartialNotification.subject.user.type = 'EnterpriseUserAccount'; - expect(filterNotificationByUserType(mockPartialNotification, 'User')).toBe( - true, - ); + expect( + userTypeFilter.filterNotification(mockPartialNotification, 'User'), + ).toBe(true); mockPartialNotification.subject.user.type = 'Bot'; - expect(filterNotificationByUserType(mockPartialNotification, 'Bot')).toBe( - true, - ); + expect( + userTypeFilter.filterNotification(mockPartialNotification, 'Bot'), + ).toBe(true); mockPartialNotification.subject.user.type = 'Organization'; expect( - filterNotificationByUserType(mockPartialNotification, 'Organization'), + userTypeFilter.filterNotification( + mockPartialNotification, + 'Organization', + ), ).toBe(true); }); }); diff --git a/src/renderer/utils/notifications/filters/userType.ts b/src/renderer/utils/notifications/filters/userType.ts index d73ae3af1..f15e764d8 100644 --- a/src/renderer/utils/notifications/filters/userType.ts +++ b/src/renderer/utils/notifications/filters/userType.ts @@ -4,8 +4,9 @@ import type { TypeDetails, } from '../../../types'; import type { Notification, UserType } from '../../../typesGitHub'; +import type { Filter } from './types'; -export const FILTERS_USER_TYPES: Record = { +const USER_TYPE_DETAILS: Record = { User: { title: 'User', }, @@ -18,48 +19,46 @@ export const FILTERS_USER_TYPES: Record = { }, } as Partial> as Record; -export function getUserTypeDetails(userType: UserType): TypeDetails { - return FILTERS_USER_TYPES[userType]; -} +export const userTypeFilter: Filter = { + FILTER_TYPES: USER_TYPE_DETAILS, -export function hasUserTypeFilters(settings: SettingsState) { - return settings.filterUserTypes.length > 0; -} + requiresDetailsNotifications: true, -export function isUserTypeFilterSet( - settings: SettingsState, - userType: UserType, -) { - return settings.filterUserTypes.includes(userType); -} + getTypeDetails(userType: UserType): TypeDetails { + return this.FILTER_TYPES[userType]; + }, -export function getUserTypeFilterCount( - notifications: AccountNotifications[], - userType: UserType, -) { - return notifications.reduce( - (sum, account) => - sum + - account.notifications.filter((n) => - filterNotificationByUserType(n, userType), - ).length, - 0, - ); -} + hasFilters(settings: SettingsState) { + return settings.filterUserTypes.length > 0; + }, -export function filterNotificationByUserType( - notification: Notification, - userType: UserType, -): boolean { - const allUserTypes = ['User', 'EnterpriseUserAccount']; + isFilterSet(settings: SettingsState, userType: UserType) { + return settings.filterUserTypes.includes(userType); + }, - if (userType === 'User') { - return allUserTypes.includes(notification.subject?.user?.type); - } + getFilterCount(notifications: AccountNotifications[], userType: UserType) { + return notifications.reduce( + (sum, account) => + sum + + account.notifications.filter((n) => + this.filterNotification(n, userType), + ).length, + 0, + ); + }, - return notification.subject?.user?.type === userType; -} + filterNotification(notification: Notification, userType: UserType): boolean { + const allUserTypes = ['User', 'EnterpriseUserAccount']; + + if (userType === 'User') { + return allUserTypes.includes(notification.subject?.user?.type); + } + + return notification.subject?.user?.type === userType; + }, +}; +// Keep this function directly exported as it's not part of the interface export function isNonHumanUser(type: UserType): boolean { return type === 'Bot' || type === 'Organization' || type === 'Mannequin'; } diff --git a/src/renderer/utils/reason.ts b/src/renderer/utils/reason.ts index cffbea0f2..90f1cf241 100644 --- a/src/renderer/utils/reason.ts +++ b/src/renderer/utils/reason.ts @@ -1,7 +1,7 @@ import type { TypeDetails } from '../types'; import type { Reason } from '../typesGitHub'; -export const FORMATTED_REASONS: Record = { +export const REASON_TYPE_DETAILS: Record = { approval_requested: { title: 'Approval Requested', description: 'You were requested to review and approve a deployment.', @@ -75,5 +75,5 @@ const UNKNOWN_REASON: TypeDetails = { }; export function getReasonDetails(reason: Reason): TypeDetails { - return FORMATTED_REASONS[reason] || UNKNOWN_REASON; + return REASON_TYPE_DETAILS[reason] || UNKNOWN_REASON; }