@@ -12,6 +12,8 @@ const {
1212 navigateMock,
1313 moveCursorMock,
1414 getFocusedPaneMock,
15+ getPaneLocationMock,
16+ setFocusedPaneMock,
1517} = vi . hoisted ( ( ) => ( {
1618 goToLatestDownloadMock : vi . fn ( ) ,
1719 downloadsWatcherStatusMock : vi . fn ( ) ,
@@ -21,6 +23,14 @@ const {
2123 navigateMock : vi . fn ( ( ) : NavigateResult => ( { status : 'started' , settled : Promise . resolve ( ) } ) ) ,
2224 moveCursorMock : vi . fn ( ( ) => Promise . resolve ( ) ) ,
2325 getFocusedPaneMock : vi . fn ( ( ) => 'left' ) ,
26+ // Pane active-tab location getter. Default: both panes elsewhere, on the main
27+ // local volume, so the pane-reuse check fails and the focused pane navigates.
28+ getPaneLocationMock : vi . fn ( ( ) : { volumeId : string ; volumePath : string ; path : string } => ( {
29+ volumeId : 'root' ,
30+ volumePath : '/' ,
31+ path : '/Users/me/elsewhere' ,
32+ } ) ) ,
33+ setFocusedPaneMock : vi . fn ( ) ,
2434} ) )
2535
2636vi . mock ( '$lib/ipc/bindings' , ( ) => ( {
@@ -58,16 +68,33 @@ import LatestDownloadFdaToastContent from './LatestDownloadFdaToastContent.svelt
5868import type { ExplorerAPI } from '../../routes/(main)/explorer-api'
5969
6070function makeExplorerStub ( ) : ExplorerAPI {
61- // Only the three methods this helper touches need real stubs. Everything
62- // else is unused; the typed stub avoids a `Partial<ExplorerAPI>` cast leaking
63- // into the helper's call sites.
71+ // Only the methods these helpers touch need real stubs. Everything else is
72+ // unused; the typed stub avoids a `Partial<ExplorerAPI>` cast leaking into the
73+ // helper's call sites.
6474 return {
6575 getFocusedPane : getFocusedPaneMock ,
6676 navigate : navigateMock ,
6777 moveCursor : moveCursorMock ,
78+ getPaneLocation : getPaneLocationMock ,
79+ setFocusedPane : setFocusedPaneMock ,
6880 } as unknown as ExplorerAPI
6981}
7082
83+ /**
84+ * Helper: wire `getPaneLocation` so a given pane's active tab shows `path` on a
85+ * real local volume (so the pane-reuse match succeeds), while the other pane
86+ * sits elsewhere. `volumeId`/`volumePath` default to the main local volume.
87+ */
88+ function paneShows (
89+ pane : 'left' | 'right' ,
90+ path : string ,
91+ volume : { volumeId : string ; volumePath : string } = { volumeId : 'root' , volumePath : '/' } ,
92+ ) : void {
93+ getPaneLocationMock . mockImplementation ( ( p : 'left' | 'right' ) =>
94+ p === pane ? { ...volume , path } : { volumeId : 'root' , volumePath : '/' , path : '/Users/me/elsewhere' } ,
95+ )
96+ }
97+
7198describe ( 'goToLatestDownload' , ( ) => {
7299 beforeEach ( ( ) => {
73100 goToLatestDownloadMock . mockReset ( )
@@ -77,6 +104,8 @@ describe('goToLatestDownload', () => {
77104 navigateMock . mockReset ( ) . mockReturnValue ( { status : 'started' , settled : Promise . resolve ( ) } )
78105 moveCursorMock . mockReset ( ) . mockResolvedValue ( undefined )
79106 getFocusedPaneMock . mockReset ( ) . mockReturnValue ( 'left' )
107+ getPaneLocationMock . mockReset ( ) . mockReturnValue ( { volumeId : 'root' , volumePath : '/' , path : '/Users/me/elsewhere' } )
108+ setFocusedPaneMock . mockReset ( )
80109 } )
81110
82111 it ( 'navigates the focused pane and selects the file on success' , async ( ) => {
@@ -97,6 +126,93 @@ describe('goToLatestDownload', () => {
97126 expect ( addToastMock ) . not . toHaveBeenCalled ( )
98127 } )
99128
129+ it ( 'moves the cursor only (no navigate, no focus shift) when the focused pane already shows the dir' , async ( ) => {
130+ goToLatestDownloadMock . mockResolvedValue ( {
131+ status : 'ok' ,
132+ data : { path : '/Users/me/Downloads/report.pdf' , parentDir : '/Users/me/Downloads' , fileName : 'report.pdf' } ,
133+ } )
134+ getFocusedPaneMock . mockReturnValue ( 'left' )
135+ paneShows ( 'left' , '/Users/me/Downloads' )
136+
137+ await goToLatestDownload ( makeExplorerStub ( ) )
138+
139+ expect ( navigateMock ) . not . toHaveBeenCalled ( )
140+ expect ( setFocusedPaneMock ) . not . toHaveBeenCalled ( )
141+ expect ( moveCursorMock ) . toHaveBeenCalledWith ( 'left' , 'report.pdf' )
142+ } )
143+
144+ it ( 'shifts focus and moves the cursor (no navigate) when the OTHER pane already shows the dir' , async ( ) => {
145+ goToLatestDownloadMock . mockResolvedValue ( {
146+ status : 'ok' ,
147+ data : { path : '/Users/me/Downloads/report.pdf' , parentDir : '/Users/me/Downloads' , fileName : 'report.pdf' } ,
148+ } )
149+ getFocusedPaneMock . mockReturnValue ( 'left' )
150+ paneShows ( 'right' , '/Users/me/Downloads' )
151+
152+ await goToLatestDownload ( makeExplorerStub ( ) )
153+
154+ expect ( navigateMock ) . not . toHaveBeenCalled ( )
155+ expect ( setFocusedPaneMock ) . toHaveBeenCalledWith ( 'right' )
156+ expect ( moveCursorMock ) . toHaveBeenCalledWith ( 'right' , 'report.pdf' )
157+ } )
158+
159+ it ( 'navigates the focused pane when neither pane shows the dir' , async ( ) => {
160+ goToLatestDownloadMock . mockResolvedValue ( {
161+ status : 'ok' ,
162+ data : { path : '/Users/me/Downloads/report.pdf' , parentDir : '/Users/me/Downloads' , fileName : 'report.pdf' } ,
163+ } )
164+ getFocusedPaneMock . mockReturnValue ( 'left' )
165+ // Both panes sit elsewhere (the beforeEach default).
166+
167+ await goToLatestDownload ( makeExplorerStub ( ) )
168+
169+ expect ( navigateMock ) . toHaveBeenCalledWith ( { pane : 'left' , to : { path : '/Users/me/Downloads' } , source : 'user' } )
170+ expect ( setFocusedPaneMock ) . not . toHaveBeenCalled ( )
171+ expect ( moveCursorMock ) . toHaveBeenCalledWith ( 'left' , 'report.pdf' )
172+ } )
173+
174+ it ( 'does NOT count a pane at an equal-looking path on a virtual or device volume as showing the dir' , async ( ) => {
175+ goToLatestDownloadMock . mockResolvedValue ( {
176+ status : 'ok' ,
177+ data : { path : '/Users/me/Downloads/report.pdf' , parentDir : '/Users/me/Downloads' , fileName : 'report.pdf' } ,
178+ } )
179+ getFocusedPaneMock . mockReturnValue ( 'left' )
180+ // The other pane's active tab reports the exact same path string, but it's on
181+ // a network volume (its volumePath is `smb://…`, not a real local mount). It
182+ // must not count, so the focused pane navigates as usual.
183+ getPaneLocationMock . mockImplementation ( ( p : 'left' | 'right' ) =>
184+ p === 'right'
185+ ? { volumeId : 'network' , volumePath : 'smb://' , path : '/Users/me/Downloads' }
186+ : { volumeId : 'root' , volumePath : '/' , path : '/Users/me/elsewhere' } ,
187+ )
188+
189+ await goToLatestDownload ( makeExplorerStub ( ) )
190+
191+ expect ( navigateMock ) . toHaveBeenCalledWith ( { pane : 'left' , to : { path : '/Users/me/Downloads' } , source : 'user' } )
192+ expect ( setFocusedPaneMock ) . not . toHaveBeenCalled ( )
193+ expect ( moveCursorMock ) . toHaveBeenCalledWith ( 'left' , 'report.pdf' )
194+ } )
195+
196+ it ( 'does NOT count an MTP pane whose path string matches the local dir' , async ( ) => {
197+ goToLatestDownloadMock . mockResolvedValue ( {
198+ status : 'ok' ,
199+ data : { path : '/Users/me/Downloads/report.pdf' , parentDir : '/Users/me/Downloads' , fileName : 'report.pdf' } ,
200+ } )
201+ getFocusedPaneMock . mockReturnValue ( 'left' )
202+ // MTP device volume: volumePath is an `mtp://…` URL, so the local Downloads
203+ // path is not on it — `isPathOnVolume` rejects the match.
204+ getPaneLocationMock . mockImplementation ( ( p : 'left' | 'right' ) =>
205+ p === 'right'
206+ ? { volumeId : 'mtp-0-1' , volumePath : 'mtp://mtp-0-1/65537' , path : '/Users/me/Downloads' }
207+ : { volumeId : 'root' , volumePath : '/' , path : '/Users/me/elsewhere' } ,
208+ )
209+
210+ await goToLatestDownload ( makeExplorerStub ( ) )
211+
212+ expect ( navigateMock ) . toHaveBeenCalledWith ( { pane : 'left' , to : { path : '/Users/me/Downloads' } , source : 'user' } )
213+ expect ( setFocusedPaneMock ) . not . toHaveBeenCalled ( )
214+ } )
215+
100216 it ( 'shows the empty INFO toast with the dedup id on GoToLatestError::Empty' , async ( ) => {
101217 goToLatestDownloadMock . mockResolvedValue ( {
102218 status : 'error' ,
@@ -128,6 +244,27 @@ describe('goToLatestDownload', () => {
128244 expect ( moveCursorMock ) . not . toHaveBeenCalled ( )
129245 } )
130246
247+ it ( 'empty-toast "Go to Downloads" reuses a pane already showing Downloads, evaluated at click time' , async ( ) => {
248+ goToLatestDownloadMock . mockResolvedValue ( { status : 'error' , error : { kind : 'empty' } } )
249+ downloadsWatcherStatusMock . mockResolvedValue ( {
250+ status : 'ok' ,
251+ data : { running : true , downloadsDir : '/Users/me/Downloads' , fdaPending : false } ,
252+ } )
253+ getFocusedPaneMock . mockReturnValue ( 'left' )
254+
255+ await goToLatestDownload ( makeExplorerStub ( ) )
256+
257+ const [ , options ] = addToastMock . mock . calls [ 0 ] as unknown as [ unknown , { props ?: { onGoToDownloads : ( ) => void } } ]
258+ // The other pane navigates to Downloads AFTER the toast was added, so the
259+ // action must re-evaluate which pane shows the dir at CLICK time.
260+ paneShows ( 'right' , '/Users/me/Downloads' )
261+ options . props ?. onGoToDownloads ( )
262+
263+ // Pane reuse: focus the pane that shows Downloads, no fresh navigation.
264+ expect ( navigateMock ) . not . toHaveBeenCalled ( )
265+ expect ( setFocusedPaneMock ) . toHaveBeenCalledWith ( 'right' )
266+ } )
267+
131268 it ( 'shows the FDA INFO toast with the dedup id on GoToLatestError::WatcherUnavailable' , async ( ) => {
132269 goToLatestDownloadMock . mockResolvedValue ( {
133270 status : 'error' ,
@@ -200,15 +337,28 @@ describe('goToDownload', () => {
200337 navigateMock . mockReset ( ) . mockReturnValue ( { status : 'started' , settled : Promise . resolve ( ) } )
201338 moveCursorMock . mockReset ( ) . mockResolvedValue ( undefined )
202339 getFocusedPaneMock . mockReset ( ) . mockReturnValue ( 'left' )
340+ getPaneLocationMock . mockReset ( ) . mockReturnValue ( { volumeId : 'root' , volumePath : '/' , path : '/Users/me/elsewhere' } )
341+ setFocusedPaneMock . mockReset ( )
203342 } )
204343
205- it ( 'navigates the focused pane to parentDir and selects the file' , async ( ) => {
344+ it ( 'navigates the focused pane to parentDir and selects the file when neither pane shows it ' , async ( ) => {
206345 await goToDownload ( makeExplorerStub ( ) , '/Users/me/Downloads' , 'report.pdf' )
207346
208347 expect ( navigateMock ) . toHaveBeenCalledWith ( { pane : 'left' , to : { path : '/Users/me/Downloads' } , source : 'user' } )
209348 expect ( moveCursorMock ) . toHaveBeenCalledWith ( 'left' , 'report.pdf' )
210349 } )
211350
351+ it ( 'reuses the focused pane (cursor only) when it already shows the dir' , async ( ) => {
352+ getFocusedPaneMock . mockReturnValue ( 'left' )
353+ paneShows ( 'left' , '/Users/me/Downloads' )
354+
355+ await goToDownload ( makeExplorerStub ( ) , '/Users/me/Downloads' , 'report.pdf' )
356+
357+ expect ( navigateMock ) . not . toHaveBeenCalled ( )
358+ expect ( setFocusedPaneMock ) . not . toHaveBeenCalled ( )
359+ expect ( moveCursorMock ) . toHaveBeenCalledWith ( 'left' , 'report.pdf' )
360+ } )
361+
212362 it ( 'does nothing when the explorer handle is missing (HMR / pre-mount)' , async ( ) => {
213363 await goToDownload ( undefined , '/Users/me/Downloads' , 'report.pdf' )
214364
0 commit comments