161 lines
4.8 KiB
TypeScript
161 lines
4.8 KiB
TypeScript
import { renderHook, act } from '@testing-library/react'
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
import type { MutableRefObject } from 'react'
|
|
import type { VaultEntry } from '../types'
|
|
import { cacheNoteContent, clearPrefetchCache } from './useTabManagement'
|
|
import {
|
|
PARSED_BLOCK_PRELOAD_DELAY_MS,
|
|
PARSED_BLOCK_PRELOAD_MIN_BYTES,
|
|
useParsedBlockPreload,
|
|
} from './editorParsedBlockPreload'
|
|
import type { NoteContentRequestOptions } from './noteContentCache'
|
|
|
|
type RefSet = {
|
|
activeTabPathRef: MutableRefObject<string | null>
|
|
editorMountedRef: MutableRefObject<boolean>
|
|
foregroundWorkAtRef: MutableRefObject<number>
|
|
rawModeRef: MutableRefObject<boolean>
|
|
}
|
|
|
|
function makeEntry(overrides: Partial<VaultEntry> = {}): VaultEntry {
|
|
return {
|
|
path: '/vault/large.md',
|
|
filename: 'large.md',
|
|
title: 'Large',
|
|
isA: 'Note',
|
|
aliases: [],
|
|
belongsTo: [],
|
|
relatedTo: [],
|
|
status: 'Active',
|
|
archived: false,
|
|
modifiedAt: 1_700_000_000,
|
|
createdAt: 1_700_000_000,
|
|
fileSize: PARSED_BLOCK_PRELOAD_MIN_BYTES + 1,
|
|
snippet: '',
|
|
wordCount: 0,
|
|
relationships: {},
|
|
icon: null,
|
|
color: null,
|
|
order: null,
|
|
sidebarLabel: null,
|
|
template: null,
|
|
sort: null,
|
|
view: null,
|
|
visible: null,
|
|
organized: false,
|
|
favorite: false,
|
|
favoriteIndex: null,
|
|
listPropertiesDisplay: [],
|
|
outgoingLinks: [],
|
|
properties: {},
|
|
hasH1: true,
|
|
fileKind: 'markdown',
|
|
...overrides,
|
|
}
|
|
}
|
|
|
|
function makeRefs(overrides: Partial<{
|
|
activeTabPath: string | null
|
|
editorMounted: boolean
|
|
foregroundWorkAt: number
|
|
rawMode: boolean
|
|
}> = {}): RefSet {
|
|
return {
|
|
activeTabPathRef: { current: overrides.activeTabPath ?? '/vault/open.md' },
|
|
editorMountedRef: { current: overrides.editorMounted ?? true },
|
|
foregroundWorkAtRef: { current: overrides.foregroundWorkAt ?? 0 },
|
|
rawModeRef: { current: overrides.rawMode ?? false },
|
|
}
|
|
}
|
|
|
|
function renderParsedPreload(refs: RefSet, prepareParsedBlocks: (event: {
|
|
entry: VaultEntry | null
|
|
path: string
|
|
content: string
|
|
}) => Promise<void>) {
|
|
return renderHook(() => useParsedBlockPreload({
|
|
...refs,
|
|
prepareParsedBlocks,
|
|
}))
|
|
}
|
|
|
|
async function emitResolvedContent(
|
|
entry: VaultEntry,
|
|
content = '# Large\n\nBody',
|
|
options?: NoteContentRequestOptions,
|
|
): Promise<void> {
|
|
await act(async () => {
|
|
cacheNoteContent(entry.path, content, entry, options)
|
|
await vi.advanceTimersByTimeAsync(PARSED_BLOCK_PRELOAD_DELAY_MS)
|
|
})
|
|
}
|
|
|
|
describe('useParsedBlockPreload', () => {
|
|
beforeEach(() => {
|
|
vi.useFakeTimers()
|
|
vi.setSystemTime(new Date('2026-05-14T12:00:00Z'))
|
|
clearPrefetchCache()
|
|
})
|
|
|
|
afterEach(() => {
|
|
clearPrefetchCache()
|
|
vi.useRealTimers()
|
|
vi.restoreAllMocks()
|
|
})
|
|
|
|
it('prepares parsed blocks for a warmed large markdown note after foreground work is idle', async () => {
|
|
const refs = makeRefs()
|
|
const prepareParsedBlocks = vi.fn(async () => {})
|
|
const entry = makeEntry()
|
|
|
|
renderParsedPreload(refs, prepareParsedBlocks)
|
|
await emitResolvedContent(entry, '# Large\n\nPrepared body.')
|
|
|
|
expect(prepareParsedBlocks).toHaveBeenCalledWith({
|
|
entry,
|
|
path: entry.path,
|
|
content: '# Large\n\nPrepared body.',
|
|
parsedBlockPreload: true,
|
|
})
|
|
})
|
|
|
|
it('skips raw prefetches that are not parsed-block warmup candidates', async () => {
|
|
const refs = makeRefs()
|
|
const prepareParsedBlocks = vi.fn(async () => {})
|
|
const entry = makeEntry({ path: '/vault/raw-only.md' })
|
|
|
|
renderParsedPreload(refs, prepareParsedBlocks)
|
|
await emitResolvedContent(entry, '# Raw only\n\nPrepared body.', { parsedBlockPreload: false })
|
|
|
|
expect(prepareParsedBlocks).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('skips entries that should not compete with the foreground editor', async () => {
|
|
const activeEntry = makeEntry({ path: '/vault/active.md' })
|
|
const refs = makeRefs({ activeTabPath: activeEntry.path })
|
|
const prepareParsedBlocks = vi.fn(async () => {})
|
|
renderParsedPreload(refs, prepareParsedBlocks)
|
|
|
|
await emitResolvedContent(activeEntry)
|
|
await emitResolvedContent(makeEntry({ path: '/vault/small.md', fileSize: PARSED_BLOCK_PRELOAD_MIN_BYTES - 1 }))
|
|
await emitResolvedContent(makeEntry({ path: '/vault/file.bin', fileKind: 'binary' }))
|
|
|
|
expect(prepareParsedBlocks).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('drops a candidate that becomes active before the idle preparse starts', async () => {
|
|
const entry = makeEntry({ path: '/vault/soon-active.md' })
|
|
const refs = makeRefs()
|
|
const prepareParsedBlocks = vi.fn(async () => {})
|
|
|
|
renderParsedPreload(refs, prepareParsedBlocks)
|
|
cacheNoteContent(entry.path, '# Soon Active\n\nBody', entry)
|
|
refs.activeTabPathRef.current = entry.path
|
|
await act(async () => {
|
|
await vi.advanceTimersByTimeAsync(PARSED_BLOCK_PRELOAD_DELAY_MS)
|
|
})
|
|
|
|
expect(prepareParsedBlocks).not.toHaveBeenCalled()
|
|
})
|
|
})
|