Skip to content

Commit 173e525

Browse files
committed
feat: search notifications
Signed-off-by: Adam Setch <[email protected]>
1 parent cc755af commit 173e525

File tree

9 files changed

+566
-734
lines changed

9 files changed

+566
-734
lines changed

src/renderer/__mocks__/partial-mocks.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import { Constants } from '../constants';
22
import type { Hostname, Link } from '../types';
3-
import type { Notification, Subject, User } from '../typesGitHub';
3+
import type { Notification, Repository, Subject, User } from '../typesGitHub';
44
import { mockGitifyUser, mockToken } from './state-mocks';
55

66
export function partialMockNotification(
77
subject: Partial<Subject>,
8+
repository?: Partial<Repository>,
89
): Notification {
910
const mockNotification: Partial<Notification> = {
1011
account: {
@@ -16,6 +17,7 @@ export function partialMockNotification(
1617
hasRequiredScopes: true,
1718
},
1819
subject: subject as Subject,
20+
repository: repository as Repository,
1921
};
2022

2123
return mockNotification as Notification;

src/renderer/components/filters/SearchFilter.test.tsx

Lines changed: 146 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,21 +11,151 @@ describe('renderer/components/filters/SearchFilter.tsx', () => {
1111
jest.clearAllMocks();
1212
});
1313

14-
it('adds include actor token with prefix', () => {
15-
render(
16-
<AppContext.Provider value={{ settings: mockSettings, updateFilter }}>
17-
<SearchFilter />
18-
</AppContext.Provider>,
19-
);
20-
21-
const includeInput = screen.getByTitle('Include actors');
22-
fireEvent.change(includeInput, { target: { value: 'user:octocat' } });
23-
fireEvent.keyDown(includeInput, { key: 'Enter' });
24-
25-
expect(updateFilter).toHaveBeenCalledWith(
26-
'filterIncludeActors',
27-
'user:octocat',
28-
true,
29-
);
14+
describe('Include Search Tokens', () => {
15+
it('adds include actor token with prefix', () => {
16+
render(
17+
<AppContext.Provider value={{ settings: mockSettings, updateFilter }}>
18+
<SearchFilter />
19+
</AppContext.Provider>,
20+
);
21+
22+
const includeInput = screen.getByTitle('Include searches');
23+
fireEvent.change(includeInput, { target: { value: 'author:octocat' } });
24+
fireEvent.keyDown(includeInput, { key: 'Enter' });
25+
26+
expect(updateFilter).toHaveBeenCalledWith(
27+
'filterIncludeSearchTokens',
28+
'author:octocat',
29+
true,
30+
);
31+
});
32+
33+
it('adds include org token with prefix', () => {
34+
render(
35+
<AppContext.Provider value={{ settings: mockSettings, updateFilter }}>
36+
<SearchFilter />
37+
</AppContext.Provider>,
38+
);
39+
40+
const includeInput = screen.getByTitle('Include searches');
41+
fireEvent.change(includeInput, { target: { value: 'org:gitify-app' } });
42+
fireEvent.keyDown(includeInput, { key: 'Enter' });
43+
44+
expect(updateFilter).toHaveBeenCalledWith(
45+
'filterIncludeSearchTokens',
46+
'org:gitify-app',
47+
true,
48+
);
49+
});
50+
51+
it('adds include repo token with prefix', () => {
52+
render(
53+
<AppContext.Provider value={{ settings: mockSettings, updateFilter }}>
54+
<SearchFilter />
55+
</AppContext.Provider>,
56+
);
57+
58+
const includeInput = screen.getByTitle('Include searches');
59+
fireEvent.change(includeInput, {
60+
target: { value: 'repo:gitify-app/gitify' },
61+
});
62+
fireEvent.keyDown(includeInput, { key: 'Enter' });
63+
64+
expect(updateFilter).toHaveBeenCalledWith(
65+
'filterIncludeSearchTokens',
66+
'repo:gitify-app/gitify',
67+
true,
68+
);
69+
});
70+
71+
it('prevent unrecognized include prefixes', () => {
72+
render(
73+
<AppContext.Provider value={{ settings: mockSettings, updateFilter }}>
74+
<SearchFilter />
75+
</AppContext.Provider>,
76+
);
77+
78+
const includeInput = screen.getByTitle('Include searches');
79+
fireEvent.change(includeInput, {
80+
target: { value: 'some:search' },
81+
});
82+
fireEvent.keyDown(includeInput, { key: 'Enter' });
83+
84+
expect(updateFilter).not.toHaveBeenCalledWith();
85+
});
86+
});
87+
88+
describe('Exclude Search Tokens', () => {
89+
it('adds exclude actor token with prefix', () => {
90+
render(
91+
<AppContext.Provider value={{ settings: mockSettings, updateFilter }}>
92+
<SearchFilter />
93+
</AppContext.Provider>,
94+
);
95+
96+
const includeInput = screen.getByTitle('Exclude searches');
97+
fireEvent.change(includeInput, { target: { value: 'author:octocat' } });
98+
fireEvent.keyDown(includeInput, { key: 'Enter' });
99+
100+
expect(updateFilter).toHaveBeenCalledWith(
101+
'filterExcludeSearchTokens',
102+
'author:octocat',
103+
true,
104+
);
105+
});
106+
107+
it('adds exclude org token with prefix', () => {
108+
render(
109+
<AppContext.Provider value={{ settings: mockSettings, updateFilter }}>
110+
<SearchFilter />
111+
</AppContext.Provider>,
112+
);
113+
114+
const excludeInput = screen.getByTitle('Exclude searches');
115+
fireEvent.change(excludeInput, { target: { value: 'org:gitify-app' } });
116+
fireEvent.keyDown(excludeInput, { key: 'Enter' });
117+
118+
expect(updateFilter).toHaveBeenCalledWith(
119+
'filterExcludeSearchTokens',
120+
'org:gitify-app',
121+
true,
122+
);
123+
});
124+
125+
it('adds exclude repo token with prefix', () => {
126+
render(
127+
<AppContext.Provider value={{ settings: mockSettings, updateFilter }}>
128+
<SearchFilter />
129+
</AppContext.Provider>,
130+
);
131+
132+
const excludeInput = screen.getByTitle('Exclude searches');
133+
fireEvent.change(excludeInput, {
134+
target: { value: 'repo:gitify-app/gitify' },
135+
});
136+
fireEvent.keyDown(excludeInput, { key: 'Enter' });
137+
138+
expect(updateFilter).toHaveBeenCalledWith(
139+
'filterExcludeSearchTokens',
140+
'repo:gitify-app/gitify',
141+
true,
142+
);
143+
});
144+
145+
it('prevent unrecognized exclude prefixes', () => {
146+
render(
147+
<AppContext.Provider value={{ settings: mockSettings, updateFilter }}>
148+
<SearchFilter />
149+
</AppContext.Provider>,
150+
);
151+
152+
const excludeInput = screen.getByTitle('Exclude searches');
153+
fireEvent.change(excludeInput, {
154+
target: { value: 'some:search' },
155+
});
156+
fireEvent.keyDown(excludeInput, { key: 'Enter' });
157+
158+
expect(updateFilter).not.toHaveBeenCalledWith();
159+
});
30160
});
31161
});

src/renderer/components/filters/SearchFilterSuggestions.tsx

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import type { FC } from 'react';
22

3-
import { ActionList, Popover, Text } from '@primer/react';
3+
import { Box, Popover, Stack, Text } from '@primer/react';
44

5+
import { Opacity } from '../../types';
6+
import { cn } from '../../utils/cn';
57
import { SEARCH_QUALIFIERS } from '../../utils/notifications/filters/search';
68

79
const QUALIFIERS = Object.values(SEARCH_QUALIFIERS);
@@ -21,21 +23,23 @@ export const SearchFilterSuggestions: FC<SearchFilterSuggestionsProps> = ({
2123

2224
return (
2325
<Popover caret={false} onOpenChange={onClose} open>
24-
<Popover.Content sx={{ p: 0, mt: 1, width: '100%' }}>
25-
<ActionList>
26+
<Popover.Content sx={{ p: 2, mt: 2, width: '100%' }}>
27+
<Stack direction="vertical" gap="condensed">
2628
{QUALIFIERS.filter(
2729
(q) =>
2830
q.prefix.startsWith(inputValue.toLowerCase()) ||
2931
inputValue === '',
3032
).map((q) => (
31-
<ActionList.Item key={q.prefix}>
32-
<Text className="text-xs">{q.prefix}</Text>
33-
<ActionList.Description variant="block">
34-
<Text className="text-xs">{q.description}</Text>
35-
</ActionList.Description>
36-
</ActionList.Item>
33+
<Box key={q.prefix}>
34+
<Stack direction="vertical" gap="none">
35+
<Text className="text-xs font-semibold">{q.prefix}</Text>
36+
<Text className={cn('text-xs', Opacity.HIGH)}>
37+
{q.description}
38+
</Text>
39+
</Stack>
40+
</Box>
3741
))}
38-
</ActionList>
42+
</Stack>
3943
</Popover.Content>
4044
</Popover>
4145
);

src/renderer/context/App.test.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -353,9 +353,11 @@ describe('renderer/context/App.tsx', () => {
353353
} as AuthState,
354354
settings: {
355355
...mockSettings,
356+
filterIncludeSearchTokens: defaultSettings.filterIncludeSearchTokens,
357+
filterExcludeSearchTokens: defaultSettings.filterExcludeSearchTokens,
356358
filterUserTypes: defaultSettings.filterUserTypes,
357-
filterIncludeActors: defaultSettings.filterIncludeSearchTokens,
358-
filterExcludeActors: defaultSettings.filterExcludeSearchTokens,
359+
filterSubjectTypes: defaultSettings.filterSubjectTypes,
360+
filterStates: defaultSettings.filterStates,
359361
filterReasons: defaultSettings.filterReasons,
360362
},
361363
});

0 commit comments

Comments
 (0)