Files
orca/src/renderer/src/components/editor/editor-panel-render-model.test.ts
T

316 lines
11 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import type { OpenFile } from '@/store/slices/editor'
import { RICH_MARKDOWN_MAX_SIZE_BYTES } from '../../../../shared/constants'
import type { GitStatusEntry } from '../../../../shared/git-status-types'
import type { FileContent } from './editor-panel-content-types'
import { getEditorPanelRenderModel } from './editor-panel-render-model'
function markdownFile(overrides: Partial<OpenFile> = {}): OpenFile {
return {
id: '/repo/README.md',
filePath: '/repo/README.md',
relativePath: 'README.md',
worktreeId: 'wt-1',
language: 'markdown',
mode: 'edit',
isDirty: false,
...overrides
}
}
function textContent(overrides: Partial<FileContent> = {}): FileContent {
return {
content: '# Hello',
isBinary: false,
...overrides
}
}
function renderModel(args: {
activeFile?: OpenFile
fileContents?: Record<string, FileContent>
editorDrafts?: Record<string, string>
markdownViewMode?: Record<string, 'source' | 'rich' | 'preview'>
markdownRichModeSizeOverridden?: boolean
markdownRichModeFaultedContent?: Record<string, string>
isChangesMode?: boolean
gitStatusByWorktree?: Record<string, GitStatusEntry[]>
}) {
const activeFile = args.activeFile ?? markdownFile()
return getEditorPanelRenderModel({
activeFile,
fileContents: args.fileContents ?? { '/repo/README.md': textContent() },
editorDrafts: args.editorDrafts ?? {},
gitStatusEntries: args.gitStatusByWorktree?.[activeFile.worktreeId],
gitBranchEntries: undefined,
markdownViewMode: args.markdownViewMode ?? {},
markdownRichModeSizeOverridden: args.markdownRichModeSizeOverridden ?? false,
markdownRichModeFaultedContent: args.markdownRichModeFaultedContent ?? {},
isChangesMode: args.isChangesMode ?? false,
canOpenWorkspaceFileBrowser: true
})
}
function htmlFile(overrides: Partial<OpenFile> = {}): OpenFile {
return {
id: '/repo/mock.html',
filePath: '/repo/mock.html',
relativePath: 'mock.html',
worktreeId: 'wt-1',
language: 'html',
mode: 'edit',
isDirty: false,
...overrides
}
}
describe('getEditorPanelRenderModel rich-mode fallback toggle', () => {
it('omits Preview in Source view until a fault is recorded for this content, even though it would fault', () => {
const model = renderModel({
markdownViewMode: { '/repo/README.md': 'source' },
editorDrafts: { '/repo/README.md': '[reference]: https://example.com' }
})
expect(model.availableEditorToggleModes).toEqual(['source', 'rich', 'changes'])
})
it('offers Preview in Source view once a stored fault matches the current content', () => {
const model = renderModel({
markdownViewMode: { '/repo/README.md': 'source' },
fileContents: {
'/repo/README.md': textContent({ content: '[reference]: https://example.com' })
},
markdownRichModeFaultedContent: {
'/repo/README.md': '[reference]: https://example.com'
}
})
expect(model.availableEditorToggleModes).toEqual(['source', 'rich', 'preview', 'changes'])
})
it('omits Preview when the stored fault is for different, stale content', () => {
const model = renderModel({
markdownViewMode: { '/repo/README.md': 'source' },
editorDrafts: { '/repo/README.md': '# Edited since the fault' },
markdownRichModeFaultedContent: {
'/repo/README.md': '[reference]: https://example.com'
}
})
expect(model.availableEditorToggleModes).toEqual(['source', 'rich', 'changes'])
})
it('offers Preview when Rich view classifies live and this content falls back', () => {
const model = renderModel({
fileContents: {
'/repo/README.md': textContent({ content: '[reference]: https://example.com' })
}
})
expect(model.availableEditorToggleModes).toEqual(['source', 'rich', 'preview', 'changes'])
})
it('omits Preview for ordinary markdown content', () => {
const model = renderModel({})
expect(model.availableEditorToggleModes).toEqual(['source', 'rich', 'changes'])
})
})
describe('getEditorPanelRenderModel HTML preview affordance', () => {
it('enables preview for HTML edit tabs', () => {
expect(renderModel({ activeFile: htmlFile(), fileContents: {} }).canOpenPreviewToSide).toBe(
true
)
})
it('disables preview when the workspace browser provider is unavailable', () => {
const activeFile = htmlFile()
const model = getEditorPanelRenderModel({
activeFile,
fileContents: {},
editorDrafts: {},
gitStatusEntries: undefined,
gitBranchEntries: undefined,
markdownViewMode: {},
markdownRichModeSizeOverridden: false,
markdownRichModeFaultedContent: {},
isChangesMode: false,
canOpenWorkspaceFileBrowser: false
})
expect(model.canOpenPreviewToSide).toBe(false)
})
it('enables preview for single HTML diffs whose file exists on disk', () => {
const model = renderModel({
activeFile: htmlFile({ mode: 'diff', diffSource: 'unstaged' } as Partial<OpenFile>),
fileContents: {},
gitStatusByWorktree: {
'wt-1': [{ path: 'mock.html', status: 'modified', area: 'unstaged' }]
}
})
expect(model.canOpenPreviewToSide).toBe(true)
})
it('disables preview for diffs of deleted HTML files', () => {
const model = renderModel({
activeFile: htmlFile({ mode: 'diff', diffSource: 'unstaged' } as Partial<OpenFile>),
fileContents: {},
gitStatusByWorktree: {
'wt-1': [{ path: 'mock.html', status: 'deleted', area: 'unstaged' }]
}
})
expect(model.canOpenPreviewToSide).toBe(false)
})
it('disables preview for commit diffs whose content may not match disk', () => {
const model = renderModel({
activeFile: htmlFile({ mode: 'diff', diffSource: 'commit' } as Partial<OpenFile>),
fileContents: {}
})
expect(model.canOpenPreviewToSide).toBe(false)
})
it('disables preview for non-HTML diffs', () => {
const model = renderModel({
activeFile: markdownFile({ mode: 'diff', diffSource: 'unstaged' } as Partial<OpenFile>),
fileContents: {}
})
expect(model.canOpenPreviewToSide).toBe(false)
})
})
describe('getEditorPanelRenderModel read-only raw rendering (AI Vault View Log)', () => {
it('renders a read-only markdown log as raw source with no markdown viewer or chrome', () => {
const model = renderModel({ activeFile: markdownFile({ readOnly: true }) })
// Raw text renderer, not the rich/preview markdown surface.
expect(model.isMarkdown).toBe(false)
expect(model.hasViewModeToggle).toBe(false)
expect(model.canShowMarkdownPreview).toBe(false)
expect(model.canExportMarkdownToPdf).toBe(false)
expect(model.canShowMarkdownTableOfContents).toBe(false)
// Real language is preserved so Monaco still tokenizes the read-only source.
expect(model.resolvedLanguage).toBe('markdown')
})
it('keeps markdown chrome for ordinary writable markdown tabs', () => {
const model = renderModel({ activeFile: markdownFile() })
expect(model.isMarkdown).toBe(true)
expect(model.hasViewModeToggle).toBe(true)
})
})
describe('getEditorPanelRenderModel markdown export affordance', () => {
it('enables export for rendered markdown edit tabs', () => {
expect(renderModel({}).canExportMarkdownToPdf).toBe(true)
})
it('disables export when an inline markdown tab renders Changes mode', () => {
expect(renderModel({ isChangesMode: true }).canExportMarkdownToPdf).toBe(false)
})
it('uses unsaved drafts when resolving rich markdown fallback', () => {
const model = renderModel({
markdownViewMode: { '/repo/README.md': 'rich' },
editorDrafts: { '/repo/README.md': '[example]: https://example.com' }
})
expect(model.canExportMarkdownToPdf).toBe(false)
})
it('disables rich export when a multibyte character crosses the byte limit', () => {
const model = renderModel({
markdownViewMode: { '/repo/README.md': 'rich' },
editorDrafts: {
'/repo/README.md': `${'a'.repeat(RICH_MARKDOWN_MAX_SIZE_BYTES)}\u00e9`
}
})
expect(model.shouldShowMarkdownExportAction).toBe(true)
expect(model.canExportMarkdownToPdf).toBe(false)
})
it('keeps rich mode for an oversized file the user chose to open anyway', () => {
const model = renderModel({
markdownViewMode: { '/repo/README.md': 'rich' },
editorDrafts: { '/repo/README.md': 'a'.repeat(RICH_MARKDOWN_MAX_SIZE_BYTES + 1) },
markdownRichModeSizeOverridden: true
})
expect(model.canExportMarkdownToPdf).toBe(true)
})
it('scopes the open-anyway override to its own file', () => {
const model = renderModel({
markdownViewMode: { '/repo/README.md': 'rich' },
editorDrafts: { '/repo/README.md': 'a'.repeat(RICH_MARKDOWN_MAX_SIZE_BYTES + 1) },
markdownRichModeSizeOverridden: false
})
expect(model.canExportMarkdownToPdf).toBe(false)
})
it('disables edit export while content is still loading, even with a draft', () => {
const model = renderModel({
fileContents: {},
editorDrafts: { '/repo/README.md': '# Draft' }
})
expect(model.shouldShowMarkdownExportAction).toBe(true)
expect(model.canExportMarkdownToPdf).toBe(false)
})
it('enables export for loaded markdown preview tabs', () => {
const preview = markdownFile({
id: 'preview:/repo/README.md',
mode: 'markdown-preview',
markdownPreviewSourceFileId: '/repo/README.md'
} as Partial<OpenFile>)
expect(
renderModel({
activeFile: preview,
fileContents: { [preview.id]: textContent() }
}).canExportMarkdownToPdf
).toBe(true)
})
it('disables preview export until rendered content can exist', () => {
const preview = markdownFile({
id: 'preview:/repo/README.md',
mode: 'markdown-preview',
markdownPreviewSourceFileId: '/repo/README.md'
} as Partial<OpenFile>)
expect(renderModel({ activeFile: preview, fileContents: {} }).canExportMarkdownToPdf).toBe(
false
)
expect(
renderModel({
activeFile: preview,
fileContents: { [preview.id]: textContent({ loadError: 'missing' }) }
}).canExportMarkdownToPdf
).toBe(false)
expect(
renderModel({
activeFile: preview,
fileContents: { [preview.id]: textContent({ isBinary: true }) }
}).canExportMarkdownToPdf
).toBe(false)
})
})
it('hides disk preview while a fallback draft is unsaved', () => {
const model = renderModel({
editorDrafts: { '/repo/README.md': '[reference]: https://example.com' }
})
expect(model.availableEditorToggleModes).not.toContain('preview')
expect(model.canOpenPreviewToSide).toBe(false)
})