Skip to content

Commit 5bc7709

Browse files
test(editor): pin the rollback and reset behaviours the blocking mutation rows named (split 5/6 of #1066, unit U6a)
22 of the 50 blocking rows the mutation-diff leg published at d52c161 sit on lines this unit's own diff defines; every one of them now ends as a test proved against a hand-made mutation of the exact reported span, and the file was byte-identical to its pre-proof copy after every row. No Stryker suppression directive was added; src/eslint-suppressions.json is byte-identical to the head it sits on. The other 28 rows sit on lines that U3 #1931, U4 #1929 or U5a #1930 adds first against its own merge base: named there, not cut across here.
1 parent d52c161 commit 5bc7709

1 file changed

Lines changed: 319 additions & 1 deletion

File tree

‎src/integrations/editor/__tests__/DiffViewProvider.spec.ts‎

Lines changed: 319 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,12 @@ import type { Task } from "../../../core/task/Task"
55
import type { Mock } from "vitest"
66
import * as vscode from "vscode"
77
import delay from "delay"
8-
import { DEFAULT_WRITE_DELAY_MS } from "@roo-code/types"
8+
import {
9+
DEFAULT_AUTO_CLOSE_ZOO_OPENED_FILES,
10+
DEFAULT_AUTO_CLOSE_ZOO_OPENED_FILES_AFTER_USER_EDITED,
11+
DEFAULT_AUTO_CLOSE_ZOO_OPENED_NEW_FILES,
12+
DEFAULT_WRITE_DELAY_MS,
13+
} from "@roo-code/types"
914

1015
import { makePosition, makeRange, makeTextDocument, makeTextEditor, makeUri } from "../../../test-utils/vscode"
1116

@@ -3142,4 +3147,317 @@ describe("DiffViewProvider", () => {
31423147
expect(diffViewProvider.isEditingDifferentPath("dir/path.ts")).toBe(false)
31433148
})
31443149
})
3150+
3151+
describe("blocking rows this unit owns at head d52c1614f6e4", () => {
3152+
// Each it below closes a line of the 50 blocking rows the mutation-diff leg published at
3153+
// p1066/u6-execute-error-path-cleanup d52c1614f6e4. The lines named here are the ones this
3154+
// unit's own diff defines: for each of them no earlier-merging unit in the chain
3155+
// (U3 #1931, U4 #1929, U5a #1930, U5b #2435) adds the same line against its own merge base,
3156+
// so the construct is this unit's to settle and no suppression directive is used anywhere.
3157+
const settleRelPath = "settled-target-file.ts"
3158+
const settleTargetPath = `${mockCwd}/settled-target-file.ts`
3159+
3160+
// revertChanges() reads only uri/getText/positionAt/isDirty/save off the diff document,
3161+
// so the double stays that narrow and every input the rollback decides on is an argument.
3162+
const makeRollbackDocument = (content: string, isDirty: boolean, saved: boolean) => ({
3163+
uri: makeUri(settleTargetPath),
3164+
getText: vi.fn().mockReturnValue(content),
3165+
positionAt: vi.fn().mockReturnValue({ line: 0, character: 0 }),
3166+
isDirty,
3167+
save: vi.fn().mockResolvedValue(saved),
3168+
})
3169+
3170+
const makeDirtyFileTab = () => ({
3171+
input: Object.assign(new vscode.TabInputText(makeUri(settleTargetPath)), {
3172+
uri: makeUri(settleTargetPath),
3173+
}),
3174+
isDirty: true,
3175+
label: settleRelPath,
3176+
})
3177+
3178+
// Both fixtures below sit on module-level VS Code properties the provider reads live, so each
3179+
// run installs its own value and hands the original descriptor back rather than leaking state.
3180+
const withTabGroupsAll = (tabs: unknown[], run: () => Promise<unknown>) => {
3181+
const original = Object.getOwnPropertyDescriptor(vscode.window.tabGroups, "all")
3182+
Object.defineProperty(vscode.window.tabGroups, "all", { get: () => [{ tabs }], configurable: true })
3183+
return run().finally(() => {
3184+
if (original) {
3185+
Object.defineProperty(vscode.window.tabGroups, "all", original)
3186+
}
3187+
})
3188+
}
3189+
3190+
const withTextDocuments = (documents: unknown[], run: () => Promise<unknown>) => {
3191+
const original = Object.getOwnPropertyDescriptor(vscode.workspace, "textDocuments")
3192+
Object.defineProperty(vscode.workspace, "textDocuments", { value: documents, configurable: true })
3193+
return run().finally(() => {
3194+
if (original) {
3195+
Object.defineProperty(vscode.workspace, "textDocuments", original)
3196+
}
3197+
})
3198+
}
3199+
3200+
it("line 596: reports no different path when the session flag is up but its retained path is gone", () => {
3201+
// The guard's other exit. isEditing can stay true with relPath already cleared by a reset
3202+
// whose tab close rejected, and falling through to the comparison would then call every
3203+
// later write a second write, because undefined differs from every path.
3204+
Object.assign(diffViewProvider, { isEditing: true, relPath: undefined })
3205+
3206+
expect(diffViewProvider.isEditingDifferentPath("any/path.ts")).toBe(false)
3207+
})
3208+
3209+
it("line 833: restores the recorded content rather than an empty buffer when a new-file rollback meets a dirty diff buffer", async () => {
3210+
const document = makeRollbackDocument("partial model output", true, true)
3211+
Object.assign(diffViewProvider, {
3212+
relPath: settleRelPath,
3213+
activeDiffEditor: { document },
3214+
editType: "create",
3215+
createdDirs: [],
3216+
placeholderPath: undefined,
3217+
originalContent: "the content the file held before this write",
3218+
})
3219+
vi.mocked(vscode.workspace.applyEdit).mockResolvedValue(true)
3220+
3221+
await withTabGroupsAll([makeDirtyFileTab()], () => diffViewProvider.revertChanges())
3222+
3223+
const [, , replacedText] = mockWorkspaceEdit.replace.mock.calls[0]
3224+
expect(replacedText).toBe("the content the file held before this write")
3225+
})
3226+
3227+
it("line 833: falls back to an empty buffer text, not a placeholder string, when a new-file rollback has no recorded original", async () => {
3228+
const document = makeRollbackDocument("partial model output", true, true)
3229+
Object.assign(diffViewProvider, {
3230+
relPath: settleRelPath,
3231+
activeDiffEditor: { document },
3232+
editType: "create",
3233+
createdDirs: [],
3234+
placeholderPath: undefined,
3235+
originalContent: undefined,
3236+
})
3237+
vi.mocked(vscode.workspace.applyEdit).mockResolvedValue(true)
3238+
3239+
await withTabGroupsAll([makeDirtyFileTab()], () => diffViewProvider.revertChanges())
3240+
3241+
const [, , replacedText] = mockWorkspaceEdit.replace.mock.calls[0]
3242+
expect(replacedText).toBe("")
3243+
})
3244+
3245+
it("line 841: reports the unconfirmed save of the diff buffer it just restored, naming the path and the stopped work", async () => {
3246+
// applyEdit confirmed, document.save() did not: the rollback stops before any close or
3247+
// unlink, and the report is the only thing the caller gets, so it has to carry the reason.
3248+
const document = makeRollbackDocument("partial model output", true, false)
3249+
Object.assign(diffViewProvider, {
3250+
relPath: settleRelPath,
3251+
activeDiffEditor: { document },
3252+
editType: "create",
3253+
createdDirs: [],
3254+
placeholderPath: settleTargetPath,
3255+
originalContent: "",
3256+
})
3257+
vi.mocked(vscode.workspace.applyEdit).mockResolvedValue(true)
3258+
3259+
await withTabGroupsAll([makeDirtyFileTab()], async () => {
3260+
await expect(diffViewProvider.revertChanges()).rejects.toThrowError(
3261+
new Error(
3262+
`Rollback could not save the restored buffer for ${settleTargetPath}; its clean state is unconfirmed, so the rollback stopped before closing tabs or deleting the file.`,
3263+
),
3264+
)
3265+
})
3266+
3267+
expect(vscode.window.tabGroups.close).not.toHaveBeenCalled()
3268+
expect(fs.unlink).not.toHaveBeenCalled()
3269+
})
3270+
3271+
it("line 883: restores the recorded content rather than an empty buffer when an existing file is rolled back", async () => {
3272+
const document = makeRollbackDocument("streamed content", false, true)
3273+
Object.assign(diffViewProvider, {
3274+
relPath: settleRelPath,
3275+
activeDiffEditor: { document },
3276+
editType: "modify",
3277+
createdDirs: [],
3278+
originalContent: "the content the file held before this write",
3279+
})
3280+
vi.mocked(vscode.workspace.applyEdit).mockResolvedValue(true)
3281+
diffViewProvider["closeAllDiffViews"] = vi.fn().mockResolvedValue(undefined)
3282+
diffViewProvider["keepOrCloseEditedFile"] = vi.fn().mockResolvedValue(undefined)
3283+
3284+
await diffViewProvider.revertChanges()
3285+
3286+
const [, , replacedText] = mockWorkspaceEdit.replace.mock.calls[0]
3287+
expect(replacedText).toBe("the content the file held before this write")
3288+
})
3289+
3290+
it("line 883: strips an empty buffer text rather than a stand-in when a modify rollback has no recorded original", async () => {
3291+
const document = makeRollbackDocument("streamed content", false, true)
3292+
Object.assign(diffViewProvider, {
3293+
relPath: settleRelPath,
3294+
activeDiffEditor: { document },
3295+
editType: "modify",
3296+
createdDirs: [],
3297+
originalContent: undefined,
3298+
})
3299+
vi.mocked(vscode.workspace.applyEdit).mockResolvedValue(true)
3300+
diffViewProvider["closeAllDiffViews"] = vi.fn().mockResolvedValue(undefined)
3301+
diffViewProvider["keepOrCloseEditedFile"] = vi.fn().mockResolvedValue(undefined)
3302+
3303+
await diffViewProvider.revertChanges()
3304+
3305+
const [, , replacedText] = mockWorkspaceEdit.replace.mock.calls[0]
3306+
expect(replacedText).toBe("")
3307+
})
3308+
3309+
it("lines 896 and 901 and 902 and 904: rolls back to the centralized defaults when the task behind the rollback is gone", async () => {
3310+
// revertChanges() reads its auto-close settings through taskRef -> providerRef -> getState().
3311+
// Any link of that chain can already be gone by the time a denial lands, and a rollback that
3312+
// throws while reading them would take the session teardown with it. It must instead fall
3313+
// back to the same defaults saveChanges() uses.
3314+
const document = makeRollbackDocument("streamed content", false, true)
3315+
const keepOrClose = vi.fn().mockResolvedValue(undefined)
3316+
Object.assign(diffViewProvider, {
3317+
relPath: settleRelPath,
3318+
activeDiffEditor: { document },
3319+
editType: "modify",
3320+
createdDirs: [],
3321+
originalContent: "original",
3322+
})
3323+
diffViewProvider["taskRef"] = { deref: () => undefined } as unknown as WeakRef<Task>
3324+
vi.mocked(vscode.workspace.applyEdit).mockResolvedValue(true)
3325+
diffViewProvider["closeAllDiffViews"] = vi.fn().mockResolvedValue(undefined)
3326+
diffViewProvider["keepOrCloseEditedFile"] = keepOrClose
3327+
3328+
await diffViewProvider.revertChanges()
3329+
3330+
const args = keepOrClose.mock.calls[0]
3331+
expect(args[0]).toBe(settleTargetPath)
3332+
expect(args[1]).toBe(false)
3333+
expect(args[2]).toBe(DEFAULT_AUTO_CLOSE_ZOO_OPENED_FILES)
3334+
expect(args[3]).toBe(DEFAULT_AUTO_CLOSE_ZOO_OPENED_FILES_AFTER_USER_EDITED)
3335+
expect(args[4]).toBe(DEFAULT_AUTO_CLOSE_ZOO_OPENED_NEW_FILES)
3336+
})
3337+
3338+
it("line 896: rolls back to the centralized defaults when the task no longer holds a provider", async () => {
3339+
const document = makeRollbackDocument("streamed content", false, true)
3340+
const keepOrClose = vi.fn().mockResolvedValue(undefined)
3341+
Object.assign(diffViewProvider, {
3342+
relPath: settleRelPath,
3343+
activeDiffEditor: { document },
3344+
editType: "modify",
3345+
createdDirs: [],
3346+
originalContent: "original",
3347+
})
3348+
diffViewProvider["taskRef"] = {
3349+
deref: () => ({ providerRef: { deref: () => undefined } }),
3350+
} as unknown as WeakRef<Task>
3351+
vi.mocked(vscode.workspace.applyEdit).mockResolvedValue(true)
3352+
diffViewProvider["closeAllDiffViews"] = vi.fn().mockResolvedValue(undefined)
3353+
diffViewProvider["keepOrCloseEditedFile"] = keepOrClose
3354+
3355+
await diffViewProvider.revertChanges()
3356+
3357+
const args = keepOrClose.mock.calls[0]
3358+
expect(args[2]).toBe(DEFAULT_AUTO_CLOSE_ZOO_OPENED_FILES)
3359+
expect(args[3]).toBe(DEFAULT_AUTO_CLOSE_ZOO_OPENED_FILES_AFTER_USER_EDITED)
3360+
expect(args[4]).toBe(DEFAULT_AUTO_CLOSE_ZOO_OPENED_NEW_FILES)
3361+
})
3362+
3363+
it("lines 902 and 904: carries the settings the state holds through the rollback path", async () => {
3364+
// The rollback path is the one the auto-close decision table never exercised: every row of
3365+
// that table drives saveChanges(). Opting in on the rollback path has to close the tab, or
3366+
// the settings would mean one thing on accept and another on deny.
3367+
const document = makeRollbackDocument("streamed content", false, true)
3368+
const closeFileTab = vi.fn().mockResolvedValue(undefined)
3369+
const showEditedFile = vi.fn().mockResolvedValue(undefined)
3370+
Object.assign(diffViewProvider, {
3371+
relPath: settleRelPath,
3372+
activeDiffEditor: { document },
3373+
editType: "modify",
3374+
createdDirs: [],
3375+
originalContent: "original",
3376+
documentWasOpen: false,
3377+
userTouchedDocument: false,
3378+
userTouchedDiffEditor: false,
3379+
})
3380+
diffViewProvider["taskRef"] = {
3381+
deref: () =>
3382+
makeTaskDouble({
3383+
autoCloseZooOpenedFiles: true,
3384+
autoCloseZooOpenedFilesAfterUserEdited: true,
3385+
autoCloseZooOpenedNewFiles: true,
3386+
}),
3387+
} as unknown as WeakRef<Task>
3388+
vi.mocked(vscode.workspace.applyEdit).mockResolvedValue(true)
3389+
diffViewProvider["closeAllDiffViews"] = vi.fn().mockResolvedValue(undefined)
3390+
diffViewProvider["closeFileTab"] = closeFileTab
3391+
diffViewProvider["showEditedFileWithoutDisruptingFocus"] = showEditedFile
3392+
3393+
await diffViewProvider.revertChanges()
3394+
3395+
expect(closeFileTab).toHaveBeenCalledWith(settleTargetPath)
3396+
expect(showEditedFile).not.toHaveBeenCalled()
3397+
})
3398+
3399+
it("lines 1209 and 1211: stops the rollback and reports the refused restore of the file-scheme buffer by name", async () => {
3400+
// The diff-side restore confirms; the plain file-scheme buffer holding the same unapproved
3401+
// content is the one VS Code refuses to put back. Closing it anyway would hand the user a
3402+
// save prompt over exactly the content this rollback discards.
3403+
const document = makeRollbackDocument("partial model output", true, true)
3404+
const streamedBuffer = {
3405+
uri: makeUri(settleTargetPath),
3406+
scheme: "file",
3407+
getText: vi.fn().mockReturnValue("partial model output"),
3408+
positionAt: vi.fn().mockReturnValue({ line: 0, character: 0 }),
3409+
isDirty: true,
3410+
save: vi.fn().mockResolvedValue(true),
3411+
}
3412+
Object.assign(diffViewProvider, {
3413+
relPath: settleRelPath,
3414+
activeDiffEditor: { document },
3415+
editType: "create",
3416+
createdDirs: [],
3417+
placeholderPath: settleTargetPath,
3418+
originalContent: "",
3419+
})
3420+
vi.mocked(vscode.workspace.applyEdit).mockResolvedValueOnce(true).mockResolvedValueOnce(false)
3421+
3422+
await withTextDocuments([streamedBuffer], () =>
3423+
withTabGroupsAll([makeDirtyFileTab()], async () => {
3424+
await expect(diffViewProvider.revertChanges()).rejects.toThrowError(
3425+
new Error(
3426+
`Rollback could not restore the buffer for ${settleTargetPath}; it may still hold unapproved content, so the tab was left open rather than closed.`,
3427+
),
3428+
)
3429+
}),
3430+
)
3431+
3432+
expect(streamedBuffer.save).not.toHaveBeenCalled()
3433+
expect(vscode.window.tabGroups.close).not.toHaveBeenCalled()
3434+
expect(fs.unlink).not.toHaveBeenCalled()
3435+
})
3436+
3437+
it("lines 1558 and 1559 and 1563 and 1564 and 1569 and 1570 and 1571: clears every session field the reset owns, not only the ones with a visible effect", async () => {
3438+
// A flag that survives reset() is read by the NEXT edit as if it described that edit:
3439+
// documentWasOpen decides whether the tab is restored instead of closed, the two touched
3440+
// flags decide auto-close, and streamedLines/preDiagnostics/snapshotPreviewTabs carry one
3441+
// edit's stream, diagnostics and tab snapshot into the next one.
3442+
Object.assign(diffViewProvider, {
3443+
documentWasOpen: true,
3444+
documentWasPinned: true,
3445+
streamedLines: ["leftover streamed line"],
3446+
preDiagnostics: [{ message: "leftover diagnostic" }],
3447+
userTouchedDocument: true,
3448+
userTouchedDiffEditor: true,
3449+
snapshotPreviewTabs: [makePreviewTab(`${mockCwd}/leftover-preview.ts`)],
3450+
})
3451+
3452+
await diffViewProvider.reset()
3453+
3454+
expect(diffViewProvider["documentWasOpen"]).toBe(false)
3455+
expect(diffViewProvider["documentWasPinned"]).toBe(false)
3456+
expect(diffViewProvider["userTouchedDocument"]).toBe(false)
3457+
expect(diffViewProvider["userTouchedDiffEditor"]).toBe(false)
3458+
expect(diffViewProvider["streamedLines"]).toEqual([])
3459+
expect(diffViewProvider["preDiagnostics"]).toEqual([])
3460+
expect(diffViewProvider["snapshotPreviewTabs"]).toEqual([])
3461+
})
3462+
})
31453463
})

0 commit comments

Comments
 (0)