@@ -106,8 +106,8 @@ function button(label: string) {
106106async function click ( label : string ) {
107107 await act ( async ( ) => button ( label ) . click ( ) )
108108}
109- async function action ( label : string ) {
110- const trigger = container . querySelector < HTMLButtonElement > ( ' [aria-label="Sim Search actions"]' ) !
109+ async function action ( label : string , name = 'Sim Search (custom bot)' ) {
110+ const trigger = container . querySelector < HTMLButtonElement > ( ` [aria-label="${ name } actions"]` ) !
111111 await act ( async ( ) => {
112112 trigger . dispatchEvent ( new KeyboardEvent ( 'keydown' , { key : 'Enter' , bubbles : true } ) )
113113 } )
@@ -137,27 +137,33 @@ describe('Slack Search settings and shared wizard', () => {
137137 } ,
138138 } )
139139 await render ( )
140- await click ( 'Install Sim Search' )
141- expect ( document . querySelector ( '[role="dialog"]' ) ) . toHaveTextContent ( 'Install Sim Search' )
140+ if ( installations . length ) {
141+ expect ( container ) . toHaveTextContent ( 'Reconnect required' )
142+ expect ( container ) . toHaveTextContent ( 'Sim Search (custom bot)' )
143+ await action ( 'Install Sim Search' )
144+ } else {
145+ await click ( 'Install Sim Search' )
146+ }
147+ expect ( document . querySelector ( '[role="dialog"]' ) ) . toHaveTextContent (
148+ 'Install the Sim Search app'
149+ )
142150 expect ( document . querySelectorAll ( 'input' ) ) . toHaveLength ( 0 )
143151 expect ( mocks . install ) . not . toHaveBeenCalled ( )
144- await click ( 'Install Sim Search ' )
152+ await click ( 'Continue with Slack ' )
145153 expect ( mocks . install ) . toHaveBeenCalledExactlyOnceWith (
146154 {
147155 organizationId : 'org-1' ,
148- installationId : undefined ,
156+ installationId : installations [ 0 ] ?. id ,
149157 name : 'Sim Search' ,
150158 description : expect . any ( String ) ,
151159 mode : 'shared' ,
152160 } ,
153161 expect . any ( Object )
154162 )
155- mocks . installError = new Error (
156- 'Remove the previous Slack source configuration before switching apps; members must reconnect'
157- )
163+ mocks . installError = new Error ( 'Slack authorization failed. Try again.' )
158164 await render ( )
159165 expect ( document . querySelector ( '[role="dialog"] [role="alert"]' ) ) . toHaveTextContent (
160- 'Remove the previous Slack source configuration '
166+ 'Slack authorization failed '
161167 )
162168 expect ( mocks . configure ) . not . toHaveBeenCalled ( )
163169 expect ( mocks . remove ) . not . toHaveBeenCalled ( )
@@ -175,8 +181,8 @@ describe('Slack Search settings and shared wizard', () => {
175181 mocks . manifest . mockReturnValue ( { data : { sharedAppId : 'A_SHARED' , existingApp : null } } )
176182 await render ( )
177183 expect ( container ) . not . toHaveTextContent ( 'Install Sim Search' )
178- await action ( 'Reconnect' )
179- await click ( 'Install Sim Search ' )
184+ await action ( 'Reconnect' , 'Sim Search' )
185+ await click ( 'Continue with Slack ' )
180186 expect ( mocks . install ) . toHaveBeenCalledWith (
181187 expect . objectContaining ( { mode : 'shared' , installationId : installation . id } ) ,
182188 expect . any ( Object )
@@ -192,12 +198,12 @@ describe('Slack Search settings and shared wizard', () => {
192198 refetch : mocks . refetch ,
193199 } )
194200 await render ( )
195- await click ( 'Install Sim Search' )
201+ await action ( 'Install Sim Search' )
196202 expect ( document . querySelector ( '[role="dialog"] [role="alert"]' ) ) . toHaveTextContent (
197203 'Sim Search installation is unavailable'
198204 )
199205 expect ( document . querySelector ( '[role="dialog"]' ) ) . not . toHaveTextContent ( 'Create Slack app' )
200- expect ( button ( 'Install Sim Search ' ) ) . toBeDisabled ( )
206+ expect ( button ( 'Continue with Slack ' ) ) . toBeDisabled ( )
201207 expect ( mocks . install ) . not . toHaveBeenCalled ( )
202208 await click ( 'Retry' )
203209 expect ( mocks . refetch ) . toHaveBeenCalledOnce ( )
@@ -213,11 +219,11 @@ describe('Slack Search settings and shared wizard', () => {
213219 refetch : mocks . refetch ,
214220 } )
215221 await render ( )
216- await click ( 'Install Sim Search' )
222+ await action ( 'Install Sim Search' )
217223 expect ( document . querySelector ( '[role="dialog"] [role="alert"]' ) ) . toHaveTextContent (
218224 'Could not load Slack setup'
219225 )
220- expect ( button ( 'Install Sim Search ' ) ) . toBeDisabled ( )
226+ expect ( button ( 'Continue with Slack ' ) ) . toBeDisabled ( )
221227 await click ( 'Retry' )
222228 expect ( mocks . refetch ) . toHaveBeenCalledOnce ( )
223229 expect ( mocks . install ) . not . toHaveBeenCalled ( )
@@ -229,6 +235,58 @@ describe('Slack Search settings and shared wizard', () => {
229235 expect ( container ) . toHaveTextContent ( 'Open in Slack' )
230236 } )
231237
238+ it ( 'prompts the existing custom bot to reconnect when the feature becomes available' , async ( ) => {
239+ await render ( true )
240+ expect ( container ) . toHaveTextContent ( 'Sim Search (custom bot)' )
241+ expect ( container ) . toHaveTextContent ( 'Enabled' )
242+ expect ( container ) . not . toHaveTextContent ( 'Reconnect required' )
243+ mocks . list . mockReturnValue ( {
244+ data : { sharedAppAvailable : true , installations : [ installation ] , bots : [ ] } ,
245+ } )
246+ mocks . manifest . mockReturnValue ( { data : { sharedAppId : 'A_SHARED' , existingApp : null } } )
247+ await render ( )
248+ expect ( container ) . toHaveTextContent ( 'Reconnect required' )
249+ expect ( container ) . not . toHaveTextContent ( 'Install Sim Search' )
250+ expect ( mocks . install ) . not . toHaveBeenCalled ( )
251+ expect ( mocks . configure ) . not . toHaveBeenCalled ( )
252+ await action ( 'Install Sim Search' )
253+ expect ( document . querySelector ( '[role="dialog"]' ) ) . toHaveTextContent (
254+ 'Install the Sim Search app'
255+ )
256+ expect ( button ( 'Continue with Slack' ) ) . not . toBeDisabled ( )
257+ await click ( 'Cancel' )
258+ expect ( mocks . install ) . not . toHaveBeenCalled ( )
259+ expect ( mocks . remove ) . not . toHaveBeenCalled ( )
260+ } )
261+
262+ it ( 'shows the native app alongside the retained custom bot after installing' , async ( ) => {
263+ mocks . list . mockReturnValue ( {
264+ data : {
265+ sharedAppAvailable : true ,
266+ installations : [
267+ { ...installation , enabled : false } ,
268+ {
269+ ...installation ,
270+ id : 'native-installation' ,
271+ credentialId : 'native-credential' ,
272+ appId : 'A_SHARED' ,
273+ appKind : 'shared' ,
274+ } ,
275+ ] ,
276+ bots : [ ] ,
277+ } ,
278+ } )
279+ await render ( )
280+ expect ( container . querySelector ( '[aria-label="Sim Search (custom bot) actions"]' ) ) . not . toBeNull ( )
281+ expect ( container . querySelector ( '[aria-label="Sim Search actions"]' ) ) . not . toBeNull ( )
282+ expect ( container ) . toHaveTextContent ( 'Disabled' )
283+ expect ( container ) . toHaveTextContent ( 'Enabled' )
284+ expect ( container ) . not . toHaveTextContent ( 'Reconnect required' )
285+ expect ( container ) . not . toHaveTextContent ( 'Install Sim Search' )
286+ expect ( container . querySelectorAll ( 'a[href*="slack.com/app_redirect"]' ) ) . toHaveLength ( 2 )
287+ expect ( mocks . install ) . not . toHaveBeenCalled ( )
288+ } )
289+
232290 it ( 'starts with one setup action and a Slack app link, with no manifest preview or form' , async ( ) => {
233291 await render ( )
234292 expect ( container . querySelectorAll ( 'button' ) ) . toHaveLength ( 1 )
@@ -246,13 +304,15 @@ describe('Slack Search settings and shared wizard', () => {
246304
247305 it ( 'shows setup errors and blocks progression until the manifest loads' , async ( ) => {
248306 mocks . manifest . mockReturnValue ( {
249- error : new Error ( 'Slack needs a public HTTPS URL to send messages to Sim .' ) ,
307+ error : new Error ( 'Slack app configuration is unavailable .' ) ,
250308 refetch : mocks . refetch ,
251309 isPending : false ,
252310 } )
253311 await render ( )
254312 await click ( 'Set up' )
255- expect ( document . querySelector ( '[role="alert"]' ) ) . toHaveTextContent ( 'public HTTPS' )
313+ expect ( document . querySelector ( '[role="alert"]' ) ) . toHaveTextContent (
314+ 'Slack app configuration is unavailable.'
315+ )
256316 expect ( document . querySelector ( '[role="dialog"]' ) ) . not . toHaveTextContent ( 'Step 1' )
257317 expect ( document . querySelector ( '[role="dialog"]' ) ) . not . toHaveTextContent ( 'Continue' )
258318 await click ( 'Retry' )
@@ -294,7 +354,7 @@ describe('Slack Search settings and shared wizard', () => {
294354 expect ( document . querySelector ( '[role="dialog"]' ) ) . not . toHaveTextContent ( 'Loading Slack setup' )
295355 if ( mode === 'shared' ) {
296356 expect ( document . querySelector ( '[role="dialog"]' ) ) . not . toHaveTextContent ( 'Step 1' )
297- await click ( 'Install Sim Search ' )
357+ await click ( 'Continue with Slack ' )
298358 expect ( mocks . install ) . toHaveBeenCalledWith (
299359 expect . objectContaining ( { organizationId : 'org-1' , mode : 'shared' } ) ,
300360 expect . any ( Object )
0 commit comments