fix(notes): serialize immediate note creation writes
This commit is contained in:
@@ -220,7 +220,11 @@ describe('useNoteActions hook', () => {
|
||||
await flushAsyncWork()
|
||||
})
|
||||
await act(async () => {
|
||||
vi.advanceTimersByTime(RAPID_CREATE_NOTE_SETTLE_MS * 2)
|
||||
vi.advanceTimersByTime(RAPID_CREATE_NOTE_SETTLE_MS)
|
||||
await flushAsyncWork()
|
||||
})
|
||||
await act(async () => {
|
||||
vi.advanceTimersByTime(RAPID_CREATE_NOTE_SETTLE_MS)
|
||||
await flushAsyncWork()
|
||||
})
|
||||
|
||||
@@ -471,7 +475,11 @@ describe('useNoteActions hook', () => {
|
||||
await flushAsyncWork()
|
||||
})
|
||||
await act(async () => {
|
||||
vi.advanceTimersByTime(RAPID_CREATE_NOTE_SETTLE_MS * 2)
|
||||
vi.advanceTimersByTime(RAPID_CREATE_NOTE_SETTLE_MS)
|
||||
await flushAsyncWork()
|
||||
})
|
||||
await act(async () => {
|
||||
vi.advanceTimersByTime(RAPID_CREATE_NOTE_SETTLE_MS)
|
||||
await flushAsyncWork()
|
||||
})
|
||||
|
||||
|
||||
@@ -261,7 +261,11 @@ describe('useNoteCreation hook', () => {
|
||||
await flushImmediateCreate()
|
||||
})
|
||||
await act(async () => {
|
||||
vi.advanceTimersByTime(RAPID_CREATE_NOTE_SETTLE_MS * 2)
|
||||
vi.advanceTimersByTime(RAPID_CREATE_NOTE_SETTLE_MS)
|
||||
await flushImmediateCreate()
|
||||
})
|
||||
await act(async () => {
|
||||
vi.advanceTimersByTime(RAPID_CREATE_NOTE_SETTLE_MS)
|
||||
await flushImmediateCreate()
|
||||
})
|
||||
const filenames = addEntry.mock.calls.map(([e]: [VaultEntry]) => e.filename)
|
||||
@@ -326,6 +330,49 @@ describe('useNoteCreation hook', () => {
|
||||
vi.restoreAllMocks()
|
||||
})
|
||||
|
||||
it('waits for slow immediate note persistence before starting the queued create', async () => {
|
||||
vi.useFakeTimers()
|
||||
vi.mocked(isTauri).mockReturnValue(true)
|
||||
vi.spyOn(Date, 'now').mockReturnValue(1700000000000)
|
||||
let resolveFirstWrite: () => void
|
||||
const firstWrite = new Promise<void>((resolve) => {
|
||||
resolveFirstWrite = resolve
|
||||
})
|
||||
vi.mocked(invoke)
|
||||
.mockImplementationOnce(() => firstWrite)
|
||||
.mockResolvedValue(undefined)
|
||||
const createCalls = () => vi.mocked(invoke).mock.calls.filter(([command]) => command === 'create_note_content')
|
||||
const { result } = renderHook(() => useNoteCreation(makeConfig(), tabDeps))
|
||||
|
||||
await act(async () => {
|
||||
result.current.handleCreateNoteImmediate()
|
||||
result.current.handleCreateNoteImmediate()
|
||||
await flushImmediateCreate()
|
||||
})
|
||||
|
||||
expect(createCalls()).toHaveLength(1)
|
||||
|
||||
await act(async () => {
|
||||
vi.advanceTimersByTime(RAPID_CREATE_NOTE_SETTLE_MS * 3)
|
||||
await flushImmediateCreate()
|
||||
})
|
||||
|
||||
expect(createCalls()).toHaveLength(1)
|
||||
|
||||
await act(async () => {
|
||||
resolveFirstWrite()
|
||||
await flushImmediateCreate()
|
||||
})
|
||||
await act(async () => {
|
||||
vi.advanceTimersByTime(RAPID_CREATE_NOTE_SETTLE_MS)
|
||||
await flushImmediateCreate()
|
||||
})
|
||||
|
||||
expect(createCalls()).toHaveLength(2)
|
||||
expect(addEntry).toHaveBeenCalledTimes(2)
|
||||
vi.restoreAllMocks()
|
||||
})
|
||||
|
||||
it('handleCreateNoteImmediate accepts custom type', async () => {
|
||||
const { result } = renderHook(() => useNoteCreation(makeConfig(), tabDeps))
|
||||
await act(async () => {
|
||||
|
||||
@@ -536,18 +536,26 @@ function useImmediateCreateQueue(config: ImmediateCreateQueueConfig): (type?: st
|
||||
const queuedImmediateCreatesRef = useRef<ImmediateCreateRequest[]>([])
|
||||
const immediateCreateLockedRef = useRef(false)
|
||||
const immediateCreateTimerRef = useRef<number | null>(null)
|
||||
const queueMountedRef = useRef(true)
|
||||
const { latestDepsRef, syncDeps } = useLatestImmediateCreateDeps(config, pendingSlugsRef)
|
||||
|
||||
const executeRequest = useCallback((request: ImmediateCreateRequest) => {
|
||||
const executeRequest = useCallback(async (request: ImmediateCreateRequest): Promise<void> => {
|
||||
const deps = latestDepsRef.current
|
||||
if (!deps) return
|
||||
void createNoteImmediate(deps, request.type).then((didCreate) => trackImmediateCreate(request, didCreate))
|
||||
|
||||
try {
|
||||
const didCreate = await createNoteImmediate(deps, request.type)
|
||||
trackImmediateCreate(request, didCreate)
|
||||
} catch (error) {
|
||||
console.warn('Failed to create immediate note:', error)
|
||||
}
|
||||
}, [latestDepsRef])
|
||||
|
||||
const scheduleQueuedBurst = useCallback(function scheduleQueuedBurst() {
|
||||
if (!queueMountedRef.current) return
|
||||
if (immediateCreateTimerRef.current !== null) return
|
||||
|
||||
immediateCreateTimerRef.current = window.setTimeout(() => {
|
||||
immediateCreateTimerRef.current = window.setTimeout(async () => {
|
||||
immediateCreateTimerRef.current = null
|
||||
const next = queuedImmediateCreatesRef.current.shift()
|
||||
if (!next) {
|
||||
@@ -555,14 +563,18 @@ function useImmediateCreateQueue(config: ImmediateCreateQueueConfig): (type?: st
|
||||
return
|
||||
}
|
||||
|
||||
executeRequest(next)
|
||||
await executeRequest(next)
|
||||
scheduleQueuedBurst()
|
||||
}, RAPID_CREATE_NOTE_SETTLE_MS)
|
||||
}, [executeRequest])
|
||||
|
||||
useEffect(() => () => {
|
||||
if (immediateCreateTimerRef.current !== null) {
|
||||
window.clearTimeout(immediateCreateTimerRef.current)
|
||||
useEffect(() => {
|
||||
queueMountedRef.current = true
|
||||
return () => {
|
||||
queueMountedRef.current = false
|
||||
if (immediateCreateTimerRef.current !== null) {
|
||||
window.clearTimeout(immediateCreateTimerRef.current)
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
@@ -575,8 +587,7 @@ function useImmediateCreateQueue(config: ImmediateCreateQueueConfig): (type?: st
|
||||
}
|
||||
|
||||
immediateCreateLockedRef.current = true
|
||||
executeRequest(request)
|
||||
scheduleQueuedBurst()
|
||||
void executeRequest(request).then(scheduleQueuedBurst)
|
||||
}, [syncDeps, executeRequest, scheduleQueuedBurst])
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user