Files
tolaria/src/components/Sidebar.typeActions.test.tsx
2026-05-01 06:34:25 +02:00

140 lines
4.2 KiB
TypeScript

import { fireEvent, render, screen } from '@testing-library/react'
import type { ComponentProps } from 'react'
import { describe, expect, it, vi } from 'vitest'
import { Sidebar } from './Sidebar'
import type { SidebarSelection, VaultEntry } from '../types'
const defaultSelection: SidebarSelection = { kind: 'filter', filter: 'active' }
function makeEntry(overrides: {
path: string
filename: string
title: string
isA: string
}): VaultEntry {
return {
...overrides,
aliases: [],
belongsTo: [],
relatedTo: [],
status: null,
owner: null,
cadence: null,
archived: false,
modifiedAt: 1700000000,
createdAt: null,
fileSize: 200,
snippet: '',
wordCount: 0,
relationships: {},
icon: null,
color: null,
order: null,
sidebarLabel: null,
outgoingLinks: [],
properties: {},
}
}
const projectNote = makeEntry({
path: '/vault/project/build-app.md',
filename: 'build-app.md',
title: 'Build Laputa App',
isA: 'Project',
})
const projectTypeEntry = makeEntry({
path: '/vault/project.md',
filename: 'project.md',
title: 'Project',
isA: 'Type',
})
const entries = [projectNote]
function renderSidebar(props: Partial<ComponentProps<typeof Sidebar>> = {}) {
return render(
<Sidebar
entries={entries}
selection={defaultSelection}
onSelect={() => {}}
{...props}
/>,
)
}
function getProjectsHeader() {
return screen.getByText('Projects').closest('[class*="group/section"]') as HTMLElement
}
function openProjectsContextMenu() {
renderSidebar()
fireEvent.contextMenu(getProjectsHeader())
}
describe('Sidebar Type row actions', () => {
it('shows Type-specific context menu labels on right-click', () => {
openProjectsContextMenu()
expect(screen.getByText('Rename type…')).toBeInTheDocument()
expect(screen.getByText('Customize icon & color…')).toBeInTheDocument()
expect(screen.getByText('Delete type')).toBeInTheDocument()
})
it('dismisses the type context menu on Escape', () => {
openProjectsContextMenu()
fireEvent.keyDown(document, { key: 'Escape' })
expect(screen.queryByText('Rename type…')).not.toBeInTheDocument()
})
it('calls onDeleteType from the context menu', () => {
const onDeleteType = vi.fn()
renderSidebar({ onDeleteType })
fireEvent.contextMenu(getProjectsHeader())
fireEvent.click(screen.getByText('Delete type'))
expect(onDeleteType).toHaveBeenCalledWith('Project')
})
it('starts inline rename from the context menu', () => {
openProjectsContextMenu()
fireEvent.click(screen.getByText('Rename type…'))
expect(screen.getByRole('textbox', { name: 'Section name' })).toBeInTheDocument()
})
it('starts inline rename when the type label is double-clicked', () => {
renderSidebar()
fireEvent.doubleClick(screen.getByText('Projects'))
expect(screen.getByRole('textbox', { name: 'Section name' })).toBeInTheDocument()
})
it('submits the renamed type label on Enter', () => {
const onRenameSection = vi.fn()
renderSidebar({ onRenameSection })
fireEvent.contextMenu(getProjectsHeader())
fireEvent.click(screen.getByText('Rename type…'))
const input = screen.getByRole('textbox', { name: 'Section name' })
fireEvent.change(input, { target: { value: 'My Projects' } })
fireEvent.keyDown(input, { key: 'Enter' })
expect(onRenameSection).toHaveBeenCalledWith('Project', 'My Projects')
})
it('cancels inline rename on Escape', () => {
const onRenameSection = vi.fn()
renderSidebar({ onRenameSection })
fireEvent.contextMenu(getProjectsHeader())
fireEvent.click(screen.getByText('Rename type…'))
fireEvent.keyDown(screen.getByRole('textbox', { name: 'Section name' }), { key: 'Escape' })
expect(onRenameSection).not.toHaveBeenCalled()
expect(screen.queryByRole('textbox', { name: 'Section name' })).not.toBeInTheDocument()
})
it('opens the Type note when a type row is double-clicked away from the label', () => {
const onSelectNote = vi.fn()
renderSidebar({ entries: [...entries, projectTypeEntry], onSelectNote })
fireEvent.doubleClick(getProjectsHeader())
expect(onSelectNote).toHaveBeenCalledWith(projectTypeEntry)
})
})