diff --git a/src/renderer/src/components/right-sidebar/FileExplorer.test.tsx b/src/renderer/src/components/right-sidebar/FileExplorer.test.tsx index 1688b0721..37fce9ae6 100644 --- a/src/renderer/src/components/right-sidebar/FileExplorer.test.tsx +++ b/src/renderer/src/components/right-sidebar/FileExplorer.test.tsx @@ -293,6 +293,14 @@ function makeToolbar(overrides: Partial[0 }) } +function setDownloadPlatform(platform: NodeJS.Platform): void { + ;( + window as unknown as { + api: { platform: { get: () => { platform: NodeJS.Platform } } } + } + ).api.platform = { get: () => ({ platform }) } +} + beforeEach(() => { toastErrorMock.mockReset() toastSuccessMock.mockReset() @@ -677,7 +685,10 @@ describe('FileExplorerRow collapse folder action', () => { it('calls the preload download API and shows success only when not canceled', async () => { const downloadFile = vi .fn() - .mockResolvedValueOnce({ canceled: false, destinationPath: '/downloads/index.ts' }) + .mockResolvedValueOnce({ + canceled: false, + destinationPath: '/downloads/renamed\\entry.ts' + }) .mockResolvedValueOnce({ canceled: true }) const openPath = vi.fn().mockResolvedValue(undefined) ;( @@ -690,6 +701,7 @@ describe('FileExplorerRow collapse folder action', () => { } } ).window = { api: { fs: { downloadFile }, shell: { openPath } } } + setDownloadPlatform('linux') await downloadRemoteFile(fileNode, 'ssh-1') await downloadRemoteFile(fileNode, 'ssh-1') @@ -699,7 +711,7 @@ describe('FileExplorerRow collapse folder action', () => { connectionId: 'ssh-1' }) expect(toastSuccessMock).toHaveBeenCalledTimes(1) - expect(toastSuccessMock).toHaveBeenCalledWith("Downloaded 'index.ts'", { + expect(toastSuccessMock).toHaveBeenCalledWith("Downloaded 'renamed\\entry.ts'", { action: { label: 'Open', onClick: expect.any(Function) @@ -709,10 +721,30 @@ describe('FileExplorerRow collapse folder action', () => { | { onClick: () => void } | undefined action?.onClick() - expect(openPath).toHaveBeenCalledWith('/downloads/index.ts') + expect(openPath).toHaveBeenCalledWith('/downloads/renamed\\entry.ts') expect(toastErrorMock).not.toHaveBeenCalled() }) + it('reports the saved folder name from a Windows destination path', async () => { + const downloadFolder = vi.fn().mockResolvedValue({ + canceled: false, + destinationPath: 'C:\\Users\\dev\\Downloads\\src-copy' + }) + ;( + globalThis as unknown as { + window: { api: { fs: { downloadFolder: typeof downloadFolder } } } + } + ).window = { api: { fs: { downloadFolder } } } + setDownloadPlatform('win32') + + await downloadRemoteFile(directoryNode, 'ssh-1') + + expect(toastSuccessMock).toHaveBeenCalledWith( + "Downloaded folder 'src-copy'", + expect.objectContaining({ action: expect.anything() }) + ) + }) + it('calls the preload folder download API for SSH directory rows', async () => { const downloadFolder = vi.fn().mockResolvedValue({ canceled: false, @@ -729,6 +761,7 @@ describe('FileExplorerRow collapse folder action', () => { } } ).window = { api: { fs: { downloadFolder }, shell: { openPath } } } + setDownloadPlatform('linux') await downloadRemoteFile(directoryNode, 'ssh-1') @@ -765,6 +798,7 @@ describe('FileExplorerRow collapse folder action', () => { } } ).window = { api: { shell: { openPath } } } + setDownloadPlatform('linux') await downloadRemoteFile(fileNode, runtimeContext) diff --git a/src/renderer/src/components/right-sidebar/FileExplorerRow.tsx b/src/renderer/src/components/right-sidebar/FileExplorerRow.tsx index 1b621454a..6167f08d5 100644 --- a/src/renderer/src/components/right-sidebar/FileExplorerRow.tsx +++ b/src/renderer/src/components/right-sidebar/FileExplorerRow.tsx @@ -342,6 +342,14 @@ export function shouldShowCopyFileAction( ) } +function getLocalDownloadName(destinationPath: string, platform: NodeJS.Platform): string { + const lastSeparatorIndex = + platform === 'win32' + ? Math.max(destinationPath.lastIndexOf('/'), destinationPath.lastIndexOf('\\')) + : destinationPath.lastIndexOf('/') + return destinationPath.slice(lastSeparatorIndex + 1) +} + export async function downloadRemoteFile( node: TreeNode, connectionIdOrRuntimeContext: string | RuntimeFileOperationArgs @@ -363,17 +371,22 @@ export async function downloadRemoteFile( if (result.canceled) { return } + // Why: POSIX permits backslashes in saved names; only Windows treats them as separators. + const savedName = getLocalDownloadName( + result.destinationPath, + window.api.platform.get().platform + ) toast.success( node.isDirectory ? translate( 'auto.components.right.sidebar.FileExplorerRow.a4029c996b', "Downloaded folder '{{value0}}'", - { value0: node.name } + { value0: savedName } ) : translate( 'auto.components.right.sidebar.FileExplorerRow.bce4d4e44f', "Downloaded '{{value0}}'", - { value0: node.name } + { value0: savedName } ), { action: {