Stop directory pickers creating typed prefixes (#6114)
This commit is contained in:
parent
9de696a357
commit
cea8d97aec
|
|
@ -1,14 +1,24 @@
|
|||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
const { handlers, appExitMock, appQuitMock, appRelaunchMock, execFileMock, destroySystemTrayMock } =
|
||||
vi.hoisted(() => ({
|
||||
handlers: new Map<string, (_event: unknown, args?: unknown) => unknown>(),
|
||||
appExitMock: vi.fn(),
|
||||
appQuitMock: vi.fn(),
|
||||
appRelaunchMock: vi.fn(),
|
||||
execFileMock: vi.fn(),
|
||||
destroySystemTrayMock: vi.fn()
|
||||
}))
|
||||
const {
|
||||
handlers,
|
||||
appExitMock,
|
||||
appQuitMock,
|
||||
appRelaunchMock,
|
||||
execFileMock,
|
||||
destroySystemTrayMock,
|
||||
showOpenDialogMock,
|
||||
grantFloatingWorkspaceDirectoryMock
|
||||
} = vi.hoisted(() => ({
|
||||
handlers: new Map<string, (_event: unknown, args?: unknown) => unknown>(),
|
||||
appExitMock: vi.fn(),
|
||||
appQuitMock: vi.fn(),
|
||||
appRelaunchMock: vi.fn(),
|
||||
execFileMock: vi.fn(),
|
||||
destroySystemTrayMock: vi.fn(),
|
||||
showOpenDialogMock: vi.fn(),
|
||||
grantFloatingWorkspaceDirectoryMock: vi.fn()
|
||||
}))
|
||||
|
||||
vi.mock('node:child_process', () => ({
|
||||
execFile: execFileMock
|
||||
|
|
@ -26,7 +36,7 @@ vi.mock('electron', () => ({
|
|||
fromWebContents: vi.fn(() => null)
|
||||
},
|
||||
dialog: {
|
||||
showOpenDialog: vi.fn()
|
||||
showOpenDialog: showOpenDialogMock
|
||||
},
|
||||
ipcMain: {
|
||||
handle: vi.fn((channel: string, handler: (_event: unknown, args?: unknown) => unknown) => {
|
||||
|
|
@ -43,6 +53,12 @@ vi.mock('../tray/system-tray', () => ({
|
|||
destroySystemTray: destroySystemTrayMock
|
||||
}))
|
||||
|
||||
vi.mock('./floating-workspace-directory', () => ({
|
||||
ensureDefaultFloatingWorkspacePath: vi.fn(),
|
||||
grantFloatingWorkspaceDirectory: grantFloatingWorkspaceDirectoryMock,
|
||||
resolveFloatingTerminalCwd: vi.fn()
|
||||
}))
|
||||
|
||||
import { registerAppHandlers } from './app'
|
||||
|
||||
describe('registerAppHandlers', () => {
|
||||
|
|
@ -56,6 +72,8 @@ describe('registerAppHandlers', () => {
|
|||
appRelaunchMock.mockReset()
|
||||
execFileMock.mockReset()
|
||||
destroySystemTrayMock.mockReset()
|
||||
showOpenDialogMock.mockReset()
|
||||
grantFloatingWorkspaceDirectoryMock.mockReset()
|
||||
Object.defineProperty(process, 'platform', { value: originalPlatform, configurable: true })
|
||||
})
|
||||
|
||||
|
|
@ -177,4 +195,21 @@ describe('registerAppHandlers', () => {
|
|||
await expect(resultPromise).resolves.toBeNull()
|
||||
expect(killMock).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('picks an existing floating workspace directory without enabling native directory creation', async () => {
|
||||
const store = {}
|
||||
showOpenDialogMock.mockResolvedValue({
|
||||
canceled: false,
|
||||
filePaths: ['/Users/kaylee/notes']
|
||||
})
|
||||
registerAppHandlers(store as never)
|
||||
|
||||
await expect(
|
||||
handlers.get('app:pickFloatingWorkspaceDirectory')?.({ sender: {} })
|
||||
).resolves.toBe('/Users/kaylee/notes')
|
||||
expect(showOpenDialogMock).toHaveBeenCalledWith({
|
||||
properties: ['openDirectory']
|
||||
})
|
||||
expect(grantFloatingWorkspaceDirectoryMock).toHaveBeenCalledWith(store, '/Users/kaylee/notes')
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -57,7 +57,9 @@ async function pickFloatingWorkspaceDirectory(
|
|||
): Promise<string | null> {
|
||||
const parentWindow = BrowserWindow.fromWebContents(event.sender)
|
||||
const options = {
|
||||
properties: ['openDirectory', 'createDirectory']
|
||||
// Why: this picker grants access to an existing workspace directory.
|
||||
// Creation belongs to explicit file/write actions, not typeahead input.
|
||||
properties: ['openDirectory']
|
||||
} satisfies Electron.OpenDialogOptions
|
||||
const result = parentWindow
|
||||
? await dialog.showOpenDialog(parentWindow, options)
|
||||
|
|
|
|||
|
|
@ -64,6 +64,14 @@ describe('repos folder pickers', () => {
|
|||
return handler(null, undefined) as Promise<string[]>
|
||||
}
|
||||
|
||||
const callPickDirectory = (): Promise<string | null> => {
|
||||
const handler = handlers.get('repos:pickDirectory')
|
||||
if (!handler) {
|
||||
throw new Error('repos:pickDirectory handler was never registered')
|
||||
}
|
||||
return handler(null, undefined) as Promise<string | null>
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
handlers.clear()
|
||||
handleMock.mockReset()
|
||||
|
|
@ -101,4 +109,18 @@ describe('repos folder pickers', () => {
|
|||
|
||||
await expect(callPickFolders()).resolves.toEqual([])
|
||||
})
|
||||
|
||||
it('picks an existing directory without enabling native directory creation', async () => {
|
||||
const parentDir = join(sep, 'projects')
|
||||
showOpenDialogMock.mockResolvedValue({
|
||||
canceled: false,
|
||||
filePaths: [parentDir]
|
||||
})
|
||||
|
||||
await expect(callPickDirectory()).resolves.toBe(parentDir)
|
||||
|
||||
expect(showOpenDialogMock).toHaveBeenCalledWith(mockWindow, {
|
||||
properties: ['openDirectory']
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -2070,7 +2070,9 @@ export function registerRepoHandlers(mainWindow: BrowserWindow, store: Store): v
|
|||
// destination directory that may not be a git repo yet.
|
||||
ipcMain.handle('repos:pickDirectory', async () => {
|
||||
const result = await dialog.showOpenDialog(mainWindow, {
|
||||
properties: ['openDirectory', 'createDirectory']
|
||||
// Why: macOS can materialize typed partial paths when directory creation
|
||||
// is enabled; clone/create actions already create the final path on submit.
|
||||
properties: ['openDirectory']
|
||||
})
|
||||
if (result.canceled || result.filePaths.length === 0) {
|
||||
return null
|
||||
|
|
|
|||
|
|
@ -132,6 +132,22 @@ describe('registerShellHandlers', () => {
|
|||
await expect(handler({})).resolves.toBeNull()
|
||||
})
|
||||
|
||||
it('picks an existing directory without enabling native directory creation', async () => {
|
||||
showOpenDialogMock.mockResolvedValue({
|
||||
canceled: false,
|
||||
filePaths: ['/Users/kaylee/projects']
|
||||
})
|
||||
|
||||
const handler = getHandler('shell:pickDirectory')
|
||||
await expect(handler({}, { defaultPath: '/Users/kaylee' })).resolves.toBe(
|
||||
'/Users/kaylee/projects'
|
||||
)
|
||||
expect(showOpenDialogMock).toHaveBeenCalledWith({
|
||||
defaultPath: '/Users/kaylee',
|
||||
properties: ['openDirectory']
|
||||
})
|
||||
})
|
||||
|
||||
describe('shell:openPath', () => {
|
||||
it('ignores relative paths', async () => {
|
||||
const handler = getHandler('shell:openPath')
|
||||
|
|
|
|||
|
|
@ -205,7 +205,9 @@ export function registerShellHandlers(): void {
|
|||
async (_event, args: { defaultPath?: string }): Promise<string | null> => {
|
||||
const result = await dialog.showOpenDialog({
|
||||
defaultPath: args.defaultPath,
|
||||
properties: ['openDirectory', 'createDirectory']
|
||||
// Why: callers only need an existing folder grant; enabling native
|
||||
// creation can leave typed prefix directories behind on macOS.
|
||||
properties: ['openDirectory']
|
||||
})
|
||||
if (result.canceled || result.filePaths.length === 0) {
|
||||
return null
|
||||
|
|
|
|||
Loading…
Reference in New Issue