597 lines
20 KiB
TypeScript
597 lines
20 KiB
TypeScript
import { useState, type ComponentProps } from 'react'
|
|
import { act, fireEvent, render, screen, within } from '@testing-library/react'
|
|
import { describe, it, expect, vi } from 'vitest'
|
|
import { FolderTree } from './FolderTree'
|
|
import { FOLDER_ROW_SINGLE_CLICK_DELAY_MS } from './folder-tree/useFolderRowInteractions'
|
|
import { FOLDER_ROW_NESTING_INDENT, getFolderConnectorLeft } from './folder-tree/folderTreeLayout'
|
|
import { CREATE_NOTE_IN_FOLDER_EVENT } from '../hooks/noteCreationRequests'
|
|
import type { FolderNode, SidebarSelection } from '../types'
|
|
|
|
const mockFolders: FolderNode[] = [
|
|
{
|
|
name: 'projects',
|
|
path: 'projects',
|
|
children: [
|
|
{ name: 'laputa', path: 'projects/laputa', children: [] },
|
|
{ name: 'portfolio', path: 'projects/portfolio', children: [] },
|
|
],
|
|
},
|
|
{ name: 'areas', path: 'areas', children: [] },
|
|
{ name: 'journal', path: 'journal', children: [] },
|
|
]
|
|
|
|
const defaultSelection: SidebarSelection = { kind: 'filter', filter: 'all' }
|
|
const vaultRootPath = '/Users/luca/Laputa'
|
|
|
|
function renderTree(props: Partial<ComponentProps<typeof FolderTree>> = {}) {
|
|
const onSelect = props.onSelect ?? vi.fn()
|
|
render(
|
|
<FolderTree
|
|
folders={mockFolders}
|
|
selection={defaultSelection}
|
|
onSelect={onSelect}
|
|
{...props}
|
|
/>,
|
|
)
|
|
return { onSelect }
|
|
}
|
|
|
|
function clickFolderRow(path: string) {
|
|
fireEvent.click(screen.getByTestId(`folder-row:${path}`))
|
|
}
|
|
|
|
async function submitNewFolder(name: string) {
|
|
fireEvent.click(screen.getByTestId('create-folder-btn'))
|
|
const input = screen.getByTestId('new-folder-input')
|
|
fireEvent.change(input, { target: { value: name } })
|
|
fireEvent.keyDown(input, { key: 'Enter' })
|
|
}
|
|
|
|
describe('FolderTree', () => {
|
|
it('renders nothing when folders is empty', () => {
|
|
const { container } = render(
|
|
<FolderTree folders={[]} selection={defaultSelection} onSelect={vi.fn()} />,
|
|
)
|
|
expect(container.firstChild).toBeNull()
|
|
})
|
|
|
|
it('renders FOLDERS header and top-level folders', () => {
|
|
render(<FolderTree folders={mockFolders} selection={defaultSelection} onSelect={vi.fn()} />)
|
|
expect(screen.getByText('FOLDERS')).toBeInTheDocument()
|
|
expect(screen.getByText('projects')).toBeInTheDocument()
|
|
expect(screen.getByText('areas')).toBeInTheDocument()
|
|
expect(screen.getByText('journal')).toBeInTheDocument()
|
|
})
|
|
|
|
it('renders the vault root as the top-level folder when a vault path is available', () => {
|
|
render(
|
|
<FolderTree
|
|
folders={mockFolders}
|
|
selection={defaultSelection}
|
|
onSelect={vi.fn()}
|
|
vaultRootPath={vaultRootPath}
|
|
/>,
|
|
)
|
|
|
|
expect(screen.getByText('Laputa')).toBeInTheDocument()
|
|
expect(screen.getByRole('button', { name: 'Laputa' })).toBeInTheDocument()
|
|
expect(screen.getByText('projects')).toBeInTheDocument()
|
|
expect(screen.getByText('areas')).toBeInTheDocument()
|
|
expect(screen.getByText('journal')).toBeInTheDocument()
|
|
})
|
|
|
|
it('renders the vault root even when the vault has no subfolders', () => {
|
|
render(
|
|
<FolderTree
|
|
folders={[]}
|
|
selection={defaultSelection}
|
|
onSelect={vi.fn()}
|
|
vaultRootPath={vaultRootPath}
|
|
/>,
|
|
)
|
|
|
|
expect(screen.getByText('Laputa')).toBeInTheDocument()
|
|
})
|
|
|
|
it('renders one scoped root per mounted workspace and selects folders inside that root', () => {
|
|
const onSelect = vi.fn()
|
|
const folders: FolderNode[] = [
|
|
{
|
|
name: 'Personal',
|
|
path: '',
|
|
rootPath: '/Users/luca/Personal',
|
|
children: [{ name: 'projects', path: 'projects', rootPath: '/Users/luca/Personal', children: [] }],
|
|
},
|
|
{
|
|
name: 'Team',
|
|
path: '',
|
|
rootPath: '/Users/luca/Team',
|
|
children: [{ name: 'projects', path: 'projects', rootPath: '/Users/luca/Team', children: [] }],
|
|
},
|
|
]
|
|
|
|
render(
|
|
<FolderTree
|
|
folders={folders}
|
|
selection={defaultSelection}
|
|
onSelect={onSelect}
|
|
vaultRootPath="/Users/luca/Personal"
|
|
/>,
|
|
)
|
|
|
|
expect(screen.getByText('Personal')).toBeInTheDocument()
|
|
expect(screen.getByText('Team')).toBeInTheDocument()
|
|
expect(screen.getAllByTestId('folder-row:projects')).toHaveLength(2)
|
|
|
|
fireEvent.click(screen.getAllByTestId('folder-row:projects')[1])
|
|
|
|
expect(onSelect).toHaveBeenCalledWith({
|
|
kind: 'folder',
|
|
path: 'projects',
|
|
rootPath: '/Users/luca/Team',
|
|
})
|
|
})
|
|
|
|
it('lets the vault root collapse and expand from the row', () => {
|
|
vi.useFakeTimers()
|
|
render(
|
|
<FolderTree
|
|
folders={mockFolders}
|
|
selection={defaultSelection}
|
|
onSelect={vi.fn()}
|
|
vaultRootPath={vaultRootPath}
|
|
/>,
|
|
)
|
|
|
|
fireEvent.click(screen.getByTestId('folder-row:'))
|
|
act(() => {
|
|
vi.advanceTimersByTime(FOLDER_ROW_SINGLE_CLICK_DELAY_MS)
|
|
})
|
|
expect(screen.queryByText('projects')).not.toBeInTheDocument()
|
|
|
|
fireEvent.click(screen.getByTestId('folder-row:'))
|
|
act(() => {
|
|
vi.advanceTimersByTime(FOLDER_ROW_SINGLE_CLICK_DELAY_MS)
|
|
})
|
|
expect(screen.getByText('projects')).toBeInTheDocument()
|
|
vi.useRealTimers()
|
|
})
|
|
|
|
it('expands children when clicking a folder row', () => {
|
|
vi.useFakeTimers()
|
|
render(<FolderTree folders={mockFolders} selection={defaultSelection} onSelect={vi.fn()} />)
|
|
expect(screen.queryByText('laputa')).not.toBeInTheDocument()
|
|
fireEvent.click(screen.getByTestId('folder-row:projects'))
|
|
act(() => {
|
|
vi.advanceTimersByTime(FOLDER_ROW_SINGLE_CLICK_DELAY_MS)
|
|
})
|
|
expect(screen.getByText('laputa')).toBeInTheDocument()
|
|
expect(screen.getByText('portfolio')).toBeInTheDocument()
|
|
vi.useRealTimers()
|
|
})
|
|
|
|
it('calls onSelect with folder kind when clicking a folder row', () => {
|
|
const onSelect = vi.fn()
|
|
render(<FolderTree folders={mockFolders} selection={defaultSelection} onSelect={onSelect} />)
|
|
fireEvent.click(screen.getByTestId('folder-row:projects'))
|
|
expect(onSelect).toHaveBeenCalledWith({ kind: 'folder', path: 'projects' })
|
|
})
|
|
|
|
it('selects the vault root with the root path attached', () => {
|
|
const { onSelect } = renderTree({ vaultRootPath })
|
|
|
|
clickFolderRow('')
|
|
expect(onSelect).toHaveBeenCalledWith({ kind: 'folder', path: '', rootPath: vaultRootPath })
|
|
})
|
|
|
|
it('selects child folders with the vault root path attached when the tree has a root', () => {
|
|
const { onSelect } = renderTree({ vaultRootPath })
|
|
|
|
clickFolderRow('areas')
|
|
|
|
expect(onSelect).toHaveBeenCalledWith({ kind: 'folder', path: 'areas', rootPath: vaultRootPath })
|
|
})
|
|
|
|
it('expands children when single-clicking a folder row with children', () => {
|
|
vi.useFakeTimers()
|
|
function FolderTreeHarness() {
|
|
const [selection, setSelection] = useState<SidebarSelection>(defaultSelection)
|
|
return <FolderTree folders={mockFolders} selection={selection} onSelect={setSelection} />
|
|
}
|
|
|
|
render(<FolderTreeHarness />)
|
|
|
|
fireEvent.click(screen.getByTestId('folder-row:projects'))
|
|
act(() => {
|
|
vi.advanceTimersByTime(FOLDER_ROW_SINGLE_CLICK_DELAY_MS)
|
|
})
|
|
|
|
expect(screen.getByText('laputa')).toBeInTheDocument()
|
|
expect(screen.getByText('portfolio')).toBeInTheDocument()
|
|
|
|
fireEvent.click(screen.getByTestId('folder-row:projects'))
|
|
act(() => {
|
|
vi.advanceTimersByTime(FOLDER_ROW_SINGLE_CLICK_DELAY_MS)
|
|
})
|
|
|
|
expect(screen.queryByText('laputa')).not.toBeInTheDocument()
|
|
vi.useRealTimers()
|
|
})
|
|
|
|
it('collapses section when clicking the FOLDERS header', () => {
|
|
render(<FolderTree folders={mockFolders} selection={defaultSelection} onSelect={vi.fn()} />)
|
|
expect(screen.getByText('projects')).toBeInTheDocument()
|
|
fireEvent.click(screen.getByText('FOLDERS'))
|
|
expect(screen.queryByText('projects')).not.toBeInTheDocument()
|
|
})
|
|
|
|
it('highlights the selected folder row', () => {
|
|
const selection: SidebarSelection = { kind: 'folder', path: 'areas' }
|
|
render(<FolderTree folders={mockFolders} selection={selection} onSelect={vi.fn()} />)
|
|
expect(screen.getByTestId('folder-row:areas').className).toContain('text-primary')
|
|
})
|
|
|
|
it('opens the create-folder input from the header action', () => {
|
|
render(
|
|
<FolderTree
|
|
folders={mockFolders}
|
|
selection={defaultSelection}
|
|
onSelect={vi.fn()}
|
|
onCreateFolder={vi.fn().mockResolvedValue(true)}
|
|
/>,
|
|
)
|
|
fireEvent.click(screen.getByTestId('create-folder-btn'))
|
|
expect(screen.getByTestId('new-folder-input')).toBeInTheDocument()
|
|
})
|
|
|
|
it('passes the selected folder as the parent when creating a new folder', async () => {
|
|
const onCreateFolder = vi.fn().mockResolvedValue(true)
|
|
renderTree({
|
|
selection: { kind: 'folder', path: 'projects', rootPath: vaultRootPath },
|
|
onCreateFolder,
|
|
vaultRootPath,
|
|
})
|
|
|
|
await submitNewFolder('laputa')
|
|
|
|
await vi.waitFor(() => {
|
|
expect(onCreateFolder).toHaveBeenCalledWith('laputa', {
|
|
path: 'projects',
|
|
rootPath: vaultRootPath,
|
|
})
|
|
})
|
|
})
|
|
|
|
it('passes the target vault root when the vault root is selected', async () => {
|
|
const onCreateFolder = vi.fn().mockResolvedValue(true)
|
|
const otherVault = '/Users/luca/Team'
|
|
const folders: FolderNode[] = [
|
|
{
|
|
name: 'Personal',
|
|
path: '',
|
|
rootPath: vaultRootPath,
|
|
children: [{ name: 'areas', path: 'areas', rootPath: vaultRootPath, children: [] }],
|
|
},
|
|
{
|
|
name: 'Team',
|
|
path: '',
|
|
rootPath: otherVault,
|
|
children: [{ name: 'areas', path: 'areas', rootPath: otherVault, children: [] }],
|
|
},
|
|
]
|
|
|
|
renderTree({
|
|
folders,
|
|
selection: { kind: 'folder', path: '', rootPath: otherVault },
|
|
onCreateFolder,
|
|
vaultRootPath,
|
|
})
|
|
|
|
await submitNewFolder('inbox')
|
|
|
|
await vi.waitFor(() => {
|
|
expect(onCreateFolder).toHaveBeenCalledWith('inbox', {
|
|
path: '',
|
|
rootPath: otherVault,
|
|
})
|
|
})
|
|
})
|
|
|
|
it('omits parent context when nothing folder-like is selected', async () => {
|
|
const onCreateFolder = vi.fn().mockResolvedValue(true)
|
|
renderTree({ onCreateFolder, vaultRootPath })
|
|
|
|
await submitNewFolder('inbox')
|
|
|
|
await vi.waitFor(() => {
|
|
expect(onCreateFolder).toHaveBeenCalledWith('inbox', undefined)
|
|
})
|
|
})
|
|
|
|
it('expands the selected parent after a successful create so the new child is visible', async () => {
|
|
const onCreateFolder = vi.fn().mockResolvedValue(true)
|
|
render(
|
|
<FolderTree
|
|
folders={mockFolders}
|
|
selection={{ kind: 'folder', path: 'projects' }}
|
|
onSelect={vi.fn()}
|
|
onCreateFolder={onCreateFolder}
|
|
/>,
|
|
)
|
|
|
|
// Sanity: selecting a folder auto-expands its ancestors but not the folder
|
|
// itself, so 'projects' starts collapsed.
|
|
expect(screen.getByRole('button', { name: 'projects' })).toHaveAttribute('aria-expanded', 'false')
|
|
|
|
fireEvent.click(screen.getByTestId('create-folder-btn'))
|
|
const input = screen.getByTestId('new-folder-input')
|
|
fireEvent.change(input, { target: { value: 'laputa' } })
|
|
fireEvent.keyDown(input, { key: 'Enter' })
|
|
|
|
await vi.waitFor(() => {
|
|
expect(onCreateFolder).toHaveBeenCalled()
|
|
})
|
|
await vi.waitFor(() => {
|
|
expect(screen.getByRole('button', { name: 'projects' })).toHaveAttribute('aria-expanded', 'true')
|
|
})
|
|
})
|
|
|
|
it('leaves expansion alone when create resolves falsy (validation rejected, etc.)', async () => {
|
|
const onCreateFolder = vi.fn().mockResolvedValue(false)
|
|
render(
|
|
<FolderTree
|
|
folders={mockFolders}
|
|
selection={{ kind: 'folder', path: 'projects' }}
|
|
onSelect={vi.fn()}
|
|
onCreateFolder={onCreateFolder}
|
|
/>,
|
|
)
|
|
|
|
fireEvent.click(screen.getByTestId('create-folder-btn'))
|
|
const input = screen.getByTestId('new-folder-input')
|
|
fireEvent.change(input, { target: { value: 'laputa' } })
|
|
fireEvent.keyDown(input, { key: 'Enter' })
|
|
|
|
await vi.waitFor(() => {
|
|
expect(onCreateFolder).toHaveBeenCalled()
|
|
})
|
|
expect(screen.getByRole('button', { name: 'projects' })).toHaveAttribute('aria-expanded', 'false')
|
|
})
|
|
|
|
it('starts rename on folder double-click', () => {
|
|
const onStartRenameFolder = vi.fn()
|
|
render(
|
|
<FolderTree
|
|
folders={mockFolders}
|
|
selection={defaultSelection}
|
|
onSelect={vi.fn()}
|
|
onRenameFolder={vi.fn().mockResolvedValue(true)}
|
|
onStartRenameFolder={onStartRenameFolder}
|
|
onCancelRenameFolder={vi.fn()}
|
|
/>,
|
|
)
|
|
fireEvent.doubleClick(screen.getByTestId('folder-row:projects'))
|
|
expect(onStartRenameFolder).toHaveBeenCalledWith('projects')
|
|
})
|
|
|
|
it('keeps rename and delete out of row hover actions', () => {
|
|
render(
|
|
<FolderTree
|
|
folders={mockFolders}
|
|
selection={defaultSelection}
|
|
onSelect={vi.fn()}
|
|
onDeleteFolder={vi.fn()}
|
|
onRenameFolder={vi.fn().mockResolvedValue(true)}
|
|
onStartRenameFolder={vi.fn()}
|
|
onCancelRenameFolder={vi.fn()}
|
|
/>,
|
|
)
|
|
|
|
expect(screen.queryByTestId('rename-folder-btn:projects')).not.toBeInTheDocument()
|
|
expect(screen.queryByTestId('delete-folder-btn:projects')).not.toBeInTheDocument()
|
|
})
|
|
|
|
it('does not render folder-level disclosure buttons', () => {
|
|
render(<FolderTree folders={mockFolders} selection={defaultSelection} onSelect={vi.fn()} />)
|
|
|
|
const leafRowContainer = screen.getByTestId('folder-row:areas').parentElement
|
|
const parentRowContainer = screen.getByTestId('folder-row:projects').parentElement
|
|
|
|
expect(leafRowContainer).not.toBeNull()
|
|
expect(parentRowContainer).not.toBeNull()
|
|
expect(within(leafRowContainer as HTMLElement).queryAllByRole('button')).toHaveLength(1)
|
|
expect(within(parentRowContainer as HTMLElement).queryAllByRole('button')).toHaveLength(1)
|
|
expect(screen.queryByLabelText('Expand projects')).not.toBeInTheDocument()
|
|
})
|
|
|
|
it('aligns nested folders with the parent folder name and centers connectors on parent icons', () => {
|
|
render(
|
|
<FolderTree
|
|
folders={mockFolders}
|
|
selection={defaultSelection}
|
|
onSelect={vi.fn()}
|
|
vaultRootPath={vaultRootPath}
|
|
/>,
|
|
)
|
|
|
|
expect(screen.getByTestId('folder-row:').parentElement).toHaveStyle({ paddingLeft: '0px' })
|
|
expect(screen.getByTestId('folder-row:projects').parentElement).toHaveStyle({ paddingLeft: `${FOLDER_ROW_NESTING_INDENT}px` })
|
|
expect(screen.getByTestId('folder-connector:')).toHaveStyle({ left: `${getFolderConnectorLeft(0)}px` })
|
|
})
|
|
|
|
it('shows the rename input when a folder is being renamed', () => {
|
|
render(
|
|
<FolderTree
|
|
folders={mockFolders}
|
|
selection={{ kind: 'folder', path: 'areas' }}
|
|
onSelect={vi.fn()}
|
|
onRenameFolder={vi.fn().mockResolvedValue(true)}
|
|
renamingFolderPath="areas"
|
|
onCancelRenameFolder={vi.fn()}
|
|
/>,
|
|
)
|
|
expect(screen.getByTestId('rename-folder-input')).toBeInTheDocument()
|
|
})
|
|
|
|
it('keeps folder toggling healthy after cancelling rename', () => {
|
|
vi.useFakeTimers()
|
|
const onCancelRenameFolder = vi.fn()
|
|
const { rerender } = render(
|
|
<FolderTree
|
|
folders={mockFolders}
|
|
selection={{ kind: 'folder', path: 'projects' }}
|
|
onSelect={vi.fn()}
|
|
onRenameFolder={vi.fn().mockResolvedValue(true)}
|
|
renamingFolderPath="projects"
|
|
onCancelRenameFolder={onCancelRenameFolder}
|
|
/>,
|
|
)
|
|
|
|
fireEvent.keyDown(screen.getByTestId('rename-folder-input'), { key: 'Escape' })
|
|
expect(onCancelRenameFolder).toHaveBeenCalledTimes(1)
|
|
|
|
rerender(
|
|
<FolderTree
|
|
folders={mockFolders}
|
|
selection={{ kind: 'folder', path: 'projects' }}
|
|
onSelect={vi.fn()}
|
|
onRenameFolder={vi.fn().mockResolvedValue(true)}
|
|
onCancelRenameFolder={onCancelRenameFolder}
|
|
/>,
|
|
)
|
|
|
|
const wasExpanded = screen.queryByText('laputa') !== null
|
|
fireEvent.click(screen.getByTestId('folder-row:projects'))
|
|
act(() => {
|
|
vi.advanceTimersByTime(FOLDER_ROW_SINGLE_CLICK_DELAY_MS)
|
|
})
|
|
|
|
expect(screen.queryByText('laputa') !== null).toBe(!wasExpanded)
|
|
vi.useRealTimers()
|
|
})
|
|
|
|
it('opens a context menu with a delete action on right-click', () => {
|
|
const onDeleteFolder = vi.fn()
|
|
render(
|
|
<FolderTree
|
|
folders={mockFolders}
|
|
selection={defaultSelection}
|
|
onSelect={vi.fn()}
|
|
onDeleteFolder={onDeleteFolder}
|
|
onStartRenameFolder={vi.fn()}
|
|
/>,
|
|
)
|
|
fireEvent.contextMenu(screen.getByText('projects'))
|
|
expect(screen.getByTestId('folder-context-menu')).toBeInTheDocument()
|
|
fireEvent.click(screen.getByTestId('delete-folder-menu-item'))
|
|
expect(onDeleteFolder).toHaveBeenCalledWith('projects')
|
|
})
|
|
|
|
it('dismisses the folder context menu on Escape', () => {
|
|
render(
|
|
<FolderTree
|
|
folders={mockFolders}
|
|
selection={defaultSelection}
|
|
onSelect={vi.fn()}
|
|
onDeleteFolder={vi.fn()}
|
|
onStartRenameFolder={vi.fn()}
|
|
/>,
|
|
)
|
|
fireEvent.contextMenu(screen.getByText('projects'))
|
|
fireEvent.keyDown(document, { key: 'Escape' })
|
|
expect(screen.queryByTestId('folder-context-menu')).not.toBeInTheDocument()
|
|
})
|
|
|
|
it('opens folder file actions from the context menu', () => {
|
|
const onRevealFolder = vi.fn()
|
|
const onCopyFolderPath = vi.fn()
|
|
render(
|
|
<FolderTree
|
|
folders={mockFolders}
|
|
selection={defaultSelection}
|
|
onSelect={vi.fn()}
|
|
folderFileActions={{
|
|
copyFolderPath: onCopyFolderPath,
|
|
revealFolder: onRevealFolder,
|
|
}}
|
|
onStartRenameFolder={vi.fn()}
|
|
/>,
|
|
)
|
|
|
|
fireEvent.contextMenu(screen.getByText('projects'))
|
|
fireEvent.click(screen.getByTestId('reveal-folder-menu-item'))
|
|
expect(onRevealFolder).toHaveBeenCalledWith('projects')
|
|
|
|
fireEvent.contextMenu(screen.getByText('projects'))
|
|
fireEvent.click(screen.getByTestId('copy-folder-path-menu-item'))
|
|
expect(onCopyFolderPath).toHaveBeenCalledWith('projects')
|
|
})
|
|
|
|
it('creates a note in the right-clicked mounted folder', () => {
|
|
const onCreateNoteInFolder = vi.fn()
|
|
const folders: FolderNode[] = [
|
|
{
|
|
name: 'Personal',
|
|
path: '',
|
|
rootPath: '/Users/luca/Personal',
|
|
children: [{ name: 'projects', path: 'projects', rootPath: '/Users/luca/Personal', children: [] }],
|
|
},
|
|
{
|
|
name: 'Team',
|
|
path: '',
|
|
rootPath: '/Users/luca/Team',
|
|
children: [{ name: 'projects', path: 'projects', rootPath: '/Users/luca/Team', children: [] }],
|
|
},
|
|
]
|
|
|
|
render(
|
|
<FolderTree
|
|
folders={folders}
|
|
selection={defaultSelection}
|
|
onSelect={vi.fn()}
|
|
vaultRootPath="/Users/luca/Team"
|
|
/>,
|
|
)
|
|
|
|
window.addEventListener(CREATE_NOTE_IN_FOLDER_EVENT, onCreateNoteInFolder)
|
|
fireEvent.contextMenu(screen.getAllByTestId('folder-row:projects')[1])
|
|
fireEvent.click(screen.getByTestId('create-node-in-folder-menu-item'))
|
|
|
|
expect(onCreateNoteInFolder).toHaveBeenCalledOnce()
|
|
expect((onCreateNoteInFolder.mock.calls[0][0] as CustomEvent).detail).toEqual({
|
|
folderPath: 'projects',
|
|
rootPath: '/Users/luca/Team',
|
|
})
|
|
window.removeEventListener(CREATE_NOTE_IN_FOLDER_EVENT, onCreateNoteInFolder)
|
|
})
|
|
|
|
it('keeps destructive folder actions off the vault root row and menu', () => {
|
|
render(
|
|
<FolderTree
|
|
folders={mockFolders}
|
|
selection={defaultSelection}
|
|
onSelect={vi.fn()}
|
|
folderFileActions={{
|
|
copyFolderPath: vi.fn(),
|
|
revealFolder: vi.fn(),
|
|
}}
|
|
onDeleteFolder={vi.fn()}
|
|
onRenameFolder={vi.fn().mockResolvedValue(true)}
|
|
onStartRenameFolder={vi.fn()}
|
|
onCancelRenameFolder={vi.fn()}
|
|
vaultRootPath={vaultRootPath}
|
|
/>,
|
|
)
|
|
|
|
expect(screen.queryByTestId('rename-folder-btn:')).not.toBeInTheDocument()
|
|
expect(screen.queryByTestId('delete-folder-btn:')).not.toBeInTheDocument()
|
|
|
|
fireEvent.contextMenu(screen.getByText('Laputa'))
|
|
|
|
expect(screen.getByTestId('reveal-folder-menu-item')).toBeInTheDocument()
|
|
expect(screen.getByTestId('copy-folder-path-menu-item')).toBeInTheDocument()
|
|
expect(screen.queryByText('Rename folder...')).not.toBeInTheDocument()
|
|
expect(screen.queryByTestId('delete-folder-menu-item')).not.toBeInTheDocument()
|
|
})
|
|
})
|