Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions packages/rangelink-vscode-extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,12 @@
"category": "RangeLink",
"icon": "$(link)"
},
{
"command": "rangelink.bindToCustomAiById",
"title": "Bind to Custom AI by ID",
"category": "RangeLink",
"icon": "$(link)"
},
{
"command": "rangelink.bindToGeminiCodeAssist",
"title": "Bind to Gemini Code Assist",
Expand Down Expand Up @@ -713,6 +719,10 @@
"command": "rangelink.bindToTextEditorHere",
"when": "false"
},
{
"command": "rangelink.bindToCustomAiById",
"when": "false"
},
{
"command": "rangelink.pasteFileAbsolutePath",
"when": "false"
Expand Down
12 changes: 6 additions & 6 deletions packages/rangelink-vscode-extension/qa/qa-test-cases-v1.1.0.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1830,39 +1830,39 @@ test_cases:
feature: 'Custom AI Assistants — Tier 1 Paste Flow'
scenario: 'Tier 1 direct insert delivers text to dummy extension textarea'
expected_result: 'tier1 textarea contains the generated link; tier2 textarea is empty'
automated: assisted
automated: true

- id: custom-ai-assistant-011
labels:
- clipboard
feature: 'Custom AI Assistants — Tier 1 Clipboard'
scenario: 'Tier 1 paste preserves clipboard (sentinel restored after R-L)'
expected_result: 'Clipboard contains the sentinel (outer preserve restores it; DirectInsertFactory never touches clipboard)'
automated: assisted
automated: true

- id: custom-ai-assistant-012
labels:
- clipboard
feature: 'Custom AI Assistants — Tier 3 Toast'
scenario: 'Tier 3 focusCommands shows manual-paste toast after R-L'
expected_result: 'Info toast "Paste (Cmd/Ctrl+V) in Dummy AI (Tier 3) to use." logged; ManualPasteInsertFactory success logged'
automated: assisted
automated: true

- id: custom-ai-assistant-013
labels:
- clipboard
feature: 'Custom AI Assistants — Tier 2→3 Fallback'
scenario: 'Tier 2 focusAndPasteCommands not registered — falls through to Tier 3'
expected_result: 'Tier 2 skip log (nonexistent.paste not registered), Tier 3 resolution log (dummyAi.focusPanel), manual-paste toast shown'
automated: assisted
automated: true

- id: custom-ai-assistant-014
labels:
- clipboard
feature: 'Custom AI Assistants — ${content} Template'
scenario: 'insertCommands with ${content} template interpolation delivers text via insertWithArgs'
expected_result: 'tier1 textarea contains the generated link; DirectInsertFactory success log mentions dummyAi.insertWithArgs'
automated: assisted
automated: true

- id: custom-ai-assistant-015
labels:
Expand All @@ -1886,7 +1886,7 @@ test_cases:
feature: 'Custom AI Assistants — Cold Start'
scenario: 'Tier 1 direct insert works when the AI extension panel is not yet visible'
expected_result: 'Panel auto-initializes on first insert; tier1 textarea contains the generated link; tier2 is empty'
automated: assisted
automated: true

# ---------------------------------------------------------------------------
# Section — Built-in AI Assistants
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import assert from 'node:assert';

import * as vscode from 'vscode';

import type { LogCapture } from '../../LogCapture';

export const CLIPBOARD_SENTINEL = 'rangelink-test-sentinel-value';

export const writeClipboardSentinel = async (): Promise<void> => {
Expand All @@ -26,3 +28,38 @@ export const assertClipboardRestored = async (context: string): Promise<void> =>
`${context}: clipboard should be restored to sentinel`,
);
};

export const assertClipboardPreservationRan = (
logCapture: LogCapture,
markName: string,
operationLabel: string,
): void => {
const lines = logCapture.getLinesSince(markName);
const savedIdx = lines.findIndex((l) => l.includes('Clipboard saved'));
const restoredIdx = lines.findIndex((l) => l.includes('Clipboard restored'));
assert.ok(
savedIdx >= 0,
'Expected "Clipboard saved" log entry — preservation must read clipboard',
);
assert.ok(
restoredIdx > savedIdx,
`Expected "Clipboard restored" log entry after ${operationLabel} operation`,
);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
};

export const assertClipboardPreservationDidNotRun = (
logCapture: LogCapture,
markName: string,
): void => {
const lines = logCapture.getLinesSince(markName);
assert.strictEqual(
lines.find((l) => l.includes('Clipboard saved')),
undefined,
'Expected no "Clipboard saved" — no operation ran',
);
assert.strictEqual(
lines.find((l) => l.includes('Clipboard restored')),
undefined,
'Expected no "Clipboard restored" — no operation ran',
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
} from '../../utils/aiAssistants/builtInAiAssistants';
import {
assertClipboardChanged,
assertClipboardPreservationRan,
assertClipboardRestored,
assertStatusBarMsgLogged,
extractGeneratedLink,
Expand Down Expand Up @@ -443,9 +444,12 @@ standardSuite('Built-in AI Assistants', (ss) => {
await ss.settle();

await writeClipboardSentinel();
const logCapture = getLogCapture();
logCapture.mark('before-clip-011');

await vscode.commands.executeCommand(CMD_COPY_LINK_RELATIVE);
await ss.settle();
assertClipboardPreservationRan(logCapture, 'before-clip-011', 'R-L');
await assertClipboardRestored('clipboard-preservation-011: always + Claude Code cold paste');
ss.log('✓ clipboard-preservation-011: prior clipboard restored after cold paste');
});
Expand All @@ -470,9 +474,12 @@ standardSuite('Built-in AI Assistants', (ss) => {
await ss.settle();

await writeClipboardSentinel();
const logCapture = getLogCapture();
logCapture.mark('before-clip-012');

await vscode.commands.executeCommand(CMD_COPY_LINK_RELATIVE);
await ss.settle();
assertClipboardPreservationRan(logCapture, 'before-clip-012', 'R-L');
await assertClipboardRestored('clipboard-preservation-012: always + Claude Code warm paste');
ss.log('✓ clipboard-preservation-012: prior clipboard restored after warm paste');
});
Expand All @@ -489,9 +496,12 @@ standardSuite('Built-in AI Assistants', (ss) => {
await ss.settle();

await writeClipboardSentinel();
const logCapture = getLogCapture();
logCapture.mark('before-clip-013');

await vscode.commands.executeCommand(CMD_COPY_LINK_RELATIVE);
await ss.settle();
assertClipboardPreservationRan(logCapture, 'before-clip-013', 'R-L');
await assertClipboardRestored('clipboard-preservation-013: always + Cursor AI cold paste');
ss.log('✓ clipboard-preservation-013: cold Cursor AI paste — clipboard restored');
});
Expand All @@ -516,9 +526,12 @@ standardSuite('Built-in AI Assistants', (ss) => {
await ss.settle();

await writeClipboardSentinel();
const logCapture = getLogCapture();
logCapture.mark('before-clip-014');

await vscode.commands.executeCommand(CMD_COPY_LINK_RELATIVE);
await ss.settle();
assertClipboardPreservationRan(logCapture, 'before-clip-014', 'R-L');
await assertClipboardRestored('clipboard-preservation-014: always + Cursor AI warm paste');
ss.log('✓ clipboard-preservation-014: warm Cursor AI paste — clipboard restored');
});
Expand All @@ -535,9 +548,12 @@ standardSuite('Built-in AI Assistants', (ss) => {
await ss.settle();

await writeClipboardSentinel();
const logCapture = getLogCapture();
logCapture.mark('before-clip-015');

await vscode.commands.executeCommand(CMD_COPY_LINK_RELATIVE);
await ss.settle();
assertClipboardPreservationRan(logCapture, 'before-clip-015', 'R-L');
await assertClipboardRestored('clipboard-preservation-015: always + Copilot Chat cold paste');
ss.log('✓ clipboard-preservation-015: cold Copilot Chat paste — clipboard restored');
});
Expand All @@ -562,9 +578,12 @@ standardSuite('Built-in AI Assistants', (ss) => {
await ss.settle();

await writeClipboardSentinel();
const logCapture = getLogCapture();
logCapture.mark('before-clip-016');

await vscode.commands.executeCommand(CMD_COPY_LINK_RELATIVE);
await ss.settle();
assertClipboardPreservationRan(logCapture, 'before-clip-016', 'R-L');
await assertClipboardRestored('clipboard-preservation-016: always + Copilot Chat warm paste');
ss.log('✓ clipboard-preservation-016: warm Copilot Chat paste — clipboard restored');
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import {
import { VSCODE_CMD_TERMINAL_SELECT_ALL } from '../../constants/vscodeCommandIds';
import {
assertClipboardChanged,
assertClipboardPreservationDidNotRun,
assertClipboardPreservationRan,
assertClipboardRestored,
assertTerminalBufferContains,
extractGeneratedLink,
Expand Down Expand Up @@ -44,8 +46,12 @@ standardSuite('Clipboard Preservation', (ss) => {
.getConfiguration('rangelink')
.update('clipboard.preserve', 'always', vscode.ConfigurationTarget.Global);

const logCapture = getLogCapture();
logCapture.mark('before-003');
capturing.clearCaptured();
await vscode.commands.executeCommand(CMD_PASTE_CURRENT_FILE_PATH_RELATIVE);
await ss.settle();
assertClipboardPreservationRan(logCapture, 'before-003', 'R-F');
await assertClipboardRestored('R-F with preserve=always');
assertTerminalBufferContains(capturing.getCapturedText(), 'clipboard');
});
Expand Down Expand Up @@ -157,6 +163,7 @@ standardSuite('Clipboard Preservation — Assisted', (ss) => {
const generatedLink = extractGeneratedLink(lines);
assert.ok(generatedLink, 'Expected "Generated link:" log line');

assertClipboardPreservationRan(logCapture, 'before-001', 'R-L');
await assertClipboardRestored('clipboard-preservation-001: always + R-L');
assertTerminalBufferContains(capturing.getCapturedText(), generatedLink);
ss.log('✓ Clipboard restored to sentinel after R-L; terminal received link');
Expand Down Expand Up @@ -184,8 +191,11 @@ standardSuite('Clipboard Preservation — Assisted', (ss) => {
// Sentinel written after selectAll so copyOnSelection cannot overwrite it
await writeClipboardSentinel();

const logCapture = getLogCapture();
logCapture.mark('before-002');
await vscode.commands.executeCommand(CMD_TERMINAL_PASTE_SELECTED_TEXT);
await ss.settle();
assertClipboardPreservationRan(logCapture, 'before-002', 'R-V');

const destContent = (await vscode.workspace.openTextDocument(fileUri)).getText();
assert.ok(
Expand All @@ -208,6 +218,8 @@ standardSuite('Clipboard Preservation — Assisted', (ss) => {
editor.selection = new vscode.Selection(0, 0, 2, 6);
await ss.settle();

const logCapture = getLogCapture();
logCapture.mark('before-004');
await waitForHuman(
'clipboard-preservation-004',
`Press Cmd+R Cmd+D → bind "Dummy AI (Tier 1)", click back into the editor, press Cmd+R Cmd+L`,
Expand All @@ -220,6 +232,7 @@ standardSuite('Clipboard Preservation — Assisted', (ss) => {
);

await ss.settle();
assertClipboardPreservationRan(logCapture, 'before-004', 'R-L');
const dummyText = (await vscode.commands.executeCommand('dummyAi.getText')) as {
tier1: string;
tier2: string;
Expand Down Expand Up @@ -257,6 +270,7 @@ standardSuite('Clipboard Preservation — Assisted', (ss) => {
const generatedLink = extractGeneratedLink(lines);
assert.ok(generatedLink, 'Expected "Generated link:" log line');

assertClipboardPreservationRan(logCapture, 'before-005', 'R-L');
await assertClipboardRestored('clipboard-preservation-005: always + terminal paste');
assertTerminalBufferContains(capturing.getCapturedText(), generatedLink);
ss.log('✓ Clipboard restored to sentinel after terminal paste (preserve=always)');
Expand Down Expand Up @@ -315,8 +329,11 @@ standardSuite('Clipboard Preservation — Assisted', (ss) => {
await ss.settle();
await writeClipboardSentinel();

const logCapture = getLogCapture();
logCapture.mark('before-009');
await openAndDismiss(CMD_COPY_LINK_RELATIVE);

await ss.settle();
assertClipboardPreservationDidNotRun(logCapture, 'before-009');
await assertClipboardRestored('clipboard-preservation-009: always + picker dismissed');
ss.log('✓ Clipboard unchanged after picker dismissed (no operation performed)');
});
Expand Down
Loading
Loading