Files
tolaria/src/components/BreadcrumbBar.test.tsx
2026-04-10 17:59:21 +02:00

273 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { render, screen, fireEvent } from '@testing-library/react'
import { describe, it, expect, vi } from 'vitest'
import { BreadcrumbBar } from './BreadcrumbBar'
import type { VaultEntry } from '../types'
const baseEntry: VaultEntry = {
path: '/vault/note/test.md',
filename: 'test.md',
title: 'Test Note',
isA: 'Note',
aliases: [],
belongsTo: [],
relatedTo: [],
status: null,
archived: false,
modifiedAt: 1700000000,
createdAt: null,
fileSize: 100,
snippet: '',
wordCount: 0,
relationships: {},
icon: null,
color: null,
order: null,
outgoingLinks: [],
template: null,
sort: null,
sidebarLabel: null,
view: null,
visible: null,
properties: {},
organized: false,
favorite: false,
favoriteIndex: null,
listPropertiesDisplay: [],
hasH1: false,
}
const archivedEntry: VaultEntry = {
...baseEntry,
archived: true,
}
const defaultProps = {
wordCount: 100,
showDiffToggle: false,
diffMode: false,
diffLoading: false,
onToggleDiff: vi.fn(),
}
describe('BreadcrumbBar — drag region', () => {
it('has data-tauri-drag-region on the container', () => {
const { container } = render(<BreadcrumbBar entry={baseEntry} {...defaultProps} />)
const bar = container.firstElementChild as HTMLElement
expect(bar.dataset.tauriDragRegion).toBeDefined()
})
})
describe('BreadcrumbBar — delete', () => {
it('shows delete button', () => {
render(<BreadcrumbBar entry={baseEntry} {...defaultProps} onDelete={vi.fn()} />)
expect(screen.getByTitle('Delete (Cmd+Delete)')).toBeInTheDocument()
})
it('calls onDelete when delete button is clicked', () => {
const onDelete = vi.fn()
render(<BreadcrumbBar entry={baseEntry} {...defaultProps} onDelete={onDelete} />)
fireEvent.click(screen.getByTitle('Delete (Cmd+Delete)'))
expect(onDelete).toHaveBeenCalledOnce()
})
})
describe('BreadcrumbBar — archive/unarchive', () => {
it('shows archive button for non-archived note', () => {
render(<BreadcrumbBar entry={baseEntry} {...defaultProps} onArchive={vi.fn()} onUnarchive={vi.fn()} />)
expect(screen.getByTitle('Archive')).toBeInTheDocument()
expect(screen.queryByTitle('Unarchive')).not.toBeInTheDocument()
})
it('shows unarchive button for archived note', () => {
render(<BreadcrumbBar entry={archivedEntry} {...defaultProps} onArchive={vi.fn()} onUnarchive={vi.fn()} />)
expect(screen.getByTitle('Unarchive')).toBeInTheDocument()
expect(screen.queryByTitle('Archive')).not.toBeInTheDocument()
})
it('calls onArchive when archive button is clicked', () => {
const onArchive = vi.fn()
render(<BreadcrumbBar entry={baseEntry} {...defaultProps} onArchive={onArchive} />)
fireEvent.click(screen.getByTitle('Archive'))
expect(onArchive).toHaveBeenCalledOnce()
})
it('calls onUnarchive when unarchive button is clicked', () => {
const onUnarchive = vi.fn()
render(<BreadcrumbBar entry={archivedEntry} {...defaultProps} onUnarchive={onUnarchive} />)
fireEvent.click(screen.getByTitle('Unarchive'))
expect(onUnarchive).toHaveBeenCalledOnce()
})
})
describe('BreadcrumbBar — organized shortcut hint', () => {
it('shows Cmd+E on the organized toggle tooltip', () => {
render(<BreadcrumbBar entry={baseEntry} {...defaultProps} onToggleOrganized={vi.fn()} />)
expect(screen.getByTitle('Mark as organized (remove from Inbox) (Cmd+E)')).toBeInTheDocument()
})
it('hides the organized toggle when the workflow is disabled', () => {
render(<BreadcrumbBar entry={baseEntry} {...defaultProps} />)
expect(screen.queryByTitle('Mark as organized (remove from Inbox) (Cmd+E)')).not.toBeInTheDocument()
})
})
describe('BreadcrumbBar — title in breadcrumb (always rendered, CSS-toggled)', () => {
it('always renders title elements in the DOM', () => {
render(<BreadcrumbBar entry={baseEntry} {...defaultProps} />)
expect(screen.getByText('Note')).toBeInTheDocument()
expect(screen.getByText('')).toBeInTheDocument()
expect(screen.getByText('test')).toBeInTheDocument()
})
it('renders emoji note icons in the breadcrumb title', () => {
const entryWithEmoji = { ...baseEntry, icon: '🚀' }
render(<BreadcrumbBar entry={entryWithEmoji} {...defaultProps} />)
expect(screen.getByTestId('breadcrumb-note-icon')).toHaveTextContent('🚀')
})
it('renders Phosphor note icons in the breadcrumb title', () => {
const entryWithPhosphor = { ...baseEntry, icon: 'cooking-pot' }
render(<BreadcrumbBar entry={entryWithPhosphor} {...defaultProps} />)
expect(screen.getByTestId('breadcrumb-note-icon').tagName.toLowerCase()).toBe('svg')
})
it('falls back to "Note" when isA is null', () => {
const entryNoType = { ...baseEntry, isA: null }
render(<BreadcrumbBar entry={entryNoType} {...defaultProps} />)
expect(screen.getByText('Note')).toBeInTheDocument()
})
it('separator visibility is controlled by data-title-hidden while using the shared border chrome', () => {
const { container } = render(<BreadcrumbBar entry={baseEntry} {...defaultProps} />)
const bar = container.querySelector('.breadcrumb-bar')!
expect(bar).toHaveClass('border-b', 'border-transparent')
expect(bar).not.toHaveAttribute('data-title-hidden')
bar.setAttribute('data-title-hidden', '')
expect(bar).toHaveAttribute('data-title-hidden')
})
it('uses the active separator state when raw mode forces the title into the breadcrumb', () => {
const { container } = render(
<BreadcrumbBar entry={baseEntry} {...defaultProps} rawMode onToggleRaw={vi.fn()} />,
)
expect(container.querySelector('.breadcrumb-bar')).toHaveAttribute('data-title-hidden')
})
it('keeps the breadcrumb title visible when the separate title section is absent', () => {
const { container } = render(
<BreadcrumbBar entry={baseEntry} {...defaultProps} showTitleSection={false} />,
)
expect(container.querySelector('.breadcrumb-bar')).toHaveAttribute('data-title-hidden')
})
})
describe('BreadcrumbBar — filename controls', () => {
it('shows the sync button when the filename diverges from the title slug', () => {
const entry = { ...baseEntry, title: 'Fresh Title', filename: 'untitled-note-123.md' }
render(<BreadcrumbBar entry={entry} {...defaultProps} onRenameFilename={vi.fn()} />)
expect(screen.getByTestId('breadcrumb-sync-button')).toBeInTheDocument()
})
it('hides the sync button when the filename already matches the title slug', () => {
const entry = { ...baseEntry, title: 'Test Note', filename: 'test-note.md' }
render(<BreadcrumbBar entry={entry} {...defaultProps} onRenameFilename={vi.fn()} />)
expect(screen.queryByTestId('breadcrumb-sync-button')).not.toBeInTheDocument()
})
it('clicking the sync button renames the file to the title slug', () => {
const onRenameFilename = vi.fn()
const entry = { ...baseEntry, title: 'Fresh Title', filename: 'untitled-note-123.md' }
render(<BreadcrumbBar entry={entry} {...defaultProps} onRenameFilename={onRenameFilename} />)
fireEvent.click(screen.getByTestId('breadcrumb-sync-button'))
expect(onRenameFilename).toHaveBeenCalledWith(entry.path, 'fresh-title')
})
it('lets keyboard users press Enter on the filename to start editing', () => {
render(<BreadcrumbBar entry={baseEntry} {...defaultProps} onRenameFilename={vi.fn()} />)
fireEvent.keyDown(screen.getByTestId('breadcrumb-filename-trigger'), { key: 'Enter' })
expect(screen.getByTestId('breadcrumb-filename-input')).toHaveValue('test')
})
it('double-clicking the filename enters edit mode and Enter confirms the rename', () => {
const onRenameFilename = vi.fn()
render(<BreadcrumbBar entry={baseEntry} {...defaultProps} onRenameFilename={onRenameFilename} />)
fireEvent.doubleClick(screen.getByTestId('breadcrumb-filename-trigger'))
const input = screen.getByTestId('breadcrumb-filename-input')
fireEvent.change(input, { target: { value: 'renamed-file' } })
fireEvent.keyDown(input, { key: 'Enter' })
expect(onRenameFilename).toHaveBeenCalledWith(baseEntry.path, 'renamed-file')
})
it('pressing Escape while editing cancels the inline rename', () => {
const onRenameFilename = vi.fn()
render(<BreadcrumbBar entry={baseEntry} {...defaultProps} onRenameFilename={onRenameFilename} />)
fireEvent.doubleClick(screen.getByTestId('breadcrumb-filename-trigger'))
const input = screen.getByTestId('breadcrumb-filename-input')
fireEvent.change(input, { target: { value: 'renamed-file' } })
fireEvent.keyDown(input, { key: 'Escape' })
expect(onRenameFilename).not.toHaveBeenCalled()
expect(screen.queryByTestId('breadcrumb-filename-input')).not.toBeInTheDocument()
})
it('blur confirms the inline rename when the value changed', () => {
const onRenameFilename = vi.fn()
render(<BreadcrumbBar entry={baseEntry} {...defaultProps} onRenameFilename={onRenameFilename} />)
fireEvent.doubleClick(screen.getByTestId('breadcrumb-filename-trigger'))
const input = screen.getByTestId('breadcrumb-filename-input')
fireEvent.change(input, { target: { value: 'renamed-on-blur' } })
fireEvent.blur(input)
expect(onRenameFilename).toHaveBeenCalledWith(baseEntry.path, 'renamed-on-blur')
})
})
describe('BreadcrumbBar — action buttons always right-aligned', () => {
it('actions container has ml-auto so buttons are always right-aligned', () => {
const { container } = render(<BreadcrumbBar entry={baseEntry} {...defaultProps} />)
const actions = container.querySelector('.breadcrumb-bar__actions')
expect(actions).toBeInTheDocument()
expect(actions).toHaveClass('ml-auto')
})
})
describe('BreadcrumbBar — raw editor toggle', () => {
it('shows Raw editor button with tooltip "Raw editor" when rawMode is off', () => {
const onToggleRaw = vi.fn()
render(<BreadcrumbBar entry={baseEntry} {...defaultProps} rawMode={false} onToggleRaw={onToggleRaw} />)
expect(screen.getByTitle('Raw editor')).toBeInTheDocument()
})
it('shows "Back to editor" tooltip when rawMode is on', () => {
const onToggleRaw = vi.fn()
render(<BreadcrumbBar entry={baseEntry} {...defaultProps} rawMode={true} onToggleRaw={onToggleRaw} />)
expect(screen.getByTitle('Back to editor')).toBeInTheDocument()
})
it('calls onToggleRaw when raw button is clicked', () => {
const onToggleRaw = vi.fn()
render(<BreadcrumbBar entry={baseEntry} {...defaultProps} rawMode={false} onToggleRaw={onToggleRaw} />)
fireEvent.click(screen.getByTitle('Raw editor'))
expect(onToggleRaw).toHaveBeenCalledOnce()
})
it('hides raw toggle when forceRawMode is true (non-markdown file)', () => {
const onToggleRaw = vi.fn()
render(<BreadcrumbBar entry={baseEntry} {...defaultProps} rawMode={true} onToggleRaw={onToggleRaw} forceRawMode={true} />)
expect(screen.queryByTitle('Raw editor')).not.toBeInTheDocument()
expect(screen.queryByTitle('Back to editor')).not.toBeInTheDocument()
})
it('shows raw toggle when forceRawMode is false (markdown file)', () => {
const onToggleRaw = vi.fn()
render(<BreadcrumbBar entry={baseEntry} {...defaultProps} rawMode={false} onToggleRaw={onToggleRaw} forceRawMode={false} />)
expect(screen.getByTitle('Raw editor')).toBeInTheDocument()
})
})