From e58ea41563c3608fd9e42e994d4eb206f05128fa Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Sat, 14 Mar 2026 22:01:45 +0100 Subject: [PATCH 01/12] Made reusable editor more stable --- .../src/api/CursorlessEngineApi.ts | 1 + .../cursorless-engine/src/cursorlessEngine.ts | 1 + .../src/languages/LanguageDefinitions.ts | 17 ++++--- .../src/endToEndTestSetup.ts | 7 ++- .../src/constructTestHelpers.ts | 2 + packages/cursorless-vscode/src/extension.ts | 2 + packages/vscode-common/src/TestHelpers.ts | 1 + .../src/testUtil/getReusableEditor.ts | 45 ++++++------------- .../src/testUtil/openNewEditor.ts | 14 +++--- 9 files changed, 40 insertions(+), 50 deletions(-) diff --git a/packages/cursorless-engine/src/api/CursorlessEngineApi.ts b/packages/cursorless-engine/src/api/CursorlessEngineApi.ts index 6d70b8e3c7..24d73103f1 100644 --- a/packages/cursorless-engine/src/api/CursorlessEngineApi.ts +++ b/packages/cursorless-engine/src/api/CursorlessEngineApi.ts @@ -23,6 +23,7 @@ export interface CursorlessEngine { storedTargets: StoredTargetMap; hatTokenMap: HatTokenMap; injectIde: (ide: IDE) => void; + loadLanguage(languageId: string): Promise; addCommandRunnerDecorator: ( commandRunnerDecorator: CommandRunnerDecorator, ) => void; diff --git a/packages/cursorless-engine/src/cursorlessEngine.ts b/packages/cursorless-engine/src/cursorlessEngine.ts index 9f63c477f3..84df58d156 100644 --- a/packages/cursorless-engine/src/cursorlessEngine.ts +++ b/packages/cursorless-engine/src/cursorlessEngine.ts @@ -154,6 +154,7 @@ export async function createCursorlessEngine({ storedTargets, hatTokenMap, injectIde: (ide) => injectedIde.setIde(ide), + loadLanguage: (languageId) => languageDefinitions.loadLanguage(languageId), addCommandRunnerDecorator: (decorator: CommandRunnerDecorator) => { commandRunnerDecorators.push(decorator); }, diff --git a/packages/cursorless-engine/src/languages/LanguageDefinitions.ts b/packages/cursorless-engine/src/languages/LanguageDefinitions.ts index c416eb802c..97af8664f0 100644 --- a/packages/cursorless-engine/src/languages/LanguageDefinitions.ts +++ b/packages/cursorless-engine/src/languages/LanguageDefinitions.ts @@ -122,15 +122,14 @@ export class LanguageDefinitionsImpl implements LanguageDefinitions { return; } - const definition = - (await LanguageDefinition.create( - this.ide, - this.treeSitterQueryProvider, - this.treeSitter, - languageId, - )) ?? LANGUAGE_UNDEFINED; - - this.languageDefinitions.set(languageId, definition); + const definition = await LanguageDefinition.create( + this.ide, + this.treeSitterQueryProvider, + this.treeSitter, + languageId, + ); + + this.languageDefinitions.set(languageId, definition ?? LANGUAGE_UNDEFINED); } private async reloadLanguageDefinitions(): Promise { diff --git a/packages/cursorless-vscode-e2e/src/endToEndTestSetup.ts b/packages/cursorless-vscode-e2e/src/endToEndTestSetup.ts index b1c56b7d69..2dd36ace5f 100644 --- a/packages/cursorless-vscode-e2e/src/endToEndTestSetup.ts +++ b/packages/cursorless-vscode-e2e/src/endToEndTestSetup.ts @@ -1,6 +1,9 @@ import type { IDE } from "@cursorless/common"; import { shouldUpdateFixtures, sleep, SpyIDE } from "@cursorless/common"; -import { getCursorlessApi } from "@cursorless/vscode-common"; +import { + getCursorlessApi, + resetReusableEditor, +} from "@cursorless/vscode-common"; import type { Context } from "mocha"; import * as sinon from "sinon"; @@ -43,11 +46,13 @@ export function endToEndTestSetup( testHelpers.commandServerApi.setFocusedElementType(undefined); spyIde = new SpyIDE(originalIde); injectIde(spyIde); + resetReusableEditor(); }); teardown(() => { sinon.restore(); injectIde(originalIde); + resetReusableEditor(); }); return { diff --git a/packages/cursorless-vscode/src/constructTestHelpers.ts b/packages/cursorless-vscode/src/constructTestHelpers.ts index 34bdef326e..ac43ef2fee 100644 --- a/packages/cursorless-vscode/src/constructTestHelpers.ts +++ b/packages/cursorless-vscode/src/constructTestHelpers.ts @@ -37,11 +37,13 @@ export function constructTestHelpers( scopeProvider: ScopeProvider, vscodeTutorial: VscodeTutorial, injectIde: (ide: IDE) => void, + loadLanguage: (languageId: string) => Promise, ): VscodeTestHelpers | undefined { return { commandServerApi: commandServerApi!, ide: normalizedIde, injectIde, + loadLanguage, scopeProvider, toVscodeEditor, diff --git a/packages/cursorless-vscode/src/extension.ts b/packages/cursorless-vscode/src/extension.ts index a84b257e4d..63a195f7e5 100644 --- a/packages/cursorless-vscode/src/extension.ts +++ b/packages/cursorless-vscode/src/extension.ts @@ -93,6 +93,7 @@ export async function activate( hatTokenMap, scopeProvider, injectIde, + loadLanguage, addCommandRunnerDecorator, customSpokenFormGenerator, } = await createCursorlessEngine(engineProps); @@ -185,6 +186,7 @@ export async function activate( scopeProvider, vscodeTutorial, injectIde, + loadLanguage, ) : undefined, }; diff --git a/packages/vscode-common/src/TestHelpers.ts b/packages/vscode-common/src/TestHelpers.ts index 04e04de498..d9a5484975 100644 --- a/packages/vscode-common/src/TestHelpers.ts +++ b/packages/vscode-common/src/TestHelpers.ts @@ -12,6 +12,7 @@ import type { SpyWebViewEvent } from "./SpyWebViewEvent"; export interface VscodeTestHelpers extends TestHelpers { ide: NormalizedIDE; injectIde(ide: IDE): void; + loadLanguage(languageId: string): Promise; clearCache(): void; scopeProvider: ScopeProvider; diff --git a/packages/vscode-common/src/testUtil/getReusableEditor.ts b/packages/vscode-common/src/testUtil/getReusableEditor.ts index 25881425c7..53acb1376e 100644 --- a/packages/vscode-common/src/testUtil/getReusableEditor.ts +++ b/packages/vscode-common/src/testUtil/getReusableEditor.ts @@ -1,12 +1,5 @@ -import { - commands, - EndOfLine, - languages, - Range, - window, - type TextEditor, -} from "vscode"; -import { getCursorlessApi, getParseTreeApi } from "../getExtensionApi"; +import { EndOfLine, Range, window, type TextEditor } from "vscode"; +import { getCursorlessApi } from "../getExtensionApi"; import { closeUiElements } from "./closeUiElements"; import { openNewEditor } from "./openNewEditor"; @@ -15,36 +8,20 @@ let editor: TextEditor | undefined; export async function getReusableEditor( content: string, languageId = "plaintext", - openBeside = false, ): Promise { - await closeUiElements(); - - if (openBeside) { - return await openNewEditor(content, languageId, true); - } - - if (editor == null) { + // Current editor is not fit for purpose, open a new one + if ( + editor == null || + editor !== window.activeTextEditor || + editor.document.languageId !== languageId + ) { editor = await openNewEditor(content, languageId); return editor; } + await closeUiElements(); (await getCursorlessApi()).testHelpers!.clearCache(); - // If the editor is not already active, make it active and close all other editors - if (editor !== window.activeTextEditor) { - editor = await window.showTextDocument(editor.document); - // Close other groups - await commands.executeCommand("workbench.action.closeEditorsInOtherGroups"); - // Close other editors in the same group - await commands.executeCommand("workbench.action.closeOtherEditors"); - } - - // If the editor is not already the right language, change its language and reload the parse tree - if (editor.document.languageId !== languageId) { - await languages.setTextDocumentLanguage(editor.document, languageId); - await (await getParseTreeApi()).loadLanguage(languageId); - } - // Replace the entire contents of the editor with the new content await editor.edit((editBuilder) => { editBuilder.replace( @@ -63,3 +40,7 @@ export async function getReusableEditor( return editor; } + +export function resetReusableEditor() { + editor = undefined; +} diff --git a/packages/vscode-common/src/testUtil/openNewEditor.ts b/packages/vscode-common/src/testUtil/openNewEditor.ts index d1de4de5d0..904b53990d 100644 --- a/packages/vscode-common/src/testUtil/openNewEditor.ts +++ b/packages/vscode-common/src/testUtil/openNewEditor.ts @@ -1,5 +1,5 @@ import * as vscode from "vscode"; -import { getCursorlessApi, getParseTreeApi } from "../getExtensionApi"; +import { getCursorlessApi } from "../getExtensionApi"; import { closeUiElements } from "./closeUiElements"; export async function openNewEditor( @@ -18,9 +18,8 @@ export async function openNewEditor( content, }); - await (await getParseTreeApi()).loadLanguage(languageId); - (await getCursorlessApi()).testHelpers!.clearCache(); + await (await getCursorlessApi()).testHelpers!.loadLanguage(languageId); const editor = await vscode.window.showTextDocument( document, @@ -41,12 +40,12 @@ export async function openNewEditor( * Open a new notebook editor with the given cells * @param cellContents A list of strings each of which will become the contents * of a cell in the notebook - * @param language The language id to use for all the cells in the notebook + * @param languageId The language id to use for all the cells in the notebook * @returns notebook */ export async function openNewNotebookEditor( cellContents: string[], - language: string = "plaintext", + languageId: string = "plaintext", ) { await vscode.commands.executeCommand("workbench.action.closeAllEditors"); @@ -58,15 +57,14 @@ export async function openNewNotebookEditor( new vscode.NotebookCellData( vscode.NotebookCellKind.Code, contents, - language, + languageId, ), ), ), ); - await (await getParseTreeApi()).loadLanguage(language); - (await getCursorlessApi()).testHelpers!.clearCache(); + await (await getCursorlessApi()).testHelpers!.loadLanguage(languageId); // FIXME: There seems to be some timing issue when you create a notebook // editor From 4c5cda9d12a2d97aaed294e6f38f233bc70ef7a2 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Sat, 14 Mar 2026 23:08:42 +0100 Subject: [PATCH 02/12] Added wait for utility --- .../common/src/ide/types/Configuration.ts | 2 +- packages/common/src/index.ts | 1 + packages/common/src/testUtil/waitFor.ts | 23 ++++ .../intraCellSetSelection.vscode.test.ts | 17 ++- .../assertCalledWithScopeInfo.ts | 55 ++++++--- .../runCustomRegexScopeInfoTest.ts | 4 - .../suite/tutorial/tutorial.vscode.test.ts | 105 ++++++++---------- 7 files changed, 131 insertions(+), 76 deletions(-) create mode 100644 packages/common/src/testUtil/waitFor.ts diff --git a/packages/common/src/ide/types/Configuration.ts b/packages/common/src/ide/types/Configuration.ts index dd9cf32d69..c2be8277a6 100644 --- a/packages/common/src/ide/types/Configuration.ts +++ b/packages/common/src/ide/types/Configuration.ts @@ -25,7 +25,7 @@ export const CONFIGURATION_DEFAULTS: CursorlessConfiguration = { symbolsToPreserve: [], }, wordSeparators: ["_"], - decorationDebounceDelayMs: 50, + decorationDebounceDelayMs: 0, experimental: { hatStability: HatStability.balanced, keyboardTargetFollowsSelection: false, diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index 8490084871..11ab8fbe4d 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -53,6 +53,7 @@ export * from "./testUtil/spyToPlainObject"; export * from "./testUtil/TestCaseSnapshot"; export * from "./testUtil/testConstants"; export * from "./testUtil/viteHtmlParamsPlugin"; +export * from "./testUtil/waitFor"; export * from "./types/command/ActionDescriptor"; export * from "./types/command/command.types"; export * from "./types/command/CommandV6.types"; diff --git a/packages/common/src/testUtil/waitFor.ts b/packages/common/src/testUtil/waitFor.ts new file mode 100644 index 0000000000..04a4a880d0 --- /dev/null +++ b/packages/common/src/testUtil/waitFor.ts @@ -0,0 +1,23 @@ +import sleep from "../util/sleep"; + +type Predicate = () => boolean | Promise; + +/** + * Waits for a predicate to become true, checking every 25ms. Returns true if + * the predicate becomes true within the given number of iterations, and false + * otherwise. + */ +export async function waitFor( + predicate: Predicate, + iterations = 10, +): Promise { + for (let i = 0; i < iterations; i++) { + if (await Promise.resolve(predicate())) { + return true; + } + + await sleep(25); + } + + return false; +} diff --git a/packages/cursorless-vscode-e2e/src/suite/intraCellSetSelection.vscode.test.ts b/packages/cursorless-vscode-e2e/src/suite/intraCellSetSelection.vscode.test.ts index 7b237192b8..1322a5af0d 100644 --- a/packages/cursorless-vscode-e2e/src/suite/intraCellSetSelection.vscode.test.ts +++ b/packages/cursorless-vscode-e2e/src/suite/intraCellSetSelection.vscode.test.ts @@ -1,4 +1,4 @@ -import { LATEST_VERSION } from "@cursorless/common"; +import { LATEST_VERSION, waitFor, type HatTokenMap } from "@cursorless/common"; import { getCursorlessApi, openNewNotebookEditor, @@ -21,6 +21,7 @@ async function runTest() { await openNewNotebookEditor(['"hello world"']); await hatTokenMap.allocateHats(); + await waitForHat(hatTokenMap, "default", "r"); await runCursorlessCommand({ version: LATEST_VERSION, @@ -46,3 +47,17 @@ async function runTest() { assert.deepStrictEqual(editor.document.getText(editor.selection), "world"); } + +async function waitForHat( + hatTokenMap: HatTokenMap, + hatStyle: "default", + character: string, +) { + const success = await waitFor(async () => { + const readableHatMap = await hatTokenMap.getReadableMap(false); + return readableHatMap.getToken(hatStyle, character) != null; + }); + if (!success) { + assert.fail(`Timed out waiting for mark ${hatStyle} '${character}'`); + } +} diff --git a/packages/cursorless-vscode-e2e/src/suite/scopeProvider/assertCalledWithScopeInfo.ts b/packages/cursorless-vscode-e2e/src/suite/scopeProvider/assertCalledWithScopeInfo.ts index da342d6b36..8f76530c14 100644 --- a/packages/cursorless-vscode-e2e/src/suite/scopeProvider/assertCalledWithScopeInfo.ts +++ b/packages/cursorless-vscode-e2e/src/suite/scopeProvider/assertCalledWithScopeInfo.ts @@ -1,18 +1,30 @@ -import type { ScopeType, ScopeTypeInfo } from "@cursorless/common"; -import * as sinon from "sinon"; +import { + waitFor, + type ScopeType, + type ScopeTypeInfo, +} from "@cursorless/common"; import { assert } from "chai"; -import { sleepWithBackoff } from "../../endToEndTestSetup"; import { isEqual } from "lodash-es"; +import type { SinonSpy } from "sinon"; export async function assertCalledWithScopeInfo( - fake: sinon.SinonSpy<[scopeInfos: T[]], void>, + fake: SinonSpy<[scopeInfos: T[]], void>, ...expectedScopeInfos: T[] ) { - await sleepWithBackoff(100); - sinon.assert.called(fake); + const scopeInfos = await waitForScopeInfos(fake, (scopeInfos) => + expectedScopeInfos.every((expectedScopeInfo) => { + const actualScopeInfo = scopeInfos.find((scopeInfo) => + isEqual(scopeInfo.scopeType, expectedScopeInfo.scopeType), + ); + + return ( + actualScopeInfo != null && isEqual(actualScopeInfo, expectedScopeInfo) + ); + }), + ); for (const expectedScopeInfo of expectedScopeInfos) { - const actualScopeInfo = fake.lastCall.args[0].find((scopeInfo) => + const actualScopeInfo = scopeInfos.find((scopeInfo) => isEqual(scopeInfo.scopeType, expectedScopeInfo.scopeType), ); assert.isDefined(actualScopeInfo); @@ -23,19 +35,36 @@ export async function assertCalledWithScopeInfo( } export async function assertCalledWithoutScopeInfo( - fake: sinon.SinonSpy<[scopeInfos: T[]], void>, + fake: SinonSpy<[scopeInfos: T[]], void>, ...scopeTypes: ScopeType[] ) { - await sleepWithBackoff(100); - sinon.assert.called(fake); + const scopeInfos = await waitForScopeInfos(fake, (scopeInfos) => + scopeTypes.every( + (scopeType) => + scopeInfos.find((scopeInfo) => + isEqual(scopeInfo.scopeType, scopeType), + ) == null, + ), + ); for (const scopeType of scopeTypes) { assert.isUndefined( - fake.lastCall.args[0].find((scopeInfo) => - isEqual(scopeInfo.scopeType, scopeType), - ), + scopeInfos.find((scopeInfo) => isEqual(scopeInfo.scopeType, scopeType)), ); } fake.resetHistory(); } + +async function waitForScopeInfos( + fake: SinonSpy<[scopeInfos: T[]], void>, + predicate: (scopeInfos: T[]) => boolean, +): Promise { + const success = await waitFor( + () => fake.called && predicate(fake.lastCall.args[0]), + ); + if (success) { + return fake.lastCall.args[0]; + } + assert.fail("Timed out waiting for expected scope info"); +} diff --git a/packages/cursorless-vscode-e2e/src/suite/scopeProvider/runCustomRegexScopeInfoTest.ts b/packages/cursorless-vscode-e2e/src/suite/scopeProvider/runCustomRegexScopeInfoTest.ts index 6b9699bb8c..920d709cbf 100644 --- a/packages/cursorless-vscode-e2e/src/suite/scopeProvider/runCustomRegexScopeInfoTest.ts +++ b/packages/cursorless-vscode-e2e/src/suite/scopeProvider/runCustomRegexScopeInfoTest.ts @@ -33,10 +33,6 @@ export async function runCustomRegexScopeInfoTest() { await assertCalledWithScopeInfo(fake, unsupported); await openNewEditor(contents); - // The scope provider relies on the open document event (among others) to - // update available scopes. Add a short sleep here to give it time to - // trigger. - await sleep(100); await assertCalledWithScopeInfo(fake, present); await unlink(cursorlessTalonStateJsonPath); diff --git a/packages/cursorless-vscode-e2e/src/suite/tutorial/tutorial.vscode.test.ts b/packages/cursorless-vscode-e2e/src/suite/tutorial/tutorial.vscode.test.ts index 3571c56d50..de672b6da8 100644 --- a/packages/cursorless-vscode-e2e/src/suite/tutorial/tutorial.vscode.test.ts +++ b/packages/cursorless-vscode-e2e/src/suite/tutorial/tutorial.vscode.test.ts @@ -3,19 +3,20 @@ import { LATEST_VERSION, asyncSafety, getSnapshotForComparison, - sleep, + waitFor, } from "@cursorless/common"; import { getRecordedTestsDirPath, loadFixture } from "@cursorless/node-common"; import { getCursorlessApi, runCursorlessCommand, + type SpyWebViewEvent, } from "@cursorless/vscode-common"; +import { isEqual } from "lodash-es"; import assert from "node:assert"; import path from "path"; import sinon from "sinon"; import { commands } from "vscode"; -import { endToEndTestSetup, sleepWithBackoff } from "../../endToEndTestSetup"; -import { isEqual } from "lodash-es"; +import { endToEndTestSetup } from "../../endToEndTestSetup"; suite("tutorial", async function () { const { getSpy } = endToEndTestSetup(this); @@ -73,56 +74,42 @@ async function runBasicTutorialTest(spyIde: SpyIDE) { ); await checkStepSetup(fixtures[0]); - // Allow for debounce - await sleep(100); - - // Another sleep just in case - await sleepWithBackoff(50); - // We allow duplicate messages because they're idempotent. Not sure why some // platforms get the init message twice but it doesn't matter. - const result = getTutorialWebviewEventLog(); + // This is the initial message that the webview sends to the extension. // Seeing this means that the javascript in the webview successfully loaded. - assert( - result.some((e) => - isEqual(e, { - type: "messageReceived", - data: { - type: "getInitialState", - }, - }), - ), + await waitForEvent( + getTutorialWebviewEventLog, + (e) => e.type === "messageReceived" && e.data.type === "getInitialState", ); // This is the response from the extension to the webview's initial message. - assert( - result.some((e) => - isEqual(e, { - type: "messageSent", - data: { - type: "doingTutorial", - hasErrors: false, - id: "tutorial-1-basics", - stepNumber: 0, - stepContent: [ - [ - { - type: "string", - value: "Say ", - }, - { - type: "command", - value: "take cap", - }, - ], + await waitForEvent(getTutorialWebviewEventLog, (e) => + isEqual(e, { + type: "messageSent", + data: { + type: "doingTutorial", + hasErrors: false, + id: "tutorial-1-basics", + stepNumber: 0, + stepContent: [ + [ + { + type: "string", + value: "Say ", + }, + { + type: "command", + value: "take cap", + }, ], - stepCount: 11, - title: "Introduction", - preConditionsMet: true, - }, - }), - ), + ], + stepCount: 11, + title: "Introduction", + preConditionsMet: true, + }, + }), ); // Check that we focus the tutorial webview when the user starts the tutorial @@ -149,18 +136,10 @@ async function runBasicTutorialTest(spyIde: SpyIDE) { usePrePhraseSnapshot: false, }); - // Allow for debounce - await sleep(100); - - // Another sleep just in case - await sleepWithBackoff(50); - - assert( - getTutorialWebviewEventLog().some( - (message) => - message.type === "messageSent" && - message.data.preConditionsMet === false, - ), + await waitForEvent( + getTutorialWebviewEventLog, + (event) => + event.type === "messageSent" && event.data.preConditionsMet === false, ); // Test resuming tutorial @@ -234,3 +213,15 @@ const runNoOpCursorlessCommand = () => }, usePrePhraseSnapshot: false, }); + +async function waitForEvent( + getTutorialWebviewEventLog: () => SpyWebViewEvent[], + predicate: (event: SpyWebViewEvent) => boolean, +) { + const success = await waitFor(() => + getTutorialWebviewEventLog().some(predicate), + ); + if (!success) { + assert.fail("Timed out waiting for tutorial event log"); + } +} From 2a7612d803bab449cd8561f1b76157004dc02df3 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Sat, 14 Mar 2026 23:29:11 +0100 Subject: [PATCH 03/12] more cleanup --- .../common/src/ide/types/Configuration.ts | 2 +- packages/common/src/index.ts | 1 - packages/common/src/testUtil/waitFor.ts | 23 ------------------- .../intraCellSetSelection.vscode.test.ts | 3 ++- .../assertCalledWithScopeInfo.ts | 7 ++---- .../scopeVisualizer.vscode.test.ts | 6 ++++- .../suite/tutorial/tutorial.vscode.test.ts | 2 +- .../src/suite/waitFor.ts | 23 +++++++++++++++++++ 8 files changed, 34 insertions(+), 33 deletions(-) delete mode 100644 packages/common/src/testUtil/waitFor.ts create mode 100644 packages/cursorless-vscode-e2e/src/suite/waitFor.ts diff --git a/packages/common/src/ide/types/Configuration.ts b/packages/common/src/ide/types/Configuration.ts index c2be8277a6..dd9cf32d69 100644 --- a/packages/common/src/ide/types/Configuration.ts +++ b/packages/common/src/ide/types/Configuration.ts @@ -25,7 +25,7 @@ export const CONFIGURATION_DEFAULTS: CursorlessConfiguration = { symbolsToPreserve: [], }, wordSeparators: ["_"], - decorationDebounceDelayMs: 0, + decorationDebounceDelayMs: 50, experimental: { hatStability: HatStability.balanced, keyboardTargetFollowsSelection: false, diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index 11ab8fbe4d..8490084871 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -53,7 +53,6 @@ export * from "./testUtil/spyToPlainObject"; export * from "./testUtil/TestCaseSnapshot"; export * from "./testUtil/testConstants"; export * from "./testUtil/viteHtmlParamsPlugin"; -export * from "./testUtil/waitFor"; export * from "./types/command/ActionDescriptor"; export * from "./types/command/command.types"; export * from "./types/command/CommandV6.types"; diff --git a/packages/common/src/testUtil/waitFor.ts b/packages/common/src/testUtil/waitFor.ts deleted file mode 100644 index 04a4a880d0..0000000000 --- a/packages/common/src/testUtil/waitFor.ts +++ /dev/null @@ -1,23 +0,0 @@ -import sleep from "../util/sleep"; - -type Predicate = () => boolean | Promise; - -/** - * Waits for a predicate to become true, checking every 25ms. Returns true if - * the predicate becomes true within the given number of iterations, and false - * otherwise. - */ -export async function waitFor( - predicate: Predicate, - iterations = 10, -): Promise { - for (let i = 0; i < iterations; i++) { - if (await Promise.resolve(predicate())) { - return true; - } - - await sleep(25); - } - - return false; -} diff --git a/packages/cursorless-vscode-e2e/src/suite/intraCellSetSelection.vscode.test.ts b/packages/cursorless-vscode-e2e/src/suite/intraCellSetSelection.vscode.test.ts index 1322a5af0d..cdeb0f7665 100644 --- a/packages/cursorless-vscode-e2e/src/suite/intraCellSetSelection.vscode.test.ts +++ b/packages/cursorless-vscode-e2e/src/suite/intraCellSetSelection.vscode.test.ts @@ -1,4 +1,4 @@ -import { LATEST_VERSION, waitFor, type HatTokenMap } from "@cursorless/common"; +import { LATEST_VERSION, type HatTokenMap } from "@cursorless/common"; import { getCursorlessApi, openNewNotebookEditor, @@ -7,6 +7,7 @@ import { import assert from "assert"; import { window } from "vscode"; import { endToEndTestSetup } from "../endToEndTestSetup"; +import { waitFor } from "./waitFor"; // Check that setSelection is able to focus the correct cell suite("Within cell set selection", async function () { diff --git a/packages/cursorless-vscode-e2e/src/suite/scopeProvider/assertCalledWithScopeInfo.ts b/packages/cursorless-vscode-e2e/src/suite/scopeProvider/assertCalledWithScopeInfo.ts index 8f76530c14..9466ff14d4 100644 --- a/packages/cursorless-vscode-e2e/src/suite/scopeProvider/assertCalledWithScopeInfo.ts +++ b/packages/cursorless-vscode-e2e/src/suite/scopeProvider/assertCalledWithScopeInfo.ts @@ -1,11 +1,8 @@ -import { - waitFor, - type ScopeType, - type ScopeTypeInfo, -} from "@cursorless/common"; +import { type ScopeType, type ScopeTypeInfo } from "@cursorless/common"; import { assert } from "chai"; import { isEqual } from "lodash-es"; import type { SinonSpy } from "sinon"; +import { waitFor } from "../waitFor"; export async function assertCalledWithScopeInfo( fake: SinonSpy<[scopeInfos: T[]], void>, diff --git a/packages/cursorless-vscode-e2e/src/suite/scopeVisualizer/scopeVisualizer.vscode.test.ts b/packages/cursorless-vscode-e2e/src/suite/scopeVisualizer/scopeVisualizer.vscode.test.ts index eca98b1570..ac5965eec1 100644 --- a/packages/cursorless-vscode-e2e/src/suite/scopeVisualizer/scopeVisualizer.vscode.test.ts +++ b/packages/cursorless-vscode-e2e/src/suite/scopeVisualizer/scopeVisualizer.vscode.test.ts @@ -1,14 +1,18 @@ +import { asyncSafety } from "@cursorless/common"; +import { getCursorlessApi } from "@cursorless/vscode-common"; import { commands } from "vscode"; import { endToEndTestSetup } from "../../endToEndTestSetup"; import { runBasicMultilineContentTest } from "./runBasicMultilineContentTest"; import { runBasicRemovalTest } from "./runBasicRemovalTest"; import { runNestedMultilineContentTest } from "./runNestedMultilineContentTest"; import { runUpdateTest } from "./runUpdateTest"; -import { asyncSafety } from "@cursorless/common"; suite("scope visualizer", async function () { endToEndTestSetup(this); + const { ide } = (await getCursorlessApi()).testHelpers!; + ide.configuration.mockConfiguration("decorationDebounceDelayMs", 0); + teardown(() => commands.executeCommand("cursorless.hideScopeVisualizer")); test( diff --git a/packages/cursorless-vscode-e2e/src/suite/tutorial/tutorial.vscode.test.ts b/packages/cursorless-vscode-e2e/src/suite/tutorial/tutorial.vscode.test.ts index de672b6da8..71d07b7e34 100644 --- a/packages/cursorless-vscode-e2e/src/suite/tutorial/tutorial.vscode.test.ts +++ b/packages/cursorless-vscode-e2e/src/suite/tutorial/tutorial.vscode.test.ts @@ -3,7 +3,6 @@ import { LATEST_VERSION, asyncSafety, getSnapshotForComparison, - waitFor, } from "@cursorless/common"; import { getRecordedTestsDirPath, loadFixture } from "@cursorless/node-common"; import { @@ -17,6 +16,7 @@ import path from "path"; import sinon from "sinon"; import { commands } from "vscode"; import { endToEndTestSetup } from "../../endToEndTestSetup"; +import { waitFor } from "../waitFor"; suite("tutorial", async function () { const { getSpy } = endToEndTestSetup(this); diff --git a/packages/cursorless-vscode-e2e/src/suite/waitFor.ts b/packages/cursorless-vscode-e2e/src/suite/waitFor.ts new file mode 100644 index 0000000000..4158b603bc --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/waitFor.ts @@ -0,0 +1,23 @@ +import { sleepWithBackoff } from "../endToEndTestSetup"; + +type Predicate = () => boolean | Promise; + +/** + * Waits for a predicate to become true, checking periodically with an + * increasing delay between checks. Returns true if the predicate becomes true + * within the given number of iterations, and false otherwise. + */ +export async function waitFor( + predicate: Predicate, + iterations = 20, +): Promise { + for (let i = 0; i < iterations; i++) { + if (await Promise.resolve(predicate())) { + return true; + } + + await sleepWithBackoff(25); + } + + return false; +} From 0e4d9992045c231cf2e2f5ec8f3758e8a7f60016 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Sat, 14 Mar 2026 23:33:45 +0100 Subject: [PATCH 04/12] Fixed notebook test --- .../intraCellSetSelection.vscode.test.ts | 38 +++++++++---------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/packages/cursorless-vscode-e2e/src/suite/intraCellSetSelection.vscode.test.ts b/packages/cursorless-vscode-e2e/src/suite/intraCellSetSelection.vscode.test.ts index cdeb0f7665..ae98c1c1ee 100644 --- a/packages/cursorless-vscode-e2e/src/suite/intraCellSetSelection.vscode.test.ts +++ b/packages/cursorless-vscode-e2e/src/suite/intraCellSetSelection.vscode.test.ts @@ -1,5 +1,6 @@ -import { LATEST_VERSION, type HatTokenMap } from "@cursorless/common"; +import { LATEST_VERSION, splitKey } from "@cursorless/common"; import { + getCellIndex, getCursorlessApi, openNewNotebookEditor, runCursorlessCommand, @@ -7,7 +8,6 @@ import { import assert from "assert"; import { window } from "vscode"; import { endToEndTestSetup } from "../endToEndTestSetup"; -import { waitFor } from "./waitFor"; // Check that setSelection is able to focus the correct cell suite("Within cell set selection", async function () { @@ -17,12 +17,22 @@ suite("Within cell set selection", async function () { }); async function runTest() { - const { hatTokenMap } = (await getCursorlessApi()).testHelpers!; + const { hatTokenMap, toVscodeEditor } = (await getCursorlessApi()) + .testHelpers!; - await openNewNotebookEditor(['"hello world"']); + const notebook = await openNewNotebookEditor(['"hello world"']); await hatTokenMap.allocateHats(); - await waitForHat(hatTokenMap, "default", "r"); + const hatMap = await hatTokenMap.getReadableMap(false); + const targetHat = hatMap.getEntries().find(([, token]) => { + const editor = toVscodeEditor(token.editor); + return ( + getCellIndex(notebook, editor.document) === 0 && token.text === "world" + ); + }); + + assert(targetHat != null, 'Expected a default hat for "world" in the cell'); + const { hatStyle, character } = splitKey(targetHat[0]); await runCursorlessCommand({ version: LATEST_VERSION, @@ -33,8 +43,8 @@ async function runTest() { type: "primitive", mark: { type: "decoratedSymbol", - symbolColor: "default", - character: "r", + symbolColor: hatStyle, + character, }, }, }, @@ -48,17 +58,3 @@ async function runTest() { assert.deepStrictEqual(editor.document.getText(editor.selection), "world"); } - -async function waitForHat( - hatTokenMap: HatTokenMap, - hatStyle: "default", - character: string, -) { - const success = await waitFor(async () => { - const readableHatMap = await hatTokenMap.getReadableMap(false); - return readableHatMap.getToken(hatStyle, character) != null; - }); - if (!success) { - assert.fail(`Timed out waiting for mark ${hatStyle} '${character}'`); - } -} From e6396b72b72d675558efd031be9956c78d706a28 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Sat, 14 Mar 2026 23:45:51 +0100 Subject: [PATCH 05/12] Clear mocks --- packages/common/src/ide/fake/FakeConfiguration.ts | 8 ++++++++ packages/cursorless-vscode-e2e/src/endToEndTestSetup.ts | 5 +++-- .../suite/scopeVisualizer/scopeVisualizer.vscode.test.ts | 4 +++- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/packages/common/src/ide/fake/FakeConfiguration.ts b/packages/common/src/ide/fake/FakeConfiguration.ts index 0638b8c6b9..56af3df8b0 100644 --- a/packages/common/src/ide/fake/FakeConfiguration.ts +++ b/packages/common/src/ide/fake/FakeConfiguration.ts @@ -63,6 +63,14 @@ export default class FakeConfiguration implements Configuration { this.notifier.notifyListeners(); } } + + clearMockConfiguration() { + this.mocks = { + ...CONFIGURATION_DEFAULTS, + }; + this.scopes = []; + this.notifier.notifyListeners(); + } } function scopeMatches( diff --git a/packages/cursorless-vscode-e2e/src/endToEndTestSetup.ts b/packages/cursorless-vscode-e2e/src/endToEndTestSetup.ts index 2dd36ace5f..88b3bef0ec 100644 --- a/packages/cursorless-vscode-e2e/src/endToEndTestSetup.ts +++ b/packages/cursorless-vscode-e2e/src/endToEndTestSetup.ts @@ -1,4 +1,4 @@ -import type { IDE } from "@cursorless/common"; +import type { IDE, NormalizedIDE } from "@cursorless/common"; import { shouldUpdateFixtures, sleep, SpyIDE } from "@cursorless/common"; import { getCursorlessApi, @@ -32,7 +32,7 @@ export function endToEndTestSetup( suite.timeout(timeout); suite.retries(retries); - let originalIde: IDE; + let originalIde: NormalizedIDE; let injectIde: (ide: IDE) => void; let spyIde: SpyIDE | undefined; @@ -51,6 +51,7 @@ export function endToEndTestSetup( teardown(() => { sinon.restore(); + originalIde.configuration.clearMockConfiguration(); injectIde(originalIde); resetReusableEditor(); }); diff --git a/packages/cursorless-vscode-e2e/src/suite/scopeVisualizer/scopeVisualizer.vscode.test.ts b/packages/cursorless-vscode-e2e/src/suite/scopeVisualizer/scopeVisualizer.vscode.test.ts index ac5965eec1..1575c3ad8c 100644 --- a/packages/cursorless-vscode-e2e/src/suite/scopeVisualizer/scopeVisualizer.vscode.test.ts +++ b/packages/cursorless-vscode-e2e/src/suite/scopeVisualizer/scopeVisualizer.vscode.test.ts @@ -13,7 +13,9 @@ suite("scope visualizer", async function () { const { ide } = (await getCursorlessApi()).testHelpers!; ide.configuration.mockConfiguration("decorationDebounceDelayMs", 0); - teardown(() => commands.executeCommand("cursorless.hideScopeVisualizer")); + teardown(() => { + commands.executeCommand("cursorless.hideScopeVisualizer"); + }); test( "basic multiline content", From a1e0eed7da525fd30b86bc5821f6fc2712cd7117 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Sat, 14 Mar 2026 23:50:59 +0100 Subject: [PATCH 06/12] Remove import --- .../suite/scopeVisualizer/scopeVisualizer.vscode.test.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/cursorless-vscode-e2e/src/suite/scopeVisualizer/scopeVisualizer.vscode.test.ts b/packages/cursorless-vscode-e2e/src/suite/scopeVisualizer/scopeVisualizer.vscode.test.ts index 1575c3ad8c..15a0e6659b 100644 --- a/packages/cursorless-vscode-e2e/src/suite/scopeVisualizer/scopeVisualizer.vscode.test.ts +++ b/packages/cursorless-vscode-e2e/src/suite/scopeVisualizer/scopeVisualizer.vscode.test.ts @@ -1,5 +1,4 @@ import { asyncSafety } from "@cursorless/common"; -import { getCursorlessApi } from "@cursorless/vscode-common"; import { commands } from "vscode"; import { endToEndTestSetup } from "../../endToEndTestSetup"; import { runBasicMultilineContentTest } from "./runBasicMultilineContentTest"; @@ -10,12 +9,7 @@ import { runUpdateTest } from "./runUpdateTest"; suite("scope visualizer", async function () { endToEndTestSetup(this); - const { ide } = (await getCursorlessApi()).testHelpers!; - ide.configuration.mockConfiguration("decorationDebounceDelayMs", 0); - - teardown(() => { - commands.executeCommand("cursorless.hideScopeVisualizer"); - }); + teardown(() => commands.executeCommand("cursorless.hideScopeVisualizer")); test( "basic multiline content", From 3dbdda42577320aeceeb075d29228e69b3ac1d05 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Sat, 14 Mar 2026 23:51:26 +0100 Subject: [PATCH 07/12] Properly await --- .../src/suite/scopeVisualizer/scopeVisualizer.vscode.test.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/cursorless-vscode-e2e/src/suite/scopeVisualizer/scopeVisualizer.vscode.test.ts b/packages/cursorless-vscode-e2e/src/suite/scopeVisualizer/scopeVisualizer.vscode.test.ts index 15a0e6659b..ac5965eec1 100644 --- a/packages/cursorless-vscode-e2e/src/suite/scopeVisualizer/scopeVisualizer.vscode.test.ts +++ b/packages/cursorless-vscode-e2e/src/suite/scopeVisualizer/scopeVisualizer.vscode.test.ts @@ -1,4 +1,5 @@ import { asyncSafety } from "@cursorless/common"; +import { getCursorlessApi } from "@cursorless/vscode-common"; import { commands } from "vscode"; import { endToEndTestSetup } from "../../endToEndTestSetup"; import { runBasicMultilineContentTest } from "./runBasicMultilineContentTest"; @@ -9,6 +10,9 @@ import { runUpdateTest } from "./runUpdateTest"; suite("scope visualizer", async function () { endToEndTestSetup(this); + const { ide } = (await getCursorlessApi()).testHelpers!; + ide.configuration.mockConfiguration("decorationDebounceDelayMs", 0); + teardown(() => commands.executeCommand("cursorless.hideScopeVisualizer")); test( From 2f5bfba3fcf8f7286eba51b46acf16017dbbc8e9 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Sun, 15 Mar 2026 00:01:27 +0100 Subject: [PATCH 08/12] Clean up --- .../suite/scopeVisualizer/scopeVisualizer.vscode.test.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/cursorless-vscode-e2e/src/suite/scopeVisualizer/scopeVisualizer.vscode.test.ts b/packages/cursorless-vscode-e2e/src/suite/scopeVisualizer/scopeVisualizer.vscode.test.ts index ac5965eec1..43e4d15b8f 100644 --- a/packages/cursorless-vscode-e2e/src/suite/scopeVisualizer/scopeVisualizer.vscode.test.ts +++ b/packages/cursorless-vscode-e2e/src/suite/scopeVisualizer/scopeVisualizer.vscode.test.ts @@ -7,11 +7,13 @@ import { runBasicRemovalTest } from "./runBasicRemovalTest"; import { runNestedMultilineContentTest } from "./runNestedMultilineContentTest"; import { runUpdateTest } from "./runUpdateTest"; -suite("scope visualizer", async function () { +suite("scope visualizer", function () { endToEndTestSetup(this); - const { ide } = (await getCursorlessApi()).testHelpers!; - ide.configuration.mockConfiguration("decorationDebounceDelayMs", 0); + setup(async () => { + const { ide } = (await getCursorlessApi()).testHelpers!; + ide.configuration.mockConfiguration("decorationDebounceDelayMs", 0); + }); teardown(() => commands.executeCommand("cursorless.hideScopeVisualizer")); From d85b7c34b9c9a9ea83c309c3375c68bf9401797a Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Sun, 15 Mar 2026 00:08:06 +0100 Subject: [PATCH 09/12] fix --- packages/cursorless-vscode-e2e/src/endToEndTestSetup.ts | 5 ++++- .../src/suite/scopeVisualizer/scopeVisualizer.vscode.test.ts | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/cursorless-vscode-e2e/src/endToEndTestSetup.ts b/packages/cursorless-vscode-e2e/src/endToEndTestSetup.ts index 88b3bef0ec..dc47bf9e6e 100644 --- a/packages/cursorless-vscode-e2e/src/endToEndTestSetup.ts +++ b/packages/cursorless-vscode-e2e/src/endToEndTestSetup.ts @@ -51,8 +51,11 @@ export function endToEndTestSetup( teardown(() => { sinon.restore(); - originalIde.configuration.clearMockConfiguration(); injectIde(originalIde); + }); + + suiteTeardown(() => { + originalIde.configuration.clearMockConfiguration(); resetReusableEditor(); }); diff --git a/packages/cursorless-vscode-e2e/src/suite/scopeVisualizer/scopeVisualizer.vscode.test.ts b/packages/cursorless-vscode-e2e/src/suite/scopeVisualizer/scopeVisualizer.vscode.test.ts index 43e4d15b8f..4452ffba4e 100644 --- a/packages/cursorless-vscode-e2e/src/suite/scopeVisualizer/scopeVisualizer.vscode.test.ts +++ b/packages/cursorless-vscode-e2e/src/suite/scopeVisualizer/scopeVisualizer.vscode.test.ts @@ -10,7 +10,7 @@ import { runUpdateTest } from "./runUpdateTest"; suite("scope visualizer", function () { endToEndTestSetup(this); - setup(async () => { + suiteSetup(async () => { const { ide } = (await getCursorlessApi()).testHelpers!; ide.configuration.mockConfiguration("decorationDebounceDelayMs", 0); }); From 864afcda0c333dc2827b31f6107b2e1f92ea7eb2 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Sun, 15 Mar 2026 00:10:14 +0100 Subject: [PATCH 10/12] Small fix --- packages/cursorless-vscode-e2e/src/endToEndTestSetup.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/cursorless-vscode-e2e/src/endToEndTestSetup.ts b/packages/cursorless-vscode-e2e/src/endToEndTestSetup.ts index dc47bf9e6e..001de17e10 100644 --- a/packages/cursorless-vscode-e2e/src/endToEndTestSetup.ts +++ b/packages/cursorless-vscode-e2e/src/endToEndTestSetup.ts @@ -46,7 +46,6 @@ export function endToEndTestSetup( testHelpers.commandServerApi.setFocusedElementType(undefined); spyIde = new SpyIDE(originalIde); injectIde(spyIde); - resetReusableEditor(); }); teardown(() => { From a55f662e2372ba783b8f83af0b331945a323ce03 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Sun, 15 Mar 2026 00:18:08 +0100 Subject: [PATCH 11/12] Another update --- packages/common/src/ide/fake/FakeConfiguration.ts | 8 -------- packages/common/src/ide/normalized/NormalizedIDE.ts | 1 + packages/cursorless-vscode-e2e/src/endToEndTestSetup.ts | 1 - .../suite/scopeVisualizer/scopeVisualizer.vscode.test.ts | 6 ------ 4 files changed, 1 insertion(+), 15 deletions(-) diff --git a/packages/common/src/ide/fake/FakeConfiguration.ts b/packages/common/src/ide/fake/FakeConfiguration.ts index 56af3df8b0..0638b8c6b9 100644 --- a/packages/common/src/ide/fake/FakeConfiguration.ts +++ b/packages/common/src/ide/fake/FakeConfiguration.ts @@ -63,14 +63,6 @@ export default class FakeConfiguration implements Configuration { this.notifier.notifyListeners(); } } - - clearMockConfiguration() { - this.mocks = { - ...CONFIGURATION_DEFAULTS, - }; - this.scopes = []; - this.notifier.notifyListeners(); - } } function scopeMatches( diff --git a/packages/common/src/ide/normalized/NormalizedIDE.ts b/packages/common/src/ide/normalized/NormalizedIDE.ts index 7f54a05bb4..e475ceb761 100644 --- a/packages/common/src/ide/normalized/NormalizedIDE.ts +++ b/packages/common/src/ide/normalized/NormalizedIDE.ts @@ -41,6 +41,7 @@ export class NormalizedIDE extends PassthroughIDE { { wordSeparators: ["_", "-"] }, true, ); + this.configuration.mockConfiguration("decorationDebounceDelayMs", 0); this.configuration.mockConfiguration("experimental", { hatStability: this.configuration.getOwnConfiguration( "experimental.hatStability", diff --git a/packages/cursorless-vscode-e2e/src/endToEndTestSetup.ts b/packages/cursorless-vscode-e2e/src/endToEndTestSetup.ts index 001de17e10..4d13c122a8 100644 --- a/packages/cursorless-vscode-e2e/src/endToEndTestSetup.ts +++ b/packages/cursorless-vscode-e2e/src/endToEndTestSetup.ts @@ -54,7 +54,6 @@ export function endToEndTestSetup( }); suiteTeardown(() => { - originalIde.configuration.clearMockConfiguration(); resetReusableEditor(); }); diff --git a/packages/cursorless-vscode-e2e/src/suite/scopeVisualizer/scopeVisualizer.vscode.test.ts b/packages/cursorless-vscode-e2e/src/suite/scopeVisualizer/scopeVisualizer.vscode.test.ts index 4452ffba4e..a24020e947 100644 --- a/packages/cursorless-vscode-e2e/src/suite/scopeVisualizer/scopeVisualizer.vscode.test.ts +++ b/packages/cursorless-vscode-e2e/src/suite/scopeVisualizer/scopeVisualizer.vscode.test.ts @@ -1,5 +1,4 @@ import { asyncSafety } from "@cursorless/common"; -import { getCursorlessApi } from "@cursorless/vscode-common"; import { commands } from "vscode"; import { endToEndTestSetup } from "../../endToEndTestSetup"; import { runBasicMultilineContentTest } from "./runBasicMultilineContentTest"; @@ -10,11 +9,6 @@ import { runUpdateTest } from "./runUpdateTest"; suite("scope visualizer", function () { endToEndTestSetup(this); - suiteSetup(async () => { - const { ide } = (await getCursorlessApi()).testHelpers!; - ide.configuration.mockConfiguration("decorationDebounceDelayMs", 0); - }); - teardown(() => commands.executeCommand("cursorless.hideScopeVisualizer")); test( From 12f92229115de8afa951ca60e88c0a3930e1c2fb Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Sun, 15 Mar 2026 00:22:47 +0100 Subject: [PATCH 12/12] Another clean up --- packages/common/src/ide/normalized/NormalizedIDE.ts | 1 - .../suite/scopeVisualizer/scopeVisualizer.vscode.test.ts | 6 ++++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/common/src/ide/normalized/NormalizedIDE.ts b/packages/common/src/ide/normalized/NormalizedIDE.ts index e475ceb761..7f54a05bb4 100644 --- a/packages/common/src/ide/normalized/NormalizedIDE.ts +++ b/packages/common/src/ide/normalized/NormalizedIDE.ts @@ -41,7 +41,6 @@ export class NormalizedIDE extends PassthroughIDE { { wordSeparators: ["_", "-"] }, true, ); - this.configuration.mockConfiguration("decorationDebounceDelayMs", 0); this.configuration.mockConfiguration("experimental", { hatStability: this.configuration.getOwnConfiguration( "experimental.hatStability", diff --git a/packages/cursorless-vscode-e2e/src/suite/scopeVisualizer/scopeVisualizer.vscode.test.ts b/packages/cursorless-vscode-e2e/src/suite/scopeVisualizer/scopeVisualizer.vscode.test.ts index a24020e947..4452ffba4e 100644 --- a/packages/cursorless-vscode-e2e/src/suite/scopeVisualizer/scopeVisualizer.vscode.test.ts +++ b/packages/cursorless-vscode-e2e/src/suite/scopeVisualizer/scopeVisualizer.vscode.test.ts @@ -1,4 +1,5 @@ import { asyncSafety } from "@cursorless/common"; +import { getCursorlessApi } from "@cursorless/vscode-common"; import { commands } from "vscode"; import { endToEndTestSetup } from "../../endToEndTestSetup"; import { runBasicMultilineContentTest } from "./runBasicMultilineContentTest"; @@ -9,6 +10,11 @@ import { runUpdateTest } from "./runUpdateTest"; suite("scope visualizer", function () { endToEndTestSetup(this); + suiteSetup(async () => { + const { ide } = (await getCursorlessApi()).testHelpers!; + ide.configuration.mockConfiguration("decorationDebounceDelayMs", 0); + }); + teardown(() => commands.executeCommand("cursorless.hideScopeVisualizer")); test(