diff --git a/src/renderer/__mocks__/state-mocks.ts b/src/renderer/__mocks__/state-mocks.ts index 7905387d8..4a879e857 100644 --- a/src/renderer/__mocks__/state-mocks.ts +++ b/src/renderer/__mocks__/state-mocks.ts @@ -109,6 +109,7 @@ const mockFilters: FilterSettingsState = { filterUserTypes: [], filterIncludeHandles: [], filterExcludeHandles: [], + filterStates: [], filterReasons: [], }; diff --git a/src/renderer/components/filters/ReasonFilter.test.tsx b/src/renderer/components/filters/ReasonFilter.test.tsx index 85ebd67a2..2eee6490e 100644 --- a/src/renderer/components/filters/ReasonFilter.test.tsx +++ b/src/renderer/components/filters/ReasonFilter.test.tsx @@ -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'; @@ -12,7 +13,7 @@ describe('renderer/components/filters/ReasonFilter.tsx', () => { diff --git a/src/renderer/components/filters/StateFilter.test.tsx b/src/renderer/components/filters/StateFilter.test.tsx new file mode 100644 index 000000000..8f3a06ad2 --- /dev/null +++ b/src/renderer/components/filters/StateFilter.test.tsx @@ -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( + + + + + , + ); + + 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(); + }); +}); diff --git a/src/renderer/components/filters/StateFilter.tsx b/src/renderer/components/filters/StateFilter.tsx new file mode 100644 index 000000000..5d6fce034 --- /dev/null +++ b/src/renderer/components/filters/StateFilter.tsx @@ -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 ( +
+ + 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} + /> + ); + })} + +
+ ); +}; diff --git a/src/renderer/components/filters/UserHandleFilter.test.tsx b/src/renderer/components/filters/UserHandleFilter.test.tsx index 0179adc9f..0892de338 100644 --- a/src/renderer/components/filters/UserHandleFilter.test.tsx +++ b/src/renderer/components/filters/UserHandleFilter.test.tsx @@ -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'; @@ -12,44 +13,46 @@ describe('renderer/components/filters/UserHandleFilter.tsx', () => { jest.clearAllMocks(); }); - it('should render itself & its children - detailed notifications enabled', () => { - const tree = render( - - - - - , - ); - - expect(tree).toMatchSnapshot(); - }); + describe('should render itself & its children', () => { + it('with detailed notifications enabled', () => { + const tree = render( + + + + + , + ); + + expect(tree).toMatchSnapshot(); + }); - it('should render itself & its children - detailed notifications disabled', () => { - const tree = render( - - - - - , - ); - - expect(tree).toMatchSnapshot(); + it('with detailed notifications disabled', () => { + const tree = render( + + + + + , + ); + + expect(tree).toMatchSnapshot(); + }); }); describe('Include user handles', () => { diff --git a/src/renderer/components/filters/UserTypeFilter.test.tsx b/src/renderer/components/filters/UserTypeFilter.test.tsx index 6034df435..c56c22429 100644 --- a/src/renderer/components/filters/UserTypeFilter.test.tsx +++ b/src/renderer/components/filters/UserTypeFilter.test.tsx @@ -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'; @@ -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( - - - - - , - ); + describe('should render itself & its children', () => { + it('with detailed notifications enabled', () => { + const tree = render( + + + + + , + ); - expect(tree).toMatchSnapshot(); - }); + expect(tree).toMatchSnapshot(); + }); - it('should render itself & its children - detailed notifications disabled', () => { - const tree = render( - - - - - , - ); + it('with detailed notifications disabled', () => { + const tree = render( + + + + + , + ); - expect(tree).toMatchSnapshot(); + expect(tree).toMatchSnapshot(); + }); }); it('should be able to toggle user type - none already set', async () => { diff --git a/src/renderer/components/filters/__snapshots__/ReasonFilter.test.tsx.snap b/src/renderer/components/filters/__snapshots__/ReasonFilter.test.tsx.snap index 3c8ce5fcc..6e3be90ff 100644 --- a/src/renderer/components/filters/__snapshots__/ReasonFilter.test.tsx.snap +++ b/src/renderer/components/filters/__snapshots__/ReasonFilter.test.tsx.snap @@ -1568,6 +1568,20 @@ exports[`renderer/components/filters/ReasonFilter.tsx should render itself & its /> + + +  ( + 1 + ) +
+ + +  ( + 3 + ) +
+ + +  ( + 1 + ) +
+ + +  ( + 3 + ) +
+
+ + +
+
+ + +
+ +
+
+
+ + +
+
+ + +
+ +
+
+
+ + +
+ +
+
+
+`; + +exports[`renderer/components/filters/StateFilter.tsx should be able to toggle user type - some filters already set 1`] = ` +
+
+ + +
+
+ + +
+ +
+
+
+ + +
+
+ + +
+ +
+
+
+ + +
+ +
+
+
+`; + +exports[`renderer/components/filters/StateFilter.tsx should render itself & its children with detailed notifications disabled 1`] = ` +{ + "asFragment": [Function], + "baseElement": +
+
+
+ +
+
+ +

+ State +

+
+
+
+
+ +
+
+
+
+ + +
+
+ + +
+ +
+ + +  ( + 1 + ) + +
+
+ + +
+
+ + +
+ +
+
+
+ + +
+ +
+ + +  ( + 3 + ) + +
+
+
+
+ , + "container":
+
+
+ +
+
+ +

+ State +

+
+
+
+
+ +
+
+
+
+ + +
+
+ + +
+ +
+ + +  ( + 1 + ) + +
+
+ + +
+
+ + +
+ +
+
+
+ + +
+ +
+ + +  ( + 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`] = ` +{ + "asFragment": [Function], + "baseElement": +
+
+
+ +
+
+ +

+ State +

+
+
+
+
+ +
+
+
+
+ + +
+
+ + +
+ +
+ + +  ( + 1 + ) + +
+
+ + +
+
+ + +
+ +
+
+
+ + +
+ +
+ + +  ( + 3 + ) + +
+
+
+
+ , + "container":
+
+
+ +
+
+ +

+ State +

+
+
+
+
+ +
+
+
+
+ + +
+
+ + +
+ +
+ + +  ( + 1 + ) + +
+
+ + +
+
+ + +
+ +
+
+
+ + +
+ +
+ + +  ( + 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__/UserHandleFilter.test.tsx.snap b/src/renderer/components/filters/__snapshots__/UserHandleFilter.test.tsx.snap index ffba23026..266a158f8 100644 --- a/src/renderer/components/filters/__snapshots__/UserHandleFilter.test.tsx.snap +++ b/src/renderer/components/filters/__snapshots__/UserHandleFilter.test.tsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`renderer/components/filters/UserHandleFilter.tsx should render itself & its children - detailed notifications disabled 1`] = ` +exports[`renderer/components/filters/UserHandleFilter.tsx should render itself & its children with detailed notifications disabled 1`] = ` { "asFragment": [Function], "baseElement": @@ -517,7 +517,7 @@ exports[`renderer/components/filters/UserHandleFilter.tsx should render itself & } `; -exports[`renderer/components/filters/UserHandleFilter.tsx should render itself & its children - detailed notifications enabled 1`] = ` +exports[`renderer/components/filters/UserHandleFilter.tsx should render itself & its children with detailed notifications enabled 1`] = ` { "asFragment": [Function], "baseElement": diff --git a/src/renderer/components/filters/__snapshots__/UserTypeFilter.test.tsx.snap b/src/renderer/components/filters/__snapshots__/UserTypeFilter.test.tsx.snap index 37d0a2883..d5cc4230f 100644 --- a/src/renderer/components/filters/__snapshots__/UserTypeFilter.test.tsx.snap +++ b/src/renderer/components/filters/__snapshots__/UserTypeFilter.test.tsx.snap @@ -205,7 +205,7 @@ exports[`renderer/components/filters/UserTypeFilter.tsx should be able to toggle `; -exports[`renderer/components/filters/UserTypeFilter.tsx should render itself & its children - detailed notifications disabled 1`] = ` +exports[`renderer/components/filters/UserTypeFilter.tsx should render itself & its children with detailed notifications disabled 1`] = ` { "asFragment": [Function], "baseElement": @@ -317,6 +317,20 @@ exports[`renderer/components/filters/UserTypeFilter.tsx should render itself & i > User + + +  ( + 1 + ) +
User + + +  ( + 1 + ) +
@@ -737,6 +765,20 @@ exports[`renderer/components/filters/UserTypeFilter.tsx should render itself & i > User + + +  ( + 1 + ) +
User + + +  ( + 1 + ) +
{ + diff --git a/src/renderer/routes/__snapshots__/Filters.test.tsx.snap b/src/renderer/routes/__snapshots__/Filters.test.tsx.snap index d89afcdbb..dd4639376 100644 --- a/src/renderer/routes/__snapshots__/Filters.test.tsx.snap +++ b/src/renderer/routes/__snapshots__/Filters.test.tsx.snap @@ -498,6 +498,272 @@ exports[`renderer/routes/Filters.tsx General should render itself & its children
+
+
+ +
+
+ +

+ State +

+
+
+
+
+ +
+
+
+
+ + +
+
+ + +
+ +
+
+
+ + +
+
+ + +
+ +
+
+
+ + +
+ +
+
+
+
; label: string; }; + +export type FilterStateType = 'open' | 'closed' | 'merged' | 'draft' | 'other'; diff --git a/src/renderer/utils/notifications/filters/filter.test.ts b/src/renderer/utils/notifications/filters/filter.test.ts index 4c2c3d58a..f85cc866f 100644 --- a/src/renderer/utils/notifications/filters/filter.test.ts +++ b/src/renderer/utils/notifications/filters/filter.test.ts @@ -33,13 +33,14 @@ describe('renderer/utils/notifications/filters/filter.ts', () => { }), ]; - it('should ignore user type or handle filters if detailed notifications not enabled', async () => { + it('should ignore user type, handle filters and state filters if detailed notifications not enabled', async () => { const result = filterNotifications(mockNotifications, { ...mockSettings, detailedNotifications: false, filterUserTypes: ['Bot'], filterIncludeHandles: ['github-user'], filterExcludeHandles: ['github-bot'], + filterStates: ['merged'], }); expect(result.length).toBe(2); @@ -79,6 +80,18 @@ describe('renderer/utils/notifications/filters/filter.ts', () => { expect(result).toEqual([mockNotifications[0]]); }); + it('should filter notifications by state when provided', async () => { + mockNotifications[0].subject.state = 'open'; + mockNotifications[1].subject.state = 'closed'; + const result = filterNotifications(mockNotifications, { + ...mockSettings, + filterStates: ['closed'], + }); + + expect(result.length).toBe(1); + expect(result).toEqual([mockNotifications[1]]); + }); + it('should filter notifications by reasons when provided', async () => { mockNotifications[0].reason = 'subscribed'; mockNotifications[1].reason = 'manual'; @@ -121,6 +134,14 @@ describe('renderer/utils/notifications/filters/filter.ts', () => { expect(hasAnyFiltersSet(settings)).toBe(true); }); + it('non-default state filters', () => { + const settings = { + ...defaultSettings, + filterStates: ['draft', 'merged'], + } as SettingsState; + expect(hasAnyFiltersSet(settings)).toBe(true); + }); + it('non-default reason filters', () => { const settings = { ...defaultSettings, diff --git a/src/renderer/utils/notifications/filters/filter.ts b/src/renderer/utils/notifications/filters/filter.ts index c3872a50f..1c951527a 100644 --- a/src/renderer/utils/notifications/filters/filter.ts +++ b/src/renderer/utils/notifications/filters/filter.ts @@ -6,6 +6,7 @@ import { hasIncludeHandleFilters, } from './handles'; import { filterNotificationByReason, hasReasonFilters } from './reason'; +import { filterNotificationByState, hasStateFilters } from './state'; import { filterNotificationByUserType, hasUserTypeFilters } from './userType'; export function filterNotifications( @@ -39,6 +40,14 @@ export function filterNotifications( filterNotificationByHandle(notification, handle), ); } + + if (hasStateFilters(settings)) { + passesFilters = + passesFilters && + settings.filterStates.some((state) => + filterNotificationByState(notification, state), + ); + } } if (hasReasonFilters(settings)) { @@ -58,6 +67,7 @@ export function hasAnyFiltersSet(settings: SettingsState): boolean { hasUserTypeFilters(settings) || hasIncludeHandleFilters(settings) || hasExcludeHandleFilters(settings) || + hasStateFilters(settings) || hasReasonFilters(settings) ); } diff --git a/src/renderer/utils/notifications/filters/state.test.ts b/src/renderer/utils/notifications/filters/state.test.ts new file mode 100644 index 000000000..aaf738c1f --- /dev/null +++ b/src/renderer/utils/notifications/filters/state.test.ts @@ -0,0 +1,61 @@ +import type { Notification } from '../../../typesGitHub'; +import { filterNotificationByState } from './state'; + +describe('renderer/utils/notifications/filters/state.ts', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + it('can filter by notification states', () => { + const mockPartialNotification = { + subject: { + state: 'open', + }, + } as Partial as Notification; + + // Open states + mockPartialNotification.subject.state = 'open'; + expect(filterNotificationByState(mockPartialNotification, 'open')).toBe( + true, + ); + + mockPartialNotification.subject.state = 'reopened'; + expect(filterNotificationByState(mockPartialNotification, 'open')).toBe( + true, + ); + + // Closed states + mockPartialNotification.subject.state = 'closed'; + expect(filterNotificationByState(mockPartialNotification, 'closed')).toBe( + true, + ); + + mockPartialNotification.subject.state = 'completed'; + expect(filterNotificationByState(mockPartialNotification, 'closed')).toBe( + true, + ); + + mockPartialNotification.subject.state = 'not_planned'; + expect(filterNotificationByState(mockPartialNotification, 'closed')).toBe( + true, + ); + + // Merged states + mockPartialNotification.subject.state = 'merged'; + expect(filterNotificationByState(mockPartialNotification, 'merged')).toBe( + true, + ); + + // Draft states + mockPartialNotification.subject.state = 'draft'; + expect(filterNotificationByState(mockPartialNotification, 'draft')).toBe( + true, + ); + + // Other states + mockPartialNotification.subject.state = 'OUTDATED'; + expect(filterNotificationByState(mockPartialNotification, 'other')).toBe( + true, + ); + }); +}); diff --git a/src/renderer/utils/notifications/filters/state.ts b/src/renderer/utils/notifications/filters/state.ts new file mode 100644 index 000000000..cf2004314 --- /dev/null +++ b/src/renderer/utils/notifications/filters/state.ts @@ -0,0 +1,89 @@ +import type { + AccountNotifications, + FilterStateType, + SettingsState, + TypeDetails, +} from '../../../types'; +import type { Notification } from '../../../typesGitHub'; + +export const FILTERS_STATE_TYPES: Record = { + draft: { + title: 'Draft', + }, + open: { + title: 'Open', + description: 'Open or reopened', + }, + merged: { + title: 'Merged', + }, + closed: { + title: 'Closed', + description: 'Closed, completed or not planned', + }, + other: { + title: 'Other', + description: 'Catch all for any other notification states', + }, +} as Partial> as Record< + FilterStateType, + TypeDetails +>; + +export function getStateDetails(stateType: FilterStateType): TypeDetails { + return FILTERS_STATE_TYPES[stateType]; +} + +export function hasStateFilters(settings: SettingsState) { + return settings.filterStates.length > 0; +} + +export function isStateFilterSet( + 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, + ); +} + +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, + ]; + + 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/userType.test.ts b/src/renderer/utils/notifications/filters/userType.test.ts index 6d19be2bf..4a07060b5 100644 --- a/src/renderer/utils/notifications/filters/userType.test.ts +++ b/src/renderer/utils/notifications/filters/userType.test.ts @@ -1,4 +1,5 @@ -import { isNonHumanUser } from './userType'; +import type { Notification } from '../../../typesGitHub'; +import { filterNotificationByUserType, isNonHumanUser } from './userType'; describe('renderer/utils/notifications/filters/userType.ts', () => { afterEach(() => { @@ -12,4 +13,34 @@ describe('renderer/utils/notifications/filters/userType.ts', () => { expect(isNonHumanUser('Organization')).toBe(true); expect(isNonHumanUser('Mannequin')).toBe(true); }); + + it('can filter by user types', () => { + const mockPartialNotification = { + subject: { + user: { + type: 'User', + }, + }, + } as Partial as Notification; + + mockPartialNotification.subject.user.type = 'User'; + expect(filterNotificationByUserType(mockPartialNotification, 'User')).toBe( + true, + ); + + mockPartialNotification.subject.user.type = 'EnterpriseUserAccount'; + expect(filterNotificationByUserType(mockPartialNotification, 'User')).toBe( + true, + ); + + mockPartialNotification.subject.user.type = 'Bot'; + expect(filterNotificationByUserType(mockPartialNotification, 'Bot')).toBe( + true, + ); + + mockPartialNotification.subject.user.type = 'Organization'; + expect( + filterNotificationByUserType(mockPartialNotification, 'Organization'), + ).toBe(true); + }); }); diff --git a/src/renderer/utils/notifications/filters/userType.ts b/src/renderer/utils/notifications/filters/userType.ts index 0597934db..d73ae3af1 100644 --- a/src/renderer/utils/notifications/filters/userType.ts +++ b/src/renderer/utils/notifications/filters/userType.ts @@ -51,11 +51,10 @@ export function filterNotificationByUserType( notification: Notification, userType: UserType, ): boolean { + const allUserTypes = ['User', 'EnterpriseUserAccount']; + if (userType === 'User') { - return ( - notification.subject?.user?.type === 'User' || - notification.subject?.user?.type === 'EnterpriseUserAccount' - ); + return allUserTypes.includes(notification.subject?.user?.type); } return notification.subject?.user?.type === userType;