@@ -116,8 +116,9 @@ describe('App', () => {
116116 expect ( screen . getByLabelText ( 'html2rss' ) ) . toBeInTheDocument ( ) ;
117117 expect ( screen . getByRole ( 'link' , { name : 'html2rss' } ) ) . toHaveAttribute ( 'href' , '/create' ) ;
118118 expect ( screen . getByLabelText ( 'Page URL' ) ) . toBeInTheDocument ( ) ;
119- expect ( screen . getByRole ( 'button' , { name : 'More' } ) ) . toBeInTheDocument ( ) ;
120- expect ( screen . queryByRole ( 'link' , { name : 'Bookmarklet' } ) ) . not . toBeInTheDocument ( ) ;
119+ expect ( screen . queryByRole ( 'combobox' ) ) . not . toBeInTheDocument ( ) ;
120+ expect ( screen . getByLabelText ( 'Utilities' ) ) . toBeInTheDocument ( ) ;
121+ expect ( screen . getByRole ( 'link' , { name : 'Bookmarklet' } ) ) . toBeInTheDocument ( ) ;
121122 expect ( document . querySelector ( '.form-shell' ) ) . toHaveAttribute ( 'data-state' , 'idle' ) ;
122123 } ) ;
123124
@@ -139,25 +140,58 @@ describe('App', () => {
139140 } ) ;
140141 } ) ;
141142
142- it ( 'prefers faraday as the default strategy when available' , ( ) => {
143+ it ( 'submits create requests with the faraday default strategy' , async ( ) => {
144+ mockUseAccessToken . mockReturnValue ( {
145+ token : 'saved-token' ,
146+ hasToken : true ,
147+ saveToken : mockSaveToken ,
148+ clearToken : mockClearToken ,
149+ isLoading : false ,
150+ error : undefined ,
151+ } ) ;
152+
143153 render ( < App /> ) ;
144154
145- return waitFor ( ( ) => {
146- expect ( screen . getByRole ( 'combobox' ) ) . toHaveValue ( 'faraday' ) ;
155+ fireEvent . input ( screen . getByLabelText ( 'Page URL' ) , {
156+ target : { value : 'https://example.com/articles' } ,
157+ } ) ;
158+ fireEvent . click ( screen . getByRole ( 'button' , { name : 'Generate feed URL' } ) ) ;
159+
160+ await waitFor ( ( ) => {
161+ expect ( mockConvertFeed ) . toHaveBeenCalledWith ( 'https://example.com/articles' , 'faraday' , 'saved-token' ) ;
147162 } ) ;
148163 } ) ;
149164
150- it ( 'falls back to the first available strategy when browserless is unavailable' , ( ) => {
165+ it ( 'falls back to the first available strategy when faraday is unavailable' , async ( ) => {
151166 mockUseStrategies . mockReturnValue ( {
152- strategies : [ { id : 'faraday' , name : 'faraday' , display_name : 'Default' } ] ,
167+ strategies : [
168+ { id : 'browserless' , name : 'browserless' , display_name : 'JavaScript pages (recommended)' } ,
169+ ] ,
170+ isLoading : false ,
171+ error : undefined ,
172+ } ) ;
173+ mockUseAccessToken . mockReturnValue ( {
174+ token : 'saved-token' ,
175+ hasToken : true ,
176+ saveToken : mockSaveToken ,
177+ clearToken : mockClearToken ,
153178 isLoading : false ,
154179 error : undefined ,
155180 } ) ;
156181
157182 render ( < App /> ) ;
158183
159- return waitFor ( ( ) => {
160- expect ( screen . getByRole ( 'combobox' ) ) . toHaveValue ( 'faraday' ) ;
184+ fireEvent . input ( screen . getByLabelText ( 'Page URL' ) , {
185+ target : { value : 'https://example.com/articles' } ,
186+ } ) ;
187+ fireEvent . click ( screen . getByRole ( 'button' , { name : 'Generate feed URL' } ) ) ;
188+
189+ await waitFor ( ( ) => {
190+ expect ( mockConvertFeed ) . toHaveBeenCalledWith (
191+ 'https://example.com/articles' ,
192+ 'browserless' ,
193+ 'saved-token'
194+ ) ;
161195 } ) ;
162196 } ) ;
163197
@@ -245,8 +279,8 @@ describe('App', () => {
245279 expect ( globalThis . location . pathname ) . toBe ( '/token' ) ;
246280 expect ( document . querySelector ( '.form-shell' ) ) . toHaveAttribute ( 'data-state' , 'token_required' ) ;
247281 expect ( screen . getByLabelText ( 'Page URL' ) ) . toBeDisabled ( ) ;
248- expect ( screen . getByRole ( 'combobox' ) ) . toBeDisabled ( ) ;
249- expect ( screen . queryByRole ( 'button' , { name : 'More' } ) ) . not . toBeInTheDocument ( ) ;
282+ expect ( screen . queryByRole ( 'combobox' ) ) . not . toBeInTheDocument ( ) ;
283+ expect ( screen . queryByLabelText ( 'Utilities' ) ) . not . toBeInTheDocument ( ) ;
250284 expect ( screen . getByRole ( 'link' , { name : 'Set up your own instance with Docker.' } ) ) . toBeInTheDocument ( ) ;
251285 expect ( screen . getByText ( 'Required by this instance.' ) ) . toBeInTheDocument ( ) ;
252286 expect ( screen . queryByText ( 'Paste an access token to keep going.' ) ) . not . toBeInTheDocument ( ) ;
@@ -326,7 +360,7 @@ describe('App', () => {
326360
327361 expect ( document . querySelector ( '.result-shell' ) ) . toHaveAttribute ( 'data-state' , 'failed' ) ;
328362 expect ( screen . getByRole ( 'button' , { name : 'Create another feed' } ) ) . toBeInTheDocument ( ) ;
329- expect ( screen . queryByRole ( 'link' , { name : 'Bookmarklet' } ) ) . not . toBeInTheDocument ( ) ;
363+ expect ( screen . getByRole ( 'link' , { name : 'Bookmarklet' } ) ) . toBeInTheDocument ( ) ;
330364 expect ( screen . getByText ( 'Example Feed' ) ) . toBeInTheDocument ( ) ;
331365 expect ( screen . getByText ( 'Preview unavailable right now.' ) ) . toBeInTheDocument ( ) ;
332366
@@ -350,7 +384,7 @@ describe('App', () => {
350384
351385 render ( < App /> ) ;
352386
353- expect ( screen . getByText ( 'Could not create feed link' ) ) . toBeInTheDocument ( ) ;
387+ expect ( screen . getByText ( "Couldn't create feed yet" ) ) . toBeInTheDocument ( ) ;
354388 expect ( screen . getByText ( 'Access denied' ) ) . toBeInTheDocument ( ) ;
355389 } ) ;
356390
@@ -384,7 +418,6 @@ describe('App', () => {
384418
385419 render ( < App /> ) ;
386420
387- fireEvent . click ( screen . getByRole ( 'button' , { name : 'More' } ) ) ;
388421 fireEvent . click ( screen . getByRole ( 'button' , { name : 'Clear saved token' } ) ) ;
389422
390423 expect ( mockClearToken ) . toHaveBeenCalled ( ) ;
@@ -402,8 +435,6 @@ describe('App', () => {
402435
403436 render ( < App /> ) ;
404437
405- fireEvent . click ( screen . getByRole ( 'button' , { name : 'More' } ) ) ;
406-
407438 const utilityItems = [
408439 ...screen
409440 . getByLabelText ( 'Utilities' )
@@ -490,7 +521,7 @@ describe('App', () => {
490521 await waitFor ( ( ) => {
491522 expect ( globalThis . location . pathname ) . toBe ( '/create' ) ;
492523 } ) ;
493- expect ( screen . queryByText ( 'Could not create feed link' ) ) . not . toBeInTheDocument ( ) ;
524+ expect ( screen . queryByText ( "Couldn't create feed yet" ) ) . not . toBeInTheDocument ( ) ;
494525 expect ( screen . queryByText ( 'Unauthorized' ) ) . not . toBeInTheDocument ( ) ;
495526 } ) ;
496527
@@ -515,7 +546,6 @@ describe('App', () => {
515546 globalThis . history . replaceState ( { } , '' , 'http://localhost:3000/create' ) ;
516547 render ( < App /> ) ;
517548
518- fireEvent . click ( screen . getByRole ( 'button' , { name : 'More' } ) ) ;
519549 const bookmarklet = screen . getByRole ( 'link' , { name : 'Bookmarklet' } ) ;
520550 expect ( bookmarklet . getAttribute ( 'href' ) ) . toContain ( '/?url=' ) ;
521551 expect ( bookmarklet . getAttribute ( 'href' ) ) . not . toContain ( '%27+encodeURIComponent' ) ;
@@ -568,7 +598,7 @@ describe('App', () => {
568598 } ) ;
569599 } ) ;
570600
571- it ( 'does not offer a duplicate retry action after automatic fallback already failed' , async ( ) => {
601+ it ( 'shows Try again after automatic fallback already failed and reruns the create flow ' , async ( ) => {
572602 mockUseAccessToken . mockReturnValue ( {
573603 token : 'saved-token' ,
574604 hasToken : true ,
@@ -577,11 +607,13 @@ describe('App', () => {
577607 isLoading : false ,
578608 error : undefined ,
579609 } ) ;
580- mockConvertFeed . mockRejectedValueOnce (
581- Object . assign ( new Error ( 'Tried faraday first, then browserless. Browserless failed.' ) , {
582- manualRetryStrategy : '' ,
583- } )
584- ) ;
610+ mockConvertFeed
611+ . mockRejectedValueOnce (
612+ Object . assign ( new Error ( 'Tried faraday first, then browserless. Browserless failed.' ) , {
613+ manualRetryStrategy : '' ,
614+ } )
615+ )
616+ . mockResolvedValueOnce ( mockCreatedFeedResult ) ;
585617
586618 render ( < App /> ) ;
587619
@@ -590,8 +622,17 @@ describe('App', () => {
590622 } ) ;
591623 fireEvent . click ( screen . getByRole ( 'button' , { name : 'Generate feed URL' } ) ) ;
592624
593- await screen . findByText ( 'Tried faraday first, then browserless. Browserless failed.' ) ;
594- expect ( screen . queryByRole ( 'button' , { name : / R e t r y w i t h .* / } ) ) . not . toBeInTheDocument ( ) ;
625+ await screen . findByRole ( 'button' , { name : 'Try again' } ) ;
626+ fireEvent . click ( screen . getByRole ( 'button' , { name : 'Try again' } ) ) ;
627+
628+ await waitFor ( ( ) => {
629+ expect ( mockConvertFeed ) . toHaveBeenCalledTimes ( 2 ) ;
630+ expect ( mockConvertFeed ) . toHaveBeenLastCalledWith (
631+ 'https://example.com/articles' ,
632+ 'faraday' ,
633+ 'saved-token'
634+ ) ;
635+ } ) ;
595636 } ) ;
596637
597638 it ( 'does not treat non-token forbidden failures as token rejection or strategy-recovery UX' , async ( ) => {
@@ -622,15 +663,14 @@ describe('App', () => {
622663 expect (
623664 screen . queryByText ( 'Access token was rejected. Paste a valid token to continue.' )
624665 ) . not . toBeInTheDocument ( ) ;
666+ expect ( screen . queryByRole ( 'button' , { name : 'Try again' } ) ) . not . toBeInTheDocument ( ) ;
625667 expect ( screen . queryByRole ( 'button' , { name : / R e t r y w i t h .* / } ) ) . not . toBeInTheDocument ( ) ;
626668 } ) ;
627669
628670 it ( 'shows the utility links in a user-focused order' , ( ) => {
629671 globalThis . history . replaceState ( { } , '' , 'http://localhost:3000/create' ) ;
630672 render ( < App /> ) ;
631673
632- fireEvent . click ( screen . getByRole ( 'button' , { name : 'More' } ) ) ;
633-
634674 const utilityLinks = [
635675 ...screen . getByLabelText ( 'Utilities' ) . querySelectorAll ( '.utility-strip__items > a' ) ,
636676 ] . map ( ( link ) => link . textContent ) ;
@@ -679,10 +719,18 @@ describe('App', () => {
679719 globalThis . history . replaceState ( { } , '' , 'http://localhost:3000/create' ) ;
680720 render ( < App /> ) ;
681721
682- fireEvent . click ( screen . getByRole ( 'button' , { name : 'More' } ) ) ;
683722 expect ( screen . getByRole ( 'link' , { name : 'OpenAPI spec' } ) ) . toHaveAttribute (
684723 'href' ,
685724 'http://localhost:3000/openapi.yaml'
686725 ) ;
687726 } ) ;
727+
728+ it ( 'shows footer utilities on result routes' , async ( ) => {
729+ globalThis . history . replaceState ( { } , '' , 'http://localhost:3000/result/generated-token' ) ;
730+ render ( < App /> ) ;
731+
732+ await waitFor ( ( ) => {
733+ expect ( screen . getByLabelText ( 'Utilities' ) ) . toBeInTheDocument ( ) ;
734+ } ) ;
735+ } ) ;
688736} ) ;
0 commit comments