11import { OrganizationFixture } from 'sentry-fixture/organization' ;
22import { OrganizationIntegrationsFixture } from 'sentry-fixture/organizationIntegrations' ;
33
4- import { act , renderHookWithProviders , waitFor } from 'sentry-test/reactTestingLibrary' ;
4+ import { act , renderHookWithProviders } from 'sentry-test/reactTestingLibrary' ;
55
6- import {
7- OnboardingContextProvider ,
8- type OnboardingSessionState ,
9- } from 'sentry/components/onboarding/onboardingContext' ;
106import type { IntegrationRepository } from 'sentry/types/integrations' ;
11- import { sessionStorageWrapper } from 'sentry/utils/sessionStorage' ;
127
138import { useScmRepoSelection } from './useScmRepoSelection' ;
149
15- function makeOnboardingWrapper ( initialState ?: OnboardingSessionState ) {
16- return function OnboardingWrapper ( { children} : { children ?: React . ReactNode } ) {
17- return (
18- < OnboardingContextProvider initialValue = { initialState } >
19- { children }
20- </ OnboardingContextProvider >
21- ) ;
22- } ;
23- }
24-
2510describe ( 'useScmRepoSelection' , ( ) => {
2611 const organization = OrganizationFixture ( ) ;
2712
@@ -49,29 +34,56 @@ describe('useScmRepoSelection', () => {
4934 isInstalled : false ,
5035 } ;
5136
52- const mockInstalledRepo : IntegrationRepository = {
53- identifier : 'getsentry/sentry' ,
54- name : 'sentry' ,
55- isInstalled : true ,
56- } ;
57-
5837 beforeEach ( ( ) => {
59- sessionStorageWrapper . clear ( ) ;
6038 onSelect = jest . fn ( ) ;
6139 reposByIdentifier = new Map ( [ [ 'getsentry/sentry' , mockRepo ] ] ) ;
40+ } ) ;
6241
63- // Default: no existing repos
42+ afterEach ( ( ) => {
43+ MockApiClient . clearMockResponses ( ) ;
44+ } ) ;
45+
46+ it ( 'uses existing repo when GET finds it, skips POST' , async ( ) => {
6447 MockApiClient . addMockResponse ( {
6548 url : `/organizations/${ organization . slug } /repos/` ,
66- body : [ ] ,
49+ body : [
50+ {
51+ id : '99' ,
52+ name : 'getsentry/sentry' ,
53+ externalSlug : 'getsentry/sentry' ,
54+ status : 'active' ,
55+ } ,
56+ ] ,
6757 } ) ;
68- } ) ;
6958
70- afterEach ( ( ) => {
71- MockApiClient . clearMockResponses ( ) ;
59+ const addRequest = MockApiClient . addMockResponse ( {
60+ url : `/organizations/${ organization . slug } /repos/` ,
61+ method : 'POST' ,
62+ body : { } ,
63+ } ) ;
64+
65+ const { result} = renderHookWithProviders (
66+ ( ) =>
67+ useScmRepoSelection ( { integration : mockIntegration , onSelect, reposByIdentifier} ) ,
68+ { organization}
69+ ) ;
70+
71+ await act ( async ( ) => {
72+ await result . current . handleSelect ( { value : 'getsentry/sentry' } ) ;
73+ } ) ;
74+
75+ expect ( addRequest ) . not . toHaveBeenCalled ( ) ;
76+ expect ( onSelect ) . toHaveBeenCalledWith (
77+ expect . objectContaining ( { id : '99' , name : 'getsentry/sentry' } )
78+ ) ;
7279 } ) ;
7380
74- it ( 'calls POST to add new repo and updates onSelect with server ID' , async ( ) => {
81+ it ( 'creates repo via POST when GET finds nothing' , async ( ) => {
82+ MockApiClient . addMockResponse ( {
83+ url : `/organizations/${ organization . slug } /repos/` ,
84+ body : [ ] ,
85+ } ) ;
86+
7587 const addRequest = MockApiClient . addMockResponse ( {
7688 url : `/organizations/${ organization . slug } /repos/` ,
7789 method : 'POST' ,
@@ -86,10 +98,7 @@ describe('useScmRepoSelection', () => {
8698 const { result} = renderHookWithProviders (
8799 ( ) =>
88100 useScmRepoSelection ( { integration : mockIntegration , onSelect, reposByIdentifier} ) ,
89- {
90- organization,
91- additionalWrapper : makeOnboardingWrapper ( { } ) ,
92- }
101+ { organization}
93102 ) ;
94103
95104 await act ( async ( ) => {
@@ -101,171 +110,104 @@ describe('useScmRepoSelection', () => {
101110 expect ( onSelect ) . toHaveBeenCalledWith (
102111 expect . objectContaining ( { id : '' , name : 'sentry' } )
103112 ) ;
104- // Then real call with server response spread over optimistic
113+ // Then real call with server response
105114 expect ( onSelect ) . toHaveBeenCalledWith (
106115 expect . objectContaining ( { id : '42' , name : 'getsentry/sentry' } )
107116 ) ;
108117 } ) ;
109118
110- it ( 'does not POST for already-installed repos, uses existing repo ID' , async ( ) => {
111- reposByIdentifier = new Map ( [ [ 'getsentry/sentry' , mockInstalledRepo ] ] ) ;
112-
113- // Override repos response with an existing repo
114- MockApiClient . clearMockResponses ( ) ;
119+ it ( 'reverts onSelect on POST failure' , async ( ) => {
115120 MockApiClient . addMockResponse ( {
116121 url : `/organizations/${ organization . slug } /repos/` ,
117- body : [
118- {
119- id : '99' ,
120- name : 'getsentry/sentry' ,
121- externalSlug : 'getsentry/sentry' ,
122- status : 'active' ,
123- } ,
124- ] ,
122+ body : [ ] ,
125123 } ) ;
126124
127- const addRequest = MockApiClient . addMockResponse ( {
125+ MockApiClient . addMockResponse ( {
128126 url : `/organizations/${ organization . slug } /repos/` ,
129127 method : 'POST' ,
130- body : { } ,
128+ statusCode : 500 ,
129+ body : { detail : 'Internal Error' } ,
131130 } ) ;
132131
133132 const { result} = renderHookWithProviders (
134133 ( ) =>
135134 useScmRepoSelection ( { integration : mockIntegration , onSelect, reposByIdentifier} ) ,
136- {
137- organization,
138- additionalWrapper : makeOnboardingWrapper ( { } ) ,
139- }
135+ { organization}
140136 ) ;
141137
142- // Wait for existing repos query to resolve
143- await waitFor ( ( ) => expect ( result . current . busy ) . toBe ( false ) ) ;
144-
145138 await act ( async ( ) => {
146139 await result . current . handleSelect ( { value : 'getsentry/sentry' } ) ;
147140 } ) ;
148141
149- expect ( addRequest ) . not . toHaveBeenCalled ( ) ;
142+ // Optimistic, then revert
150143 expect ( onSelect ) . toHaveBeenCalledWith (
151- expect . objectContaining ( { id : '99 ' , name : 'getsentry/ sentry' } )
144+ expect . objectContaining ( { id : '' , name : 'sentry' } )
152145 ) ;
146+ expect ( onSelect ) . toHaveBeenCalledWith ( undefined ) ;
153147 } ) ;
154148
155- it ( 'reverts onSelect on addRepository failure' , async ( ) => {
149+ it ( 'reverts onSelect on GET failure' , async ( ) => {
156150 MockApiClient . addMockResponse ( {
157151 url : `/organizations/${ organization . slug } /repos/` ,
158- method : 'POST' ,
159152 statusCode : 500 ,
160153 body : { detail : 'Internal Error' } ,
161154 } ) ;
162155
163156 const { result} = renderHookWithProviders (
164157 ( ) =>
165158 useScmRepoSelection ( { integration : mockIntegration , onSelect, reposByIdentifier} ) ,
166- {
167- organization,
168- additionalWrapper : makeOnboardingWrapper ( { } ) ,
169- }
159+ { organization}
170160 ) ;
171161
172162 await act ( async ( ) => {
173163 await result . current . handleSelect ( { value : 'getsentry/sentry' } ) ;
174164 } ) ;
175165
176- // Optimistic, then revert
177166 expect ( onSelect ) . toHaveBeenCalledWith (
178167 expect . objectContaining ( { id : '' , name : 'sentry' } )
179168 ) ;
180169 expect ( onSelect ) . toHaveBeenCalledWith ( undefined ) ;
181170 } ) ;
182171
183- it ( 'cleans up previously added repo when selecting a new one' , async ( ) => {
184- MockApiClient . addMockResponse ( {
185- url : `/organizations/${ organization . slug } /repos/` ,
186- method : 'POST' ,
187- body : {
188- id : '42' ,
189- name : 'getsentry/sentry' ,
190- externalSlug : 'getsentry/sentry' ,
191- status : 'active' ,
192- } ,
193- } ) ;
194-
195- const hideRequest = MockApiClient . addMockResponse ( {
196- url : `/organizations/${ organization . slug } /repos/42/` ,
197- method : 'PUT' ,
198- body : { } ,
199- } ) ;
200-
172+ it ( 'handleRemove calls onSelect with undefined' , ( ) => {
201173 const { result} = renderHookWithProviders (
202174 ( ) =>
203175 useScmRepoSelection ( { integration : mockIntegration , onSelect, reposByIdentifier} ) ,
204- {
205- organization,
206- additionalWrapper : makeOnboardingWrapper ( { } ) ,
207- }
176+ { organization}
208177 ) ;
209178
210- // First selection
211- await act ( async ( ) => {
212- await result . current . handleSelect ( { value : 'getsentry/sentry' } ) ;
179+ act ( ( ) => {
180+ result . current . handleRemove ( ) ;
213181 } ) ;
214182
215- // Add a second repo
216- const secondRepo : IntegrationRepository = {
217- identifier : 'getsentry/relay' ,
218- name : 'relay' ,
219- isInstalled : false ,
220- } ;
221- reposByIdentifier . set ( 'getsentry/relay' , secondRepo ) ;
183+ expect ( onSelect ) . toHaveBeenCalledWith ( undefined ) ;
184+ } ) ;
222185
186+ it ( 'clears busy state after selection completes' , async ( ) => {
223187 MockApiClient . addMockResponse ( {
224188 url : `/organizations/${ organization . slug } /repos/` ,
225- method : 'POST' ,
226- body : {
227- id : '43' ,
228- name : 'getsentry/relay' ,
229- externalSlug : 'getsentry/relay' ,
230- status : 'active' ,
231- } ,
232- } ) ;
233-
234- // Second selection should clean up the first
235- await act ( async ( ) => {
236- await result . current . handleSelect ( { value : 'getsentry/relay' } ) ;
237- } ) ;
238-
239- expect ( hideRequest ) . toHaveBeenCalled ( ) ;
240- } ) ;
241-
242- it ( 'does not call hideRepository on remove if repo was pre-existing' , async ( ) => {
243- const hideRequest = MockApiClient . addMockResponse ( {
244- url : `/organizations/${ organization . slug } /repos/99/` ,
245- method : 'PUT' ,
246- body : { } ,
189+ body : [
190+ {
191+ id : '99' ,
192+ name : 'getsentry/sentry' ,
193+ externalSlug : 'getsentry/sentry' ,
194+ status : 'active' ,
195+ } ,
196+ ] ,
247197 } ) ;
248198
249199 const { result} = renderHookWithProviders (
250200 ( ) =>
251201 useScmRepoSelection ( { integration : mockIntegration , onSelect, reposByIdentifier} ) ,
252- {
253- organization,
254- additionalWrapper : makeOnboardingWrapper ( {
255- selectedRepository : {
256- id : '99' ,
257- name : 'sentry' ,
258- externalSlug : 'getsentry/sentry' ,
259- } as any ,
260- } ) ,
261- }
202+ { organization}
262203 ) ;
263204
205+ expect ( result . current . busy ) . toBe ( false ) ;
206+
264207 await act ( async ( ) => {
265- await result . current . handleRemove ( ) ;
208+ await result . current . handleSelect ( { value : 'getsentry/sentry' } ) ;
266209 } ) ;
267210
268- expect ( onSelect ) . toHaveBeenCalledWith ( undefined ) ;
269- expect ( hideRequest ) . not . toHaveBeenCalled ( ) ;
211+ expect ( result . current . busy ) . toBe ( false ) ;
270212 } ) ;
271213} ) ;
0 commit comments