Merge remote-tracking branch 'refs/remotes/pr/740'
This commit is contained in:
107
src/components/status-bar/VaultMenu.applyMountedChange.test.ts
Normal file
107
src/components/status-bar/VaultMenu.applyMountedChange.test.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { applyMountedChange } from './vaultMenuMountedChange'
|
||||
import type { VaultOption } from './types'
|
||||
|
||||
function vault(path: string, mounted = true): VaultOption {
|
||||
return { label: path, path, alias: path.replace('/', ''), mounted, available: true }
|
||||
}
|
||||
|
||||
interface ChangeOverrides {
|
||||
defaultPath?: string
|
||||
vaultPath?: string
|
||||
path?: string
|
||||
mounted?: boolean
|
||||
includedVaults?: VaultOption[]
|
||||
}
|
||||
|
||||
function callApply(overrides: ChangeOverrides) {
|
||||
const onSetDefaultWorkspace = vi.fn()
|
||||
const onSwitchVault = vi.fn()
|
||||
const onUpdateWorkspaceIdentity = vi.fn()
|
||||
applyMountedChange({
|
||||
defaultPath: overrides.defaultPath ?? '/a',
|
||||
vaultPath: overrides.vaultPath ?? '/a',
|
||||
includedVaults: overrides.includedVaults ?? [vault('/a'), vault('/b')],
|
||||
mounted: overrides.mounted ?? false,
|
||||
path: overrides.path ?? '/a',
|
||||
callbacks: { onSetDefaultWorkspace, onSwitchVault, onUpdateWorkspaceIdentity },
|
||||
})
|
||||
return { onSetDefaultWorkspace, onSwitchVault, onUpdateWorkspaceIdentity }
|
||||
}
|
||||
|
||||
describe('applyMountedChange', () => {
|
||||
it('reroutes both the default workspace and the active vault when the unmounted vault is both', () => {
|
||||
const { onSetDefaultWorkspace, onSwitchVault, onUpdateWorkspaceIdentity } = callApply({
|
||||
defaultPath: '/a',
|
||||
vaultPath: '/a',
|
||||
path: '/a',
|
||||
})
|
||||
|
||||
expect(onSetDefaultWorkspace).toHaveBeenCalledWith('/b')
|
||||
expect(onSwitchVault).toHaveBeenCalledWith('/b')
|
||||
expect(onUpdateWorkspaceIdentity).toHaveBeenCalledWith('/a', { mounted: false })
|
||||
})
|
||||
|
||||
it('reroutes the active vault when only the active vault is unmounted (default workspace differs)', () => {
|
||||
const { onSetDefaultWorkspace, onSwitchVault, onUpdateWorkspaceIdentity } = callApply({
|
||||
defaultPath: '/b',
|
||||
vaultPath: '/a',
|
||||
path: '/a',
|
||||
})
|
||||
|
||||
expect(onSetDefaultWorkspace).not.toHaveBeenCalled()
|
||||
expect(onSwitchVault).toHaveBeenCalledWith('/b')
|
||||
expect(onUpdateWorkspaceIdentity).toHaveBeenCalledWith('/a', { mounted: false })
|
||||
})
|
||||
|
||||
it('reroutes the default workspace when only the default is unmounted (active vault differs)', () => {
|
||||
const { onSetDefaultWorkspace, onSwitchVault, onUpdateWorkspaceIdentity } = callApply({
|
||||
defaultPath: '/a',
|
||||
vaultPath: '/b',
|
||||
path: '/a',
|
||||
})
|
||||
|
||||
expect(onSetDefaultWorkspace).toHaveBeenCalledWith('/b')
|
||||
expect(onSwitchVault).not.toHaveBeenCalled()
|
||||
expect(onUpdateWorkspaceIdentity).toHaveBeenCalledWith('/a', { mounted: false })
|
||||
})
|
||||
|
||||
it('does not reroute when the unmounted vault is neither default nor active', () => {
|
||||
const { onSetDefaultWorkspace, onSwitchVault, onUpdateWorkspaceIdentity } = callApply({
|
||||
defaultPath: '/a',
|
||||
vaultPath: '/a',
|
||||
path: '/b',
|
||||
includedVaults: [vault('/a'), vault('/b'), vault('/c')],
|
||||
})
|
||||
|
||||
expect(onSetDefaultWorkspace).not.toHaveBeenCalled()
|
||||
expect(onSwitchVault).not.toHaveBeenCalled()
|
||||
expect(onUpdateWorkspaceIdentity).toHaveBeenCalledWith('/b', { mounted: false })
|
||||
})
|
||||
|
||||
it('bails out without changing anything when no alternative mounted vault exists', () => {
|
||||
const { onSetDefaultWorkspace, onSwitchVault, onUpdateWorkspaceIdentity } = callApply({
|
||||
defaultPath: '/a',
|
||||
vaultPath: '/a',
|
||||
path: '/a',
|
||||
includedVaults: [vault('/a')],
|
||||
})
|
||||
|
||||
expect(onSetDefaultWorkspace).not.toHaveBeenCalled()
|
||||
expect(onSwitchVault).not.toHaveBeenCalled()
|
||||
expect(onUpdateWorkspaceIdentity).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('only marks the vault unmounted (no reroute) when remounting', () => {
|
||||
const { onSetDefaultWorkspace, onSwitchVault, onUpdateWorkspaceIdentity } = callApply({
|
||||
defaultPath: '/a',
|
||||
vaultPath: '/a',
|
||||
path: '/b',
|
||||
mounted: true,
|
||||
})
|
||||
|
||||
expect(onSetDefaultWorkspace).not.toHaveBeenCalled()
|
||||
expect(onSwitchVault).not.toHaveBeenCalled()
|
||||
expect(onUpdateWorkspaceIdentity).toHaveBeenCalledWith('/b', { mounted: true })
|
||||
})
|
||||
})
|
||||
@@ -19,6 +19,7 @@ import type { VaultOption } from './types'
|
||||
import { useDismissibleLayer } from './useDismissibleLayer'
|
||||
import { workspaceAliasFromOption, workspaceIdentityFromVault } from '../../utils/workspaces'
|
||||
import { reorderVaultPath, vaultPathList } from '../../utils/vaultOrdering'
|
||||
import { applyMountedChange } from './vaultMenuMountedChange'
|
||||
|
||||
interface VaultMenuProps {
|
||||
vaults: VaultOption[]
|
||||
@@ -90,6 +91,7 @@ interface VaultMenuInteractionOptions {
|
||||
onSwitchVault: (path: string) => void
|
||||
onUpdateWorkspaceIdentity?: (path: string, patch: Partial<VaultOption>) => void
|
||||
setOpen: (open: boolean) => void
|
||||
vaultPath: string
|
||||
}
|
||||
|
||||
interface MountToggleRequest {
|
||||
@@ -104,10 +106,6 @@ interface VaultPathSelection extends VaultMenuInteractionOptions {
|
||||
path: string
|
||||
}
|
||||
|
||||
interface VaultMountChangeRequest extends VaultMenuInteractionOptions {
|
||||
mounted: boolean
|
||||
path: string
|
||||
}
|
||||
|
||||
function getVaultTriggerClassName(open: boolean, compact: boolean) {
|
||||
if (compact) {
|
||||
@@ -293,10 +291,6 @@ function useIncludedVaults(vaults: VaultOption[], defaultPath: string): VaultOpt
|
||||
return useMemo(() => vaults.filter((vault) => isIncludedVault(vault, defaultPath)), [defaultPath, vaults])
|
||||
}
|
||||
|
||||
function nextIncludedVaultPath(includedVaults: VaultOption[], currentPath: string): string | null {
|
||||
return includedVaults.find((vault) => vault.path !== currentPath)?.path ?? null
|
||||
}
|
||||
|
||||
function shouldDisableMountToggle({
|
||||
canSetDefaultWorkspace,
|
||||
defaultPath,
|
||||
@@ -321,22 +315,6 @@ function selectVaultPath({
|
||||
setOpen(false)
|
||||
}
|
||||
|
||||
function applyMountedChange({
|
||||
defaultPath,
|
||||
includedVaults,
|
||||
mounted,
|
||||
onSetDefaultWorkspace,
|
||||
onUpdateWorkspaceIdentity,
|
||||
path,
|
||||
}: VaultMountChangeRequest): void {
|
||||
if (!mounted && path === defaultPath) {
|
||||
const nextDefaultPath = nextIncludedVaultPath(includedVaults, path)
|
||||
if (!nextDefaultPath) return
|
||||
onSetDefaultWorkspace?.(nextDefaultPath)
|
||||
}
|
||||
onUpdateWorkspaceIdentity?.(path, { mounted })
|
||||
}
|
||||
|
||||
function useVaultMenuInteractions({
|
||||
defaultPath,
|
||||
includedVaults,
|
||||
@@ -345,6 +323,7 @@ function useVaultMenuInteractions({
|
||||
onSwitchVault,
|
||||
onUpdateWorkspaceIdentity,
|
||||
setOpen,
|
||||
vaultPath,
|
||||
}: VaultMenuInteractionOptions) {
|
||||
const disableMountToggleForPath = useCallback((path: string) => (
|
||||
shouldDisableMountToggle({
|
||||
@@ -366,22 +345,24 @@ function useVaultMenuInteractions({
|
||||
onUpdateWorkspaceIdentity,
|
||||
path,
|
||||
setOpen,
|
||||
vaultPath,
|
||||
})
|
||||
}, [defaultPath, includedVaults, multiWorkspaceEnabled, onSetDefaultWorkspace, onSwitchVault, onUpdateWorkspaceIdentity, setOpen])
|
||||
}, [defaultPath, includedVaults, multiWorkspaceEnabled, onSetDefaultWorkspace, onSwitchVault, onUpdateWorkspaceIdentity, setOpen, vaultPath])
|
||||
|
||||
const handleMountedChange = useCallback((path: string, mounted: boolean) => {
|
||||
applyMountedChange({
|
||||
defaultPath,
|
||||
vaultPath,
|
||||
includedVaults,
|
||||
mounted,
|
||||
multiWorkspaceEnabled,
|
||||
onSetDefaultWorkspace,
|
||||
onSwitchVault,
|
||||
onUpdateWorkspaceIdentity,
|
||||
path,
|
||||
setOpen,
|
||||
callbacks: {
|
||||
onSetDefaultWorkspace,
|
||||
onSwitchVault,
|
||||
onUpdateWorkspaceIdentity,
|
||||
},
|
||||
})
|
||||
}, [defaultPath, includedVaults, multiWorkspaceEnabled, onSetDefaultWorkspace, onSwitchVault, onUpdateWorkspaceIdentity, setOpen])
|
||||
}, [defaultPath, includedVaults, onSetDefaultWorkspace, onSwitchVault, onUpdateWorkspaceIdentity, vaultPath])
|
||||
|
||||
return { disableMountToggleForPath, handleMountedChange, handleSelectVault }
|
||||
}
|
||||
@@ -747,6 +728,7 @@ export function VaultMenu(props: VaultMenuProps) {
|
||||
onSwitchVault,
|
||||
onUpdateWorkspaceIdentity,
|
||||
setOpen,
|
||||
vaultPath,
|
||||
})
|
||||
|
||||
useDismissibleLayer(open, menuRef, () => setOpen(false))
|
||||
|
||||
37
src/components/status-bar/vaultMenuMountedChange.ts
Normal file
37
src/components/status-bar/vaultMenuMountedChange.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import type { VaultOption } from './types'
|
||||
|
||||
export interface VaultMountChangeCallbacks {
|
||||
onSetDefaultWorkspace?: (path: string) => void
|
||||
onSwitchVault: (path: string) => void
|
||||
onUpdateWorkspaceIdentity?: (path: string, patch: Partial<VaultOption>) => void
|
||||
}
|
||||
|
||||
export interface VaultMountChangeRequest {
|
||||
defaultPath: string
|
||||
vaultPath: string
|
||||
includedVaults: VaultOption[]
|
||||
mounted: boolean
|
||||
path: string
|
||||
callbacks: VaultMountChangeCallbacks
|
||||
}
|
||||
|
||||
function nextIncludedVaultPath(includedVaults: VaultOption[], currentPath: string): string | null {
|
||||
return includedVaults.find((vault) => vault.path !== currentPath)?.path ?? null
|
||||
}
|
||||
|
||||
export function applyMountedChange({
|
||||
defaultPath,
|
||||
vaultPath,
|
||||
includedVaults,
|
||||
mounted,
|
||||
path,
|
||||
callbacks,
|
||||
}: VaultMountChangeRequest): void {
|
||||
if (!mounted && (path === defaultPath || path === vaultPath)) {
|
||||
const nextPath = nextIncludedVaultPath(includedVaults, path)
|
||||
if (!nextPath) return
|
||||
if (path === defaultPath) callbacks.onSetDefaultWorkspace?.(nextPath)
|
||||
if (path === vaultPath) callbacks.onSwitchVault(nextPath)
|
||||
}
|
||||
callbacks.onUpdateWorkspaceIdentity?.(path, { mounted })
|
||||
}
|
||||
Reference in New Issue
Block a user