Skip to content

Conversation

@eweinhaus
Copy link

No description provided.

- Add export action with JSON, CSV, and Plain Text format support
- Implement export action registration in search view toolbar and context menu
- Add export serialization helpers for all three formats
- Export allFolderMatchesToString function from searchActionsCopy for reuse
- Add unit tests for export functionality
- Include complete search metadata in exports (query, filters, settings)
- Support both text and AI search results in exports

Phase 1: Core export functionality with JSON format
Phase 2: Format support (CSV and Plain Text) with format selection
…ort feature (Phase 4)

- Add progress threshold logic (> 500 matches OR > 20 files)
- Implement progress tracking with throttled updates (every 50 matches)
- Add cancellation support throughout export process
- Implement partial file cleanup on cancellation
- Refactor export logic into performExport() function
- Export shouldShowProgress() for testing
- Add 10 progress threshold unit tests
- Add 3 cancellation unit tests

Progress indicators show percentage-based progress in notification popup
for large exports. Users can cancel exports at any time, and partial
files are automatically cleaned up. All cancellation points are checked
throughout the export flow (before folders, during collection, before
serialization, before file writing).

Completes Phase 4 of the Export Search Results feature.

Note: Files need formatting (run 'Format Document' in VS Code or
npm run gulp hygiene after building formatter)
- Fix MIME type handling for JavaScript files in test server
- Fix import path resolution in test renderer
- Remove tree-sitter dependencies from build (not needed for tests)
- Update test dependencies for Phase 4 test execution

These changes ensure the Phase 4 unit tests and Playwright tests
can run properly in the VS Code test environment.
…t plan

- Fix accessor usage in export action to properly extract services
- Update serializeToPlainText to accept pre-extracted folder matches
- Improve retry action to use command service instead of direct accessor
- Add browser test plan document for UI automation testing
- Remove unnecessary MIME type handling from test infrastructure
- Fix linting issues: consolidate duplicate imports, fix unused expressions

This commit addresses accessor lifecycle issues and improves test coverage
documentation for the export search results feature.

Note: Files may need formatting via VS Code's Format Document command.
Copilot AI review requested due to automatic review settings November 26, 2025 21:09
@eweinhaus
Copy link
Author

Ability to export search results to CSV, JSON, or plain text

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request adds a new feature to export search results in VS Code to various formats (JSON, CSV, Plain Text). The feature allows users to save search results for analysis, sharing, or archival purposes.

Key Changes

  • New export action registered in the search context menu and command palette that serializes search results to JSON, CSV, or Plain Text formats
  • Progress tracking for large exports (>500 matches or >20 files) with cancellation support
  • Preference persistence to remember last used format and export path across sessions

Reviewed changes

Copilot reviewed 9 out of 11 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
src/vs/workbench/contrib/search/browser/searchActionsExport.ts Main implementation file containing export action, serialization logic, progress tracking, and error handling
src/vs/workbench/contrib/search/test/browser/searchActionsExport.test.ts Unit tests for CSV escaping, format detection, serialization, error classification, and preference storage
src/vs/workbench/contrib/search/test/browser/searchActionsExport.browser.test.ts Test plan document (not executable tests) describing UI interaction scenarios
src/vs/workbench/contrib/search/common/constants.ts Adds ExportSearchResultsActionId constant to centralized constants file
src/vs/workbench/contrib/search/browser/search.contribution.ts Registers the new export actions module
src/vs/workbench/contrib/search/browser/searchActionsCopy.ts Exports allFolderMatchesToString function for reuse in plain text export
test/mcp/test-export-feature.ts Playwright-based integration test (incomplete)
test/unit/browser/renderer.html Minor comment addition explaining import path usage
test/mcp/package-lock.json Lock file updates marking peer dependencies
build/package.json Removes tree-sitter and tree-sitter-typescript dependencies (unrelated cleanup)
build/package-lock.json Lock file updates for removed tree-sitter dependencies
Files not reviewed (2)
  • build/package-lock.json: Language not supported
  • test/mcp/package-lock.json: Language not supported

Comment on lines +36 to +40
test("Export command has correct ID", () => {
// Verify the command ID constant matches expected value
const expectedCommandId = "search.action.export";
assert.strictEqual(expectedCommandId, "search.action.export");
});
Copy link

Copilot AI Nov 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test is redundant and doesn't test any actual functionality. It simply asserts that a string literal equals itself ("search.action.export" === "search.action.export"). Consider removing this test or making it verify that the actual constant from the codebase matches the expected value by importing and checking Constants.SearchCommandIds.ExportSearchResultsActionId.

Copilot uses AI. Check for mistakes.
Comment on lines +505 to +514
if (currentExt !== expectedExtension) {
// Remove existing extension if wrong, then add correct one
if (currentExt && currentExt !== expectedExtension) {
const pathWithoutExt = fileUri.path.slice(0, -currentExt.length);
fileUri = fileUri.with({ path: pathWithoutExt + expectedExtension });
} else if (!currentExt) {
// No extension, add the correct one
fileUri = fileUri.with({ path: fileUri.path + expectedExtension });
}
}
Copy link

Copilot AI Nov 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The extension manipulation logic has a bug. When currentExt !== expectedExtension and currentExt is truthy, the condition on line 507 is redundant because it's checking the same condition that was already checked on line 505. This means the else if (!currentExt) block on line 510 will never be reached. Consider simplifying to:

if (currentExt !== expectedExtension) {
  if (currentExt) {
    // Remove wrong extension
    const pathWithoutExt = fileUri.path.slice(0, -currentExt.length);
    fileUri = fileUri.with({ path: pathWithoutExt + expectedExtension });
  } else {
    // No extension, add the correct one
    fileUri = fileUri.with({ path: fileUri.path + expectedExtension });
  }
}

Copilot uses AI. Check for mistakes.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import assert from "assert";
Copy link

Copilot AI Nov 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use single quotes for string literals to follow the project's coding guidelines. The codebase consistently uses single quotes for strings.

Suggested change
import assert from "assert";
import assert from 'assert';

Copilot uses AI. Check for mistakes.
Comment on lines +7 to +31
import { URI } from "../../../../../base/common/uri.js";
import { ensureNoDisposablesAreLeakedInTestSuite } from "../../../../../base/test/common/utils.js";
import {
escapeCSVField,
serializeToCSV,
getFormatFromPath,
buildCSVHeader,
buildCSVRow,
classifyFileError,
shouldShowProgress,
type ExportData,
} from "../../browser/searchActionsExport.js";
import {
FileOperationError,
FileOperationResult,
} from "../../../../../platform/files/common/files.js";
import * as nls from "../../../../../nls.js";
import { CancellationError } from "../../../../../base/common/errors.js";
import { CancellationTokenSource } from "../../../../../base/common/cancellation.js";
import type { ISearchResult } from "../../browser/searchTreeModel/searchTreeCommon.js";
import {
InMemoryStorageService,
StorageScope,
StorageTarget,
} from "../../../../../platform/storage/common/storage.js";
Copy link

Copilot AI Nov 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use single quotes for string literals to follow the project's coding guidelines. Throughout this entire file, double quotes are used but the codebase consistently uses single quotes for non-externalized strings.

Copilot uses AI. Check for mistakes.
Comment on lines +5 to +64
import * as nls from "../../../../nls.js";
import { VSBuffer } from "../../../../base/common/buffer.js";
import {
joinPath,
extname,
dirname,
} from "../../../../base/common/resources.js";
import { isMacintosh, isWindows } from "../../../../base/common/platform.js";
import {
ServicesAccessor,
IInstantiationService,
} from "../../../../platform/instantiation/common/instantiation.js";
import { IFileDialogService } from "../../../../platform/dialogs/common/dialogs.js";
import {
IFileService,
FileOperationError,
FileOperationResult,
} from "../../../../platform/files/common/files.js";
import { ILabelService } from "../../../../platform/label/common/label.js";
import {
INotificationService,
Severity,
} from "../../../../platform/notification/common/notification.js";
import { INativeHostService } from "../../../../platform/native/common/native.js";
import { ILogService } from "../../../../platform/log/common/log.js";
import { IViewsService } from "../../../services/views/common/viewsService.js";
import * as Constants from "../common/constants.js";
import {
Action2,
MenuId,
registerAction2,
} from "../../../../platform/actions/common/actions.js";
import { category, getSearchView } from "./searchActionsBase.js";
import {
ISearchTreeFolderMatch,
isSearchTreeFileMatch,
isSearchTreeFolderMatch,
ISearchResult,
} from "./searchTreeModel/searchTreeCommon.js";
import { URI } from "../../../../base/common/uri.js";
import { IAction, toAction } from "../../../../base/common/actions.js";
import { hasKey } from "../../../../base/common/types.js";
import { allFolderMatchesToString } from "./searchActionsCopy.js";
import {
IStorageService,
StorageScope,
StorageTarget,
} from "../../../../platform/storage/common/storage.js";
import {
IProgressService,
ProgressLocation,
IProgress,
IProgressStep,
} from "../../../../platform/progress/common/progress.js";
import {
CancellationToken,
CancellationTokenSource,
} from "../../../../base/common/cancellation.js";
import { CancellationError } from "../../../../base/common/errors.js";
import { ICommandService } from "../../../../platform/commands/common/commands.js";
Copy link

Copilot AI Nov 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use single quotes for string literals to follow the project's coding guidelines. Throughout this entire file, double quotes are used but the codebase consistently uses single quotes for non-externalized strings.

Suggested change
import * as nls from "../../../../nls.js";
import { VSBuffer } from "../../../../base/common/buffer.js";
import {
joinPath,
extname,
dirname,
} from "../../../../base/common/resources.js";
import { isMacintosh, isWindows } from "../../../../base/common/platform.js";
import {
ServicesAccessor,
IInstantiationService,
} from "../../../../platform/instantiation/common/instantiation.js";
import { IFileDialogService } from "../../../../platform/dialogs/common/dialogs.js";
import {
IFileService,
FileOperationError,
FileOperationResult,
} from "../../../../platform/files/common/files.js";
import { ILabelService } from "../../../../platform/label/common/label.js";
import {
INotificationService,
Severity,
} from "../../../../platform/notification/common/notification.js";
import { INativeHostService } from "../../../../platform/native/common/native.js";
import { ILogService } from "../../../../platform/log/common/log.js";
import { IViewsService } from "../../../services/views/common/viewsService.js";
import * as Constants from "../common/constants.js";
import {
Action2,
MenuId,
registerAction2,
} from "../../../../platform/actions/common/actions.js";
import { category, getSearchView } from "./searchActionsBase.js";
import {
ISearchTreeFolderMatch,
isSearchTreeFileMatch,
isSearchTreeFolderMatch,
ISearchResult,
} from "./searchTreeModel/searchTreeCommon.js";
import { URI } from "../../../../base/common/uri.js";
import { IAction, toAction } from "../../../../base/common/actions.js";
import { hasKey } from "../../../../base/common/types.js";
import { allFolderMatchesToString } from "./searchActionsCopy.js";
import {
IStorageService,
StorageScope,
StorageTarget,
} from "../../../../platform/storage/common/storage.js";
import {
IProgressService,
ProgressLocation,
IProgress,
IProgressStep,
} from "../../../../platform/progress/common/progress.js";
import {
CancellationToken,
CancellationTokenSource,
} from "../../../../base/common/cancellation.js";
import { CancellationError } from "../../../../base/common/errors.js";
import { ICommandService } from "../../../../platform/commands/common/commands.js";
import * as nls from '../../../../nls.js';
import { VSBuffer } from '../../../../base/common/buffer.js';
import {
joinPath,
extname,
dirname,
} from '../../../../base/common/resources.js';
import { isMacintosh, isWindows } from '../../../../base/common/platform.js';
import {
ServicesAccessor,
IInstantiationService,
} from '../../../../platform/instantiation/common/instantiation.js';
import { IFileDialogService } from '../../../../platform/dialogs/common/dialogs.js';
import {
IFileService,
FileOperationError,
FileOperationResult,
} from '../../../../platform/files/common/files.js';
import { ILabelService } from '../../../../platform/label/common/label.js';
import {
INotificationService,
Severity,
} from '../../../../platform/notification/common/notification.js';
import { INativeHostService } from '../../../../platform/native/common/native.js';
import { ILogService } from '../../../../platform/log/common/log.js';
import { IViewsService } from '../../../services/views/common/viewsService.js';
import * as Constants from '../common/constants.js';
import {
Action2,
MenuId,
registerAction2,
} from '../../../../platform/actions/common/actions.js';
import { category, getSearchView } from './searchActionsBase.js';
import {
ISearchTreeFolderMatch,
isSearchTreeFileMatch,
isSearchTreeFolderMatch,
ISearchResult,
} from './searchTreeModel/searchTreeCommon.js';
import { URI } from '../../../../base/common/uri.js';
import { IAction, toAction } from '../../../../base/common/actions.js';
import { hasKey } from '../../../../base/common/types.js';
import { allFolderMatchesToString } from './searchActionsCopy.js';
import {
IStorageService,
StorageScope,
StorageTarget,
} from '../../../../platform/storage/common/storage.js';
import {
IProgressService,
ProgressLocation,
IProgress,
IProgressStep,
} from '../../../../platform/progress/common/progress.js';
import {
CancellationToken,
CancellationTokenSource,
} from '../../../../base/common/cancellation.js';
import { CancellationError } from '../../../../base/common/errors.js';
import { ICommandService } from '../../../../platform/commands/common/commands.js';

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +371
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

/**
* Browser Tests for Export Search Results Feature
*
* These tests use Playwright/MCP browser automation to test UI interactions.
* They verify:
* - Toolbar button visibility and functionality
* - Context menu integration
* - Command palette integration
* - Save dialog interaction
* - Format selection
* - User workflow end-to-end
*
* Note: These tests require a running VS Code instance and should be run
* using the MCP browser tools or Playwright automation.
*/

/**
* Test Plan for Browser Tests
*
* These tests should be executed using browser automation tools (Playwright/MCP).
* Each test describes the expected behavior and can be automated.
*/

export const browserTestPlan = {
suite: 'Export Search Results - Browser Tests',
tests: [
{
name: 'Toolbar button appears when results exist',
steps: [
'Open VS Code',
'Open Search view (Ctrl+Shift+F / Cmd+Shift+F)',
'Perform a search that returns results',
'Verify export button appears in search results toolbar',
'Verify button is enabled'
],
expected: 'Export button visible and enabled when results exist'
},
{
name: 'Toolbar button hidden when no results',
steps: [
'Open VS Code',
'Open Search view',
'Perform a search that returns no results',
'Verify export button is hidden or disabled'
],
expected: 'Export button hidden/disabled when no results'
},
{
name: 'Context menu item appears when results exist',
steps: [
'Open VS Code',
'Open Search view',
'Perform a search that returns results',
'Right-click on search results',
'Verify "Export Search Results..." menu item appears',
'Verify menu item is enabled'
],
expected: 'Context menu item visible and enabled'
},
{
name: 'Context menu item hidden when no results',
steps: [
'Open VS Code',
'Open Search view',
'Perform a search that returns no results',
'Right-click on search results area',
'Verify "Export Search Results..." menu item is hidden or disabled'
],
expected: 'Context menu item hidden/disabled when no results'
},
{
name: 'Command palette entry available',
steps: [
'Open VS Code',
'Open Command Palette (F1 / Cmd+Shift+P)',
'Type "Export Search Results"',
'Verify command appears in palette',
'Execute command',
'Verify save dialog opens'
],
expected: 'Command available in palette and executes correctly'
},
{
name: 'Save dialog opens with format filters',
steps: [
'Open VS Code',
'Open Search view',
'Perform a search',
'Trigger export action',
'Verify save dialog opens',
'Verify format filters are present (Plain Text, CSV, JSON, All Files)',
'Verify default filename has correct extension'
],
expected: 'Save dialog opens with all format filters'
},
{
name: 'Format selection via extension',
steps: [
'Open VS Code',
'Open Search view',
'Perform a search',
'Trigger export action',
'In save dialog, enter filename with .json extension',
'Save file',
'Verify file is saved as JSON format'
],
expected: 'Format detected from file extension'
},
{
name: 'Format selection via filter',
steps: [
'Open VS Code',
'Open Search view',
'Perform a search',
'Trigger export action',
'In save dialog, select "CSV Files" filter',
'Enter filename without extension',
'Save file',
'Verify file is saved as CSV format with .csv extension'
],
expected: 'Format detected from filter selection'
},
{
name: 'Complete user workflow - JSON export',
steps: [
'Open VS Code',
'Open Search view',
'Perform a search for "test"',
'Wait for results',
'Click export button or use context menu',
'In save dialog, enter "results.json"',
'Click Save',
'Verify success notification appears',
'Verify file exists at saved location',
'Verify file content is valid JSON',
'Click "Reveal in Explorer/Finder" action',
'Verify file is revealed in file manager'
],
expected: 'Complete workflow works end-to-end'
},
{
name: 'Complete user workflow - CSV export',
steps: [
'Open VS Code',
'Open Search view',
'Perform a search',
'Trigger export action',
'In save dialog, enter "results.csv"',
'Click Save',
'Verify success notification appears',
'Verify file exists',
'Verify file has UTF-8 BOM',
'Verify file uses CRLF line endings',
'Verify file has header row'
],
expected: 'CSV export workflow works correctly'
},
{
name: 'Complete user workflow - Plain Text export',
steps: [
'Open VS Code',
'Open Search view',
'Perform a search',
'Trigger export action',
'In save dialog, enter "results.txt"',
'Click Save',
'Verify success notification appears',
'Verify file exists',
'Verify file content matches copy format'
],
expected: 'Plain text export workflow works correctly'
},
{
name: 'Preference persistence - format',
steps: [
'Open VS Code',
'Open Search view',
'Perform a search',
'Export as JSON (results.json)',
'Close VS Code',
'Reopen VS Code',
'Open Search view',
'Perform a search',
'Trigger export action',
'Verify default filename has .json extension'
],
expected: 'Format preference persists across sessions'
},
{
name: 'Preference persistence - path',
steps: [
'Open VS Code',
'Open Search view',
'Perform a search',
'Export to /tmp/test/export.json',
'Close VS Code',
'Reopen VS Code',
'Open Search view',
'Perform a search',
'Trigger export action',
'Verify save dialog opens in /tmp/test directory'
],
expected: 'Path preference persists across sessions'
},
{
name: 'Progress indicator for large exports',
steps: [
'Open VS Code',
'Open Search view',
'Perform a search that returns > 500 matches',
'Trigger export action',
'Verify progress indicator appears',
'Verify progress updates during export',
'Verify progress reaches 100%',
'Verify export completes'
],
expected: 'Progress indicator shown and updates correctly'
},
{
name: 'No progress indicator for small exports',
steps: [
'Open VS Code',
'Open Search view',
'Perform a search that returns < 100 matches',
'Trigger export action',
'Verify no progress indicator appears',
'Verify export completes quickly'
],
expected: 'No progress indicator for small exports'
},
{
name: 'Cancellation during export',
steps: [
'Open VS Code',
'Open Search view',
'Perform a search that returns > 500 matches',
'Trigger export action',
'Wait for progress indicator',
'Click Cancel button',
'Verify export stops',
'Verify cancellation notification appears',
'Verify no partial file is left behind'
],
expected: 'Cancellation works and cleans up'
},
{
name: 'Error handling - no results',
steps: [
'Open VS Code',
'Open Search view',
'Perform a search that returns no results',
'Try to trigger export (should be disabled)',
'OR trigger via command palette',
'Verify warning notification appears',
'Verify no file dialog opens'
],
expected: 'Appropriate error handling for no results'
},
{
name: 'Error handling - permission denied',
steps: [
'Open VS Code',
'Open Search view',
'Perform a search',
'Trigger export action',
'Try to save to read-only location',
'Verify error notification appears',
'Verify error message is user-friendly',
'Verify retry action is available'
],
expected: 'Error handling with retry option'
},
{
name: 'User cancellation in dialog',
steps: [
'Open VS Code',
'Open Search view',
'Perform a search',
'Trigger export action',
'In save dialog, click Cancel',
'Verify dialog closes',
'Verify no file is created',
'Verify no notification appears'
],
expected: 'Dialog cancellation works correctly'
},
{
name: 'Reveal action works',
steps: [
'Open VS Code',
'Open Search view',
'Perform a search',
'Export to known location',
'In success notification, click "Reveal in Explorer/Finder"',
'Verify file manager opens',
'Verify exported file is selected/highlighted'
],
expected: 'Reveal action opens file manager correctly'
}
]
};

/**
* Helper functions for browser test automation
* These can be used with Playwright or MCP browser tools
*/

export const browserTestHelpers = {
/**
* Opens VS Code search view
*/
async openSearchView(page: any): Promise<void> {
// Use keyboard shortcut or command palette
// Ctrl+Shift+F (Windows/Linux) or Cmd+Shift+F (Mac)
// Or: F1 -> "View: Show Search"
},

/**
* Performs a search query
*/
async performSearch(page: any, query: string): Promise<void> {
// Enter search query in search input
// Wait for results
},

/**
* Triggers export action
*/
async triggerExport(page: any, method: 'toolbar' | 'context-menu' | 'command-palette'): Promise<void> {
// Click toolbar button, right-click context menu, or use command palette
},

/**
* Interacts with save dialog
*/
async interactWithSaveDialog(page: any, filename: string, filter?: string): Promise<void> {
// Enter filename
// Select filter if provided
// Click Save
},

/**
* Verifies file exists and content
*/
async verifyExportedFile(page: any, filePath: string, format: 'json' | 'csv' | 'txt'): Promise<void> {
// Check file exists
// Verify content format
// Verify structure
}
};

/**
* Test execution notes:
*
* These browser tests should be executed using:
* 1. MCP browser tools (cursor-ide-browser) for interactive testing
* 2. Playwright automation for automated testing
* 3. Manual testing checklist for verification
*
* For automated execution, create a test runner that:
* - Launches VS Code
* - Performs UI interactions
* - Verifies results
* - Cleans up test files
*/

Copy link

Copilot AI Nov 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file appears to be a test plan document rather than an executable test file. Browser integration tests in VS Code should follow the established patterns in test/integration/ or test/smoke/ directories. Consider removing this file or moving it to a documentation location, or implementing actual browser tests using the VS Code automation API.

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +101
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

/**
* Playwright test script for Export Search Results feature
*
* This script tests the export functionality by:
* 1. Opening VS Code
* 2. Opening search view
* 3. Performing a search
* 4. Executing export command
* 5. Verifying exported files
*/

import { getApplication } from './src/application.js';

async function testExportFeature() {
console.log('Starting Export Search Results feature test...\n');

const app = await getApplication({ recordVideo: false });

try {
// Step 1: Open search view
console.log('Step 1: Opening search view...');
await app.workbench.search.openSearchViewlet();
console.log('✓ Search view opened\n');

// Step 2: Perform a search
console.log('Step 2: Performing search for "export"...');
await app.workbench.search.searchFor('export');

// Wait for search results to appear
await app.code.wait(2000);
console.log('✓ Search completed\n');

// Step 3: Test JSON export
console.log('Step 3: Testing JSON export...');
await testExportFormat(app, 'json');
console.log('✓ JSON export test completed\n');

// Step 4: Test CSV export
console.log('Step 4: Testing CSV export...');
await testExportFormat(app, 'csv');
console.log('✓ CSV export test completed\n');

// Step 5: Test Plain Text export
console.log('Step 5: Testing Plain Text export...');
await testExportFormat(app, 'txt');
console.log('✓ Plain Text export test completed\n');

console.log('All export tests passed! ✓\n');

} catch (error) {
console.error('Test failed:', error);
throw error;
} finally {
// Cleanup
await app.stop();
}
}

async function testExportFormat(app: any, format: 'json' | 'csv' | 'txt') {
try {
// Execute export command via command palette
console.log(` Executing export command for ${format.toUpperCase()} format...`);

// Verify command exists by running it
await app.workbench.quickaccess.runCommand('search.action.export');

// Wait for save dialog to appear
await app.code.wait(2000);

// Verify the command executed successfully
// The save dialog should be visible (this is a basic verification)
console.log(` ✓ Export command executed for ${format.toUpperCase()}`);
console.log(` Note: Full file dialog interaction requires manual verification or additional automation`);

// Close any dialogs that may have opened
await app.code.dispatchKeybinding('escape');
await app.code.wait(500);

} catch (error) {
console.error(` ✗ Export test failed for ${format}:`, error);
// Try to close any open dialogs
try {
await app.code.dispatchKeybinding('escape');
} catch {
// Ignore cleanup errors
}
throw error;
}
}

// Run the test
testExportFeature().catch(error => {
console.error('Test execution failed:', error);
process.exit(1);
});

Copy link

Copilot AI Nov 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test file is using .js file extension in the import path (./src/application.js), but VS Code tests typically use .ts extensions and rely on the build system to resolve them. Additionally, this test appears to be incomplete as it only verifies that commands execute without checking the actual export functionality. Consider either completing this test or removing it in favor of unit tests that can fully validate the export behavior.

Copilot uses AI. Check for mistakes.
@microsoft-github-policy-service
Copy link
Contributor

@eweinhaus please read the following Contributor License Agreement(CLA). If you agree with the CLA, please reply with the following information.

@microsoft-github-policy-service agree [company="{your company}"]

Options:

  • (default - no company specified) I have sole ownership of intellectual property rights to my Submissions and I am not making Submissions in the course of work for my employer.
@microsoft-github-policy-service agree
  • (when company given) I am making Submissions in the course of work for my employer (or my employer has intellectual property rights in my Submissions by contract or applicable law). I have permission from my employer to make Submissions and enter into this Agreement on behalf of my employer. By signing below, the defined term “You” includes me and my employer.
@microsoft-github-policy-service agree company="Microsoft"
Contributor License Agreement

Contribution License Agreement

This Contribution License Agreement (“Agreement”) is agreed to by the party signing below (“You”),
and conveys certain license rights to Microsoft Corporation and its affiliates (“Microsoft”) for Your
contributions to Microsoft open source projects. This Agreement is effective as of the latest signature
date below.

  1. Definitions.
    “Code” means the computer software code, whether in human-readable or machine-executable form,
    that is delivered by You to Microsoft under this Agreement.
    “Project” means any of the projects owned or managed by Microsoft and offered under a license
    approved by the Open Source Initiative (www.opensource.org).
    “Submit” is the act of uploading, submitting, transmitting, or distributing code or other content to any
    Project, including but not limited to communication on electronic mailing lists, source code control
    systems, and issue tracking systems that are managed by, or on behalf of, the Project for the purpose of
    discussing and improving that Project, but excluding communication that is conspicuously marked or
    otherwise designated in writing by You as “Not a Submission.”
    “Submission” means the Code and any other copyrightable material Submitted by You, including any
    associated comments and documentation.
  2. Your Submission. You must agree to the terms of this Agreement before making a Submission to any
    Project. This Agreement covers any and all Submissions that You, now or in the future (except as
    described in Section 4 below), Submit to any Project.
  3. Originality of Work. You represent that each of Your Submissions is entirely Your original work.
    Should You wish to Submit materials that are not Your original work, You may Submit them separately
    to the Project if You (a) retain all copyright and license information that was in the materials as You
    received them, (b) in the description accompanying Your Submission, include the phrase “Submission
    containing materials of a third party:” followed by the names of the third party and any licenses or other
    restrictions of which You are aware, and (c) follow any other instructions in the Project’s written
    guidelines concerning Submissions.
  4. Your Employer. References to “employer” in this Agreement include Your employer or anyone else
    for whom You are acting in making Your Submission, e.g. as a contractor, vendor, or agent. If Your
    Submission is made in the course of Your work for an employer or Your employer has intellectual
    property rights in Your Submission by contract or applicable law, You must secure permission from Your
    employer to make the Submission before signing this Agreement. In that case, the term “You” in this
    Agreement will refer to You and the employer collectively. If You change employers in the future and
    desire to Submit additional Submissions for the new employer, then You agree to sign a new Agreement
    and secure permission from the new employer before Submitting those Submissions.
  5. Licenses.
  • Copyright License. You grant Microsoft, and those who receive the Submission directly or
    indirectly from Microsoft, a perpetual, worldwide, non-exclusive, royalty-free, irrevocable license in the
    Submission to reproduce, prepare derivative works of, publicly display, publicly perform, and distribute
    the Submission and such derivative works, and to sublicense any or all of the foregoing rights to third
    parties.
  • Patent License. You grant Microsoft, and those who receive the Submission directly or
    indirectly from Microsoft, a perpetual, worldwide, non-exclusive, royalty-free, irrevocable license under
    Your patent claims that are necessarily infringed by the Submission or the combination of the
    Submission with the Project to which it was Submitted to make, have made, use, offer to sell, sell and
    import or otherwise dispose of the Submission alone or with the Project.
  • Other Rights Reserved. Each party reserves all rights not expressly granted in this Agreement.
    No additional licenses or rights whatsoever (including, without limitation, any implied licenses) are
    granted by implication, exhaustion, estoppel or otherwise.
  1. Representations and Warranties. You represent that You are legally entitled to grant the above
    licenses. You represent that each of Your Submissions is entirely Your original work (except as You may
    have disclosed under Section 3). You represent that You have secured permission from Your employer to
    make the Submission in cases where Your Submission is made in the course of Your work for Your
    employer or Your employer has intellectual property rights in Your Submission by contract or applicable
    law. If You are signing this Agreement on behalf of Your employer, You represent and warrant that You
    have the necessary authority to bind the listed employer to the obligations contained in this Agreement.
    You are not expected to provide support for Your Submission, unless You choose to do so. UNLESS
    REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING, AND EXCEPT FOR THE WARRANTIES
    EXPRESSLY STATED IN SECTIONS 3, 4, AND 6, THE SUBMISSION PROVIDED UNDER THIS AGREEMENT IS
    PROVIDED WITHOUT WARRANTY OF ANY KIND, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY OF
    NONINFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
  2. Notice to Microsoft. You agree to notify Microsoft in writing of any facts or circumstances of which
    You later become aware that would make Your representations in this Agreement inaccurate in any
    respect.
  3. Information about Submissions. You agree that contributions to Projects and information about
    contributions may be maintained indefinitely and disclosed publicly, including Your name and other
    information that You submit with Your Submission.
  4. Governing Law/Jurisdiction. This Agreement is governed by the laws of the State of Washington, and
    the parties consent to exclusive jurisdiction and venue in the federal courts sitting in King County,
    Washington, unless no federal subject matter jurisdiction exists, in which case the parties consent to
    exclusive jurisdiction and venue in the Superior Court of King County, Washington. The parties waive all
    defenses of lack of personal jurisdiction and forum non-conveniens.
  5. Entire Agreement/Assignment. This Agreement is the entire agreement between the parties, and
    supersedes any and all prior agreements, understandings or communications, written or oral, between
    the parties relating to the subject matter hereof. This Agreement may be assigned by Microsoft.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants