From 1f621d4c9e9b797dfcf11de1d98d10a3a8280d15 Mon Sep 17 00:00:00 2001 From: Charles Ouimet Date: Wed, 13 May 2026 12:55:28 -0400 Subject: [PATCH 1/7] [issues/557] Automate 33 assisted integration tests via `closeQuickOpen` dismissal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary `workbench.action.closeQuickOpen` can programmatically dismiss VS Code QuickPicks and InputBoxes from tests — the human was only needed to press Escape, not to select items. This converts 33 `[assisted]` tests to fully automated across 9 test files, reducing the assisted count from 147 to 112 and increasing automated from 86 to 119. Also removes the now-dead `{ assisted: true }` option from `standardSuite` since it only printed a cosmetic banner — test filtering is handled by the `[assisted]` prefix in test names. ## Changes - Added `openAndDismiss(command)` helper that fires a command, settles, dismisses via `closeQuickOpen`, and awaits the promise — a one-liner replacing every `waitForHuman` Escape prompt - Converted 33 `[assisted]` tests to automated across 9 files: `terminalPicker.test.ts` (12), `filePicker.test.ts` (8), `statusBarMenu.test.ts` (4), `coreSendCommands.test.ts` (3), `customAiAssistants.test.ts` (2), `bindToDestination.test.ts` (1), `editorBindingValidation.test.ts` (1), `clipboardPreservation.test.ts` (1), `goToRangeLink.test.ts` (1) - Removed `StandardSuiteOptions` interface and `assisted` parameter from `standardSuite` — all 33 call sites are now two-argument form - Removed `printAssistedBanner` function from `assistedTestHelper.ts` (dead code after option removal) - Updated TESTING.md to document the `closeQuickOpen` dismissal pattern and `openAndDismiss` helper - Flipped 33 QA YAML entries from `automated: assisted` to `automated: true` ## Test Plan - [x] All 1874 unit tests pass (98.51% coverage) - [x] QA validator passes (119 automated / 112 assisted / 4 false) - [x] `test:release:automated` — 161 of 173 automated tests pass (12 pre-existing flaky failures in smartPadding, textEditorDestination, sendFilePath — unrelated to this change) ## Related - Closes https://github.com/couimet/rangeLink/issues/557 --- .../rangelink-vscode-extension/TESTING.md | 24 +++++-- .../qa/qa-test-cases-v1.1.0.yaml | 66 +++++++++---------- .../helpers/assistedTestHelper.ts | 17 +---- .../__integration-tests__/helpers/index.ts | 9 +-- .../helpers/standardSuite.ts | 18 +---- .../__integration-tests__/helpers/testEnv.ts | 12 ++++ .../suite/bindToDestination.test.ts | 8 ++- .../suite/clipboardPreservation.test.ts | 25 +++---- .../suite/commandRegistration.test.ts | 2 +- .../suite/contextMenuEditorContent.test.ts | 2 +- .../suite/contextMenuEditorTab.test.ts | 2 +- .../suite/contextMenuExplorer.test.ts | 2 +- .../suite/contextMenuTerminal.test.ts | 2 +- .../suite/coreSendCommands.test.ts | 38 +++-------- .../suite/customAiAssistants.test.ts | 34 +++------- .../suite/dirtyBufferWarning.test.ts | 4 +- .../suite/editorBindingValidation.test.ts | 24 ++----- .../suite/filePathNavigation.test.ts | 2 +- .../suite/filePicker.test.ts | 36 +++++----- .../suite/filenameOnlyNavigation.test.ts | 2 +- .../suite/goToRangeLink.test.ts | 8 ++- .../suite/linkGeneration.test.ts | 4 +- .../suite/navigationClamping.test.ts | 2 +- .../suite/navigationPrecision.test.ts | 2 +- .../suite/navigationToastSettings.test.ts | 2 +- .../suite/platformKeybindings.test.ts | 2 +- .../suite/releaseNotifier.test.ts | 2 +- .../suite/sendFilePath.test.ts | 2 +- .../suite/smartPadding.test.ts | 2 +- .../suite/statusBarMenu.test.ts | 34 +++++----- .../suite/terminalPicker.test.ts | 52 ++++++++------- .../suite/textEditorDestination.test.ts | 2 +- .../suite/unbind.test.ts | 2 +- .../suite/untitledNavigation.test.ts | 2 +- 34 files changed, 198 insertions(+), 249 deletions(-) diff --git a/packages/rangelink-vscode-extension/TESTING.md b/packages/rangelink-vscode-extension/TESTING.md index a0023bae..607a7b76 100644 --- a/packages/rangelink-vscode-extension/TESTING.md +++ b/packages/rangelink-vscode-extension/TESTING.md @@ -75,13 +75,29 @@ Integration tests run inside a real VS Code process via `@vscode/test-cli`. They pnpm test:release ``` -### QuickPick limitation +### QuickPick and InputBox dismissal -VS Code's extension host test runner provides no API to interact with QuickPick UI — tests cannot programmatically select items, dismiss pickers, or read picker contents. A QuickPick that opens during a test **will stall the test indefinitely** because it blocks the command from completing, and there is no way to dismiss it from test code. +VS Code's extension host test runner provides no API to programmatically select QuickPick items or interact with dialogs. However, `workbench.action.closeQuickOpen` can programmatically dismiss QuickPicks and InputBoxes — meaning tests that open a picker, read its logged content, and dismiss it **can now be fully automated**. -**Workaround — command bypass:** Many TCs that involve a QuickPick as a means to an end (e.g., "bind via picker, verify toast") can be automated by calling the underlying command directly (`rangelink.bindToTerminalHere`, `rangelink.bindToTextEditorHere`) to bypass the picker entirely, then asserting the outcome via log-based UI assertions. +**`openAndDismiss` helper:** The pattern for automated picker-open-and-dismiss is encapsulated in `openAndDismiss(command)`: -**What cannot be fully automated:** TCs that verify picker content itself (item ordering, badges, grouping, placeholder text) or dialog interaction (confirmation buttons, cancel behavior) require a human to open/dismiss the picker. Mark these `automated: assisted` in the QA YAML — the test automates setup and validates content via log-based QuickPick assertions while the human performs the mechanical UI action. See [Assisted mode](#assisted-mode-assisted-tests) below. Only TCs that genuinely cannot be tested even with human-in-the-loop assistance should remain `automated: false`. +```typescript +// Fires the command (which opens a QuickPick), waits for render + log emission, +// dismisses with closeQuickOpen, then settles. +await openAndDismiss(CMD_BIND_TO_DESTINATION); +const items = extractQuickPickItemsLogged(logCapture.getLinesSince('before-test')); +// assert on items as usual +``` + +**Workaround — command bypass:** TCs that use a picker as a means to an end (e.g., "bind via picker, verify toast") can be automated by calling the underlying command directly (`rangelink.bindToTerminalHere`, `rangelink.bindToTextEditorHere`) to bypass the picker entirely. + +**What still requires assisted mode:** TCs that need to: + +- Select a specific item from a picker (closeQuickOpen only dismisses, it cannot choose) +- Navigate a multi-picker flow (select item in picker A → picker B opens → verify B's content) +- Verify dialog interactions (confirmation buttons with Yes/No) + +Mark these `automated: assisted` in the QA YAML. See [Assisted mode](#assisted-mode-assisted-tests) below. Only TCs that genuinely cannot be tested even with human-in-the-loop assistance should remain `automated: false`. See https://github.com/couimet/rangeLink/issues/483 for the full triage of automatable vs manual TCs. diff --git a/packages/rangelink-vscode-extension/qa/qa-test-cases-v1.1.0.yaml b/packages/rangelink-vscode-extension/qa/qa-test-cases-v1.1.0.yaml index 4f26218f..e385200c 100644 --- a/packages/rangelink-vscode-extension/qa/qa-test-cases-v1.1.0.yaml +++ b/packages/rangelink-vscode-extension/qa/qa-test-cases-v1.1.0.yaml @@ -40,7 +40,7 @@ test_cases: steps: - 'Click the $(link) RangeLink item in the status bar' expected_result: "A QuickPick menu opens titled 'RangeLink Menu'. Menu items are visible." - automated: assisted + automated: true - id: status-bar-menu-003 feature: 'R-M Status Bar Menu' @@ -50,7 +50,7 @@ test_cases: steps: - 'Press Cmd+R Cmd+M' expected_result: 'Same QuickPick menu opens as clicking the status bar item.' - automated: assisted + automated: true - id: status-bar-menu-004 labels: @@ -73,7 +73,7 @@ test_cases: - 'Open the R-M menu (keybinding or status bar click)' - 'Observe the menu items' expected_result: "Menu contains 'Jump to Bound Destination' with the destination name shown. 'Unbind Destination' is also visible." - automated: assisted + automated: true - id: status-bar-menu-006 feature: 'R-M Status Bar Menu' @@ -84,7 +84,7 @@ test_cases: - 'Open the R-M menu' - 'Observe the first item' expected_result: "Menu shows a bind item that opens the destination picker. 'Jump to Bound Destination' is absent." - automated: assisted + automated: true - id: status-bar-menu-007 feature: 'R-M Status Bar Menu' @@ -198,7 +198,7 @@ test_cases: - 'Open R-D picker' - 'Press Escape without selecting anything' expected_result: 'Picker closes. Binding state is unchanged.' - automated: assisted + automated: true - id: bind-to-destination-011 labels: @@ -236,7 +236,7 @@ test_cases: - 'Open R-D destination picker (Cmd+R Cmd+D or Ctrl+R Ctrl+D)' - 'Observe the picker items' expected_result: 'Picker shows inline terminal items up to maxInline limit, "More terminals..." overflow item, inline file items (current-in-group), "More files..." overflow item, and standard command items (Go to Link, Show Version Info).' - automated: assisted + automated: true - id: bind-to-destination-014 feature: 'R-D Bind to Destination' @@ -275,7 +275,7 @@ test_cases: - "Click a terminal tab (e.g., 'Gamma') to make it the active terminal" - 'Open the terminal picker via R-D or R-M' expected_result: "'Gamma' shows an 'active' badge in the picker. Other terminals have no badge." - automated: assisted + automated: true - id: terminal-picker-002 feature: 'Terminal Picker' @@ -286,7 +286,7 @@ test_cases: steps: - 'Open the terminal picker' expected_result: "'Beta' shows a 'bound' badge. 'Gamma' shows an 'active' badge." - automated: assisted + automated: true - id: terminal-picker-003 feature: 'Terminal Picker' @@ -297,7 +297,7 @@ test_cases: - "Click the 'Beta' terminal tab to make it active" - 'Open the terminal picker' expected_result: "'Beta' shows a combined 'bound · active' badge (both indicators present on the same item)." - automated: assisted + automated: true - id: terminal-picker-004 feature: 'Terminal Picker' @@ -308,7 +308,7 @@ test_cases: steps: - 'Open the terminal picker' expected_result: "'Beta' is the first item in the terminal list, regardless of the order terminals were opened." - automated: assisted + automated: true - id: terminal-picker-005 feature: 'Terminal Picker' @@ -319,7 +319,7 @@ test_cases: steps: - 'Open the terminal picker' expected_result: "'Beta' (bound) is first. 'Gamma' (active, not bound) is second. Remaining terminals follow in any order." - automated: assisted + automated: true - id: terminal-picker-006 labels: @@ -332,7 +332,7 @@ test_cases: steps: - 'Open the terminal picker' expected_result: 'Hidden background terminals do not appear in the picker. Only user-visible terminals are listed.' - automated: assisted + automated: true - id: terminal-picker-007 feature: 'Terminal Picker' @@ -343,7 +343,7 @@ test_cases: steps: - 'Open R-D picker' expected_result: "All terminals appear inline. No 'More terminals...' item is visible." - automated: assisted + automated: true - id: terminal-picker-008 feature: 'Terminal Picker' @@ -354,7 +354,7 @@ test_cases: steps: - 'Open R-D picker or terminal sub-section of R-M' expected_result: "Only 3 terminals shown inline. A 'More terminals...' item appears at the bottom of the terminal section." - automated: assisted + automated: true - id: terminal-picker-009 feature: 'Terminal Picker' @@ -385,7 +385,7 @@ test_cases: - 'Set rangelink.terminalPicker.maxInline = 2 in VS Code settings' - 'Open R-D picker' expected_result: "Only 2 terminals shown inline. 'More terminals...' appears. Previously all 4 were inline with higher maxInline." - automated: assisted + automated: true - id: terminal-picker-012 feature: 'Terminal Picker' @@ -397,7 +397,7 @@ test_cases: - 'Open R-M menu' - 'Observe the terminal entries in the menu' expected_result: 'Terminal picker items appear inline within the R-M menu. Selecting one binds that terminal.' - automated: assisted + automated: true - id: terminal-picker-013 feature: 'Terminal Picker' @@ -408,7 +408,7 @@ test_cases: - 'Press Cmd+R Cmd+D' - 'Observe that terminal entries are part of the combined destination picker' expected_result: 'Terminal entries appear in the R-D picker with the same badges and ordering as when accessed from R-M.' - automated: assisted + automated: true # --------------------------------------------------------------------------- # Section 4 — File Picker @@ -423,7 +423,7 @@ test_cases: steps: - 'Open R-D picker' expected_result: "src/utils/helper.ts appears at the top of the file list with a 'bound' badge." - automated: assisted + automated: true - id: file-picker-002 feature: 'File Picker' @@ -436,7 +436,7 @@ test_cases: - 'Click src/index.ts to make it active in tab group 1' - 'Open R-D picker' expected_result: 'src/index.ts appears before other files in tab group 1 within the picker.' - automated: assisted + automated: true - id: file-picker-003 feature: 'File Picker' @@ -447,7 +447,7 @@ test_cases: - 'Open both files in the editor' - 'Open R-D picker (file section)' expected_result: 'Both index.ts files appear with enough path context to distinguish them (e.g., utils/index.ts and types/index.ts).' - automated: assisted + automated: true - id: file-picker-004 feature: 'File Picker' @@ -457,7 +457,7 @@ test_cases: steps: - 'Open R-D picker' expected_result: "The open file entries appear inline in the picker. No 'More files...' item." - automated: assisted + automated: true - id: file-picker-005 feature: 'File Picker' @@ -467,7 +467,7 @@ test_cases: steps: - 'Open R-D picker' expected_result: "Only the inline limit of files is shown. A 'More files...' item appears at the bottom of the file section." - automated: assisted + automated: true - id: file-picker-006 feature: 'File Picker' @@ -510,7 +510,7 @@ test_cases: steps: - 'Open R-M menu' expected_result: 'File entries appear inline within the R-M menu. Selecting one binds the file as the destination.' - automated: assisted + automated: true - id: file-picker-010 feature: 'File Picker' @@ -532,7 +532,7 @@ test_cases: steps: - 'Open R-D destination picker' expected_result: 'All three files appear with unique disambiguating path fragments that distinguish their locations.' - automated: assisted + automated: true - id: file-picker-012 feature: 'File Picker' @@ -542,7 +542,7 @@ test_cases: steps: - 'Open R-D destination picker' expected_result: 'File descriptions contain only badges and tab group labels — no disambiguation path prefix.' - automated: assisted + automated: true # --------------------------------------------------------------------------- # Section 5 — Clipboard Preservation @@ -697,7 +697,7 @@ test_cases: - 'Press Escape to dismiss without selecting anything' - 'Press Cancel — test reads clipboard and asserts it still equals the sentinel' expected_result: 'Clipboard contains the sentinel value. assertClipboardRestored passes — no preserve cycle was triggered because the operation was cancelled before any transport.' - automated: assisted + automated: true - id: clipboard-preservation-010 labels: @@ -1730,7 +1730,7 @@ test_cases: steps: - 'Press Cmd+R Cmd+G' expected_result: 'An input box appears with placeholder text indicating it expects a RangeLink (e.g., path/to/file.ts#L10).' - automated: assisted + automated: true # go-to-link-002 removed — Ctrl+R Ctrl+G on Win/Linux tests VS Code's keybinding # resolution, not RangeLink behavior. Both bindings are declared in package.json; @@ -1911,7 +1911,7 @@ test_cases: - 'Confirm the .png file is NOT listed' - 'Press Escape to dismiss' expected_result: 'The .png file does not appear in the picker despite being open. The .txt file does appear. The isBinaryFile filter in the R-D picker is the upstream defense; runtime rejection in PasteDestinationManager.bindTextEditor is a defense-in-depth layer covered by unit tests.' - automated: assisted + automated: true - id: editor-binding-validation-005 feature: 'Editor Binding Validation' @@ -2242,7 +2242,7 @@ test_cases: - 'Press Cmd+R Cmd+L' - 'Observe — destination picker should appear' expected_result: 'Destination picker opens. Selecting a destination binds it and sends the RangeLink. Escape dismisses silently with no clipboard write.' - automated: assisted + automated: true - id: core-send-commands-r-p-001 labels: @@ -2258,7 +2258,7 @@ test_cases: - "Select 'RangeLink: Send Portable Link'" - 'Observe — destination picker should appear' expected_result: 'Destination picker opens (not a silent clipboard fallback). Selecting a destination binds it and sends the portable link.' - automated: assisted + automated: true - id: core-send-commands-r-v-001 labels: @@ -2273,7 +2273,7 @@ test_cases: - 'Select text' - "Open Command Palette, select 'RangeLink: Send Selected Text'" expected_result: 'Destination picker opens. Selecting a destination sends the selected text to it.' - automated: assisted + automated: true # --------------------------------------------------------------------------- # Section — Text Editor Destination @@ -2820,7 +2820,7 @@ test_cases: - 'Press Cmd+R Cmd+D to open destination picker' - 'Observe the picker items' expected_result: 'The custom AI assistant appears in the picker with its configured extensionName, after the built-in destinations' - automated: assisted + automated: true - id: custom-ai-assistant-004 labels: @@ -2872,7 +2872,7 @@ test_cases: - 'Press Cmd+R Cmd+D to open destination picker' - 'Observe order of custom AI assistants in the picker' expected_result: 'Custom AI assistants appear after built-in destinations, in the same order as defined in settings.json' - automated: assisted + automated: true - id: custom-ai-assistant-008 labels: diff --git a/packages/rangelink-vscode-extension/src/__integration-tests__/helpers/assistedTestHelper.ts b/packages/rangelink-vscode-extension/src/__integration-tests__/helpers/assistedTestHelper.ts index db2f1e9c..832b1196 100644 --- a/packages/rangelink-vscode-extension/src/__integration-tests__/helpers/assistedTestHelper.ts +++ b/packages/rangelink-vscode-extension/src/__integration-tests__/helpers/assistedTestHelper.ts @@ -4,22 +4,7 @@ import * as vscode from 'vscode'; const nodeConsole = new Console(process.stdout, process.stderr); -const ASSISTED_BANNER_WIDTH = 60; -const BANNER_LINE = '═'.repeat(ASSISTED_BANNER_WIDTH); -const SECTION_LINE = '─'.repeat(ASSISTED_BANNER_WIDTH); - -/** - * Prints a visible banner at suite start explaining the assisted mode workflow. - * Call this in suiteSetup() for any suite containing [assisted] tests. - */ -export const printAssistedBanner = (): void => { - nodeConsole.log(`\n${BANNER_LINE}`); - nodeConsole.log('ASSISTED TEST MODE'); - nodeConsole.log('Tests tagged [assisted] will pause for human interaction.'); - nodeConsole.log('Instructions appear as a persistent VS Code notification.'); - nodeConsole.log('Click Cancel on the notification when you have completed the action.'); - nodeConsole.log(BANNER_LINE); -}; +const SECTION_LINE = '─'.repeat(60); /** * Pauses the test until the human completes a UI action. diff --git a/packages/rangelink-vscode-extension/src/__integration-tests__/helpers/index.ts b/packages/rangelink-vscode-extension/src/__integration-tests__/helpers/index.ts index 8759a6a8..9a5ca9ef 100644 --- a/packages/rangelink-vscode-extension/src/__integration-tests__/helpers/index.ts +++ b/packages/rangelink-vscode-extension/src/__integration-tests__/helpers/index.ts @@ -1,9 +1,4 @@ -export { - type HumanVerdict, - printAssistedBanner, - waitForHuman, - waitForHumanVerdict, -} from './assistedTestHelper'; +export { type HumanVerdict, waitForHuman, waitForHumanVerdict } from './assistedTestHelper'; export { assertTerminalBufferContains, assertTerminalBufferEquals, @@ -49,12 +44,12 @@ export { createLogger } from './logHelpers'; export { navigateViaHandleLinkClick } from './navigationHelpers'; export { loadSettingsProfile, resetRangelinkSettings } from './settingsHelpers'; export { standardSuite } from './standardSuite'; -export type { StandardSuiteOptions } from './standardSuite'; export { createAndBindTerminal, createTerminal, findTerminalItems } from './terminalHelpers'; export { activateExtension, getExtensionVersion, getWorkspaceRoot, + openAndDismiss, POLL_INTERVAL_MS, POLL_TIMEOUT_MS, settle, diff --git a/packages/rangelink-vscode-extension/src/__integration-tests__/helpers/standardSuite.ts b/packages/rangelink-vscode-extension/src/__integration-tests__/helpers/standardSuite.ts index 81a7e954..0c880b51 100644 --- a/packages/rangelink-vscode-extension/src/__integration-tests__/helpers/standardSuite.ts +++ b/packages/rangelink-vscode-extension/src/__integration-tests__/helpers/standardSuite.ts @@ -1,30 +1,14 @@ -import { printAssistedBanner } from './assistedTestHelper'; import { closeAllEditors } from './fileHelpers'; import { createLogger } from './logHelpers'; import { resetRangelinkSettings } from './settingsHelpers'; import { activateExtension, settle } from './testEnv'; -export interface StandardSuiteOptions { - assisted?: boolean; -} - -export const standardSuite = ( - name: string, - options: StandardSuiteOptions, - fn: (log: (msg: string) => void) => void, -): void => { +export const standardSuite = (name: string, fn: (log: (msg: string) => void) => void): void => { suite(name, () => { const log = createLogger(name); suiteSetup(async () => { await activateExtension(); - const assisted = options.assisted ?? false; - log( - `[DEBUG] standardSuite.assisted=${assisted} (${options.assisted === undefined ? 'default' : 'provided'})`, - ); - if (assisted) { - printAssistedBanner(); - } }); setup(async () => { diff --git a/packages/rangelink-vscode-extension/src/__integration-tests__/helpers/testEnv.ts b/packages/rangelink-vscode-extension/src/__integration-tests__/helpers/testEnv.ts index 14cef8a7..44e5d1eb 100644 --- a/packages/rangelink-vscode-extension/src/__integration-tests__/helpers/testEnv.ts +++ b/packages/rangelink-vscode-extension/src/__integration-tests__/helpers/testEnv.ts @@ -29,3 +29,15 @@ export const getExtensionVersion = (): string => { export const settle = (ms: number = SETTLE_MS): Promise => new Promise((resolve) => setTimeout(resolve, ms)); + +/** + * Open a QuickPick or InputBox via command, dismiss it with closeQuickOpen, and return after + * the promise settles. The caller inspects log-captured items after this resolves. + */ +export const openAndDismiss = async (command: string): Promise => { + const promise = vscode.commands.executeCommand(command); + await settle(); + await vscode.commands.executeCommand('workbench.action.closeQuickOpen'); + await promise; + await settle(); +}; diff --git a/packages/rangelink-vscode-extension/src/__integration-tests__/suite/bindToDestination.test.ts b/packages/rangelink-vscode-extension/src/__integration-tests__/suite/bindToDestination.test.ts index 15372469..45a02384 100644 --- a/packages/rangelink-vscode-extension/src/__integration-tests__/suite/bindToDestination.test.ts +++ b/packages/rangelink-vscode-extension/src/__integration-tests__/suite/bindToDestination.test.ts @@ -3,6 +3,7 @@ import * as path from 'node:path'; import * as vscode from 'vscode'; +import { CMD_BIND_TO_DESTINATION } from '../../constants/commandIds'; import { assertNoStatusBarMsgLogged, assertStatusBarMsgLogged, @@ -15,6 +16,7 @@ import { findTerminalItems, findTestItemsByPrefix, getLogCapture, + openAndDismiss, parseQuickPickItemsFromLogLine, settle, standardSuite, @@ -22,7 +24,7 @@ import { waitForHumanVerdict, } from '../helpers'; -standardSuite('R-D Bind to Destination', { assisted: true }, (log) => { +standardSuite('R-D Bind to Destination', (log) => { const terminals: vscode.Terminal[] = []; const tmpFileUris: vscode.Uri[] = []; @@ -289,7 +291,7 @@ standardSuite('R-D Bind to Destination', { assisted: true }, (log) => { log('✓ No rebind toast — original binding preserved (human verdict + state invariant)'); }); - test('[assisted] bind-to-destination-010: Escape from destination picker dismisses without changing binding', async () => { + test('bind-to-destination-010: Escape from destination picker dismisses without changing binding', async () => { await createTerminal('rl-btd-010', terminals); await vscode.commands.executeCommand('rangelink.bindToTerminalHere'); await settle(); @@ -297,7 +299,7 @@ standardSuite('R-D Bind to Destination', { assisted: true }, (log) => { const logCapture = getLogCapture(); logCapture.mark('before-btd-010'); - await waitForHuman('bind-to-destination-010', 'Press Cmd+R Cmd+D, then Escape'); + await openAndDismiss(CMD_BIND_TO_DESTINATION); const lines = logCapture.getLinesSince('before-btd-010'); diff --git a/packages/rangelink-vscode-extension/src/__integration-tests__/suite/clipboardPreservation.test.ts b/packages/rangelink-vscode-extension/src/__integration-tests__/suite/clipboardPreservation.test.ts index 343524ee..2f5896c0 100644 --- a/packages/rangelink-vscode-extension/src/__integration-tests__/suite/clipboardPreservation.test.ts +++ b/packages/rangelink-vscode-extension/src/__integration-tests__/suite/clipboardPreservation.test.ts @@ -22,6 +22,7 @@ import { createTerminal, createWorkspaceFile, loadSettingsProfile, + openAndDismiss, openEditor, settle, standardSuite, @@ -30,7 +31,7 @@ import { writeClipboardSentinel, } from '../helpers'; -standardSuite('Clipboard Preservation', {}, (_log) => { +standardSuite('Clipboard Preservation', (_log) => { let testFileUri: vscode.Uri; let editor: vscode.TextEditor; let capturing: CapturingTerminal; @@ -117,7 +118,7 @@ standardSuite('Clipboard Preservation', {}, (_log) => { }); }); -standardSuite('Clipboard Preservation — Assisted', { assisted: true }, (log) => { +standardSuite('Clipboard Preservation — Assisted', (log) => { const tmpFileUris: vscode.Uri[] = []; const tmpTerminals: vscode.Terminal[] = []; @@ -299,28 +300,22 @@ standardSuite('Clipboard Preservation — Assisted', { assisted: true }, (log) = log('✓ Clipboard changed from sentinel and phrase landed in destination file after R-V'); }); - test('[assisted] clipboard-preservation-009: always mode — dismissed picker leaves clipboard unchanged', async () => { + test('clipboard-preservation-009: always mode — dismissed picker leaves clipboard unchanged', async () => { await vscode.commands.executeCommand(CMD_UNBIND_DESTINATION); const lines = Array.from({ length: 5 }, (_, i) => `line ${i + 1}`); const fileUri = createWorkspaceFile('cbp-009', lines.join('\n') + '\n'); tmpFileUris.push(fileUri); - await openEditor(fileUri); + const editor009 = await openEditor(fileUri); + editor009.selection = new vscode.Selection( + new vscode.Position(0, 0), + new vscode.Position(2, 0), + ); await settle(); await writeClipboardSentinel(); - await waitForHuman( - 'clipboard-preservation-009', - `clipboard.preserve="always", no destination bound. Select lines, press Cmd+R Cmd+L (picker opens), press Escape. Sentinel: "${CLIPBOARD_SENTINEL}".`, - [ - '1. Click into the test file (cbp-009-...)', - '2. Select a few lines', - '3. Press Cmd+R Cmd+L — the destination picker opens (no destination is bound)', - '4. Press Escape to dismiss without selecting anything', - '5. Press Cancel to continue (test asserts clipboard still has the sentinel)', - ], - ); + await openAndDismiss(CMD_COPY_LINK_RELATIVE); await assertClipboardRestored('clipboard-preservation-009: always + picker dismissed'); log('✓ Clipboard unchanged after picker dismissed (no operation performed)'); diff --git a/packages/rangelink-vscode-extension/src/__integration-tests__/suite/commandRegistration.test.ts b/packages/rangelink-vscode-extension/src/__integration-tests__/suite/commandRegistration.test.ts index 14b41d5a..6a600727 100644 --- a/packages/rangelink-vscode-extension/src/__integration-tests__/suite/commandRegistration.test.ts +++ b/packages/rangelink-vscode-extension/src/__integration-tests__/suite/commandRegistration.test.ts @@ -60,7 +60,7 @@ const EXPECTED_COMMAND_IDS = [ 'rangelink.handleFilePathClick', ] as const; -standardSuite('Command Registration', {}, (_log) => { +standardSuite('Command Registration', (_log) => { let registeredCommands: string[]; suiteSetup(async () => { diff --git a/packages/rangelink-vscode-extension/src/__integration-tests__/suite/contextMenuEditorContent.test.ts b/packages/rangelink-vscode-extension/src/__integration-tests__/suite/contextMenuEditorContent.test.ts index 2b0af177..216b54c3 100644 --- a/packages/rangelink-vscode-extension/src/__integration-tests__/suite/contextMenuEditorContent.test.ts +++ b/packages/rangelink-vscode-extension/src/__integration-tests__/suite/contextMenuEditorContent.test.ts @@ -26,7 +26,7 @@ import { const FILE_CONTENT = 'line 1\nline 2\nline 3\nline 4\n'; const CONTEXT_IS_BOUND_KEY = 'rangelink.isBound'; -standardSuite('Context Menus — Editor Content', { assisted: true }, (log) => { +standardSuite('Context Menus — Editor Content', (log) => { const terminals: vscode.Terminal[] = []; const tmpFileUris: vscode.Uri[] = []; let originalMultiLinePasteWarning: unknown; diff --git a/packages/rangelink-vscode-extension/src/__integration-tests__/suite/contextMenuEditorTab.test.ts b/packages/rangelink-vscode-extension/src/__integration-tests__/suite/contextMenuEditorTab.test.ts index 5a52bb3d..cfa6d042 100644 --- a/packages/rangelink-vscode-extension/src/__integration-tests__/suite/contextMenuEditorTab.test.ts +++ b/packages/rangelink-vscode-extension/src/__integration-tests__/suite/contextMenuEditorTab.test.ts @@ -23,7 +23,7 @@ import { const FILE_CONTENT = 'editor-tab context-menu test file\n'; const CONTEXT_IS_BOUND_KEY = 'rangelink.isBound'; -standardSuite('Context Menus — Editor Tab', { assisted: true }, (log) => { +standardSuite('Context Menus — Editor Tab', (log) => { const terminals: vscode.Terminal[] = []; const tmpFileUris: vscode.Uri[] = []; diff --git a/packages/rangelink-vscode-extension/src/__integration-tests__/suite/contextMenuExplorer.test.ts b/packages/rangelink-vscode-extension/src/__integration-tests__/suite/contextMenuExplorer.test.ts index 719420d8..c653d94b 100644 --- a/packages/rangelink-vscode-extension/src/__integration-tests__/suite/contextMenuExplorer.test.ts +++ b/packages/rangelink-vscode-extension/src/__integration-tests__/suite/contextMenuExplorer.test.ts @@ -26,7 +26,7 @@ import { const FILE_CONTENT = 'explorer context-menu test file\n'; const CONTEXT_IS_BOUND_KEY = 'rangelink.isBound'; -standardSuite('Context Menus — Explorer', { assisted: true }, (log) => { +standardSuite('Context Menus — Explorer', (log) => { const terminals: vscode.Terminal[] = []; const tmpFileUris: vscode.Uri[] = []; diff --git a/packages/rangelink-vscode-extension/src/__integration-tests__/suite/contextMenuTerminal.test.ts b/packages/rangelink-vscode-extension/src/__integration-tests__/suite/contextMenuTerminal.test.ts index d40306e0..a3234148 100644 --- a/packages/rangelink-vscode-extension/src/__integration-tests__/suite/contextMenuTerminal.test.ts +++ b/packages/rangelink-vscode-extension/src/__integration-tests__/suite/contextMenuTerminal.test.ts @@ -29,7 +29,7 @@ import { const CONTEXT_IS_BOUND_KEY = 'rangelink.isBound'; -standardSuite('Context Menus — Terminal', { assisted: true }, (log) => { +standardSuite('Context Menus — Terminal', (log) => { const terminals: vscode.Terminal[] = []; const tmpFileUris: vscode.Uri[] = []; diff --git a/packages/rangelink-vscode-extension/src/__integration-tests__/suite/coreSendCommands.test.ts b/packages/rangelink-vscode-extension/src/__integration-tests__/suite/coreSendCommands.test.ts index dd26465e..92e43f21 100644 --- a/packages/rangelink-vscode-extension/src/__integration-tests__/suite/coreSendCommands.test.ts +++ b/packages/rangelink-vscode-extension/src/__integration-tests__/suite/coreSendCommands.test.ts @@ -7,6 +7,8 @@ import { CMD_BIND_TO_TEXT_EDITOR_HERE, CMD_COPY_LINK_ONLY_RELATIVE, CMD_COPY_LINK_RELATIVE, + CMD_COPY_PORTABLE_LINK_RELATIVE, + CMD_PASTE_TO_DESTINATION, CMD_TERMINAL_PASTE_SELECTED_TEXT, CMD_UNBIND_DESTINATION, } from '../../constants/commandIds'; @@ -24,6 +26,7 @@ import { createWorkspaceFile, extractQuickPickItemsLogged, getLogCapture, + openAndDismiss, openEditor, settle, standardSuite, @@ -35,7 +38,7 @@ import { const NO_TERMINAL_SELECTION_MSG = 'RangeLink: No text selected in the terminal. Select text and try again.'; -standardSuite('Core Send Commands', { assisted: true }, (log) => { +standardSuite('Core Send Commands', (log) => { const tmpFileUris: vscode.Uri[] = []; const tmpTerminals: vscode.Terminal[] = []; @@ -339,7 +342,7 @@ standardSuite('Core Send Commands', { assisted: true }, (log) => { log('✓ R-V with no bound destination opens picker (log-based)'); }); - test('[assisted] core-send-commands-r-l-005: R-L with no bound destination opens picker', async () => { + test('core-send-commands-r-l-005: R-L with no bound destination opens picker', async () => { const fileUri = createWorkspaceFile('csc-r-l-005', 'test content\n'); tmpFileUris.push(fileUri); const doc = await vscode.workspace.openTextDocument(fileUri); @@ -350,14 +353,7 @@ standardSuite('Core Send Commands', { assisted: true }, (log) => { const logCaptureRl005 = getLogCapture(); logCaptureRl005.mark('before-r-l-005'); - await waitForHuman( - 'core-send-commands-r-l-005', - 'No destination bound. "test" is already selected.', - [ - 'Press Cmd+R Cmd+L — the RangeLink destination picker opens', - 'Press Escape to dismiss the picker, then click Cancel', - ], - ); + await openAndDismiss(CMD_COPY_LINK_RELATIVE); const itemsRl005 = extractQuickPickItemsLogged(logCaptureRl005.getLinesSince('before-r-l-005')); assert.ok( @@ -368,7 +364,7 @@ standardSuite('Core Send Commands', { assisted: true }, (log) => { log('✓ R-L with no destination opens picker (log-based)'); }); - test('[assisted] core-send-commands-r-p-001: Send Portable Link with no bound destination opens picker', async () => { + test('core-send-commands-r-p-001: Send Portable Link with no bound destination opens picker', async () => { const fileUri = createWorkspaceFile('csc-r-p-001', 'test content\n'); tmpFileUris.push(fileUri); const doc = await vscode.workspace.openTextDocument(fileUri); @@ -379,14 +375,7 @@ standardSuite('Core Send Commands', { assisted: true }, (log) => { const logCaptureRp001 = getLogCapture(); logCaptureRp001.mark('before-r-p-001'); - await waitForHuman( - 'core-send-commands-r-p-001', - 'No destination bound. "test" is already selected.', - [ - 'Press Cmd+Shift+P → "RangeLink: Send Portable Link" — the RangeLink destination picker opens', - 'Press Escape to dismiss the picker, then click Cancel', - ], - ); + await openAndDismiss(CMD_COPY_PORTABLE_LINK_RELATIVE); const itemsRp001 = extractQuickPickItemsLogged(logCaptureRp001.getLinesSince('before-r-p-001')); assert.ok( @@ -397,7 +386,7 @@ standardSuite('Core Send Commands', { assisted: true }, (log) => { log('✓ Send Portable Link with no destination opens picker (log-based)'); }); - test('[assisted] core-send-commands-r-v-001: Send Selected Text with no bound destination opens picker', async () => { + test('core-send-commands-r-v-001: Send Selected Text with no bound destination opens picker', async () => { const fileUri = createWorkspaceFile('csc-r-v-001', 'test content\n'); tmpFileUris.push(fileUri); const doc = await vscode.workspace.openTextDocument(fileUri); @@ -408,14 +397,7 @@ standardSuite('Core Send Commands', { assisted: true }, (log) => { const logCaptureRv001 = getLogCapture(); logCaptureRv001.mark('before-r-v-001'); - await waitForHuman( - 'core-send-commands-r-v-001', - 'No destination bound. "test" is already selected.', - [ - 'Press Cmd+Shift+P → "RangeLink: Send Selected Text" — the RangeLink destination picker opens', - 'Press Escape to dismiss the picker, then click Cancel', - ], - ); + await openAndDismiss(CMD_PASTE_TO_DESTINATION); const itemsRv001 = extractQuickPickItemsLogged(logCaptureRv001.getLinesSince('before-r-v-001')); assert.ok( diff --git a/packages/rangelink-vscode-extension/src/__integration-tests__/suite/customAiAssistants.test.ts b/packages/rangelink-vscode-extension/src/__integration-tests__/suite/customAiAssistants.test.ts index 9e361722..328c897c 100644 --- a/packages/rangelink-vscode-extension/src/__integration-tests__/suite/customAiAssistants.test.ts +++ b/packages/rangelink-vscode-extension/src/__integration-tests__/suite/customAiAssistants.test.ts @@ -2,6 +2,7 @@ import assert from 'node:assert'; import * as vscode from 'vscode'; +import { CMD_BIND_TO_DESTINATION } from '../../constants/commandIds'; import { assertClipboardChanged, assertClipboardRestored, @@ -11,6 +12,7 @@ import { createAndOpenFile, extractQuickPickItemsLogged, getLogCapture, + openAndDismiss, settle, standardSuite, waitForHuman, @@ -20,7 +22,7 @@ import { const EXPECTED_CUSTOM_ASSISTANTS_COUNT = 6; const EXPECTED_CUSTOM_AI_REGISTRATIONS = 5; -standardSuite('Custom AI Assistants', {}, (_log) => { +standardSuite('Custom AI Assistants', (_log) => { test('custom-ai-assistant-001: three-tier config is parsed and logged at activation', () => { const logCapture = getLogCapture(); const allLines = logCapture.getAllLines(); @@ -204,20 +206,12 @@ standardSuite('Custom AI Assistants', {}, (_log) => { }); }); -standardSuite('Custom AI Assistants — Destination Picker (Assisted)', { assisted: true }, (log) => { - test('[assisted] custom-ai-assistant-003: custom AI assistant appears in R-D destination picker with configured display name', async () => { +standardSuite('Custom AI Assistants — Destination Picker', (log) => { + test('custom-ai-assistant-003: custom AI assistant appears in R-D destination picker with configured display name', async () => { const logCapture = getLogCapture(); logCapture.mark('before-003'); - await waitForHuman( - 'custom-ai-assistant-003', - 'Open R-D picker (Cmd+R Cmd+D), observe that Dummy AI entries appear, then press Escape', - [ - '1. Press Cmd+R Cmd+D to open the destination picker', - '2. Observe the list — confirm "Dummy AI (Tier 1)" appears', - '3. Press Escape to close without selecting', - ], - ); + await openAndDismiss(CMD_BIND_TO_DESTINATION); const lines = logCapture.getLinesSince('before-003'); const items = extractQuickPickItemsLogged(lines); @@ -230,19 +224,11 @@ standardSuite('Custom AI Assistants — Destination Picker (Assisted)', { assist log('✓ custom-ai-assistant-003 — log confirms "Dummy AI (Tier 1)" appears in R-D picker'); }); - test('[assisted] custom-ai-assistant-007: multiple custom AI assistants listed in user-defined order', async () => { + test('custom-ai-assistant-007: multiple custom AI assistants listed in user-defined order', async () => { const logCapture = getLogCapture(); logCapture.mark('before-007'); - await waitForHuman( - 'custom-ai-assistant-007', - 'Open R-D picker (Cmd+R Cmd+D), observe Dummy AI order (Tier 1 → Tier 2 → Tier 3 → Template → Fallback), then press Escape', - [ - '1. Press Cmd+R Cmd+D to open the destination picker', - '2. Observe custom AI assistants appear in order: Tier 1, Tier 2, Tier 3, Template, Fallback', - '3. Press Escape to close without selecting', - ], - ); + await openAndDismiss(CMD_BIND_TO_DESTINATION); const lines = logCapture.getLinesSince('before-007'); const items = extractQuickPickItemsLogged(lines); @@ -277,7 +263,7 @@ standardSuite('Custom AI Assistants — Destination Picker (Assisted)', { assist }); }); -standardSuite('Custom AI Assistants — Cold Start', { assisted: true }, (log) => { +standardSuite('Custom AI Assistants — Cold Start', (log) => { const tmpFileUris: vscode.Uri[] = []; teardown(async () => { @@ -333,7 +319,7 @@ standardSuite('Custom AI Assistants — Cold Start', { assisted: true }, (log) = }); }); -standardSuite('Custom AI Assistants — Paste Flow', { assisted: true }, (log) => { +standardSuite('Custom AI Assistants — Paste Flow', (log) => { const tmpFileUris: vscode.Uri[] = []; suiteSetup(async () => { diff --git a/packages/rangelink-vscode-extension/src/__integration-tests__/suite/dirtyBufferWarning.test.ts b/packages/rangelink-vscode-extension/src/__integration-tests__/suite/dirtyBufferWarning.test.ts index 15197748..85a1222d 100644 --- a/packages/rangelink-vscode-extension/src/__integration-tests__/suite/dirtyBufferWarning.test.ts +++ b/packages/rangelink-vscode-extension/src/__integration-tests__/suite/dirtyBufferWarning.test.ts @@ -27,7 +27,7 @@ import { writeClipboardSentinel, } from '../helpers'; -standardSuite('Dirty Buffer Warning', { assisted: true }, (_log) => { +standardSuite('Dirty Buffer Warning', (_log) => { let testFileUri: vscode.Uri; suiteSetup(async () => { @@ -389,7 +389,7 @@ standardSuite('Dirty Buffer Warning', { assisted: true }, (_log) => { }); }); -standardSuite('Dirty Buffer Warning — Dialog Interaction', { assisted: true }, (log) => { +standardSuite('Dirty Buffer Warning — Dialog Interaction', (log) => { let testFileUri: vscode.Uri; suiteSetup(async () => { diff --git a/packages/rangelink-vscode-extension/src/__integration-tests__/suite/editorBindingValidation.test.ts b/packages/rangelink-vscode-extension/src/__integration-tests__/suite/editorBindingValidation.test.ts index 80e8be31..2f8dff74 100644 --- a/packages/rangelink-vscode-extension/src/__integration-tests__/suite/editorBindingValidation.test.ts +++ b/packages/rangelink-vscode-extension/src/__integration-tests__/suite/editorBindingValidation.test.ts @@ -1,10 +1,8 @@ -import assert from 'node:assert'; import * as fs from 'node:fs'; import * as path from 'node:path'; - import * as vscode from 'vscode'; -import { CMD_UNBIND_DESTINATION } from '../../constants/commandIds'; +import { CMD_BIND_TO_DESTINATION, CMD_UNBIND_DESTINATION } from '../../constants/commandIds'; import { cleanupFiles, closeAllEditors, @@ -13,13 +11,15 @@ import { findTestItemsByPrefix, getLogCapture, getWorkspaceRoot, + openAndDismiss, settle, standardSuite, - waitForHuman, waitForHumanVerdict, } from '../helpers'; -standardSuite('Editor Binding Validation', { assisted: true }, (log) => { +import assert from 'node:assert'; + +standardSuite('Editor Binding Validation', (log) => { const tmpFileUris: vscode.Uri[] = []; teardown(async () => { @@ -92,7 +92,7 @@ standardSuite('Editor Binding Validation', { assisted: true }, (log) => { log('✓ Settings UI hides file path/bind commands (human verdict)'); }); - test('[assisted] editor-binding-validation-004: binary .png file is excluded from R-D destination picker', async () => { + test('editor-binding-validation-004: binary .png file is excluded from R-D destination picker', async () => { // Minimal PNG magic bytes — enough for VSCode to detect as binary const PNG_MAGIC = new Uint8Array([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]); const pngPath = path.join(getWorkspaceRoot(), `__rl-test-ebv-004-${Date.now()}.png`); @@ -116,17 +116,7 @@ standardSuite('Editor Binding Validation', { assisted: true }, (log) => { const logCapture = getLogCapture(); logCapture.mark('before-ebv-004'); - await waitForHuman( - 'editor-binding-validation-004', - 'A .txt file is open in col 1, a .png in col 2. Press Cmd+R Cmd+D and Escape — .png must not appear in picker.', - [ - '1. The .txt control file is open in column 1, the .png is open in column 2', - '2. Press Cmd+R Cmd+D to open the destination picker', - '3. Confirm the .txt file IS listed (positive control)', - '4. Confirm no .png file appears in the list', - '5. Press Escape to dismiss', - ], - ); + await openAndDismiss(CMD_BIND_TO_DESTINATION); const lines = logCapture.getLinesSince('before-ebv-004'); const items = extractQuickPickItemsLogged(lines); diff --git a/packages/rangelink-vscode-extension/src/__integration-tests__/suite/filePathNavigation.test.ts b/packages/rangelink-vscode-extension/src/__integration-tests__/suite/filePathNavigation.test.ts index affaf07b..cdaa5f67 100644 --- a/packages/rangelink-vscode-extension/src/__integration-tests__/suite/filePathNavigation.test.ts +++ b/packages/rangelink-vscode-extension/src/__integration-tests__/suite/filePathNavigation.test.ts @@ -16,7 +16,7 @@ import { const NON_EXISTENT_PATH_SETTLE_MS = 1000; -standardSuite('File Path Navigation', {}, (_log) => { +standardSuite('File Path Navigation', (_log) => { let testFileUri: vscode.Uri; let anchorFileUri: vscode.Uri; diff --git a/packages/rangelink-vscode-extension/src/__integration-tests__/suite/filePicker.test.ts b/packages/rangelink-vscode-extension/src/__integration-tests__/suite/filePicker.test.ts index ad32b894..a0d43c9e 100644 --- a/packages/rangelink-vscode-extension/src/__integration-tests__/suite/filePicker.test.ts +++ b/packages/rangelink-vscode-extension/src/__integration-tests__/suite/filePicker.test.ts @@ -4,6 +4,7 @@ import * as path from 'node:path'; import * as vscode from 'vscode'; +import { CMD_BIND_TO_DESTINATION, CMD_OPEN_STATUS_BAR_MENU } from '../../constants/commandIds'; import { cleanupFiles, closeAllEditors, @@ -12,6 +13,7 @@ import { findTestItemsByPrefix, getLogCapture, getWorkspaceRoot, + openAndDismiss, parseQuickPickItemsFromLogLine, settle, standardSuite, @@ -21,7 +23,7 @@ import { const SEPARATOR_KIND = -1; const FILE_OVERFLOW_THRESHOLD = 5; -standardSuite('File Picker', { assisted: true }, (log) => { +standardSuite('File Picker', (log) => { const tmpFileUris: vscode.Uri[] = []; teardown(async () => { @@ -35,7 +37,7 @@ standardSuite('File Picker', { assisted: true }, (log) => { const findTestFileItems = (items: Record[]): Record[] => findTestItemsByPrefix(items, '__rl-test-fp-'); - test('[assisted] file-picker-001: bound file appears first with bound badge', async () => { + test('file-picker-001: bound file appears first with bound badge', async () => { const uriA = await createAndOpenFile( 'fp-001-a', 'line 1\nline 2\n', @@ -56,7 +58,7 @@ standardSuite('File Picker', { assisted: true }, (log) => { const logCapture = getLogCapture(); logCapture.mark('before-fp-001'); - await waitForHuman('file-picker-001', 'Press Cmd+R Cmd+D, then Escape'); + await openAndDismiss(CMD_BIND_TO_DESTINATION); const lines = logCapture.getLinesSince('before-fp-001'); const items = extractQuickPickItemsLogged(lines); @@ -92,7 +94,7 @@ standardSuite('File Picker', { assisted: true }, (log) => { log('✓ Bound file first with full semantic state + description'); }); - test('[assisted] file-picker-002: active file appears before others in its group', async () => { + test('file-picker-002: active file appears before others in its group', async () => { const uriA = await createAndOpenFile( 'fp-002-a', 'file a\n', @@ -111,7 +113,7 @@ standardSuite('File Picker', { assisted: true }, (log) => { const logCapture = getLogCapture(); logCapture.mark('before-fp-002'); - await waitForHuman('file-picker-002', 'Press Cmd+R Cmd+D, then Escape'); + await openAndDismiss(CMD_BIND_TO_DESTINATION); const lines = logCapture.getLinesSince('before-fp-002'); const items = extractQuickPickItemsLogged(lines); @@ -147,7 +149,7 @@ standardSuite('File Picker', { assisted: true }, (log) => { log('✓ Files ordered by ViewColumn (Tab Group 1 before Tab Group 2)'); }); - test('[assisted] file-picker-003: same base name shows path disambiguation', async () => { + test('file-picker-003: same base name shows path disambiguation', async () => { const wsRoot = getWorkspaceRoot(); const subDirA = path.join(wsRoot, 'src', 'dirA'); const subDirB = path.join(wsRoot, 'src', 'dirB'); @@ -176,7 +178,7 @@ standardSuite('File Picker', { assisted: true }, (log) => { const logCapture = getLogCapture(); logCapture.mark('before-fp-003'); - await waitForHuman('file-picker-003', 'Press Cmd+R Cmd+D, then Escape'); + await openAndDismiss(CMD_BIND_TO_DESTINATION); const lines = logCapture.getLinesSince('before-fp-003'); const items = extractQuickPickItemsLogged(lines); @@ -224,14 +226,14 @@ standardSuite('File Picker', { assisted: true }, (log) => { log('✓ Path disambiguation validated with disambiguator in descriptions'); }); - test('[assisted] file-picker-004: open files appear as inline items in destination picker', async () => { + test('file-picker-004: open files appear as inline items in destination picker', async () => { const uri = await createAndOpenFile('fp-004', 'content\n', undefined, tmpFileUris); const fn = path.basename(uri.fsPath); const logCapture = getLogCapture(); logCapture.mark('before-fp-004'); - await waitForHuman('file-picker-004', 'Press Cmd+R Cmd+D, then Escape'); + await openAndDismiss(CMD_BIND_TO_DESTINATION); const lines = logCapture.getLinesSince('before-fp-004'); const items = extractQuickPickItemsLogged(lines); @@ -260,7 +262,7 @@ standardSuite('File Picker', { assisted: true }, (log) => { log('✓ File appears inline in R-D picker — full assertion'); }); - test('[assisted] file-picker-005: overflow shows "More files..." when exceeding inline limit', async () => { + test('file-picker-005: overflow shows "More files..." when exceeding inline limit', async () => { for (let i = 1; i <= FILE_OVERFLOW_THRESHOLD; i++) { await createAndOpenFile(`fp-005-${i}`, `file ${i}\n`, undefined, tmpFileUris); } @@ -268,7 +270,7 @@ standardSuite('File Picker', { assisted: true }, (log) => { const logCapture = getLogCapture(); logCapture.mark('before-fp-005'); - await waitForHuman('file-picker-005', 'Press Cmd+R Cmd+D, then Escape'); + await openAndDismiss(CMD_BIND_TO_DESTINATION); const lines = logCapture.getLinesSince('before-fp-005'); const items = extractQuickPickItemsLogged(lines); @@ -537,7 +539,7 @@ standardSuite('File Picker', { assisted: true }, (log) => { log('✓ Secondary picker shows path disambiguation'); }); - test('[assisted] file-picker-011: three files with same name show deeper disambiguation paths', async () => { + test('file-picker-011: three files with same name show deeper disambiguation paths', async () => { const wsRoot = getWorkspaceRoot(); const dirA = path.join(wsRoot, 'src', 'a', 'nested'); const dirB = path.join(wsRoot, 'src', 'b', 'nested'); @@ -577,7 +579,7 @@ standardSuite('File Picker', { assisted: true }, (log) => { const logCapture = getLogCapture(); logCapture.mark('before-fp-011'); - await waitForHuman('file-picker-011', 'Press Cmd+R Cmd+D, then Escape'); + await openAndDismiss(CMD_BIND_TO_DESTINATION); const lines = logCapture.getLinesSince('before-fp-011'); const items = extractQuickPickItemsLogged(lines); @@ -632,7 +634,7 @@ standardSuite('File Picker', { assisted: true }, (log) => { log('✓ Three same-name files: same label/displayName, unique disambiguated descriptions'); }); - test('[assisted] file-picker-012: unique file names have no disambiguator in description', async () => { + test('file-picker-012: unique file names have no disambiguator in description', async () => { const uriA = await createAndOpenFile( 'fp-012-alpha', 'file a\n', @@ -651,7 +653,7 @@ standardSuite('File Picker', { assisted: true }, (log) => { const logCapture = getLogCapture(); logCapture.mark('before-fp-012'); - await waitForHuman('file-picker-012', 'Press Cmd+R Cmd+D, then Escape'); + await openAndDismiss(CMD_BIND_TO_DESTINATION); const lines = logCapture.getLinesSince('before-fp-012'); const items = extractQuickPickItemsLogged(lines); @@ -687,14 +689,14 @@ standardSuite('File Picker', { assisted: true }, (log) => { log('✓ Unique file names: no disambiguator, ordered by ViewColumn'); }); - test('[assisted] file-picker-009: file picker appears inline in R-M menu when unbound', async () => { + test('file-picker-009: file picker appears inline in R-M menu when unbound', async () => { const uri = await createAndOpenFile('fp-009', 'content\n', undefined, tmpFileUris); const fn = path.basename(uri.fsPath); const logCapture = getLogCapture(); logCapture.mark('before-fp-009'); - await waitForHuman('file-picker-009', 'Open R-M menu (Cmd+R Cmd+M), then Escape'); + await openAndDismiss(CMD_OPEN_STATUS_BAR_MENU); const lines = logCapture.getLinesSince('before-fp-009'); const items = extractQuickPickItemsLogged(lines); diff --git a/packages/rangelink-vscode-extension/src/__integration-tests__/suite/filenameOnlyNavigation.test.ts b/packages/rangelink-vscode-extension/src/__integration-tests__/suite/filenameOnlyNavigation.test.ts index e3949668..d0bf4f94 100644 --- a/packages/rangelink-vscode-extension/src/__integration-tests__/suite/filenameOnlyNavigation.test.ts +++ b/packages/rangelink-vscode-extension/src/__integration-tests__/suite/filenameOnlyNavigation.test.ts @@ -18,7 +18,7 @@ import { const DUPLICATE_FILE_CONTENT = 'duplicate file content\n'; -standardSuite('Filename-Only Navigation Fallback', {}, (_log) => { +standardSuite('Filename-Only Navigation Fallback', (_log) => { let uniqueFilename: string; let uniqueFilePath: string; let relativeFilePath: string; diff --git a/packages/rangelink-vscode-extension/src/__integration-tests__/suite/goToRangeLink.test.ts b/packages/rangelink-vscode-extension/src/__integration-tests__/suite/goToRangeLink.test.ts index 2bd77d2c..95f53192 100644 --- a/packages/rangelink-vscode-extension/src/__integration-tests__/suite/goToRangeLink.test.ts +++ b/packages/rangelink-vscode-extension/src/__integration-tests__/suite/goToRangeLink.test.ts @@ -3,6 +3,7 @@ import * as path from 'node:path'; import * as vscode from 'vscode'; +import { CMD_GO_TO_RANGELINK } from '../../constants/commandIds'; import { assertInputBoxLogged, assertToastLogged, @@ -11,6 +12,7 @@ import { createAndOpenFile, extractQuickPickItemsLogged, getLogCapture, + openAndDismiss, settle, standardSuite, waitForHuman, @@ -36,7 +38,7 @@ const assertUserCancelledInputLogged = (lines: string[]): void => { assert.ok(found, 'Expected GoToRangeLinkCommand.execute "User cancelled input" debug log'); }; -standardSuite('R-G Go to Link', { assisted: true }, (log) => { +standardSuite('R-G Go to Link', (log) => { const tmpFileUris: vscode.Uri[] = []; teardown(async () => { @@ -46,11 +48,11 @@ standardSuite('R-G Go to Link', { assisted: true }, (log) => { await settle(); }); - test('[assisted] go-to-link-001: Cmd+R Cmd+G opens the Go to Link input box', async () => { + test('go-to-link-001: Cmd+R Cmd+G opens the Go to Link input box', async () => { const logCapture = getLogCapture(); logCapture.mark('before-gtl-001'); - await waitForHuman('go-to-link-001', 'Press Cmd+R Cmd+G, then Escape the input box'); + await openAndDismiss(CMD_GO_TO_RANGELINK); const lines = logCapture.getLinesSince('before-gtl-001'); diff --git a/packages/rangelink-vscode-extension/src/__integration-tests__/suite/linkGeneration.test.ts b/packages/rangelink-vscode-extension/src/__integration-tests__/suite/linkGeneration.test.ts index c1e8e6b8..12dc2630 100644 --- a/packages/rangelink-vscode-extension/src/__integration-tests__/suite/linkGeneration.test.ts +++ b/packages/rangelink-vscode-extension/src/__integration-tests__/suite/linkGeneration.test.ts @@ -17,7 +17,7 @@ import { const LOGGER = new NoOpLogger(); -standardSuite('Link Generation', {}, (_log) => { +standardSuite('Link Generation', (_log) => { const tmpFileUris: vscode.Uri[] = []; suiteTeardown(async () => { @@ -157,7 +157,7 @@ standardSuite('Link Generation', {}, (_log) => { }); }); -standardSuite('Link Generation — Clickable Links (Assisted)', { assisted: true }, (log) => { +standardSuite('Link Generation — Clickable Links (Assisted)', (log) => { const tmpFileUris: vscode.Uri[] = []; teardown(async () => { diff --git a/packages/rangelink-vscode-extension/src/__integration-tests__/suite/navigationClamping.test.ts b/packages/rangelink-vscode-extension/src/__integration-tests__/suite/navigationClamping.test.ts index 00b56ae0..69711c57 100644 --- a/packages/rangelink-vscode-extension/src/__integration-tests__/suite/navigationClamping.test.ts +++ b/packages/rangelink-vscode-extension/src/__integration-tests__/suite/navigationClamping.test.ts @@ -18,7 +18,7 @@ import { const LINE_COUNT = 10; const LINE_CONTENT = 'abcdefghijklmnopqrst'; -standardSuite('Navigation Clamping', {}, (_log) => { +standardSuite('Navigation Clamping', (_log) => { let testFilename: string; let testFileUri: vscode.Uri; diff --git a/packages/rangelink-vscode-extension/src/__integration-tests__/suite/navigationPrecision.test.ts b/packages/rangelink-vscode-extension/src/__integration-tests__/suite/navigationPrecision.test.ts index 38373600..5a5bad96 100644 --- a/packages/rangelink-vscode-extension/src/__integration-tests__/suite/navigationPrecision.test.ts +++ b/packages/rangelink-vscode-extension/src/__integration-tests__/suite/navigationPrecision.test.ts @@ -15,7 +15,7 @@ import { standardSuite, } from '../helpers'; -standardSuite('Navigation Precision', {}, (_log) => { +standardSuite('Navigation Precision', (_log) => { let testFilename: string; let testFileUri: vscode.Uri; diff --git a/packages/rangelink-vscode-extension/src/__integration-tests__/suite/navigationToastSettings.test.ts b/packages/rangelink-vscode-extension/src/__integration-tests__/suite/navigationToastSettings.test.ts index f4953f5f..ae2c3d26 100644 --- a/packages/rangelink-vscode-extension/src/__integration-tests__/suite/navigationToastSettings.test.ts +++ b/packages/rangelink-vscode-extension/src/__integration-tests__/suite/navigationToastSettings.test.ts @@ -16,7 +16,7 @@ import { standardSuite, } from '../helpers'; -standardSuite('Navigation Toast Settings', {}, (_log) => { +standardSuite('Navigation Toast Settings', (_log) => { let testFilename: string; let testFileUri: vscode.Uri; diff --git a/packages/rangelink-vscode-extension/src/__integration-tests__/suite/platformKeybindings.test.ts b/packages/rangelink-vscode-extension/src/__integration-tests__/suite/platformKeybindings.test.ts index 072685e7..b356e987 100644 --- a/packages/rangelink-vscode-extension/src/__integration-tests__/suite/platformKeybindings.test.ts +++ b/packages/rangelink-vscode-extension/src/__integration-tests__/suite/platformKeybindings.test.ts @@ -10,7 +10,7 @@ import { waitForHumanVerdict, } from '../helpers'; -standardSuite('Platform Keybindings', { assisted: true }, (_log) => { +standardSuite('Platform Keybindings', (_log) => { const tmpFileUris: vscode.Uri[] = []; suiteTeardown(async () => { diff --git a/packages/rangelink-vscode-extension/src/__integration-tests__/suite/releaseNotifier.test.ts b/packages/rangelink-vscode-extension/src/__integration-tests__/suite/releaseNotifier.test.ts index c079e3b2..49bdfcec 100644 --- a/packages/rangelink-vscode-extension/src/__integration-tests__/suite/releaseNotifier.test.ts +++ b/packages/rangelink-vscode-extension/src/__integration-tests__/suite/releaseNotifier.test.ts @@ -13,7 +13,7 @@ const getReleaseNotifier = () => { return ext.exports.releaseNotifier; }; -standardSuite('Release Notifier', { assisted: true }, (log) => { +standardSuite('Release Notifier', (log) => { test('release-notifier-001: first install stores version silently without notification', async () => { const notifier = getReleaseNotifier(); await notifier.setLastNotifiedVersion(undefined); diff --git a/packages/rangelink-vscode-extension/src/__integration-tests__/suite/sendFilePath.test.ts b/packages/rangelink-vscode-extension/src/__integration-tests__/suite/sendFilePath.test.ts index 4dfbf703..2346df0f 100644 --- a/packages/rangelink-vscode-extension/src/__integration-tests__/suite/sendFilePath.test.ts +++ b/packages/rangelink-vscode-extension/src/__integration-tests__/suite/sendFilePath.test.ts @@ -28,7 +28,7 @@ import { writeClipboardSentinel, } from '../helpers'; -standardSuite('Send File Path', { assisted: true }, (log) => { +standardSuite('Send File Path', (log) => { const tmpFileUris: vscode.Uri[] = []; const tmpTerminals: vscode.Terminal[] = []; diff --git a/packages/rangelink-vscode-extension/src/__integration-tests__/suite/smartPadding.test.ts b/packages/rangelink-vscode-extension/src/__integration-tests__/suite/smartPadding.test.ts index 71600b0e..3855ff80 100644 --- a/packages/rangelink-vscode-extension/src/__integration-tests__/suite/smartPadding.test.ts +++ b/packages/rangelink-vscode-extension/src/__integration-tests__/suite/smartPadding.test.ts @@ -13,7 +13,7 @@ import { waitForActiveEditor, } from '../helpers'; -standardSuite('Smart Padding — Editor-to-Editor R-V', {}, (log) => { +standardSuite('Smart Padding — Editor-to-Editor R-V', (log) => { let sourceFileUri: vscode.Uri; let destFileUri: vscode.Uri; diff --git a/packages/rangelink-vscode-extension/src/__integration-tests__/suite/statusBarMenu.test.ts b/packages/rangelink-vscode-extension/src/__integration-tests__/suite/statusBarMenu.test.ts index a3fc2481..9bd9c672 100644 --- a/packages/rangelink-vscode-extension/src/__integration-tests__/suite/statusBarMenu.test.ts +++ b/packages/rangelink-vscode-extension/src/__integration-tests__/suite/statusBarMenu.test.ts @@ -2,13 +2,18 @@ import assert from 'node:assert'; import * as vscode from 'vscode'; -import { CMD_BIND_TO_TERMINAL_HERE, CMD_UNBIND_DESTINATION } from '../../constants/commandIds'; +import { + CMD_BIND_TO_TERMINAL_HERE, + CMD_OPEN_STATUS_BAR_MENU, + CMD_UNBIND_DESTINATION, +} from '../../constants/commandIds'; import { assertQuickPickItemsLogged, cleanupFiles, createWorkspaceFile, extractQuickPickItemsLogged, getLogCapture, + openAndDismiss, settle, standardSuite, TERMINAL_READY_MS, @@ -18,18 +23,18 @@ import { const SEPARATOR_KIND = -1; -standardSuite('R-M Status Bar Menu', { assisted: true }, (log) => { +standardSuite('R-M Status Bar Menu', (log) => { const tmpFileUris: vscode.Uri[] = []; suiteTeardown(async () => { cleanupFiles(tmpFileUris); }); - test('[assisted] status-bar-menu-002: clicking the status bar item opens the R-M menu', async () => { + test('status-bar-menu-002: clicking the status bar item opens the R-M menu', async () => { const logCapture = getLogCapture(); logCapture.mark('before-menu-002'); - await waitForHuman('status-bar-menu-002', 'Click the RangeLink status bar item, then Escape'); + await openAndDismiss(CMD_OPEN_STATUS_BAR_MENU); const lines = logCapture.getLinesSince('before-menu-002'); const items = extractQuickPickItemsLogged(lines); @@ -59,7 +64,7 @@ standardSuite('R-M Status Bar Menu', { assisted: true }, (log) => { log('✓ Unbound menu: no Jump item, correct structure'); }); - test('[assisted] status-bar-menu-003: Cmd+R Cmd+M keybinding opens the R-M menu', async () => { + test('status-bar-menu-003: Cmd+R Cmd+M keybinding opens the R-M menu', async () => { const testFileUri = createWorkspaceFile('menu-003', 'line 1\nline 2\n'); tmpFileUris.push(testFileUri); const doc = await vscode.workspace.openTextDocument(testFileUri); @@ -69,7 +74,7 @@ standardSuite('R-M Status Bar Menu', { assisted: true }, (log) => { const logCapture = getLogCapture(); logCapture.mark('before-menu-003'); - await waitForHuman('status-bar-menu-003', 'Press Cmd+R Cmd+M, then Escape'); + await openAndDismiss(CMD_OPEN_STATUS_BAR_MENU); const lines = logCapture.getLinesSince('before-menu-003'); const items = extractQuickPickItemsLogged(lines); @@ -99,7 +104,7 @@ standardSuite('R-M Status Bar Menu', { assisted: true }, (log) => { log('✓ Keybinding menu: no Jump item, correct structure'); }); - test('[assisted] status-bar-menu-005: R-M menu shows Jump to Bound Destination when bound', async () => { + test('status-bar-menu-005: R-M menu shows Jump to Bound Destination when bound', async () => { const terminal = vscode.window.createTerminal({ name: 'rl-menu-test' }); terminal.show(true); await settle(TERMINAL_READY_MS); @@ -111,7 +116,7 @@ standardSuite('R-M Status Bar Menu', { assisted: true }, (log) => { const logCapture = getLogCapture(); logCapture.mark('before-menu-005'); - await waitForHuman('status-bar-menu-005', 'Open R-M menu (Cmd+R Cmd+M), then Escape'); + await openAndDismiss(CMD_OPEN_STATUS_BAR_MENU); const lines = logCapture.getLinesSince('before-menu-005'); assertQuickPickItemsLogged(lines, [ @@ -148,23 +153,14 @@ standardSuite('R-M Status Bar Menu', { assisted: true }, (log) => { log('✓ Status bar item shows correct text and tooltip'); }); - test('[assisted] status-bar-menu-006: R-M menu shows destination picker items when no destination is bound', async () => { + test('status-bar-menu-006: R-M menu shows destination picker items when no destination is bound', async () => { await vscode.commands.executeCommand(CMD_UNBIND_DESTINATION); await settle(); const logCapture = getLogCapture(); logCapture.mark('before-006'); - await waitForHuman( - 'status-bar-menu-006', - 'No destination bound. Open the R-M menu (Cmd+R Cmd+M), observe the first item and the absence of "Jump to Bound Destination", then press Escape.', - [ - '1. Press Cmd+R Cmd+M to open the R-M menu', - '2. Confirm the first item says "No bound destination. Choose below to bind:"', - '3. Confirm there is NO "Jump to Bound Destination" item', - '4. Press Escape to dismiss, then click Cancel', - ], - ); + await openAndDismiss(CMD_OPEN_STATUS_BAR_MENU); const lines = logCapture.getLinesSince('before-006'); const items = extractQuickPickItemsLogged(lines); diff --git a/packages/rangelink-vscode-extension/src/__integration-tests__/suite/terminalPicker.test.ts b/packages/rangelink-vscode-extension/src/__integration-tests__/suite/terminalPicker.test.ts index f8697895..7323dbc5 100644 --- a/packages/rangelink-vscode-extension/src/__integration-tests__/suite/terminalPicker.test.ts +++ b/packages/rangelink-vscode-extension/src/__integration-tests__/suite/terminalPicker.test.ts @@ -2,6 +2,7 @@ import assert from 'node:assert'; import * as vscode from 'vscode'; +import { CMD_BIND_TO_DESTINATION, CMD_OPEN_STATUS_BAR_MENU } from '../../constants/commandIds'; import { cleanupFiles, closeAllEditors, @@ -11,6 +12,7 @@ import { findTerminalItems, getLogCapture, loadSettingsProfile, + openAndDismiss, parseQuickPickItemsFromLogLine, settle, standardSuite, @@ -22,7 +24,7 @@ const TERMINAL_OVERFLOW_COUNT = 6; const MAX_INLINE_DEFAULT = 5; const FILE_OVERFLOW_THRESHOLD = 5; -standardSuite('Terminal Picker', { assisted: true }, (log) => { +standardSuite('Terminal Picker', (log) => { const terminals: vscode.Terminal[] = []; teardown(async () => { @@ -35,7 +37,7 @@ standardSuite('Terminal Picker', { assisted: true }, (log) => { await settle(); }); - test('[assisted] terminal-picker-001: active terminal is marked with active badge', async () => { + test('terminal-picker-001: active terminal is marked with active badge', async () => { const t1 = await createTerminal('rl-tp-001-a', terminals); await createTerminal('rl-tp-001-b', terminals); t1.show(true); @@ -44,7 +46,7 @@ standardSuite('Terminal Picker', { assisted: true }, (log) => { const logCapture = getLogCapture(); logCapture.mark('before-tp-001'); - await waitForHuman('terminal-picker-001', 'Press Cmd+R Cmd+D, then Escape'); + await openAndDismiss(CMD_BIND_TO_DESTINATION); const lines = logCapture.getLinesSince('before-tp-001'); const items = extractQuickPickItemsLogged(lines); @@ -83,7 +85,7 @@ standardSuite('Terminal Picker', { assisted: true }, (log) => { log('✓ Active terminal: all 6 fields. Non-active: no badge, no isActive'); }); - test('[assisted] terminal-picker-002: bound terminal is marked with bound badge', async () => { + test('terminal-picker-002: bound terminal is marked with bound badge', async () => { await createTerminal('rl-tp-002', terminals); await vscode.commands.executeCommand('rangelink.bindToTerminalHere'); await settle(); @@ -94,7 +96,7 @@ standardSuite('Terminal Picker', { assisted: true }, (log) => { const logCapture = getLogCapture(); logCapture.mark('before-tp-002'); - await waitForHuman('terminal-picker-002', 'Press Cmd+R Cmd+D, then Escape'); + await openAndDismiss(CMD_BIND_TO_DESTINATION); const lines = logCapture.getLinesSince('before-tp-002'); const items = extractQuickPickItemsLogged(lines); @@ -133,7 +135,7 @@ standardSuite('Terminal Picker', { assisted: true }, (log) => { log('✓ Bound first (all 6 fields), active other second'); }); - test('[assisted] terminal-picker-003: terminal that is both active and bound shows dual badge', async () => { + test('terminal-picker-003: terminal that is both active and bound shows dual badge', async () => { const t = await createTerminal('rl-tp-003', terminals); await vscode.commands.executeCommand('rangelink.bindToTerminalHere'); t.show(true); @@ -142,7 +144,7 @@ standardSuite('Terminal Picker', { assisted: true }, (log) => { const logCapture = getLogCapture(); logCapture.mark('before-tp-003'); - await waitForHuman('terminal-picker-003', 'Press Cmd+R Cmd+D, then Escape'); + await openAndDismiss(CMD_BIND_TO_DESTINATION); const lines = logCapture.getLinesSince('before-tp-003'); const items = extractQuickPickItemsLogged(lines); @@ -173,7 +175,7 @@ standardSuite('Terminal Picker', { assisted: true }, (log) => { log('✓ Dual badge: all 6 fields validated'); }); - test('[assisted] terminal-picker-004: bound terminal always appears first in the list', async () => { + test('terminal-picker-004: bound terminal always appears first in the list', async () => { await createTerminal('rl-tp-004-b', terminals); await vscode.commands.executeCommand('rangelink.bindToTerminalHere'); await settle(); @@ -182,7 +184,7 @@ standardSuite('Terminal Picker', { assisted: true }, (log) => { const logCapture = getLogCapture(); logCapture.mark('before-tp-004'); - await waitForHuman('terminal-picker-004', 'Press Cmd+R Cmd+D, then Escape'); + await openAndDismiss(CMD_BIND_TO_DESTINATION); const lines = logCapture.getLinesSince('before-tp-004'); const items = extractQuickPickItemsLogged(lines); @@ -221,7 +223,7 @@ standardSuite('Terminal Picker', { assisted: true }, (log) => { log('✓ Bound terminal first — all 6 fields'); }); - test('[assisted] terminal-picker-005: active non-bound terminal appears second', async () => { + test('terminal-picker-005: active non-bound terminal appears second', async () => { await createTerminal('rl-tp-005-a', terminals); await vscode.commands.executeCommand('rangelink.bindToTerminalHere'); await settle(); @@ -232,7 +234,7 @@ standardSuite('Terminal Picker', { assisted: true }, (log) => { const logCapture = getLogCapture(); logCapture.mark('before-tp-005'); - await waitForHuman('terminal-picker-005', 'Press Cmd+R Cmd+D, then Escape'); + await openAndDismiss(CMD_BIND_TO_DESTINATION); const lines = logCapture.getLinesSince('before-tp-005'); const items = extractQuickPickItemsLogged(lines); @@ -271,13 +273,13 @@ standardSuite('Terminal Picker', { assisted: true }, (log) => { log('✓ Bound first, active second — all 6 fields'); }); - test('[assisted] terminal-picker-006: hidden IDE terminals are absent from the picker', async () => { + test('terminal-picker-006: hidden IDE terminals are absent from the picker', async () => { await createTerminal('rl-tp-006', terminals); const logCapture = getLogCapture(); logCapture.mark('before-tp-006'); - await waitForHuman('terminal-picker-006', 'Press Cmd+R Cmd+D, then Escape'); + await openAndDismiss(CMD_BIND_TO_DESTINATION); const lines = logCapture.getLinesSince('before-tp-006'); const items = extractQuickPickItemsLogged(lines); @@ -308,14 +310,14 @@ standardSuite('Terminal Picker', { assisted: true }, (log) => { log('✓ Only the test terminal appears — full field validation'); }); - test('[assisted] terminal-picker-007: all terminals shown inline when within maxInline limit', async () => { + test('terminal-picker-007: all terminals shown inline when within maxInline limit', async () => { await createTerminal('rl-tp-007-a', terminals); await createTerminal('rl-tp-007-b', terminals); const logCapture = getLogCapture(); logCapture.mark('before-tp-007'); - await waitForHuman('terminal-picker-007', 'Press Cmd+R Cmd+D, then Escape'); + await openAndDismiss(CMD_BIND_TO_DESTINATION); const lines = logCapture.getLinesSince('before-tp-007'); const items = extractQuickPickItemsLogged(lines); @@ -360,7 +362,7 @@ standardSuite('Terminal Picker', { assisted: true }, (log) => { log('✓ Both terminals inline, full field validation'); }); - test('[assisted] terminal-picker-008: overflow shows "More terminals..." when exceeding maxInline', async () => { + test('terminal-picker-008: overflow shows "More terminals..." when exceeding maxInline', async () => { for (let i = 1; i <= TERMINAL_OVERFLOW_COUNT; i++) { await createTerminal(`rl-tp-008-${i}`, terminals); } @@ -368,7 +370,7 @@ standardSuite('Terminal Picker', { assisted: true }, (log) => { const logCapture = getLogCapture(); logCapture.mark('before-tp-008'); - await waitForHuman('terminal-picker-008', 'Press Cmd+R Cmd+D, then Escape'); + await openAndDismiss(CMD_BIND_TO_DESTINATION); const lines = logCapture.getLinesSince('before-tp-008'); const items = extractQuickPickItemsLogged(lines); @@ -525,7 +527,7 @@ standardSuite('Terminal Picker', { assisted: true }, (log) => { log('✓ Parent picker reopened with identical items'); }); - test('[assisted] terminal-picker-011: maxInline setting changes overflow threshold', async () => { + test('terminal-picker-011: maxInline setting changes overflow threshold', async () => { await loadSettingsProfile('terminal-picker-low', log); const LOW_MAX_INLINE = 2; const TC_TERMINAL_COUNT = 3; @@ -537,7 +539,7 @@ standardSuite('Terminal Picker', { assisted: true }, (log) => { const logCapture = getLogCapture(); logCapture.mark('before-tp-011'); - await waitForHuman('terminal-picker-011', 'Press Cmd+R Cmd+D, then Escape'); + await openAndDismiss(CMD_BIND_TO_DESTINATION); const lines = logCapture.getLinesSince('before-tp-011'); const items = extractQuickPickItemsLogged(lines); @@ -586,13 +588,13 @@ standardSuite('Terminal Picker', { assisted: true }, (log) => { log('✓ maxInline=2: overflow + active inline terminal fully validated'); }); - test('[assisted] terminal-picker-012: terminal picker appears inline in R-M menu when unbound', async () => { + test('terminal-picker-012: terminal picker appears inline in R-M menu when unbound', async () => { await createTerminal('rl-tp-012', terminals); const logCapture = getLogCapture(); logCapture.mark('before-tp-012'); - await waitForHuman('terminal-picker-012', 'Open R-M menu (Cmd+R Cmd+M), then Escape'); + await openAndDismiss(CMD_OPEN_STATUS_BAR_MENU); const lines = logCapture.getLinesSince('before-tp-012'); const items = extractQuickPickItemsLogged(lines); @@ -628,13 +630,13 @@ standardSuite('Terminal Picker', { assisted: true }, (log) => { log('✓ Terminal inline in R-M menu with full description'); }); - test('[assisted] terminal-picker-013: terminal picker appears inline in R-D destination picker', async () => { + test('terminal-picker-013: terminal picker appears inline in R-D destination picker', async () => { await createTerminal('rl-tp-013', terminals); const logCapture = getLogCapture(); logCapture.mark('before-tp-013'); - await waitForHuman('terminal-picker-013', 'Press Cmd+R Cmd+D, then Escape'); + await openAndDismiss(CMD_BIND_TO_DESTINATION); const lines = logCapture.getLinesSince('before-tp-013'); const items = extractQuickPickItemsLogged(lines); @@ -665,7 +667,7 @@ standardSuite('Terminal Picker', { assisted: true }, (log) => { log('✓ Terminal inline in R-D picker — full fields'); }); - test('[assisted] bind-to-destination-013: R-D picker shows both overflow items when many terminals and files are open', async () => { + test('bind-to-destination-013: R-D picker shows both overflow items when many terminals and files are open', async () => { for (let i = 1; i <= TERMINAL_OVERFLOW_COUNT; i++) { await createTerminal(`rl-btd-013-${i}`, terminals); } @@ -686,7 +688,7 @@ standardSuite('Terminal Picker', { assisted: true }, (log) => { const logCapture = getLogCapture(); logCapture.mark('before-btd-013'); - await waitForHuman('bind-to-destination-013', 'Press Cmd+R Cmd+D, then Escape'); + await openAndDismiss(CMD_BIND_TO_DESTINATION); const lines = logCapture.getLinesSince('before-btd-013'); const items = extractQuickPickItemsLogged(lines); diff --git a/packages/rangelink-vscode-extension/src/__integration-tests__/suite/textEditorDestination.test.ts b/packages/rangelink-vscode-extension/src/__integration-tests__/suite/textEditorDestination.test.ts index 7c645316..cfbf63fc 100644 --- a/packages/rangelink-vscode-extension/src/__integration-tests__/suite/textEditorDestination.test.ts +++ b/packages/rangelink-vscode-extension/src/__integration-tests__/suite/textEditorDestination.test.ts @@ -19,7 +19,7 @@ import { waitForHuman, } from '../helpers'; -standardSuite('Text Editor Destination', { assisted: true }, (log) => { +standardSuite('Text Editor Destination', (log) => { const tmpFileUris: vscode.Uri[] = []; teardown(async () => { diff --git a/packages/rangelink-vscode-extension/src/__integration-tests__/suite/unbind.test.ts b/packages/rangelink-vscode-extension/src/__integration-tests__/suite/unbind.test.ts index fcd5beea..2e7038ea 100644 --- a/packages/rangelink-vscode-extension/src/__integration-tests__/suite/unbind.test.ts +++ b/packages/rangelink-vscode-extension/src/__integration-tests__/suite/unbind.test.ts @@ -12,7 +12,7 @@ import { standardSuite, } from '../helpers'; -standardSuite('Unbind Destination', {}, (_log) => { +standardSuite('Unbind Destination', (_log) => { let testFileUri: vscode.Uri; suiteSetup(async () => { diff --git a/packages/rangelink-vscode-extension/src/__integration-tests__/suite/untitledNavigation.test.ts b/packages/rangelink-vscode-extension/src/__integration-tests__/suite/untitledNavigation.test.ts index faae13b9..2de33bb8 100644 --- a/packages/rangelink-vscode-extension/src/__integration-tests__/suite/untitledNavigation.test.ts +++ b/packages/rangelink-vscode-extension/src/__integration-tests__/suite/untitledNavigation.test.ts @@ -70,7 +70,7 @@ const navigateToUntitledLink = ( }); }; -standardSuite('Untitled File Navigation', {}, (_log) => { +standardSuite('Untitled File Navigation', (_log) => { let untitledDoc: vscode.TextDocument; let untitledDisplayName: string; From 5bdf4f13fe8c69d4e631a086adb310d0eace888e Mon Sep 17 00:00:00 2001 From: Charles Ouimet Date: Wed, 13 May 2026 13:16:14 -0400 Subject: [PATCH 2/7] Ran `pnpm fix` --- .../suite/editorBindingValidation.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/rangelink-vscode-extension/src/__integration-tests__/suite/editorBindingValidation.test.ts b/packages/rangelink-vscode-extension/src/__integration-tests__/suite/editorBindingValidation.test.ts index 2f8dff74..e67c1151 100644 --- a/packages/rangelink-vscode-extension/src/__integration-tests__/suite/editorBindingValidation.test.ts +++ b/packages/rangelink-vscode-extension/src/__integration-tests__/suite/editorBindingValidation.test.ts @@ -1,5 +1,7 @@ +import assert from 'node:assert'; import * as fs from 'node:fs'; import * as path from 'node:path'; + import * as vscode from 'vscode'; import { CMD_BIND_TO_DESTINATION, CMD_UNBIND_DESTINATION } from '../../constants/commandIds'; @@ -17,8 +19,6 @@ import { waitForHumanVerdict, } from '../helpers'; -import assert from 'node:assert'; - standardSuite('Editor Binding Validation', (log) => { const tmpFileUris: vscode.Uri[] = []; From b1d16e77093eff2af6472678cdc7a5759eaea648 Mon Sep 17 00:00:00 2001 From: Charles Ouimet Date: Wed, 13 May 2026 13:28:37 -0400 Subject: [PATCH 3/7] =?UTF-8?q?[PR=20feedback]=20Address=20review=20?= =?UTF-8?q?=E2=80=94=20retry=20loop=20for=20openAndDismiss,=20magic=20numb?= =?UTF-8?q?er=20fixes,=20stale=20references?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added retry loop to openAndDismiss so the helper doesn't race against slow picker rendering on loaded CI. Extracted magic numbers into named constants per P003. Updated stale cross-references and test descriptions to reflect the switch from UI triggers to direct command invocation. Ignored Feedback: - Import order in editorBindingValidation.test.ts: False positive — assert is already at line 1 before all other imports. Ref: https://github.com/couimet/rangeLink/pull/561#pullrequestreview-4283813903 --- CLAUDE.md | 2 +- .../helpers/assistedTestHelper.ts | 3 ++- .../src/__integration-tests__/helpers/testEnv.ts | 12 +++++++++++- .../suite/clipboardPreservation.test.ts | 8 ++++++-- .../suite/statusBarMenu.test.ts | 6 +++--- 5 files changed, 23 insertions(+), 8 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index c6bc20fa..654b7843 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -627,7 +627,7 @@ The `automated` field accepts three values: `true` (fully automated), `assisted` (human-in-the-loop), `false` (fully manual) When marking a TC `automated: true`, ensure a non-`[assisted]` integration test exists in `src/__integration-tests__/suite/` on the same branch When marking a TC `automated: assisted`, ensure an `[assisted]`-tagged integration test exists in `src/__integration-tests__/suite/` on the same branch. See TESTING.md § "Assisted mode" for the `[assisted]` tag convention. - Use `automated: false` for scenarios that can't be integration-tested at all (e.g., requires AI assistant interaction, platform-specific behaviour). See TESTING.md § "QuickPick limitation" for what can and cannot be automated. + Use `automated: false` for scenarios that can't be integration-tested at all (e.g., requires AI assistant interaction, platform-specific behaviour). See TESTING.md § "QuickPick and InputBox dismissal" for what can and cannot be automated. Mark `automated: true` or `automated: assisted` based on unit tests alone — the validator only checks integration tests packages/rangelink-vscode-extension/scripts/validate-qa-coverage.sh diff --git a/packages/rangelink-vscode-extension/src/__integration-tests__/helpers/assistedTestHelper.ts b/packages/rangelink-vscode-extension/src/__integration-tests__/helpers/assistedTestHelper.ts index 832b1196..50a7ee38 100644 --- a/packages/rangelink-vscode-extension/src/__integration-tests__/helpers/assistedTestHelper.ts +++ b/packages/rangelink-vscode-extension/src/__integration-tests__/helpers/assistedTestHelper.ts @@ -4,7 +4,8 @@ import * as vscode from 'vscode'; const nodeConsole = new Console(process.stdout, process.stderr); -const SECTION_LINE = '─'.repeat(60); +const SECTION_LINE_WIDTH = 60; +const SECTION_LINE = '─'.repeat(SECTION_LINE_WIDTH); /** * Pauses the test until the human completes a UI action. diff --git a/packages/rangelink-vscode-extension/src/__integration-tests__/helpers/testEnv.ts b/packages/rangelink-vscode-extension/src/__integration-tests__/helpers/testEnv.ts index 44e5d1eb..6317c05f 100644 --- a/packages/rangelink-vscode-extension/src/__integration-tests__/helpers/testEnv.ts +++ b/packages/rangelink-vscode-extension/src/__integration-tests__/helpers/testEnv.ts @@ -37,7 +37,17 @@ export const settle = (ms: number = SETTLE_MS): Promise => export const openAndDismiss = async (command: string): Promise => { const promise = vscode.commands.executeCommand(command); await settle(); - await vscode.commands.executeCommand('workbench.action.closeQuickOpen'); + const deadline = Date.now() + POLL_TIMEOUT_MS; + // Retry dismissal until the command resolves — the picker may be slow to render on loaded CI. + for (;;) { + await vscode.commands.executeCommand('workbench.action.closeQuickOpen'); + const done = await Promise.race([ + promise.then(() => true), + settle(POLL_INTERVAL_MS).then(() => false), + ]); + if (done) break; + if (Date.now() >= deadline) break; + } await promise; await settle(); }; diff --git a/packages/rangelink-vscode-extension/src/__integration-tests__/suite/clipboardPreservation.test.ts b/packages/rangelink-vscode-extension/src/__integration-tests__/suite/clipboardPreservation.test.ts index 2f5896c0..549ad16c 100644 --- a/packages/rangelink-vscode-extension/src/__integration-tests__/suite/clipboardPreservation.test.ts +++ b/packages/rangelink-vscode-extension/src/__integration-tests__/suite/clipboardPreservation.test.ts @@ -307,10 +307,14 @@ standardSuite('Clipboard Preservation — Assisted', (log) => { const fileUri = createWorkspaceFile('cbp-009', lines.join('\n') + '\n'); tmpFileUris.push(fileUri); + const SELECTION_START_LINE = 0; + const SELECTION_END_LINE = 2; + const SELECTION_COLUMN = 0; + const editor009 = await openEditor(fileUri); editor009.selection = new vscode.Selection( - new vscode.Position(0, 0), - new vscode.Position(2, 0), + new vscode.Position(SELECTION_START_LINE, SELECTION_COLUMN), + new vscode.Position(SELECTION_END_LINE, SELECTION_COLUMN), ); await settle(); await writeClipboardSentinel(); diff --git a/packages/rangelink-vscode-extension/src/__integration-tests__/suite/statusBarMenu.test.ts b/packages/rangelink-vscode-extension/src/__integration-tests__/suite/statusBarMenu.test.ts index 9bd9c672..115eb461 100644 --- a/packages/rangelink-vscode-extension/src/__integration-tests__/suite/statusBarMenu.test.ts +++ b/packages/rangelink-vscode-extension/src/__integration-tests__/suite/statusBarMenu.test.ts @@ -30,7 +30,7 @@ standardSuite('R-M Status Bar Menu', (log) => { cleanupFiles(tmpFileUris); }); - test('status-bar-menu-002: clicking the status bar item opens the R-M menu', async () => { + test('status-bar-menu-002: invoking openStatusBarMenu command opens the R-M menu', async () => { const logCapture = getLogCapture(); logCapture.mark('before-menu-002'); @@ -64,7 +64,7 @@ standardSuite('R-M Status Bar Menu', (log) => { log('✓ Unbound menu: no Jump item, correct structure'); }); - test('status-bar-menu-003: Cmd+R Cmd+M keybinding opens the R-M menu', async () => { + test('status-bar-menu-003: invoking openStatusBarMenu command opens the R-M menu', async () => { const testFileUri = createWorkspaceFile('menu-003', 'line 1\nline 2\n'); tmpFileUris.push(testFileUri); const doc = await vscode.workspace.openTextDocument(testFileUri); @@ -101,7 +101,7 @@ standardSuite('R-M Status Bar Menu', (log) => { ], ); - log('✓ Keybinding menu: no Jump item, correct structure'); + log('✓ Direct command menu: no Jump item, correct structure'); }); test('status-bar-menu-005: R-M menu shows Jump to Bound Destination when bound', async () => { From d4ba111ec35ff5d4192949afa7f7b80deceb66ab Mon Sep 17 00:00:00 2001 From: Charles Ouimet Date: Wed, 13 May 2026 13:44:35 -0400 Subject: [PATCH 4/7] Removed reference to now deleted method. --- .../src/__integration-tests__/suite/builtInAiAssistants.test.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/rangelink-vscode-extension/src/__integration-tests__/suite/builtInAiAssistants.test.ts b/packages/rangelink-vscode-extension/src/__integration-tests__/suite/builtInAiAssistants.test.ts index 31ce2673..372aebe4 100644 --- a/packages/rangelink-vscode-extension/src/__integration-tests__/suite/builtInAiAssistants.test.ts +++ b/packages/rangelink-vscode-extension/src/__integration-tests__/suite/builtInAiAssistants.test.ts @@ -11,7 +11,6 @@ import { createWorkspaceFile, getLogCapture, openEditor, - printAssistedBanner, settle, waitForHuman, } from '../helpers'; @@ -22,7 +21,6 @@ suite('Built-in AI Assistants', () => { suiteSetup(async () => { await activateExtension(); - printAssistedBanner(); }); teardown(async () => { From ce24f24269e33cb1e33378958aea43d56e595e67 Mon Sep 17 00:00:00 2001 From: Charles Ouimet Date: Wed, 13 May 2026 14:13:48 -0400 Subject: [PATCH 5/7] [test] Add github-copilot-chat-001 automated integration test Converts the GitHub Copilot Chat destination picker visibility test from fully manual (automated: false) to fully automated using the openAndDismiss + extractQuickPickItemsLogged pattern. The test works without the Copilot Chat extension installed because isGitHubCopilotChatAvailable() detects the built-in workbench.action.chat.open command present in all modern VS Code versions. --- .../qa/qa-test-cases-v1.1.0.yaml | 6 ++--- .../suite/builtInAiAssistants.test.ts | 24 ++++++++++++++++++- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/packages/rangelink-vscode-extension/qa/qa-test-cases-v1.1.0.yaml b/packages/rangelink-vscode-extension/qa/qa-test-cases-v1.1.0.yaml index e385200c..1fb15c21 100644 --- a/packages/rangelink-vscode-extension/qa/qa-test-cases-v1.1.0.yaml +++ b/packages/rangelink-vscode-extension/qa/qa-test-cases-v1.1.0.yaml @@ -3097,15 +3097,15 @@ test_cases: - id: github-copilot-chat-001 feature: 'Built-in AI Assistants' - scenario: 'GitHub Copilot Chat appears in destination picker when extension is active' + scenario: 'GitHub Copilot Chat appears in destination picker when available' preconditions: - - 'GitHub Copilot Chat extension (GitHub.copilot-chat) is installed and active' + - 'VS Code has built-in workbench.action.chat.open command (present in all modern VS Code)' - 'No destination currently bound' steps: - 'Open destination picker via R-D or Command Palette → "Bind to Destination"' - 'Confirm "GitHub Copilot Chat" item appears in the AI Assistants group' expected_result: '"GitHub Copilot Chat" is listed in the AI Assistants group of the destination picker' - automated: false + automated: true - id: github-copilot-chat-002 labels: diff --git a/packages/rangelink-vscode-extension/src/__integration-tests__/suite/builtInAiAssistants.test.ts b/packages/rangelink-vscode-extension/src/__integration-tests__/suite/builtInAiAssistants.test.ts index 372aebe4..a128e0b8 100644 --- a/packages/rangelink-vscode-extension/src/__integration-tests__/suite/builtInAiAssistants.test.ts +++ b/packages/rangelink-vscode-extension/src/__integration-tests__/suite/builtInAiAssistants.test.ts @@ -2,16 +2,19 @@ import assert from 'node:assert'; import * as vscode from 'vscode'; -import { CMD_BIND_TO_CLAUDE_CODE, CMD_UNBIND_DESTINATION } from '../../constants/commandIds'; +import { CMD_BIND_TO_CLAUDE_CODE, CMD_BIND_TO_DESTINATION, CMD_UNBIND_DESTINATION } from '../../constants/commandIds'; import { activateExtension, cleanupFiles, closeAllEditors, createLogger, createWorkspaceFile, + extractQuickPickItemsLogged, getLogCapture, + openAndDismiss, openEditor, settle, + standardSuite, waitForHuman, } from '../helpers'; @@ -136,3 +139,22 @@ suite('Built-in AI Assistants', () => { log('✓ Warm paste: content arrived without cold-start refocus'); }); }); + +standardSuite('Built-in AI Assistants — Destination Picker', (log) => { + test('github-copilot-chat-001: GitHub Copilot Chat appears in destination picker when available', async () => { + const logCapture = getLogCapture(); + logCapture.mark('before-copilot-001'); + + await openAndDismiss(CMD_BIND_TO_DESTINATION); + + const lines = logCapture.getLinesSince('before-copilot-001'); + const items = extractQuickPickItemsLogged(lines); + assert.ok(items, 'Expected showQuickPick log entry — was the picker opened?'); + + const copilotItem = items!.find((item) => item.displayName === 'GitHub Copilot Chat'); + assert.ok(copilotItem, 'Expected "GitHub Copilot Chat" in the destination picker items'); + assert.strictEqual(copilotItem!.itemKind, 'bindable'); + + log('✓ github-copilot-chat-001 — log confirms "GitHub Copilot Chat" appears in R-D picker'); + }); +}); From 1089ad66d0198bd34f52d450bff507989f197249 Mon Sep 17 00:00:00 2001 From: Charles Ouimet Date: Wed, 13 May 2026 14:26:13 -0400 Subject: [PATCH 6/7] Ran `pnpm fix` --- .../__integration-tests__/suite/builtInAiAssistants.test.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/rangelink-vscode-extension/src/__integration-tests__/suite/builtInAiAssistants.test.ts b/packages/rangelink-vscode-extension/src/__integration-tests__/suite/builtInAiAssistants.test.ts index a128e0b8..e31dd44c 100644 --- a/packages/rangelink-vscode-extension/src/__integration-tests__/suite/builtInAiAssistants.test.ts +++ b/packages/rangelink-vscode-extension/src/__integration-tests__/suite/builtInAiAssistants.test.ts @@ -2,7 +2,11 @@ import assert from 'node:assert'; import * as vscode from 'vscode'; -import { CMD_BIND_TO_CLAUDE_CODE, CMD_BIND_TO_DESTINATION, CMD_UNBIND_DESTINATION } from '../../constants/commandIds'; +import { + CMD_BIND_TO_CLAUDE_CODE, + CMD_BIND_TO_DESTINATION, + CMD_UNBIND_DESTINATION, +} from '../../constants/commandIds'; import { activateExtension, cleanupFiles, From 72793b9b0152a79cae6697f2e7839e0b907b5352 Mon Sep 17 00:00:00 2001 From: Charles Ouimet Date: Wed, 13 May 2026 14:32:12 -0400 Subject: [PATCH 7/7] =?UTF-8?q?[PR=20feedback]=20Fix=20openAndDismiss=20de?= =?UTF-8?q?adline=20hang=20=E2=80=94=20throw=20instead=20of=20silently=20a?= =?UTF-8?q?waiting=20unresolved=20promise?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the retry loop hit the deadline, the old code broke out and then awaited the original promise, which was still pending. This hung until the Mocha timeout killed the entire test run. Now it throws immediately with a descriptive error so CI fails fast. Benefits: - CI fails in milliseconds instead of hanging until the Mocha timeout - Error message includes the command name and deadline for fast diagnosis - No silent failures — every timeout is a loud, actionable test failure Ref: https://github.com/couimet/rangeLink/pull/561#pullrequestreview-4284344438 --- .../src/__integration-tests__/helpers/testEnv.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/rangelink-vscode-extension/src/__integration-tests__/helpers/testEnv.ts b/packages/rangelink-vscode-extension/src/__integration-tests__/helpers/testEnv.ts index 6317c05f..8e1fa53c 100644 --- a/packages/rangelink-vscode-extension/src/__integration-tests__/helpers/testEnv.ts +++ b/packages/rangelink-vscode-extension/src/__integration-tests__/helpers/testEnv.ts @@ -46,7 +46,11 @@ export const openAndDismiss = async (command: string): Promise => { settle(POLL_INTERVAL_MS).then(() => false), ]); if (done) break; - if (Date.now() >= deadline) break; + if (Date.now() >= deadline) { + throw new Error( + `openAndDismiss: "${command}" did not resolve within ${POLL_TIMEOUT_MS}ms deadline`, + ); + } } await promise; await settle();