diff --git a/config/scripts/ci-shard-timings.json b/config/scripts/ci-shard-timings.json
index 47d44fe10fa..236f0156560 100644
--- a/config/scripts/ci-shard-timings.json
+++ b/config/scripts/ci-shard-timings.json
@@ -7023,7 +7023,6 @@
"src/renderer/src/components/editor/rich-markdown/schema/drag-selection-guard-lifetime.test.ts": 626,
"src/renderer/src/components/editor/autosave/editor-autosave-conflict-flow.test.ts": 1931,
"src/renderer/src/components/editor/autosave/editor-autosave-controller.test.ts": 2324,
- "src/renderer/src/components/editor/autosave/editor-autosave.test.ts": 529,
"src/renderer/src/components/editor/model-lifetime/buffer-text/editor-buffer-adapter.test.ts": 2812,
"src/renderer/src/components/editor/model-lifetime/buffer-text/editor-buffer-sharing.test.ts": 5929,
"src/renderer/src/components/editor/save/bytes/editor-bytes-documents.test.ts": 690,
diff --git a/src/renderer/src/components/editor/EditorConflictReviewSurface.live-record.test.tsx b/src/renderer/src/components/editor/EditorConflictReviewSurface.live-record.test.tsx
index 8bdb92d1dcd..55730b0cf12 100644
--- a/src/renderer/src/components/editor/EditorConflictReviewSurface.live-record.test.tsx
+++ b/src/renderer/src/components/editor/EditorConflictReviewSurface.live-record.test.tsx
@@ -9,7 +9,7 @@ import type { GitStatusEntry } from '../../../../shared/git/status/git-status-ty
import { makeWorktree } from '@/store/slices/worktrees-slice-test-fixtures'
import { createEditorStore } from './autosave/editor-autosave-controller-test-fixture'
import { createEditorPanelDraftSelector } from './editor-panel-draft-selector'
-import { editorTabModelUri } from './save/queue/editor-save-disk-precondition'
+
import { EditorConflictReviewSurface } from './EditorConflictReviewSurface'
type MonacoProps = {
@@ -163,33 +163,6 @@ afterEach(() => {
})
describe('conflict-review overview as a live read-only view of the record', () => {
- it("shows the record's unsaved text from another workspace's view, on that record's model", () => {
- const { store, view, review } = setup()
- store.getState().setEditorDraft(view.id, 'unsaved')
- store.getState().markFileDirty(view.id, true)
-
- const overview = render(store, review, 'disk')
- expect(overview.content).toBe('unsaved')
- expect(overview.readOnly).toBe(true)
- expect(overview.fileId).toBe(view.id)
- const state = store.getState()
- const overviewView = state.openFiles.find((file) => file.id === overview.fileId)!
- expect(editorTabModelUri(state, { ...overviewView, filePath: overview.filePath })).toBe(
- editorTabModelUri(state, view)
- )
- })
-
- it('follows typing in another view of the file', () => {
- const { store, view, review } = setup()
- expect(render(store, review, 'disk').content).toBe('disk')
-
- store.getState().setEditorDraft(view.id, 'a')
- store.getState().markFileDirty(view.id, true)
- expect(render(store, review, 'disk').content).toBe('a')
- store.getState().setEditorDraft(view.id, 'ab')
- expect(render(store, review, 'disk').content).toBe('ab')
- })
-
it("can't edit or save the record", () => {
const { store, view, review } = setup()
store.getState().setEditorDraft(view.id, 'unsaved')
@@ -222,71 +195,4 @@ describe('conflict-review overview as a live read-only view of the record', () =
expect(state.openFiles[0].isDirty).toBe(true)
expect(state.editorBufferKept).toEqual({})
})
-
- it('shows disk text when no view holds the file', () => {
- const { store, view, review } = setup()
- store.getState().closeFile(view.id)
- const overview = render(store, review, 'disk')
- expect(overview.fileId).toBe(PATH)
- expect(overview.content).toBe('disk')
- })
-
- it("retries a failed read through the borrowed view's own workspace", () => {
- const { store, view, review } = setup()
- const reloadContent = vi.fn()
- render(store, review, 'disk', { loadError: 'denied', reloadContent })
- retries.current[0]()
- // Retry is a user act that may start a stopped WSL distro.
- expect(reloadContent).toHaveBeenCalledWith(view, { wakeWslDistro: true })
- })
-
- it('applies a reveal explicitly owned by this overview and rejects another pane', () => {
- const { store, view, review } = setup()
- const reveal = { fileId: view.id, filePath: PATH, line: 9, column: 1, matchLength: 0 }
- const ownOverview = render(store, review, 'disk', {
- pendingEditorReveal: { ...reveal, viewStateId: 'panel-1:overview:file.ts' }
- })
- expect(ownOverview.revealLine).toBe(9)
-
- const anotherPane = render(store, review, 'disk', {
- pendingEditorReveal: { ...reveal, viewStateId: 'another-pane' }
- })
- expect(anotherPane.revealLine).toBeUndefined()
- })
-
- it('keeps a selected review editor scoped to its review pane', () => {
- const { store, review } = setup()
- store.getState().openConflictReviewFile(review.id, 'wt-2', '/repo', ENTRY, 'typescript')
- const selectedReview = store.getState().openFiles.find((file) => file.id === review.id)
- if (!selectedReview?.conflictReview?.selectedFileId) {
- throw new Error('Expected selected conflict')
- }
- const reveal = {
- fileId: selectedReview.conflictReview.selectedFileId,
- filePath: PATH,
- line: 9,
- column: 1,
- matchLength: 0
- }
- expect(
- render(store, selectedReview, 'disk', {
- pendingEditorReveal: { ...reveal, viewStateId: 'another-pane' }
- }).revealLine
- ).toBeUndefined()
- expect(
- render(store, selectedReview, 'disk', {
- pendingEditorReveal: { ...reveal, viewStateId: 'panel-1' }
- }).revealLine
- ).toBe(9)
- })
-
- it("mounts as a passive mirror: the view's workspace, no cursor tracking, ignores untargeted reveals", () => {
- const { store, view, review } = setup()
- const overview = render(store, review, 'disk', {
- pendingEditorReveal: { fileId: view.id, filePath: PATH, line: 9, column: 1, matchLength: 0 }
- })
- expect(overview.worktreeId).toBe('wt-1')
- expect(overview.passiveMirror).toBe(true)
- expect(overview.revealLine).toBeUndefined()
- })
})
diff --git a/src/renderer/src/components/editor/EditorContent.monaco-lifecycle.test.tsx b/src/renderer/src/components/editor/EditorContent.monaco-lifecycle.test.tsx
index 2e34f63a6c9..a1bb049ee00 100644
--- a/src/renderer/src/components/editor/EditorContent.monaco-lifecycle.test.tsx
+++ b/src/renderer/src/components/editor/EditorContent.monaco-lifecycle.test.tsx
@@ -2,7 +2,6 @@
import { cleanup, render } from '@testing-library/react'
import { afterEach, describe, expect, it, vi } from 'vitest'
import type { OpenFile } from '@/store/slices/editor'
-import { buildOwnedEditorFileId } from '@/store/slices/editor/file-ids/editor-file-ids'
const lifecycle = vi.hoisted(() => ({
events: [] as string[],
@@ -230,28 +229,6 @@ afterEach(() => {
})
describe('EditorContent Monaco lifecycle boundary', () => {
- it('keeps default-pane view state separate for the same path on different runtime hosts', () => {
- const filePath = '/repo/main.ts'
- const worktreeId = 'repo::/repo'
- const first = file(filePath, {
- id: buildOwnedEditorFileId(filePath, worktreeId, 'host-a'),
- runtimeEnvironmentId: 'host-a'
- })
- const second = file(filePath, {
- id: buildOwnedEditorFileId(filePath, worktreeId, 'host-b'),
- runtimeEnvironmentId: 'host-b'
- })
- const view = render(
-
- )
- const firstKey = lifecycle.viewStateKeys.at(-1)
- expect(firstKey).toBeTypeOf('string')
-
- view.rerender()
-
- expect(lifecycle.viewStateKeys.at(-1)).not.toBe(firstKey)
- })
-
it('unmounts the prior path before reconciling a stale retained target model', () => {
const first = file('/repo/first.ts')
const second = file('/repo/second.ts')
@@ -302,70 +279,4 @@ describe('EditorContent Monaco lifecycle boundary', () => {
'mount-diff:/repo/notes.ts'
])
})
-
- it('passes live-tail ownership only for a read-only live log', () => {
- const liveLog = file('/repo/session.jsonl', { readOnly: true, liveTail: true })
-
- render()
-
- expect(lifecycle.mountedProps).toEqual([
- { filePath: liveLog.filePath, readOnly: true, liveTail: true, viewStateId: 'same-pane' }
- ])
- })
-
- it('tells Monaco which pane it is so it can retire an explicit focus handoff', () => {
- const source = file('/repo/main.ts')
-
- render()
-
- // Missing pane IDs leave reveals armed, allowing later rich-mode remounts to steal focus.
- expect(lifecycle.mountedProps.at(0)?.viewStateId).toBe('same-pane')
- })
-
- it('preserves notebook save ownership and worktree routing across the extracted surface', () => {
- const notebook = file('/repo/analysis.ipynb', { language: 'notebook' })
- const notebookProps = props(notebook, '{"cells": []}')
-
- render(
-
- )
-
- expect(lifecycle.notebookProps).toHaveLength(1)
- expect(lifecycle.notebookProps[0]).toMatchObject({
- fileId: notebook.id,
- filePath: notebook.filePath,
- worktreeId: notebook.worktreeId,
- scrollCacheKey: `${notebook.filePath}::same-pane:notebook`,
- onContentChange: notebookProps.handleContentChange,
- onSave: notebookProps.handleSave
- })
- })
-
- it('forwards SSH and runtime ownership to rich markdown after extraction', () => {
- const markdown = file('/repo/notes.md', {
- language: 'markdown',
- externalSshTargetId: 'ssh-target',
- runtimeEnvironmentId: 'runtime-environment'
- })
-
- render(
-
- )
-
- expect(lifecycle.richMarkdownProps).toHaveLength(1)
- expect(lifecycle.richMarkdownProps[0]).toMatchObject({
- externalSshTargetId: 'ssh-target',
- runtimeEnvironmentId: 'runtime-environment',
- worktreeId: markdown.worktreeId
- })
- })
})
diff --git a/src/renderer/src/components/editor/EditorPanelHeaderPath.test.tsx b/src/renderer/src/components/editor/EditorPanelHeaderPath.test.tsx
index e69a31afc46..7fb27c02826 100644
--- a/src/renderer/src/components/editor/EditorPanelHeaderPath.test.tsx
+++ b/src/renderer/src/components/editor/EditorPanelHeaderPath.test.tsx
@@ -99,72 +99,6 @@ describe('EditorPanelHeaderPath inline rename', () => {
vi.stubGlobal('cancelAnimationFrame', vi.fn())
})
- it('opens a field holding the whole name', () => {
- renderPath(baseFile())
- openRenameInput()
-
- const input = getRenameInput('Rename file notes.md')
- expect(input.value).toBe('notes.md')
- })
-
- it('lets the field claim the full header width', () => {
- renderPath(baseFile())
- openRenameInput()
-
- const input = getRenameInput('Rename file notes.md')
- expect(input.className).toContain('w-full')
- expect(input.className).toContain('max-w-full')
- })
-
- it('selects the basename so typing replaces just the name', () => {
- renderPath(baseFile())
- openRenameInput()
-
- const input = getRenameInput('Rename file notes.md')
- expect(document.activeElement).toBe(input)
- expect(input.selectionStart).toBe(0)
- expect(input.selectionEnd).toBe('notes'.length)
- })
-
- it('renames to the typed name verbatim', () => {
- renderPath(baseFile())
- openRenameInput()
-
- const input = getRenameInput('Rename file notes.md')
- fireEvent.change(input, { target: { value: 'renamed.mdx' } })
- fireEvent.keyDown(input, { key: 'Enter' })
-
- expect(renameFileOnDiskMock).toHaveBeenCalledWith({
- oldPath: '/repo/notes.md',
- newName: 'renamed.mdx',
- worktreeId: 'wt-1',
- worktreePath: '/repo',
- documentScoped: false
- })
- })
-
- it('commits on blur like the tab bar and file explorer', () => {
- renderPath(baseFile())
- openRenameInput()
-
- const input = getRenameInput('Rename file notes.md')
- fireEvent.change(input, { target: { value: 'renamed.md' } })
- fireEvent.blur(input)
-
- expect(renameFileOnDiskMock).toHaveBeenCalledWith(
- expect.objectContaining({ newName: 'renamed.md' })
- )
- })
-
- it('does not request a rename when the name was not edited', () => {
- renderPath(baseFile())
- openRenameInput()
-
- fireEvent.keyDown(getRenameInput('Rename file notes.md'), { key: 'Enter' })
-
- expect(renameFileOnDiskMock).not.toHaveBeenCalled()
- })
-
it('ignores IME confirmation and its redispatch before accepting deliberate Enter', () => {
renderPath(baseFile())
openRenameInput()
@@ -236,56 +170,4 @@ describe('EditorPanelHeaderPath inline rename', () => {
expect(renameFileOnDiskMock).not.toHaveBeenCalled()
})
-
- it('selects the whole name when there is no extension', () => {
- const file = baseFile({
- id: '/repo/Makefile',
- filePath: '/repo/Makefile',
- relativePath: 'Makefile'
- })
- renderPath(file)
- openRenameInput()
-
- const input = getRenameInput('Rename file Makefile')
- expect(input.selectionStart).toBe(0)
- expect(input.selectionEnd).toBe('Makefile'.length)
- })
-})
-
-describe('EditorPanelHeaderPath reveal in file manager', () => {
- const openInFileManager = vi.fn()
-
- function openPathMenu(): HTMLElement {
- const pathRow = document.querySelector('.editor-header-path-row')
- if (!pathRow) {
- throw new Error('Missing editor header path row')
- }
- fireEvent.contextMenu(pathRow)
- return screen.getByRole('menuitem', { name: /Open Containing Folder/ })
- }
-
- beforeEach(() => {
- openInFileManager.mockReset().mockResolvedValue({ ok: true })
- Object.assign(window, { api: { shell: { openInFileManager } } })
- })
-
- it('reveals the open file through the shared reveal action', () => {
- renderPath(baseFile())
-
- fireEvent.click(openPathMenu())
-
- expect(openInFileManager).toHaveBeenCalledWith('/repo/notes.md', 'local')
- })
-
- it.each([
- ['a remote runtime owns', { runtimeEnvironmentId: 'env-1' }],
- ['opened from an SSH host outside the workspace', { externalSshTargetId: 'ssh-1' }]
- ])('disables reveal as local-only for a file %s', (_owner, overrides) => {
- renderPath(baseFile(overrides))
-
- const reveal = openPathMenu()
-
- expect(reveal.getAttribute('aria-disabled')).toBe('true')
- expect(reveal.textContent).toContain('Local only')
- })
})
diff --git a/src/renderer/src/components/editor/autosave/editor-autosave-conflict-flow.test.ts b/src/renderer/src/components/editor/autosave/editor-autosave-conflict-flow.test.ts
index 95295d7f9e7..980cd353256 100644
--- a/src/renderer/src/components/editor/autosave/editor-autosave-conflict-flow.test.ts
+++ b/src/renderer/src/components/editor/autosave/editor-autosave-conflict-flow.test.ts
@@ -56,48 +56,6 @@ describe('editor autosave changed-on-disk conflict flow', () => {
__clearSelfWriteRegistryForTests()
})
- it('preserves dirty drafts and marks the tab changed-on-disk on external file change', () => {
- stubEditorWindow()
- const store = createEditorStore()
- openDirtyFile(store)
-
- const cleanup = attachEditorAutosaveController(withLoadedBaselines(store))
- try {
- dispatchExternalChange()
-
- const file = store.getState().openFiles[0]
- expect(file?.isDirty).toBe(true)
- expect(file?.externalMutation).toBe('changed')
- expect(store.getState().editorDrafts['/repo/file.ts']).toBe('unsaved edit')
- } finally {
- cleanup()
- }
- })
-
- it('clears drafts and a stale changed-on-disk mark for clean tabs on external file change', () => {
- stubEditorWindow()
- const store = createEditorStore()
- store.getState().openFile({
- filePath: '/repo/file.ts',
- relativePath: 'file.ts',
- worktreeId: 'wt-1',
- language: 'typescript',
- mode: 'edit'
- })
- store.getState().setExternalMutation('/repo/file.ts', 'changed')
-
- const cleanup = attachEditorAutosaveController(withLoadedBaselines(store))
- try {
- dispatchExternalChange()
-
- const file = store.getState().openFiles[0]
- expect(file?.isDirty).toBe(false)
- expect(file?.externalMutation).toBeUndefined()
- } finally {
- cleanup()
- }
- })
-
it('does not backstop-mark a dirty tab for the echo of its own save', () => {
stubEditorWindow()
const store = createEditorStore()
@@ -154,21 +112,6 @@ describe('editor autosave changed-on-disk conflict flow', () => {
return { fakeDisk, store, cleanup: attachEditorAutosaveController(store) }
}
- it.each([
- ['disk still holds the baseline', 'base'],
- ['disk already holds the draft', 'restored draft']
- ])('lands and goes clean without a banner when %s', async (_label, disk) => {
- const { fakeDisk, store, cleanup } = restore(disk)
- try {
- await vi.advanceTimersByTimeAsync(1500)
- expect(fakeDisk.files.get('/repo/file.ts')).toBe('restored draft')
- expect(store.getState().openFiles[0]).toMatchObject({ isDirty: false })
- expect(store.getState().openFiles[0]?.externalMutation).toBeUndefined()
- } finally {
- cleanup()
- }
- })
-
it('is refused with the banner, keeps the draft and stops autosaving when disk changed', async () => {
const { fakeDisk, store, cleanup } = restore('agent write')
try {
@@ -212,28 +155,4 @@ describe('editor autosave changed-on-disk conflict flow', () => {
cleanup()
}
})
-
- it("clears the changed-on-disk mark after the banner's Overwrite", async () => {
- const writeFile = stubEditorWindow()
- const store = createEditorStore()
- openDirtyFile(store, 'user version')
- store.getState().setExternalMutation('/repo/file.ts', 'changed')
-
- const cleanup = attachEditorAutosaveController(withLoadedBaselines(store))
- try {
- await requestEditorFileSave({ fileId: '/repo/file.ts', overwrite: true })
-
- expect(writeFile).toHaveBeenCalledWith({
- filePath: '/repo/file.ts',
- content: 'user version',
- connectionId: undefined,
- expectedExecutionHostId: 'local'
- })
- const file = store.getState().openFiles[0]
- expect(file?.isDirty).toBe(false)
- expect(file?.externalMutation).toBeUndefined()
- } finally {
- cleanup()
- }
- })
})
diff --git a/src/renderer/src/components/editor/autosave/editor-autosave-controller.test.ts b/src/renderer/src/components/editor/autosave/editor-autosave-controller.test.ts
index e09ffd03b50..6623b544536 100644
--- a/src/renderer/src/components/editor/autosave/editor-autosave-controller.test.ts
+++ b/src/renderer/src/components/editor/autosave/editor-autosave-controller.test.ts
@@ -21,13 +21,9 @@ import {
import { clearRuntimeCompatibilityCacheForTests } from '@/runtime/runtime-rpc-client'
import { getDefaultSettings } from '../../../../../shared/constants'
import { makeWorktree } from '@/store/slices/worktrees-slice-test-fixtures'
-import { folderWorkspaceKey } from '../../../../../shared/workspace-scope'
+
import { emptyTabLayoutState } from '../../../store/slices/editor-slice-test-harness'
import { withLoadedBaselines } from './editor-autosave-controller-test-fixture'
-import {
- makeFolderWorkspace,
- makeProjectGroup
-} from '@/components/sidebar/workspace-creation/folder/folder-workspace-composer-submit-fixtures'
const mocks = vi.hoisted(() => ({
getConnectionIdForFile: vi.fn()
@@ -166,220 +162,6 @@ describe('attachEditorAutosaveController', () => {
__clearSelfWriteRegistryForTests()
})
- it('saves dirty files even when the visible EditorPanel is not mounted', async () => {
- const writeFile = vi.fn().mockResolvedValue(undefined)
- const eventTarget = new EventTarget()
- vi.stubGlobal('window', {
- addEventListener: eventTarget.addEventListener.bind(eventTarget),
- removeEventListener: eventTarget.removeEventListener.bind(eventTarget),
- dispatchEvent: eventTarget.dispatchEvent.bind(eventTarget),
- setTimeout: globalThis.setTimeout.bind(globalThis),
- clearTimeout: globalThis.clearTimeout.bind(globalThis),
- api: {
- fs: {
- writeFile
- }
- }
- } satisfies WindowStub)
-
- const store = createEditorStore()
- store.getState().openFile({
- filePath: '/repo/file.ts',
- relativePath: 'file.ts',
- worktreeId: 'wt-1',
- language: 'typescript',
- mode: 'edit'
- })
- store.getState().setEditorDraft('/repo/file.ts', 'edited')
- store.getState().markFileDirty('/repo/file.ts', true)
-
- const cleanup = attachEditorAutosaveController(withLoadedBaselines(store))
- try {
- await requestDirtyFileSave()
-
- expect(writeFile).toHaveBeenCalledWith(
- expect.objectContaining({
- filePath: '/repo/file.ts',
- content: 'edited',
- connectionId: undefined,
- expectedExecutionHostId: 'local'
- })
- )
- expect(store.getState().openFiles[0]?.isDirty).toBe(false)
- expect(store.getState().editorDrafts).toEqual({})
- } finally {
- cleanup()
- }
- })
-
- it('saves folder workspace files through the path-specific SSH connection', async () => {
- const writeFile = vi.fn().mockResolvedValue(undefined)
- const eventTarget = new EventTarget()
- vi.stubGlobal('window', {
- addEventListener: eventTarget.addEventListener.bind(eventTarget),
- removeEventListener: eventTarget.removeEventListener.bind(eventTarget),
- dispatchEvent: eventTarget.dispatchEvent.bind(eventTarget),
- setTimeout: globalThis.setTimeout.bind(globalThis),
- clearTimeout: globalThis.clearTimeout.bind(globalThis),
- api: {
- fs: {
- writeFile
- }
- }
- } satisfies WindowStub)
-
- const store = createEditorStore()
- const workspaceKey = folderWorkspaceKey('folder-workspace-1')
- store.setState({
- folderWorkspaces: [
- makeFolderWorkspace({
- folderPath: '/home/neil/platform',
- executionHostId: 'ssh:ssh-1',
- connectionId: 'ssh-1'
- })
- ],
- projectGroups: [{ ...makeProjectGroup(), executionHostId: 'ssh:ssh-1' }],
- worktreesByRepo: {
- 'folder-workspace-1': [
- makeWorktree({
- id: workspaceKey,
- repoId: 'folder-workspace-1',
- path: '/home/neil/platform',
- hostId: 'ssh:ssh-1'
- })
- ]
- },
- sshConnectionStates: new Map([
- [
- 'ssh-1',
- {
- targetId: 'ssh-1',
- status: 'connected',
- error: null,
- reconnectAttempt: 0,
- connectionGeneration: 4
- }
- ]
- ])
- })
- store.getState().openFile({
- filePath: '/home/neil/platform/api/src/file.ts',
- relativePath: 'api/src/file.ts',
- worktreeId: workspaceKey,
- language: 'typescript',
- mode: 'edit'
- })
- store.getState().setEditorDraft('/home/neil/platform/api/src/file.ts', 'edited')
- store.getState().markFileDirty('/home/neil/platform/api/src/file.ts', true)
-
- const cleanup = attachEditorAutosaveController(withLoadedBaselines(store))
- try {
- await requestDirtyFileSave()
-
- expect(writeFile).toHaveBeenCalledWith(
- expect.objectContaining({
- filePath: '/home/neil/platform/api/src/file.ts',
- content: 'edited',
- connectionId: 'ssh-1',
- expectedExecutionHostId: 'ssh:ssh-1',
- expectedSshTargetId: 'ssh-1',
- expectedSshConnectionGeneration: 4
- })
- )
- expect(store.getState().openFiles[0]?.isDirty).toBe(false)
- } finally {
- cleanup()
- }
- })
-
- it('saves runtime-owned folder workspace files through the folder root', async () => {
- clearRuntimeCompatibilityCacheForTests()
- const writeFile = vi.fn().mockResolvedValue(undefined)
- const runtimeCall = vi.fn().mockResolvedValue({
- ok: true,
- result: {},
- _meta: { runtimeId: 'runtime-env-1' }
- })
- const runtimeTransportCall = vi.fn((args: RuntimeEnvironmentCallRequest) => {
- return (
- createCompatibleRuntimeStatusResponseIfNeeded(args, 'runtime-env-1') ?? runtimeCall(args)
- )
- })
- const eventTarget = new EventTarget()
- vi.stubGlobal('window', {
- addEventListener: eventTarget.addEventListener.bind(eventTarget),
- removeEventListener: eventTarget.removeEventListener.bind(eventTarget),
- dispatchEvent: eventTarget.dispatchEvent.bind(eventTarget),
- setTimeout: globalThis.setTimeout.bind(globalThis),
- clearTimeout: globalThis.clearTimeout.bind(globalThis),
- api: {
- fs: { writeFile },
- runtimeEnvironments: { call: runtimeTransportCall }
- }
- } satisfies WindowStub)
-
- const store = createEditorStore()
- const workspaceId = 'folder-workspace-runtime'
- const workspaceKey = folderWorkspaceKey(workspaceId)
- store.setState({
- settings: {
- editorAutoSave: true,
- editorAutoSaveDelayMs: 1000,
- activeRuntimeEnvironmentId: 'env-2'
- } as never,
- worktreesByRepo: {},
- folderWorkspaces: [
- {
- id: workspaceId,
- projectGroupId: 'group-runtime',
- folderPath: '/runtime/folder',
- connectionId: null
- } as never
- ],
- projectGroups: [
- {
- id: 'group-runtime',
- connectionId: null,
- executionHostId: 'runtime:env-1'
- } as never
- ]
- })
- const fileId = store.getState().openFile({
- filePath: '/runtime/folder/src/file.ts',
- relativePath: 'src/file.ts',
- worktreeId: workspaceKey,
- language: 'typescript',
- mode: 'edit'
- })
- store.getState().setEditorDraft(fileId, 'edited')
- store.getState().markFileDirty(fileId, true)
-
- const cleanup = attachEditorAutosaveController(withLoadedBaselines(store))
- try {
- await requestDirtyFileSave()
-
- expect(runtimeCall).toHaveBeenCalledWith(
- expect.objectContaining({
- selector: 'env-1',
- method: 'files.writeIfUnchanged',
- expectedEnvironmentPairingRevision: undefined,
- params: {
- worktree: `id:${workspaceKey}`,
- relativePath: 'src/file.ts',
- content: 'edited',
- expectedExecutionHostId: 'local',
- diskPrecondition: expect.objectContaining({ allowMissing: true })
- },
- timeoutMs: 15_000
- })
- )
- expect(writeFile).not.toHaveBeenCalled()
- expect(store.getState().openFiles[0]?.isDirty).toBe(false)
- } finally {
- cleanup()
- }
- })
-
it.each(['unique', 'conflict-first', 'conflict-last'] as const)(
'keeps legacy runtime saves on a unique published directory (%s)',
async (variant) => {
diff --git a/src/renderer/src/components/editor/autosave/editor-autosave.test.ts b/src/renderer/src/components/editor/autosave/editor-autosave.test.ts
deleted file mode 100644
index 96149eff6be..00000000000
--- a/src/renderer/src/components/editor/autosave/editor-autosave.test.ts
+++ /dev/null
@@ -1,327 +0,0 @@
-import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
-import type { OpenFile } from '@/store/slices/editor/types/open-file'
-import {
- canAutoSaveOpenFile,
- getOpenFilesForExternalFileChange,
- isExternalReloadableEditorTab,
- normalizeAutoSaveDelayMs,
- ORCA_EDITOR_REQUEST_FILE_CLOSE_EVENT,
- ORCA_EDITOR_QUIESCE_FILE_SAVES_EVENT,
- requestEditorFileClose,
- requestEditorFileSave,
- requestEditorSaveQuiesce
-} from './editor-autosave'
-
-type WindowEventStub = Pick<
- Window,
- 'addEventListener' | 'removeEventListener' | 'dispatchEvent' | 'setTimeout' | 'clearTimeout'
->
-
-function makeOpenFile(overrides: Partial = {}): OpenFile {
- return {
- id: '/repo/file.ts',
- filePath: '/repo/file.ts',
- relativePath: 'file.ts',
- worktreeId: 'wt-1',
- language: 'typescript',
- isDirty: false,
- mode: 'edit',
- ...overrides
- }
-}
-
-beforeEach(() => {
- const eventTarget = new EventTarget()
- vi.stubGlobal('window', {
- addEventListener: eventTarget.addEventListener.bind(eventTarget),
- removeEventListener: eventTarget.removeEventListener.bind(eventTarget),
- dispatchEvent: eventTarget.dispatchEvent.bind(eventTarget),
- setTimeout: globalThis.setTimeout.bind(globalThis),
- clearTimeout: globalThis.clearTimeout.bind(globalThis)
- } satisfies WindowEventStub)
-})
-
-afterEach(() => {
- vi.unstubAllGlobals()
-})
-
-describe('canAutoSaveOpenFile', () => {
- it('allows normal edit tabs and unstaged single-file diffs', () => {
- expect(canAutoSaveOpenFile(makeOpenFile())).toBe(true)
- expect(
- canAutoSaveOpenFile(
- makeOpenFile({
- id: 'wt-1::diff::unstaged::file.ts',
- mode: 'diff',
- diffSource: 'unstaged'
- })
- )
- ).toBe(true)
- })
-
- it('rejects staged, combined, and conflict-review tabs', () => {
- expect(
- canAutoSaveOpenFile(
- makeOpenFile({
- id: 'wt-1::diff::staged::file.ts',
- mode: 'diff',
- diffSource: 'staged'
- })
- )
- ).toBe(false)
- expect(
- canAutoSaveOpenFile(
- makeOpenFile({
- id: 'wt-1::all-diffs::uncommitted',
- filePath: '/repo',
- relativePath: 'All Changes',
- mode: 'diff',
- diffSource: 'combined-uncommitted'
- })
- )
- ).toBe(false)
- expect(
- canAutoSaveOpenFile(
- makeOpenFile({
- id: 'wt-1::conflicts',
- filePath: '/repo',
- relativePath: 'Conflicts',
- mode: 'conflict-review'
- })
- )
- ).toBe(false)
- })
-
- it('rejects read-only edit tabs (AI Vault View Log)', () => {
- expect(canAutoSaveOpenFile(makeOpenFile({ readOnly: true }))).toBe(false)
- })
-})
-
-describe('normalizeAutoSaveDelayMs', () => {
- it('defaults and clamps invalid values', () => {
- expect(normalizeAutoSaveDelayMs(undefined)).toBe(1000)
- expect(normalizeAutoSaveDelayMs(Number.NaN)).toBe(1000)
- expect(normalizeAutoSaveDelayMs('750')).toBe(750)
- expect(normalizeAutoSaveDelayMs('oops')).toBe(1000)
- expect(normalizeAutoSaveDelayMs(10)).toBe(250)
- expect(normalizeAutoSaveDelayMs(25_000)).toBe(10_000)
- })
-})
-
-describe('requestEditorSaveQuiesce', () => {
- it('resolves immediately when no editor listener claims the request', async () => {
- const release = await requestEditorSaveQuiesce({ fileId: 'file-1' })
- expect(release).toBeTypeOf('function')
- expect(release()).toBeUndefined()
- })
-
- it('waits for a claiming listener to finish quiescing', async () => {
- let resolved = false
- const handler = (event: Event): void => {
- const detail = (event as CustomEvent).detail as {
- claim: () => void
- resolve: () => void
- }
- detail.claim()
- window.setTimeout(() => detail.resolve(), 0)
- }
-
- window.addEventListener(ORCA_EDITOR_QUIESCE_FILE_SAVES_EVENT, handler as EventListener)
- try {
- const promise = requestEditorSaveQuiesce({ fileId: 'file-1' }).then(() => {
- resolved = true
- })
-
- expect(resolved).toBe(false)
- await promise
- expect(resolved).toBe(true)
- } finally {
- window.removeEventListener(ORCA_EDITOR_QUIESCE_FILE_SAVES_EVENT, handler as EventListener)
- }
- })
-})
-
-describe('requestEditorFileSave', () => {
- it('rejects when no save controller claims the request', async () => {
- await expect(requestEditorFileSave({ fileId: 'file-1' })).rejects.toThrow(
- 'Editor save controller is unavailable.'
- )
- })
-})
-
-describe('requestEditorFileClose', () => {
- it('dispatches a close request event with the file id', () => {
- const listener = vi.fn()
- window.addEventListener(ORCA_EDITOR_REQUEST_FILE_CLOSE_EVENT, listener as EventListener)
- try {
- requestEditorFileClose('file-1')
- expect(listener).toHaveBeenCalledTimes(1)
- const event = listener.mock.calls[0][0] as CustomEvent<{ fileId: string }>
- expect(event.detail).toEqual({ fileId: 'file-1' })
- } finally {
- window.removeEventListener(ORCA_EDITOR_REQUEST_FILE_CLOSE_EVENT, listener as EventListener)
- }
- })
-})
-
-describe('isExternalReloadableEditorTab', () => {
- it('includes edit, preview, and single-file staged/unstaged diff tabs', () => {
- expect(isExternalReloadableEditorTab(makeOpenFile())).toBe(true)
- expect(
- isExternalReloadableEditorTab(
- makeOpenFile({ mode: 'markdown-preview', language: 'markdown' })
- )
- ).toBe(true)
- expect(
- isExternalReloadableEditorTab(
- makeOpenFile({ mode: 'diff', diffSource: 'unstaged', id: 'diff-unstaged' })
- )
- ).toBe(true)
- expect(
- isExternalReloadableEditorTab(
- makeOpenFile({ mode: 'diff', diffSource: 'staged', id: 'diff-staged' })
- )
- ).toBe(true)
- expect(
- isExternalReloadableEditorTab(
- makeOpenFile({ mode: 'diff', diffSource: 'combined-uncommitted', id: 'combined' })
- )
- ).toBe(false)
- })
-})
-
-describe('getOpenFilesForExternalFileChange', () => {
- it('matches edit tabs and unstaged diff tabs for the same worktree file', () => {
- const matchingEdit = makeOpenFile()
- const matchingPreview = makeOpenFile({
- id: 'markdown-preview::/repo/file.ts',
- mode: 'markdown-preview',
- language: 'markdown',
- markdownPreviewSourceFileId: '/repo/file.ts'
- })
- const matchingUnstagedDiff = makeOpenFile({
- id: 'wt-1::diff::unstaged::file.ts',
- mode: 'diff',
- diffSource: 'unstaged'
- })
- const stagedDiff = makeOpenFile({
- id: 'wt-1::diff::staged::file.ts',
- mode: 'diff',
- diffSource: 'staged'
- })
- const otherWorktree = makeOpenFile({
- id: '/other/file.ts',
- filePath: '/other/file.ts',
- worktreeId: 'wt-2'
- })
-
- expect(
- getOpenFilesForExternalFileChange(
- [matchingEdit, matchingPreview, matchingUnstagedDiff, stagedDiff, otherWorktree],
- {
- worktreeId: 'wt-1',
- worktreePath: '/repo',
- relativePath: 'file.ts'
- }
- ).map((file) => file.id)
- ).toEqual([
- '/repo/file.ts',
- 'markdown-preview::/repo/file.ts',
- 'wt-1::diff::unstaged::file.ts',
- 'wt-1::diff::staged::file.ts'
- ])
- })
-
- it('filters same-path matches by runtime owner when the watcher supplies one', () => {
- const localEdit = makeOpenFile({ id: 'local-edit', runtimeEnvironmentId: null })
- const runtimeEdit = makeOpenFile({ id: 'runtime-edit', runtimeEnvironmentId: 'env-1' })
- const runtimeDiff = makeOpenFile({
- id: 'runtime-diff',
- mode: 'diff',
- diffSource: 'unstaged',
- runtimeEnvironmentId: 'env-1'
- })
-
- expect(
- getOpenFilesForExternalFileChange([localEdit, runtimeEdit, runtimeDiff], {
- worktreeId: 'wt-1',
- worktreePath: '/repo',
- relativePath: 'file.ts',
- runtimeEnvironmentId: null
- }).map((file) => file.id)
- ).toEqual(['local-edit'])
-
- expect(
- getOpenFilesForExternalFileChange([localEdit, runtimeEdit, runtimeDiff], {
- worktreeId: 'wt-1',
- worktreePath: '/repo',
- relativePath: 'file.ts',
- runtimeEnvironmentId: 'env-1'
- }).map((file) => file.id)
- ).toEqual(['runtime-edit', 'runtime-diff'])
- })
-
- it('matches Windows edit tabs across separator and drive spellings, but not tail case', () => {
- const tab = makeOpenFile({ id: 'tab', filePath: 'c:\\Repo\\File.ts' })
- const otherCase = makeOpenFile({ id: 'other-case', filePath: 'c:\\Repo\\file.ts' })
-
- expect(
- getOpenFilesForExternalFileChange([tab, otherCase], {
- worktreeId: 'wt-1',
- worktreePath: 'C:/Repo',
- relativePath: 'File.ts'
- })
- ).toEqual([tab])
- })
-
- it('matches restored WSL aliases only for a proven local Windows watcher', () => {
- vi.stubGlobal('navigator', { userAgent: 'Windows' })
- const terminalLinkTab = makeOpenFile({
- id: '//wsl.localhost/Ubuntu/workspace/repo/file.ts',
- filePath: '//wsl.localhost/Ubuntu/workspace/repo/file.ts',
- worktreeId: 'wt-wsl'
- })
-
- expect(
- getOpenFilesForExternalFileChange([terminalLinkTab], {
- worktreeId: 'wt-wsl',
- worktreePath: '\\\\wsl.localhost\\Ubuntu\\workspace\\repo',
- relativePath: 'file.ts',
- runtimeEnvironmentId: null,
- allowLocalWindowsWslAliases: true
- }).map((file) => file.id)
- ).toEqual(['//wsl.localhost/Ubuntu/workspace/repo/file.ts'])
-
- expect(
- getOpenFilesForExternalFileChange([terminalLinkTab], {
- worktreeId: 'wt-wsl',
- worktreePath: '\\\\wsl.localhost\\Ubuntu\\workspace\\repo',
- relativePath: 'file.ts',
- runtimeEnvironmentId: null
- })
- ).toEqual([])
-
- vi.stubGlobal('navigator', { userAgent: 'Linux' })
- expect(
- getOpenFilesForExternalFileChange([terminalLinkTab], {
- worktreeId: 'wt-wsl',
- worktreePath: '\\\\wsl.localhost\\Ubuntu\\workspace\\repo',
- relativePath: 'file.ts',
- runtimeEnvironmentId: null,
- allowLocalWindowsWslAliases: true
- })
- ).toEqual([])
-
- vi.stubGlobal('navigator', { userAgent: 'Windows' })
- vi.stubGlobal('__ORCA_WEB_CLIENT__', true)
- expect(
- getOpenFilesForExternalFileChange([terminalLinkTab], {
- worktreeId: 'wt-wsl',
- worktreePath: '\\\\wsl.localhost\\Ubuntu\\workspace\\repo',
- relativePath: 'file.ts',
- runtimeEnvironmentId: null,
- allowLocalWindowsWslAliases: true
- })
- ).toEqual([])
- })
-})
diff --git a/src/renderer/src/components/editor/check-details/panel/CheckRunDetailsPanel.log-outcomes.test.tsx b/src/renderer/src/components/editor/check-details/panel/CheckRunDetailsPanel.log-outcomes.test.tsx
index bdb0c1b2002..3af6fe98a34 100644
--- a/src/renderer/src/components/editor/check-details/panel/CheckRunDetailsPanel.log-outcomes.test.tsx
+++ b/src/renderer/src/components/editor/check-details/panel/CheckRunDetailsPanel.log-outcomes.test.tsx
@@ -126,106 +126,6 @@ afterEach(() => {
})
describe('synthetic outcome payloads through real sidebar and header actions', () => {
- it('keeps the provider action and truthful manual output without inventing failed steps', () => {
- const manualCheck = { ...check, conclusion: 'neutral' as const }
- const details = {
- ...readingDetails(),
- conclusion: 'neutral',
- summary: 'Waiting for manual approval.',
- jobs: []
- }
- render(
-
-
-
- )
- expect(screen.getByText('Waiting for manual approval.')).toBeTruthy()
- expect(screen.getByRole('button', { name: 'Open details' })).toBeTruthy()
- expect(screen.queryByText('Failed jobs')).toBeNull()
- expect(screen.queryByText('Log excerpt')).toBeNull()
- expect(document.body.textContent).not.toMatch(/expired|Couldn't load logs|steps failed/)
- })
- it.each(['available', 'empty', 'not_requested', undefined] as const)(
- 'does not invent first-text navigation from null outcome %s',
- async (outcome) => {
- const first = readingDetails(null, outcome)
- const pending = gate()
- useAppStore.setState({ fetchPRCheckDetails: vi.fn(() => pending.promise) })
- open(first)
- const marker = vi.spyOn(diagnosticPosition, 'findInitialCheckLogDiagnostic')
- render(
-
-
-
- )
- expect(scrollTopCache.size).toBe(1)
- expect([...scrollTopCache.keys()].some((key) => key.includes('::intent:'))).toBe(false)
- fireEvent.click(screen.getByRole('button', { name: 'Refresh' }))
- await act(async () => pending.resolve(readingDetails(undefined, 'available')))
- expect(screen.getByTestId('outcome-pane').querySelector('pre')?.scrollTop).toBe(0)
- expect(marker).not.toHaveBeenCalled()
- }
- )
-
- it('ends zero-layout first-text setup and cannot revive it after close or late frames', async () => {
- const frames = new Map()
- let frameId = 0
- vi.spyOn(window, 'requestAnimationFrame').mockImplementation((callback) => {
- frames.set(++frameId, callback)
- return frameId
- })
- vi.spyOn(window, 'cancelAnimationFrame').mockImplementation((key) => {
- frames.delete(key)
- })
- vi.spyOn(HTMLElement.prototype, 'clientHeight', 'get').mockReturnValue(0)
- const first = payload()
- const pending = gate()
- useAppStore.setState({ fetchPRCheckDetails: vi.fn(() => pending.promise) })
- open(first)
- const marker = vi.spyOn(diagnosticPosition, 'findInitialCheckLogDiagnostic')
- const { unmount } = render(
-
-
-
- )
- expect(marker).not.toHaveBeenCalled()
- fireEvent.click(screen.getByRole('button', { name: 'Refresh' }))
- await act(async () =>
- pending.resolve(payload('Error: synthetic delayed first text', 'available'))
- )
- const queued = [...frames.values()]
- act(() => {
- frames.clear()
- for (const callback of queued) {
- callback(0)
- }
- })
- expect(marker).not.toHaveBeenCalled()
- expect(frames.size).toBe(0)
- vi.spyOn(HTMLElement.prototype, 'clientHeight', 'get').mockReturnValue(200)
- fireEvent.resize(window)
- expect(marker).not.toHaveBeenCalled()
- act(() => {
- useAppStore.setState({ openFiles: [] })
- })
- scrollTopCache.clear()
- act(() => {
- for (const callback of queued) {
- callback(0)
- }
- })
- unmount()
- expect(scrollTopCache.size).toBe(0)
- expect(frames.size).toBe(0)
- })
-
it.each([false, true])(
'retries one combined request with unchanged identities, already open=%s',
async (alreadyOpen) => {
diff --git a/src/renderer/src/components/editor/check-details/reading/check-details-reading-viewport.test.ts b/src/renderer/src/components/editor/check-details/reading/check-details-reading-viewport.test.ts
index 2bb32805872..40013f2e4b2 100644
--- a/src/renderer/src/components/editor/check-details/reading/check-details-reading-viewport.test.ts
+++ b/src/renderer/src/components/editor/check-details/reading/check-details-reading-viewport.test.ts
@@ -2,20 +2,15 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { scrollTopCache, setWithLRU } from '@/lib/scroll-cache'
import {
- checkDetailsReadingKey,
checkLogDigest,
- checkReadingIntentKey,
checkReadingLogKey,
- checkReadingOuterKey,
- checkReadingStepsKey,
- reconcileCheckDetailsReadingCache
+ checkReadingOuterKey
} from './check-details-reading-cache'
-import { createCheckDetailsReadingOwner } from './check-details-reading-owner'
+
import { attachCheckReadingViewport } from './check-details-reading-viewport'
import {
measuredReadingNode,
readingDetails,
- readingFile,
readingFixture
} from './check-details-reading-fixture'
import * as digest from '../../../../../../shared/sha256'
@@ -52,49 +47,6 @@ function flushFrames(): void {
}
describe('owned reading positions', () => {
- it.each([false, true])(
- 'allows first payload navigation only while deliberate intent survives, cancelled=%s',
- (cancelled) => {
- const { owner, store, file, identity, text, logKey } = readingFixture()
- const pending = { ...file.checkRunDetails!, details: null }
- store.setState({ openFiles: [{ ...file, checkRunDetails: pending }] })
- reconcileCheckDetailsReadingCache(file.id, undefined, pending, () => true)
- const detachOwner = owner.attach()
- const outer = measuredReadingNode()
- const detachOuter = attachCheckReadingViewport({
- node: outer,
- owner,
- cacheKey: checkReadingOuterKey(owner.key)
- })
- if (cancelled) {
- fireReading(outer)
- }
- const next = file.checkRunDetails!
- store.setState({ openFiles: [file] })
- reconcileCheckDetailsReadingCache(file.id, pending, next, () => true)
- const node = measuredReadingNode(text)
- const initial = vi.fn(() => 600)
- const detach = attachCheckReadingViewport({
- node,
- owner,
- cacheKey: logKey,
- job: identity,
- text,
- initial,
- consumeIntent: () => {
- const eligible = owner.claim(identity)
- scrollTopCache.delete(checkReadingIntentKey(owner.key, identity))
- return eligible
- }
- })
- expect(initial).toHaveBeenCalledTimes(cancelled ? 0 : 1)
- expect(node.scrollTop).toBe(cancelled ? 0 : 600)
- detach()
- detachOuter()
- detachOwner()
- }
- )
-
it('retirement cancels a pending frame and rejects a captured callback after close and reopen', () => {
const { owner, store, file, logKey, text, identity } = readingFixture()
const node = measuredReadingNode(text, 0)
@@ -119,40 +71,6 @@ describe('owned reading positions', () => {
detachOwner()
expect(scrollTopCache.has(logKey)).toBe(false)
})
-
- it('settled outer scrolling scans no cache keys and invokes no setup work', () => {
- const { owner, store, identity, text } = readingFixture()
- const node = measuredReadingNode()
- const detachOwner = owner.attach()
- const cancel = vi.fn()
- const unregister = owner.register({
- job: identity,
- text,
- start: () => {},
- retire: () => undefined,
- cancel
- })
- const initial = vi.fn(() => 400)
- const detach = attachCheckReadingViewport({
- node,
- owner,
- cacheKey: checkReadingOuterKey(owner.key),
- initial
- })
- fireReading(node)
- const keys = vi.spyOn(scrollTopCache, 'keys')
- for (let index = 0; index < 100; index++) {
- node.scrollTop = index
- node.dispatchEvent(new Event('scroll'))
- store.setState({ unrelated: index })
- }
- expect(keys).not.toHaveBeenCalled()
- expect(initial).not.toHaveBeenCalled()
- expect(cancel).toHaveBeenCalledTimes(1)
- detach()
- unregister()
- detachOwner()
- })
it.each(['outer', 'inner'])(
'preserves retained %s target through zero layout and a real remount',
(kind) => {
@@ -181,77 +99,6 @@ describe('owned reading positions', () => {
}
)
- it('saves a valid zero in a measurable short document', () => {
- const { owner, logKey, text, identity } = readingFixture()
- const node = measuredReadingNode(text)
- Object.defineProperty(node, 'scrollHeight', { value: 100 })
- setWithLRU(scrollTopCache, logKey, 180)
- const detachOwner = owner.attach()
- const detach = attachCheckReadingViewport({
- node,
- owner,
- cacheKey: logKey,
- job: identity,
- text
- })
- expect(scrollTopCache.get(logKey)).toBe(0)
- detach()
- detachOwner()
- })
-
- it('consumes an unmeasurable initial attempt once and cancels its sole frame on teardown', () => {
- const { owner, logKey, text, identity } = readingFixture()
- const node = measuredReadingNode(text, 0)
- const initial = vi.fn(() => 700)
- const detachOwner = owner.attach()
- const detach = attachCheckReadingViewport({
- node,
- owner,
- cacheKey: logKey,
- job: identity,
- text,
- initial,
- consumeIntent: () => true
- })
- expect(frames.size).toBe(1)
- flushFrames()
- expect(scrollTopCache.get(logKey)).toBe(0)
- Object.defineProperty(node, 'clientHeight', { configurable: true, value: 200 })
- node.dispatchEvent(new Event('resize'))
- node.dispatchEvent(new Event('scroll'))
- expect(initial).not.toHaveBeenCalled()
- detach()
- detachOwner()
- expect(frames.size).toBe(0)
- })
-
- it('advances the watermark on another file dirty/save update and restores the later scroll', () => {
- const { owner, store, logKey, text, identity } = readingFixture()
- const detachOwner = owner.attach()
- const node = measuredReadingNode(text)
- const options = { node, owner, cacheKey: logKey, job: identity, text }
- const detach = attachCheckReadingViewport(options)
- store.setState({
- openFiles: [
- ...store.getState().openFiles,
- { ...readingFile(), id: 'other', mode: 'edit', isDirty: true }
- ]
- })
- store.setState({
- openFiles: store
- .getState()
- .openFiles.map((file) => (file.id === 'other' ? { ...file, isDirty: false } : file))
- })
- node.scrollTop = 450
- node.dispatchEvent(new Event('scroll'))
- detach()
- node.scrollTop = 0
- const remount = attachCheckReadingViewport(options)
- expect(node.scrollTop).toBe(450)
- remount()
- detachOwner()
- })
-
it('fences callbacks published before the observer and never rewinds on a reentrant notification', () => {
const { owner, store, file, logKey, text, identity } = readingFixture()
const node = measuredReadingNode(text)
@@ -315,30 +162,6 @@ describe('owned reading positions', () => {
detachOwner()
})
- it('cancels first-payload and job intent with reading input, but ignores bubbled control clicks', () => {
- const { owner, file, text, identity } = readingFixture()
- reconcileCheckDetailsReadingCache(file.id, undefined, file.checkRunDetails!, () => true)
- const unclaimed = checkReadingIntentKey(
- checkDetailsReadingKey(file.id, null, 'context'),
- identity
- )
- const node = measuredReadingNode(text)
- const button = document.createElement('button')
- node.append(button)
- const detachOwner = owner.attach()
- const detach = attachCheckReadingViewport({
- node,
- owner,
- cacheKey: checkReadingOuterKey(owner.key)
- })
- button.dispatchEvent(new PointerEvent('pointerdown', { bubbles: true }))
- expect(scrollTopCache.get(unclaimed)).toBe(-1)
- node.dispatchEvent(new WheelEvent('wheel'))
- expect(scrollTopCache.has(unclaimed)).toBe(false)
- detach()
- detachOwner()
- })
-
it('does no hashing, marker work, Range work or frame scheduling during 100 scroll/unrelated notifications', () => {
const { owner, store, logKey, text, identity } = readingFixture()
const node = measuredReadingNode(text)
@@ -367,46 +190,4 @@ describe('owned reading positions', () => {
detachOwner()
expect(remove.mock.calls.map(([name]) => name)).toEqual(add.mock.calls.map(([name]) => name))
})
-
- it('shares the real 20-entry LRU across two five-job panes and ordinary editor keys', () => {
- const { store, file, identity, text } = readingFixture()
- const first = createCheckDetailsReadingOwner(store, file.id, 'left', 'context')
- const second = createCheckDetailsReadingOwner(store, file.id, 'right', 'context')
- for (let index = 0; index < 8; index++) {
- setWithLRU(scrollTopCache, `ordinary-${index}`, index)
- }
- for (const owner of [first, second]) {
- setWithLRU(scrollTopCache, checkReadingOuterKey(owner.key), 320)
- for (let index = 0; index < 5; index++) {
- setWithLRU(
- scrollTopCache,
- checkReadingLogKey(owner.key, `${identity}-${index}`, checkLogDigest(text)),
- 180
- )
- setWithLRU(scrollTopCache, checkReadingStepsKey(owner.key, `${identity}-${index}`, true), 0)
- expect(scrollTopCache.size).toBeLessThanOrEqual(20)
- }
- }
- expect(scrollTopCache.size).toBe(20)
- expect(scrollTopCache.has(checkReadingOuterKey(first.key))).toBe(false)
- expect([...scrollTopCache.values()].every((value) => typeof value === 'number')).toBe(true)
- expect([...scrollTopCache.keys()].some((key) => key.includes(text))).toBe(false)
- const detachOwner = first.attach()
- const node = measuredReadingNode()
- const initial = vi.fn(() => 600)
- const detach = attachCheckReadingViewport({
- node,
- owner: first,
- cacheKey: checkReadingOuterKey(first.key),
- initial
- })
- expect(node.scrollTop).toBe(0)
- expect(initial).not.toHaveBeenCalled()
- detach()
- detachOwner()
- })
})
-
-function fireReading(node: HTMLElement): void {
- node.dispatchEvent(new WheelEvent('wheel'))
-}
diff --git a/src/renderer/src/components/editor/combined-diff/load-sections/combined-diff-metadata-retention.test.tsx b/src/renderer/src/components/editor/combined-diff/load-sections/combined-diff-metadata-retention.test.tsx
index 74d9190426f..a0124391810 100644
--- a/src/renderer/src/components/editor/combined-diff/load-sections/combined-diff-metadata-retention.test.tsx
+++ b/src/renderer/src/components/editor/combined-diff/load-sections/combined-diff-metadata-retention.test.tsx
@@ -262,53 +262,3 @@ it('discards an in-flight read when the other area of its path changes', async (
expect(hook.result.current.sections[0]?.modifiedContent).toBe('current')
expect(hook.result.current.generation).toBe(generation)
})
-
-it.each([
- {
- name: 'order',
- change: (review: Review): Review => ({ ...review, status: review.status.toReversed() })
- },
- {
- name: 'worktree owner',
- change: (review: Review): Review => ({
- ...review,
- file: { ...review.file, worktreeId: 'folder:other' }
- })
- }
-])('uses the full reset when $name changes', async ({ name, change }) => {
- mocks.fetch.mockResolvedValue(textResult())
- const review: Review = { status: entries(3), file, viewStateKey: `fallback-${name}` }
- const hook = renderReview(review)
- await flush()
- const before = hook.result.current
- mocks.fetch.mockClear()
- const next = change(review)
- hook.rerender(next)
- await flush()
- expect(hook.result.current.generation).toBe(before.generation + 1)
- expect(hook.result.current.sections[0]).not.toBe(before.sections[0])
- expect(mocks.fetch.mock.calls[0]?.[0].file.worktreeId).toBe(next.file.worktreeId)
-})
-
-it('reloads a row whose submodule admission changes under an unchanged cache signature', async () => {
- mocks.fetch.mockResolvedValue(textResult())
- const review: Review = {
- status: [{ ...entries(1)[0]!, added: undefined }],
- file,
- viewStateKey: 'submodule-policy'
- }
- const hook = renderReview(review)
- await flush()
- hook.rerender({
- ...review,
- status: [
- {
- ...review.status[0]!,
- submodule: { commitChanged: true, trackedChanges: false, untrackedChanges: false }
- }
- ]
- })
- await flush()
- expect(hook.result.current.sections[0]?.loadOnDemand).toBe(false)
- expect(hook.result.current.sections[0]?.modifiedContent).toBe('after\n')
-})
diff --git a/src/renderer/src/components/editor/combined-diff/load-sections/combined-diff-stale-analysis-work-budget.test.tsx b/src/renderer/src/components/editor/combined-diff/load-sections/combined-diff-stale-analysis-work-budget.test.tsx
index 64ae5c6cbfb..b28ac0e12c6 100644
--- a/src/renderer/src/components/editor/combined-diff/load-sections/combined-diff-stale-analysis-work-budget.test.tsx
+++ b/src/renderer/src/components/editor/combined-diff/load-sections/combined-diff-stale-analysis-work-budget.test.tsx
@@ -285,22 +285,6 @@ describe('combined diff discarded-result analysis budget', () => {
}
)
- it('keeps reloading a visible section while external writes never stop', async () => {
- vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout'] })
- mocks.fetch.mockResolvedValue(textResult('base', 'edited'))
- const hook = await startViewer()
- hook.result.current.registry.renderedIndicesRef.current.add(0)
- // Watch batches every 100 ms: a restarting 300 ms coalesce would never fire.
- for (let elapsed = 0; elapsed < 3_000; elapsed += 100) {
- hook.result.current.retry.requestSectionReload(0)
- await act(async () => {
- await vi.advanceTimersByTimeAsync(100)
- })
- await flushLoads()
- }
- expect(mocks.fetch.mock.calls.length).toBeGreaterThanOrEqual(4)
- })
-
it('commits slow refetches while external writes never stop', async () => {
vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout'] })
let version = 0
@@ -393,100 +377,12 @@ describe('combined diff discarded-result analysis budget', () => {
expect(mocks.fetch).toHaveBeenCalledTimes(2)
expect(reads.count()).toBe(0)
})
-
- it.each(['offscreen', 'collapsed'])(
- 'does not force a %s stale section to reload',
- async (visibility) => {
- vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout'] })
- const body = 'obsolete hidden diff\n'.padEnd(4096, 'x')
- const pending = deferredResult()
- mocks.fetch.mockReturnValueOnce(pending.promise)
- const hook = await startViewer()
- if (visibility === 'collapsed') {
- act(() => {
- hook.result.current.setSections((sections) =>
- sections.map((section) => ({ ...section, collapsed: true }))
- )
- })
- hook.result.current.registry.renderedIndicesRef.current.add(0)
- }
- const sections = hook.result.current.sections
- hook.result.current.retry.requestSectionReload(0)
- const reads = countBodyReads(body)
- await settle(pending, textResult(body))
- reads.restore()
- expect(hook.result.current.sections).toBe(sections)
- expect(hook.result.current.registry.loadedIndicesRef.current.size).toBe(0)
- expect(hook.result.current.registry.loadingIndicesRef.current.size).toBe(0)
- expect(hook.result.current.registry.sectionLoadTokensRef.current.get(0)).toBe(2)
- expect(hook.result.current.registry.reloadTimersRef.current.size).toBe(0)
- await act(async () => {
- await vi.advanceTimersByTimeAsync(300)
- })
- expect(mocks.fetch).toHaveBeenCalledOnce()
- expect(reads.count()).toBe(0)
- }
- )
})
describe('combined diff live result compatibility', () => {
const body = 'original body\n'.padEnd(512, 'a')
const modified = 'modified body\n'.padEnd(128, 'b')
const legacy = textResult(body, modified)
- const metadata: GitDiffResult = {
- ...legacy,
- largeDiffRenderLimit: getLargeDiffRenderLimit(legacy)
- }
- const hostLimitMetadata: GitDiffTextResult = {
- ...textResult(''),
- largeDiffRenderLimit: getLargeDiffRenderLimit(textResult('\n'.repeat(120_001)))
- }
- const responseCases: [string, GitDiffResult, boolean][] = [
- ['legacy', legacy, true],
- ['native wire shape', structuredClone(legacy), true],
- ['relay wire shape', structuredClone(legacy), true],
- ['supplied metadata', metadata, false],
- [
- 'binary',
- {
- kind: 'binary',
- originalContent: body,
- modifiedContent: modified,
- originalIsBinary: true,
- modifiedIsBinary: false,
- isImage: true,
- mimeType: 'application/pdf',
- modifiedDeleted: true
- },
- false
- ],
- ['empty', textResult(''), true],
- ['line-limited legacy', textResult('\n'.repeat(120_001)), true],
- ['character-limited legacy', textResult('x'.repeat(6_000_001)), false],
- ['native limit metadata', structuredClone(hostLimitMetadata), false],
- ['relay limit metadata', structuredClone(hostLimitMetadata), false]
- ]
-
- it.each(responseCases)('preserves the complete %s result', async (_name, response, scansBody) => {
- const pending = deferredResult()
- mocks.fetch.mockReturnValueOnce(pending.promise)
- const hook = await startViewer()
- const initial = hook.result.current.sections[0]!
- const reads = countBodyReads(body)
- await settle(pending, response)
- reads.restore()
- expect(hook.result.current.sections).toEqual([expectedSection(initial, response)])
- if (hook.result.current.sections[0]?.largeDiffRenderLimit?.limited) {
- expect(hook.result.current.sections[0]?.diffResult).not.toBe(response)
- } else {
- expect(hook.result.current.sections[0]?.diffResult).toBe(response)
- }
- expect(hook.result.current.registry.loadedIndicesRef.current.has(0)).toBe(true)
- expect(hook.result.current.registry.loadingIndicesRef.current.size).toBe(0)
- expect(hook.result.current.sectionHeights).toEqual({})
- expect(mocks.fetch).toHaveBeenCalledOnce()
- expect(reads.count()).toBe(scansBody && response.originalContent === body ? body.length : 0)
- })
it('preserves section identity on unchanged reload and remeasures changed content once', async () => {
const first = deferredResult()
@@ -523,27 +419,6 @@ describe('combined diff live result compatibility', () => {
expect(mocks.fetch).toHaveBeenCalledTimes(3)
})
- it.each([new Error(' original remote failure '), new Error(' '), 'rejected value'])(
- 'keeps the existing rejection message for %s',
- async (error) => {
- const pending = deferredResult()
- mocks.fetch.mockReturnValueOnce(pending.promise)
- const hook = await startViewer()
- const initial = hook.result.current.sections[0]!
- await act(async () => {
- pending.reject(error)
- await Promise.resolve()
- })
- const message =
- error instanceof Error && error.message.trim() ? error.message : 'Unable to load diff.'
- expect(hook.result.current.sections).toEqual([
- expectedSection(initial, textResult(''), message)
- ])
- expect(hook.result.current.registry.loadedIndicesRef.current.has(0)).toBe(true)
- expect(hook.result.current.registry.loadingIndicesRef.current.size).toBe(0)
- }
- )
-
it('preserves the actual 30 second deadline and clears its timer after failure', async () => {
vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout'] })
const pending = deferredResult()
@@ -608,18 +483,4 @@ describe('combined diff live result compatibility', () => {
expect(hook.result.current.sections).toEqual([expectedSection(sections[0]!, legacy)])
expect(mocks.fetch).toHaveBeenCalledTimes(2)
})
-
- it('preserves a synchronous fetch failure', async () => {
- const failure = new Error('original synchronous failure')
- mocks.fetch.mockImplementationOnce(() => {
- throw failure
- })
- const hook = await startViewer()
- expect(hook.result.current.sections[0]?.error).toBe(failure.message)
- expect(hook.result.current.sections[0]?.diffResult).toEqual(textResult(''))
- expect(hook.result.current.sections[0]?.largeDiffRenderLimit).toBeNull()
- expect(hook.result.current.sections[0]?.loading).toBe(false)
- expect(hook.result.current.registry.loadedIndicesRef.current.has(0)).toBe(true)
- expect(hook.result.current.registry.loadingIndicesRef.current.size).toBe(0)
- })
})