mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 08:02:28 +00:00
Move contextual copy toast cleanup to editor dispose (#3356)
* Move contextual copy toast cleanup to editor dispose * Fix contextual copy dispose cleanup Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com> Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
import type { editor } from 'monaco-editor'
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import { setupContextualCopy } from './setup-contextual-copy'
|
||||
|
||||
vi.mock('@/hooks/useShortcutLabel', () => ({
|
||||
formatShortcutLabel: () => 'Copy Context'
|
||||
}))
|
||||
|
||||
vi.mock('@/lib/monaco-setup', () => ({
|
||||
monaco: {
|
||||
editor: {
|
||||
ContentWidgetPositionPreference: {
|
||||
ABOVE: 1,
|
||||
BELOW: 2
|
||||
}
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
vi.mock('@/store', () => ({
|
||||
useAppStore: {
|
||||
getState: () => ({ keybindings: {} })
|
||||
}
|
||||
}))
|
||||
|
||||
vi.mock('@/lib/primary-selection', () => ({
|
||||
PRIMARY_SELECTION_MAX_LENGTH: 10_000,
|
||||
isPrimarySelectionEnabled: () => false,
|
||||
setPrimarySelectionText: () => {}
|
||||
}))
|
||||
|
||||
describe('setupContextualCopy', () => {
|
||||
afterEach(() => {
|
||||
vi.unstubAllGlobals()
|
||||
})
|
||||
|
||||
it('clears editor-scoped contextual copy cleanup on dispose', () => {
|
||||
const clearTimeout = vi.fn()
|
||||
const clearInterval = vi.fn()
|
||||
vi.stubGlobal('window', {
|
||||
clearInterval,
|
||||
clearTimeout,
|
||||
setInterval: vi.fn(() => 1),
|
||||
setTimeout: vi.fn(() => 2)
|
||||
})
|
||||
vi.stubGlobal('document', {
|
||||
createElement: () => ({
|
||||
className: '',
|
||||
offsetHeight: 28,
|
||||
style: { display: '' },
|
||||
textContent: ''
|
||||
})
|
||||
})
|
||||
|
||||
const editorDomNode = {
|
||||
addEventListener: vi.fn(),
|
||||
removeEventListener: vi.fn()
|
||||
}
|
||||
const selectionDispose = vi.fn()
|
||||
const scrollDispose = vi.fn()
|
||||
const focusDispose = vi.fn()
|
||||
const blurDispose = vi.fn()
|
||||
let disposeEditor = (): void => {}
|
||||
const editorInstance = {
|
||||
addContentWidget: vi.fn(),
|
||||
getContainerDomNode: () => editorDomNode,
|
||||
getModel: () => null,
|
||||
getSelection: () => null,
|
||||
hasTextFocus: () => false,
|
||||
layoutContentWidget: vi.fn(),
|
||||
onDidBlurEditorText: () => ({ dispose: blurDispose }),
|
||||
onDidChangeCursorSelection: () => ({ dispose: selectionDispose }),
|
||||
onDidDispose: (listener: () => void) => {
|
||||
disposeEditor = listener
|
||||
return { dispose: vi.fn() }
|
||||
},
|
||||
onDidFocusEditorText: () => ({ dispose: focusDispose }),
|
||||
onDidScrollChange: () => ({ dispose: scrollDispose }),
|
||||
removeContentWidget: vi.fn()
|
||||
} as unknown as editor.IStandaloneCodeEditor
|
||||
const copyToastTimeoutRef = { current: 42 }
|
||||
const setCopyToast = vi.fn()
|
||||
|
||||
setupContextualCopy({
|
||||
editorInstance,
|
||||
filePath: 'src/example.ts',
|
||||
setCopyToast,
|
||||
propsRef: {
|
||||
current: {
|
||||
language: 'typescript',
|
||||
relativePath: 'src/example.ts'
|
||||
}
|
||||
},
|
||||
copyToastTimeoutRef
|
||||
})
|
||||
|
||||
disposeEditor()
|
||||
|
||||
expect(selectionDispose).toHaveBeenCalledTimes(1)
|
||||
expect(scrollDispose).toHaveBeenCalledTimes(1)
|
||||
expect(focusDispose).toHaveBeenCalledTimes(1)
|
||||
expect(blurDispose).toHaveBeenCalledTimes(1)
|
||||
expect(clearTimeout).toHaveBeenCalledWith(42)
|
||||
expect(copyToastTimeoutRef.current).toBeNull()
|
||||
expect(setCopyToast).toHaveBeenCalledWith(null)
|
||||
expect(editorDomNode.removeEventListener).toHaveBeenCalledTimes(3)
|
||||
})
|
||||
})
|
||||
@@ -300,6 +300,13 @@ export function setupContextualCopy({
|
||||
scrollListener.dispose()
|
||||
focusListener.dispose()
|
||||
blurListener.dispose()
|
||||
// Why: the confirmation toast timeout belongs to the Monaco editor that
|
||||
// scheduled it, so editor disposal is the earliest reliable cleanup point.
|
||||
if (copyToastTimeoutRef.current !== null) {
|
||||
window.clearTimeout(copyToastTimeoutRef.current)
|
||||
copyToastTimeoutRef.current = null
|
||||
setCopyToast(null)
|
||||
}
|
||||
if (primarySelectionTimer !== null) {
|
||||
window.clearTimeout(primarySelectionTimer)
|
||||
primarySelectionTimer = null
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useRef, useState, useEffect, useCallback } from 'react'
|
||||
import React, { useRef, useState, useCallback } from 'react'
|
||||
import type { editor } from 'monaco-editor'
|
||||
import { setupContextualCopy } from './setup-contextual-copy'
|
||||
|
||||
@@ -6,15 +6,6 @@ export function useContextualCopySetup() {
|
||||
const [copyToast, setCopyToast] = useState<{ left: number; top: number } | null>(null)
|
||||
const copyToastTimeoutRef = useRef<number | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
const toastRef = copyToastTimeoutRef
|
||||
return () => {
|
||||
if (toastRef.current !== null) {
|
||||
window.clearTimeout(toastRef.current)
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
const setupCopy = useCallback(
|
||||
(
|
||||
editorInstance: editor.IStandaloneCodeEditor,
|
||||
|
||||
Reference in New Issue
Block a user