mirror of
https://github.com/stablyai/orca.git
synced 2026-11-02 10:08:13 +00:00
282 lines
9.3 KiB
TypeScript
282 lines
9.3 KiB
TypeScript
// @vitest-environment happy-dom
|
|
import { act, createElement } from 'react'
|
|
import { createRoot, type Root } from 'react-dom/client'
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
import type { MarkdownViewMode, OpenFile } from '@/store/slices/editor'
|
|
import { useMarkdownDocuments } from './useMarkdownDocuments'
|
|
|
|
const runtime = vi.hoisted(() => ({
|
|
stat: vi.fn(),
|
|
list: vi.fn(),
|
|
toastError: vi.fn(),
|
|
translate: vi.fn()
|
|
}))
|
|
let runtimeConnectionId: string | null = null
|
|
const target = {
|
|
filePath: '/repo/target.md',
|
|
relativePath: 'target.md',
|
|
basename: 'target.md',
|
|
name: 'target'
|
|
}
|
|
const state = {
|
|
settings: {},
|
|
repos: [],
|
|
worktreesByRepo: { repo: [{ id: 'wt', path: '/repo' }] },
|
|
openFile: vi.fn(),
|
|
openMarkdownPreview: vi.fn()
|
|
}
|
|
|
|
vi.mock('@/store', () => ({
|
|
useAppStore: Object.assign((selector: (store: typeof state) => unknown) => selector(state), {
|
|
getState: () => state
|
|
})
|
|
}))
|
|
vi.mock('@/lib/connection-context', () => ({ getConnectionIdFromState: () => runtimeConnectionId }))
|
|
vi.mock('@/runtime/runtime-file-metadata-client', () => ({ statRuntimePath: runtime.stat }))
|
|
vi.mock('@/lib/file-owner-runtime-target', () => {
|
|
const getRuntimeTargetForFileOwner = (
|
|
_state: unknown,
|
|
_worktreeId: string | null | undefined,
|
|
owner: string | null | undefined
|
|
) => (owner ? { kind: 'environment' as const, environmentId: owner } : { kind: 'local' as const })
|
|
return {
|
|
FILE_OWNER_UNRESOLVED_MESSAGE: 'unresolved',
|
|
getRuntimeTargetForFileOwner,
|
|
requireRuntimeTargetForFileOwner: getRuntimeTargetForFileOwner,
|
|
getRuntimeTargetForWorktreeOwner: (state: unknown, worktreeId: string | null | undefined) =>
|
|
getRuntimeTargetForFileOwner(state, worktreeId, undefined)
|
|
}
|
|
})
|
|
vi.mock('./markdown-document-list-request', () => ({
|
|
requestSharedMarkdownDocumentList: runtime.list
|
|
}))
|
|
vi.mock('sonner', () => ({ toast: { error: runtime.toastError } }))
|
|
vi.mock('@/i18n/i18n', () => ({ translate: runtime.translate }))
|
|
|
|
let root: Root
|
|
let container: HTMLDivElement
|
|
let controller: ReturnType<typeof useMarkdownDocuments>
|
|
const save = vi.fn(async () => true)
|
|
|
|
function Harness({ file, viewMode }: { file: OpenFile; viewMode: MarkdownViewMode }): null {
|
|
controller = useMarkdownDocuments(file, true, viewMode, save)
|
|
return null
|
|
}
|
|
|
|
function sourceFile(mode: OpenFile['mode'], runtimeEnvironmentId: string | null = null): OpenFile {
|
|
return {
|
|
id: 'source',
|
|
filePath: '/repo/source.md',
|
|
relativePath: 'source.md',
|
|
worktreeId: 'wt',
|
|
language: 'markdown',
|
|
isDirty: false,
|
|
mode,
|
|
runtimeEnvironmentId
|
|
}
|
|
}
|
|
|
|
async function render(file: OpenFile, viewMode: MarkdownViewMode): Promise<void> {
|
|
await act(async () => {
|
|
root.render(createElement(Harness, { file, viewMode }))
|
|
})
|
|
}
|
|
|
|
beforeEach(() => {
|
|
vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true)
|
|
vi.clearAllMocks()
|
|
runtimeConnectionId = null
|
|
runtime.stat.mockResolvedValue({ isDirectory: false })
|
|
runtime.list.mockResolvedValue([target])
|
|
runtime.translate.mockReturnValue('Localized listing failure')
|
|
container = document.createElement('div')
|
|
document.body.appendChild(container)
|
|
root = createRoot(container)
|
|
})
|
|
|
|
afterEach(() => {
|
|
act(() => root.unmount())
|
|
container.remove()
|
|
vi.unstubAllGlobals()
|
|
})
|
|
|
|
describe('Markdown document navigation', () => {
|
|
it('localizes a non-Error failure at settlement without restarting the request', async () => {
|
|
let rejectListing: (reason: unknown) => void = () => {}
|
|
runtime.list.mockReturnValueOnce(
|
|
new Promise((_resolve, reject) => {
|
|
rejectListing = reject
|
|
})
|
|
)
|
|
await render(sourceFile('edit'), 'source')
|
|
runtime.translate.mockReturnValue('Current language listing failure')
|
|
await render(sourceFile('edit'), 'source')
|
|
await act(async () => rejectListing(null))
|
|
|
|
expect(runtime.list).toHaveBeenCalledOnce()
|
|
expect(runtime.translate).toHaveBeenCalledWith(
|
|
'auto.components.editor.useMarkdownDocuments.listFailed',
|
|
'Failed to list Markdown documents.'
|
|
)
|
|
expect(runtime.toastError).toHaveBeenCalledWith('Current language listing failure')
|
|
expect(controller.markdownDocuments).toEqual([])
|
|
})
|
|
|
|
it('preserves actual listing error detail', async () => {
|
|
runtime.list.mockRejectedValueOnce(new Error('SSH listing timed out'))
|
|
await render(sourceFile('edit', 'runtime-owner'), 'source')
|
|
|
|
expect(runtime.toastError).toHaveBeenCalledWith('SSH listing timed out')
|
|
expect(runtime.translate).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it.each(['superseded', 'unmounted'])('ignores a %s listing failure', async (reason) => {
|
|
let rejectListing: (error: unknown) => void = () => {}
|
|
runtime.list.mockReturnValueOnce(
|
|
new Promise((_resolve, reject) => {
|
|
rejectListing = reject
|
|
})
|
|
)
|
|
await render(sourceFile('edit'), 'source')
|
|
await (reason === 'superseded'
|
|
? render(sourceFile('edit', 'next-owner'), 'source')
|
|
: act(async () => root.render(null)))
|
|
await act(async () => rejectListing(null))
|
|
|
|
expect(runtime.toastError).not.toHaveBeenCalled()
|
|
expect(runtime.translate).not.toHaveBeenCalled()
|
|
if (reason === 'superseded') {
|
|
expect(controller.markdownDocuments).toEqual([target])
|
|
}
|
|
})
|
|
|
|
it.each([
|
|
['markdown-preview', 'source'],
|
|
['edit', 'preview'],
|
|
['diff', 'preview']
|
|
] as const)('preserves preview from %s / %s without a fragment', async (mode, viewMode) => {
|
|
await render(sourceFile(mode), viewMode)
|
|
await act(async () => {
|
|
await controller.previewProps.onOpenDocument(target)
|
|
})
|
|
|
|
expect(state.openMarkdownPreview).toHaveBeenCalledWith(
|
|
{
|
|
filePath: target.filePath,
|
|
relativePath: target.relativePath,
|
|
worktreeId: 'wt',
|
|
language: 'markdown',
|
|
runtimeEnvironmentId: null
|
|
},
|
|
{ anchor: undefined }
|
|
)
|
|
expect(state.openFile).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it.each(['source', 'rich'] as const)(
|
|
'keeps plain links from %s editing in edit mode',
|
|
async (viewMode) => {
|
|
await render(sourceFile('edit'), viewMode)
|
|
await act(async () => {
|
|
await controller.openMarkdownDocument(target)
|
|
})
|
|
|
|
expect(state.openFile).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
filePath: target.filePath,
|
|
mode: 'edit',
|
|
worktreeId: 'wt',
|
|
runtimeEnvironmentId: null
|
|
})
|
|
)
|
|
expect(state.openMarkdownPreview).not.toHaveBeenCalled()
|
|
}
|
|
)
|
|
|
|
it.each(['source', 'rich', 'preview'] as const)(
|
|
'keeps anchored links from %s in preview',
|
|
async (viewMode) => {
|
|
await render(sourceFile('edit'), viewMode)
|
|
await act(async () => {
|
|
await controller.openMarkdownDocument(target, { anchor: 'target' })
|
|
})
|
|
|
|
expect(state.openMarkdownPreview).toHaveBeenCalledWith(
|
|
expect.objectContaining({ filePath: target.filePath }),
|
|
{ anchor: 'target' }
|
|
)
|
|
expect(state.openFile).not.toHaveBeenCalled()
|
|
}
|
|
)
|
|
|
|
it('uses the new source mode after a rerender', async () => {
|
|
await render(sourceFile('markdown-preview'), 'source')
|
|
await render(sourceFile('edit'), 'source')
|
|
await act(async () => {
|
|
await controller.openMarkdownDocument(target)
|
|
})
|
|
expect(state.openFile).toHaveBeenCalledOnce()
|
|
expect(state.openMarkdownPreview).not.toHaveBeenCalled()
|
|
|
|
state.openFile.mockClear()
|
|
await render(sourceFile('edit'), 'preview')
|
|
await act(async () => {
|
|
await controller.openMarkdownDocument(target)
|
|
})
|
|
expect(state.openMarkdownPreview).toHaveBeenCalledOnce()
|
|
expect(state.openFile).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('retains SSH and runtime ownership for an indexed wiki link', async () => {
|
|
runtimeConnectionId = 'ssh-owner'
|
|
await render(sourceFile('markdown-preview', 'runtime-owner'), 'source')
|
|
await act(async () => {
|
|
controller.onOpenDocLink('target')
|
|
})
|
|
|
|
expect(runtime.stat).toHaveBeenCalledWith(
|
|
{
|
|
target: { kind: 'environment', environmentId: 'runtime-owner' },
|
|
worktreeId: 'wt',
|
|
worktreePath: '/repo',
|
|
connectionId: 'ssh-owner'
|
|
},
|
|
target.filePath
|
|
)
|
|
expect(state.openMarkdownPreview).toHaveBeenCalledWith(
|
|
expect.objectContaining({ worktreeId: 'wt', runtimeEnvironmentId: 'runtime-owner' }),
|
|
{ anchor: null }
|
|
)
|
|
expect(state.openFile).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it.each(['directory', 'missing'])('does not navigate to a %s destination', async (kind) => {
|
|
await render(sourceFile('markdown-preview'), 'source')
|
|
if (kind === 'directory') {
|
|
runtime.stat.mockResolvedValue({ isDirectory: true })
|
|
} else {
|
|
runtime.stat.mockRejectedValue(new Error('missing'))
|
|
}
|
|
await act(async () => {
|
|
await controller.previewProps.onOpenDocument(target)
|
|
})
|
|
|
|
expect(runtime.list).toHaveBeenLastCalledWith(expect.anything(), '/repo', {
|
|
requireFresh: true
|
|
})
|
|
expect(state.openFile).not.toHaveBeenCalled()
|
|
expect(state.openMarkdownPreview).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('does not navigate when the source workspace cannot be resolved', async () => {
|
|
await render({ ...sourceFile('markdown-preview'), worktreeId: 'unknown' }, 'source')
|
|
await act(async () => {
|
|
await controller.previewProps.onOpenDocument(target)
|
|
})
|
|
|
|
expect(runtime.stat).not.toHaveBeenCalled()
|
|
expect(state.openFile).not.toHaveBeenCalled()
|
|
expect(state.openMarkdownPreview).not.toHaveBeenCalled()
|
|
})
|
|
})
|