1- import { act , fireEvent , waitFor } from '@testing-library/react' ;
1+ import { act , waitFor } from '@testing-library/react' ;
22
33import { renderWithAppContext } from '../__helpers__/test-utils' ;
4+ import { mockGitHubCloudAccount } from '../__mocks__/account-mocks' ;
45import { mockGitifyNotification } from '../__mocks__/notifications-mocks' ;
56import { mockSettings } from '../__mocks__/state-mocks' ;
6- import { mockRawUser } from '../utils/api/__mocks__/response-mocks' ;
77
88import { Constants } from '../constants' ;
99
1010import { useAppContext } from '../hooks/useAppContext' ;
1111import { useNotifications } from '../hooks/useNotifications' ;
1212
13- import type { AuthState , SettingsState } from '../types' ;
14-
13+ import type {
14+ AuthState ,
15+ ClientID ,
16+ ClientSecret ,
17+ Hostname ,
18+ SettingsState ,
19+ Token ,
20+ } from '../types' ;
21+ import type { DeviceFlowSession } from '../utils/auth/types' ;
22+
23+ import * as authUtils from '../utils/auth/utils' ;
1524import * as notifications from '../utils/notifications/notifications' ;
1625import * as storage from '../utils/storage' ;
1726import * as tray from '../utils/tray' ;
@@ -20,45 +29,30 @@ import { defaultSettings } from './defaults';
2029
2130jest . mock ( '../hooks/useNotifications' ) ;
2231
23- // Helper to render a button that calls a context method when clicked
24- const renderContextButton = (
25- contextMethodName : keyof AppContextState ,
26- ...args : unknown [ ]
27- ) => {
28- const TestComponent = ( ) => {
29- const context = useAppContext ( ) ;
30-
31- const method = context [ contextMethodName ] ;
32- return (
33- < button
34- data-testid = "context-method-button"
35- onClick = { ( ) => {
36- if ( typeof method === 'function' ) {
37- ( method as ( ...args : unknown [ ] ) => void ) ( ...args ) ;
38- }
39- } }
40- type = "button"
41- >
42- { String ( contextMethodName ) }
43- </ button >
44- ) ;
32+ // Helper to render the context
33+ const renderWithContext = ( ) => {
34+ let context ! : AppContextState ;
35+
36+ const CaptureContext = ( ) => {
37+ context = useAppContext ( ) ;
38+ return null ;
4539 } ;
4640
47- const result = renderWithAppContext (
41+ renderWithAppContext (
4842 < AppProvider >
49- < TestComponent />
43+ < CaptureContext />
5044 </ AppProvider > ,
5145 ) ;
5246
53- const button = result . getByTestId ( 'context-method-button' ) ;
54- return { ...result , button } ;
47+ return ( ) => context ;
5548} ;
5649
5750describe ( 'renderer/context/App.tsx' , ( ) => {
58- const mockFetchNotifications = jest . fn ( ) ;
51+ const fetchNotificationsMock = jest . fn ( ) ;
5952 const markNotificationsAsReadMock = jest . fn ( ) ;
6053 const markNotificationsAsDoneMock = jest . fn ( ) ;
6154 const unsubscribeNotificationMock = jest . fn ( ) ;
55+ const removeAccountNotificationsMock = jest . fn ( ) ;
6256
6357 const saveStateSpy = jest
6458 . spyOn ( storage , 'saveState' )
@@ -67,10 +61,11 @@ describe('renderer/context/App.tsx', () => {
6761 beforeEach ( ( ) => {
6862 jest . useFakeTimers ( ) ;
6963 ( useNotifications as jest . Mock ) . mockReturnValue ( {
70- fetchNotifications : mockFetchNotifications ,
64+ fetchNotifications : fetchNotificationsMock ,
7165 markNotificationsAsRead : markNotificationsAsReadMock ,
7266 markNotificationsAsDone : markNotificationsAsDoneMock ,
7367 unsubscribeNotification : unsubscribeNotificationMock ,
68+ removeAccountNotifications : removeAccountNotificationsMock ,
7469 } ) ;
7570 } ) ;
7671
@@ -101,47 +96,48 @@ describe('renderer/context/App.tsx', () => {
10196 renderWithAppContext ( < AppProvider > { null } </ AppProvider > ) ;
10297
10398 await waitFor ( ( ) =>
104- expect ( mockFetchNotifications ) . toHaveBeenCalledTimes ( 1 ) ,
99+ expect ( fetchNotificationsMock ) . toHaveBeenCalledTimes ( 1 ) ,
105100 ) ;
106101
107102 act ( ( ) => {
108103 jest . advanceTimersByTime (
109104 Constants . DEFAULT_FETCH_NOTIFICATIONS_INTERVAL_MS ,
110105 ) ;
111106 } ) ;
112- expect ( mockFetchNotifications ) . toHaveBeenCalledTimes ( 2 ) ;
107+ expect ( fetchNotificationsMock ) . toHaveBeenCalledTimes ( 2 ) ;
113108
114109 act ( ( ) => {
115110 jest . advanceTimersByTime (
116111 Constants . DEFAULT_FETCH_NOTIFICATIONS_INTERVAL_MS ,
117112 ) ;
118113 } ) ;
119- expect ( mockFetchNotifications ) . toHaveBeenCalledTimes ( 3 ) ;
114+ expect ( fetchNotificationsMock ) . toHaveBeenCalledTimes ( 3 ) ;
120115
121116 act ( ( ) => {
122117 jest . advanceTimersByTime (
123118 Constants . DEFAULT_FETCH_NOTIFICATIONS_INTERVAL_MS ,
124119 ) ;
125120 } ) ;
126- expect ( mockFetchNotifications ) . toHaveBeenCalledTimes ( 4 ) ;
121+ expect ( fetchNotificationsMock ) . toHaveBeenCalledTimes ( 4 ) ;
127122 } ) ;
128123
129124 it ( 'should call fetchNotifications' , async ( ) => {
130- const { button } = renderContextButton ( 'fetchNotifications' ) ;
131-
132- mockFetchNotifications . mockReset ( ) ;
125+ const getContext = renderWithContext ( ) ;
126+ fetchNotificationsMock . mockReset ( ) ;
133127
134- fireEvent . click ( button ) ;
128+ act ( ( ) => {
129+ getContext ( ) . fetchNotifications ( ) ;
130+ } ) ;
135131
136- expect ( mockFetchNotifications ) . toHaveBeenCalledTimes ( 1 ) ;
132+ expect ( fetchNotificationsMock ) . toHaveBeenCalledTimes ( 1 ) ;
137133 } ) ;
138134
139135 it ( 'should call markNotificationsAsRead' , async ( ) => {
140- const { button } = renderContextButton ( 'markNotificationsAsRead' , [
141- mockGitifyNotification ,
142- ] ) ;
136+ const getContext = renderWithContext ( ) ;
143137
144- fireEvent . click ( button ) ;
138+ act ( ( ) => {
139+ getContext ( ) . markNotificationsAsRead ( [ mockGitifyNotification ] ) ;
140+ } ) ;
145141
146142 expect ( markNotificationsAsReadMock ) . toHaveBeenCalledTimes ( 1 ) ;
147143 expect ( markNotificationsAsReadMock ) . toHaveBeenCalledWith (
@@ -152,11 +148,11 @@ describe('renderer/context/App.tsx', () => {
152148 } ) ;
153149
154150 it ( 'should call markNotificationsAsDone' , async ( ) => {
155- const { button } = renderContextButton ( 'markNotificationsAsDone' , [
156- mockGitifyNotification ,
157- ] ) ;
151+ const getContext = renderWithContext ( ) ;
158152
159- fireEvent . click ( button ) ;
153+ act ( ( ) => {
154+ getContext ( ) . markNotificationsAsDone ( [ mockGitifyNotification ] ) ;
155+ } ) ;
160156
161157 expect ( markNotificationsAsDoneMock ) . toHaveBeenCalledTimes ( 1 ) ;
162158 expect ( markNotificationsAsDoneMock ) . toHaveBeenCalledWith (
@@ -167,12 +163,11 @@ describe('renderer/context/App.tsx', () => {
167163 } ) ;
168164
169165 it ( 'should call unsubscribeNotification' , async ( ) => {
170- const { button } = renderContextButton (
171- 'unsubscribeNotification' ,
172- mockGitifyNotification ,
173- ) ;
166+ const getContext = renderWithContext ( ) ;
174167
175- fireEvent . click ( button ) ;
168+ act ( ( ) => {
169+ getContext ( ) . unsubscribeNotification ( mockGitifyNotification ) ;
170+ } ) ;
176171
177172 expect ( unsubscribeNotificationMock ) . toHaveBeenCalledTimes ( 1 ) ;
178173 expect ( unsubscribeNotificationMock ) . toHaveBeenCalledWith (
@@ -189,13 +184,11 @@ describe('renderer/context/App.tsx', () => {
189184 . mockImplementation ( jest . fn ( ) ) ;
190185
191186 it ( 'should call updateSetting' , async ( ) => {
192- const { button } = renderContextButton (
193- 'updateSetting' ,
194- 'participating' ,
195- true ,
196- ) ;
187+ const getContext = renderWithContext ( ) ;
197188
198- fireEvent . click ( button ) ;
189+ act ( ( ) => {
190+ getContext ( ) . updateSetting ( 'participating' , true ) ;
191+ } ) ;
199192
200193 expect ( saveStateSpy ) . toHaveBeenCalledWith ( {
201194 auth : {
@@ -209,9 +202,11 @@ describe('renderer/context/App.tsx', () => {
209202 } ) ;
210203
211204 it ( 'should call resetSettings' , async ( ) => {
212- const { button } = renderContextButton ( 'resetSettings' ) ;
205+ const getContext = renderWithContext ( ) ;
213206
214- fireEvent . click ( button ) ;
207+ act ( ( ) => {
208+ getContext ( ) . resetSettings ( ) ;
209+ } ) ;
215210
216211 expect ( saveStateSpy ) . toHaveBeenCalledWith ( {
217212 auth : {
@@ -224,14 +219,11 @@ describe('renderer/context/App.tsx', () => {
224219
225220 describe ( 'filter methods' , ( ) => {
226221 it ( 'should call updateFilter - checked' , async ( ) => {
227- const { button } = renderContextButton (
228- 'updateFilter' ,
229- 'filterReasons' ,
230- 'assign' ,
231- true ,
232- ) ;
222+ const getContext = renderWithContext ( ) ;
233223
234- fireEvent . click ( button ) ;
224+ act ( ( ) => {
225+ getContext ( ) . updateFilter ( 'filterReasons' , 'assign' , true ) ;
226+ } ) ;
235227
236228 expect ( saveStateSpy ) . toHaveBeenCalledWith ( {
237229 auth : {
@@ -245,14 +237,11 @@ describe('renderer/context/App.tsx', () => {
245237 } ) ;
246238
247239 it ( 'should call updateFilter - unchecked' , async ( ) => {
248- const { button } = renderContextButton (
249- 'updateFilter' ,
250- 'filterReasons' ,
251- 'assign' ,
252- false ,
253- ) ;
240+ const getContext = renderWithContext ( ) ;
254241
255- fireEvent . click ( button ) ;
242+ act ( ( ) => {
243+ getContext ( ) . updateFilter ( 'filterReasons' , 'assign' , false ) ;
244+ } ) ;
256245
257246 expect ( saveStateSpy ) . toHaveBeenCalledWith ( {
258247 auth : {
@@ -266,9 +255,11 @@ describe('renderer/context/App.tsx', () => {
266255 } ) ;
267256
268257 it ( 'should clear filters back to default' , async ( ) => {
269- const { button } = renderContextButton ( 'clearFilters' ) ;
258+ const getContext = renderWithContext ( ) ;
270259
271- fireEvent . click ( button ) ;
260+ act ( ( ) => {
261+ getContext ( ) . clearFilters ( ) ;
262+ } ) ;
272263
273264 expect ( saveStateSpy ) . toHaveBeenCalledWith ( {
274265 auth : {
@@ -286,4 +277,96 @@ describe('renderer/context/App.tsx', () => {
286277 } ) ;
287278 } ) ;
288279 } ) ;
280+
281+ describe ( 'authentication functions' , ( ) => {
282+ const addAccountSpy = jest
283+ . spyOn ( authUtils , 'addAccount' )
284+ . mockImplementation ( jest . fn ( ) ) ;
285+ const removeAccountSpy = jest . spyOn ( authUtils , 'removeAccount' ) ;
286+
287+ beforeEach ( ( ) => {
288+ jest . clearAllMocks ( ) ;
289+ } ) ;
290+
291+ it ( 'loginWithDeviceFlowStart calls startGitHubDeviceFlow' , async ( ) => {
292+ const startGitHubDeviceFlowSpy = jest
293+ . spyOn ( authUtils , 'startGitHubDeviceFlow' )
294+ . mockImplementation ( jest . fn ( ) ) ;
295+
296+ const getContext = renderWithContext ( ) ;
297+
298+ act ( ( ) => {
299+ getContext ( ) . loginWithDeviceFlowStart ( ) ;
300+ } ) ;
301+
302+ expect ( startGitHubDeviceFlowSpy ) . toHaveBeenCalled ( ) ;
303+ } ) ;
304+
305+ it ( 'loginWithDeviceFlowPoll calls pollGitHubDeviceFlow' , async ( ) => {
306+ const pollGitHubDeviceFlowSpy = jest
307+ . spyOn ( authUtils , 'pollGitHubDeviceFlow' )
308+ . mockImplementation ( jest . fn ( ) ) ;
309+
310+ const getContext = renderWithContext ( ) ;
311+
312+ act ( ( ) => {
313+ getContext ( ) . loginWithDeviceFlowPoll (
314+ 'session' as unknown as DeviceFlowSession ,
315+ ) ;
316+ } ) ;
317+
318+ expect ( pollGitHubDeviceFlowSpy ) . toHaveBeenCalledWith ( 'session' ) ;
319+ } ) ;
320+
321+ it ( 'loginWithDeviceFlowComplete calls addAccount' , async ( ) => {
322+ const getContext = renderWithContext ( ) ;
323+
324+ act ( ( ) => {
325+ getContext ( ) . loginWithDeviceFlowComplete (
326+ 'token' as Token ,
327+ 'github.com' as Hostname ,
328+ ) ;
329+ } ) ;
330+
331+ expect ( addAccountSpy ) . toHaveBeenCalledWith (
332+ expect . anything ( ) ,
333+ 'GitHub App' ,
334+ 'token' ,
335+ 'github.com' ,
336+ ) ;
337+ } ) ;
338+
339+ it ( 'loginWithOAuthApp calls performGitHubWebOAuth' , async ( ) => {
340+ const performGitHubWebOAuthSpy = jest . spyOn (
341+ authUtils ,
342+ 'performGitHubWebOAuth' ,
343+ ) ;
344+
345+ const getContext = renderWithContext ( ) ;
346+
347+ act ( ( ) => {
348+ getContext ( ) . loginWithOAuthApp ( {
349+ clientId : 'id' as ClientID ,
350+ clientSecret : 'secret' as ClientSecret ,
351+ hostname : 'github.com' as Hostname ,
352+ } ) ;
353+ } ) ;
354+
355+ expect ( performGitHubWebOAuthSpy ) . toHaveBeenCalled ( ) ;
356+ } ) ;
357+
358+ it ( 'logoutFromAccount calls removeAccountNotifications, removeAccount' , async ( ) => {
359+ const getContext = renderWithContext ( ) ;
360+
361+ getContext ( ) . logoutFromAccount ( mockGitHubCloudAccount ) ;
362+
363+ expect ( removeAccountNotificationsMock ) . toHaveBeenCalledWith (
364+ mockGitHubCloudAccount ,
365+ ) ;
366+ expect ( removeAccountSpy ) . toHaveBeenCalledWith (
367+ expect . anything ( ) ,
368+ mockGitHubCloudAccount ,
369+ ) ;
370+ } ) ;
371+ } ) ;
289372} ) ;
0 commit comments