From 4030c9d8e0dc84ec1893fab17f4164eb9d435c66 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 25 Sep 2025 10:51:27 +0000 Subject: [PATCH 01/15] Initial plan From 73cf0838b4dcae89893aff80a032f6398793767e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 25 Sep 2025 10:58:32 +0000 Subject: [PATCH 02/15] Initial analysis of test compilation issue Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> --- .../vscode.proposed.chatParticipantAdditions.d.ts | 10 ++++++++-- src/@types/vscode.proposed.chatParticipantPrivate.d.ts | 3 +++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/@types/vscode.proposed.chatParticipantAdditions.d.ts b/src/@types/vscode.proposed.chatParticipantAdditions.d.ts index 2cb4168b43..73c8c948cc 100644 --- a/src/@types/vscode.proposed.chatParticipantAdditions.d.ts +++ b/src/@types/vscode.proposed.chatParticipantAdditions.d.ts @@ -646,7 +646,13 @@ declare module 'vscode' { } export interface ChatRequest { - modeInstructions?: string; - modeInstructionsToolReferences?: readonly ChatLanguageModelToolReference[]; + readonly modeInstructions?: string; + readonly modeInstructions2?: ChatRequestModeInstructions; + } + + export interface ChatRequestModeInstructions { + readonly content: string; + readonly toolReferences?: readonly ChatLanguageModelToolReference[]; + readonly metadata?: Record; } } diff --git a/src/@types/vscode.proposed.chatParticipantPrivate.d.ts b/src/@types/vscode.proposed.chatParticipantPrivate.d.ts index eb42d52c92..9c444026ad 100644 --- a/src/@types/vscode.proposed.chatParticipantPrivate.d.ts +++ b/src/@types/vscode.proposed.chatParticipantPrivate.d.ts @@ -187,6 +187,8 @@ declare module 'vscode' { isQuotaExceeded?: boolean; + isRateLimited?: boolean; + level?: ChatErrorLevel; code?: string; @@ -239,6 +241,7 @@ declare module 'vscode' { export class ExtendedLanguageModelToolResult extends LanguageModelToolResult { toolResultMessage?: string | MarkdownString; toolResultDetails?: Array; + toolMetadata?: unknown; } // #region Chat participant detection From f87bcf0258aeb3a70a102d672556f45d1a27eebc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 25 Sep 2025 11:06:40 +0000 Subject: [PATCH 03/15] Fix: Include tests in webpack compilation for node target --- src/test/index.ts | 40 +++++++++++++++++++++------------------- webpack.config.js | 2 ++ 2 files changed, 23 insertions(+), 19 deletions(-) diff --git a/src/test/index.ts b/src/test/index.ts index 677bc56144..ed90fdeebd 100644 --- a/src/test/index.ts +++ b/src/test/index.ts @@ -1,26 +1,15 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + // This file is providing the test runner to use when running extension tests. import * as path from 'path'; import * as vscode from 'vscode'; -import glob from 'glob'; import Mocha from 'mocha'; import { mockWebviewEnvironment } from './mocks/mockWebviewEnvironment'; import { EXTENSION_ID } from '../constants'; -function addTests(mocha: Mocha, root: string): Promise { - return new Promise((resolve, reject) => { - glob('**/**.test.js', { cwd: root }, (error, files) => { - if (error) { - return reject(error); - } - - for (const testFile of files) { - mocha.addFile(path.join(root, testFile)); - } - resolve(); - }); - }); -} - async function runAllExtensionTests(testsRoot: string, clb: (error: Error | null, failures?: number) => void): Promise { // Ensure the dev-mode extension is activated await vscode.extensions.getExtension(EXTENSION_ID)!.activate(); @@ -31,10 +20,23 @@ async function runAllExtensionTests(testsRoot: string, clb: (error: Error | null ui: 'bdd', color: true }); - mocha.addFile(path.resolve(testsRoot, 'globalHooks.js')); - await addTests(mocha, testsRoot); - await addTests(mocha, path.resolve(testsRoot, '../../../webviews/')); + // Load globalHooks if it exists + try { + mocha.addFile(path.resolve(testsRoot, 'globalHooks.js')); + } catch (e) { + // globalHooks might not exist in webpack bundle, ignore + } + + // Import all test files using webpack's require.context + try { + // Load tests from src/test directory only + // Webview tests are compiled separately with the webview configuration + const importAll = (r: __WebpackModuleApi.RequireContext) => r.keys().forEach(r); + importAll(require.context('./', true, /\.test$/)); + } catch (e) { + console.log('Error loading tests:', e); + } if (process.env.TEST_JUNIT_XML_PATH) { mocha.reporter('mocha-multi-reporters', { diff --git a/webpack.config.js b/webpack.config.js index 17f46cfe12..2fee9f7763 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -192,6 +192,8 @@ async function getExtensionConfig(target, mode, env) { }; if (target === 'webworker') { entry['test/index'] = './src/test/browser/index.ts'; + } else if (target === 'node') { + entry['test/index'] = './src/test/index.ts'; } return { From 4dd3c84f2f568606f38d2eee984f6e3ac90d0bfd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 25 Sep 2025 15:00:56 +0000 Subject: [PATCH 04/15] Fix: Include test files in TypeScript checking for webpack builds Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> --- webpack.config.js | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/webpack.config.js b/webpack.config.js index 2fee9f7763..a369f627ef 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -157,6 +157,26 @@ async function getWebviewConfig(mode, env, entry) { async function getExtensionConfig(target, mode, env) { const basePath = path.join(__dirname, 'src'); + const entry = { + extension: './src/extension.ts', + }; + if (target === 'webworker') { + entry['test/index'] = './src/test/browser/index.ts'; + } else if (target === 'node') { + entry['test/index'] = './src/test/index.ts'; + } + + // Determine if we're building tests and choose the appropriate TypeScript config + const hasTestEntry = 'test/index' in entry; + let tsConfigFile; + if (hasTestEntry) { + // When building tests, use a config that includes test files + tsConfigFile = target === 'webworker' ? 'tsconfig.browser.json' : 'tsconfig.test.json'; + } else { + // When building only extension, use standard configs + tsConfigFile = target === 'webworker' ? 'tsconfig.browser.json' : 'tsconfig.json'; + } + /** * @type WebpackConfig['plugins'] | any */ @@ -168,7 +188,8 @@ async function getExtensionConfig(target, mode, env) { async: false, formatter: 'basic', typescript: { - configFile: path.join(__dirname, target === 'webworker' ? 'tsconfig.browser.json' : 'tsconfig.json'), + configFile: path.join(__dirname, tsConfigFile), + mode: 'write-tsbuildinfo' }, }), new webpack.ContextReplacementPlugin(/mocha/, /^$/) @@ -187,15 +208,6 @@ async function getExtensionConfig(target, mode, env) { })); } - const entry = { - extension: './src/extension.ts', - }; - if (target === 'webworker') { - entry['test/index'] = './src/test/browser/index.ts'; - } else if (target === 'node') { - entry['test/index'] = './src/test/index.ts'; - } - return { name: `extension:${target}`, entry, From 9735730d87d223fa5d8ba0213353fb0654313164 Mon Sep 17 00:00:00 2001 From: Alex Ross <38270282+alexr00@users.noreply.github.com> Date: Fri, 26 Sep 2025 15:42:52 +0200 Subject: [PATCH 05/15] Just use the tsconfig.json file --- src/test/browser/index.ts | 6 ++++ .../graphql/latestReviewCommitBuilder.ts | 2 +- .../builders/graphql/pullRequestBuilder.ts | 2 +- .../builders/graphql/timelineEventsBuilder.ts | 7 +++- src/test/builders/rest/pullRequestBuilder.ts | 11 +++++-- src/test/builders/rest/refBuilder.ts | 18 ++++++++++- src/test/builders/rest/repoBuilder.ts | 8 ++--- src/test/builders/rest/userBuilder.ts | 4 ++- src/test/index.ts | 2 ++ src/test/mocks/mockGitHubRepository.ts | 9 +++--- src/test/view/prsTree.test.ts | 6 ++-- tsconfig.json | 1 - webpack.config.js | 32 ++++++------------- 13 files changed, 65 insertions(+), 43 deletions(-) diff --git a/src/test/browser/index.ts b/src/test/browser/index.ts index 4a34fbef75..ead80a2baf 100644 --- a/src/test/browser/index.ts +++ b/src/test/browser/index.ts @@ -1,3 +1,9 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// @ts-nocheck // This file is providing the test runner to use when running extension tests. import * as vscode from 'vscode'; require('mocha/mocha'); diff --git a/src/test/builders/graphql/latestReviewCommitBuilder.ts b/src/test/builders/graphql/latestReviewCommitBuilder.ts index a51807ae01..07ab147fbd 100644 --- a/src/test/builders/graphql/latestReviewCommitBuilder.ts +++ b/src/test/builders/graphql/latestReviewCommitBuilder.ts @@ -8,7 +8,7 @@ import { LatestReviewCommitResponse } from '../../../github/graphql'; import { RateLimitBuilder } from './rateLimitBuilder'; -type Repository = LatestReviewCommitResponse['repository']; +type Repository = NonNullable; type PullRequest = Repository['pullRequest']; type ViewerLatestReview = PullRequest['viewerLatestReview']; type Commit = ViewerLatestReview['commit']; diff --git a/src/test/builders/graphql/pullRequestBuilder.ts b/src/test/builders/graphql/pullRequestBuilder.ts index afd5942cb1..cd8b73682e 100644 --- a/src/test/builders/graphql/pullRequestBuilder.ts +++ b/src/test/builders/graphql/pullRequestBuilder.ts @@ -37,7 +37,7 @@ const RefBuilder = createBuilderClass()({ }), }); -type Repository = PullRequestResponse['repository']; +type Repository = NonNullable; type PullRequest = Repository['pullRequest']; type Author = PullRequest['author']; type AssigneesConn = PullRequest['assignees']; diff --git a/src/test/builders/graphql/timelineEventsBuilder.ts b/src/test/builders/graphql/timelineEventsBuilder.ts index 48eeb16e79..ec8a12b955 100644 --- a/src/test/builders/graphql/timelineEventsBuilder.ts +++ b/src/test/builders/graphql/timelineEventsBuilder.ts @@ -1,9 +1,14 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + import { createBuilderClass, createLink } from '../base'; import { TimelineEventsResponse } from '../../../github/graphql'; import { RateLimitBuilder } from './rateLimitBuilder'; -type Repository = TimelineEventsResponse['repository']; +type Repository = NonNullable; type PullRequest = Repository['pullRequest']; type TimelineConn = PullRequest['timelineItems']; diff --git a/src/test/builders/rest/pullRequestBuilder.ts b/src/test/builders/rest/pullRequestBuilder.ts index 70af54d5f8..d65f476c16 100644 --- a/src/test/builders/rest/pullRequestBuilder.ts +++ b/src/test/builders/rest/pullRequestBuilder.ts @@ -1,5 +1,10 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + import { UserBuilder } from './userBuilder'; -import { RefBuilder } from './refBuilder'; +import { NonNullUserRefBuilder, RefBuilder } from './refBuilder'; import { createLink, createBuilderClass } from '../base'; import { OctokitCommon } from '../../../github/common'; @@ -40,8 +45,8 @@ export const PullRequestBuilder = createBuilderClass()({ closed_at: { default: '' }, merged_at: { default: '' }, merge_commit_sha: { default: '' }, - head: { linked: RefBuilder }, - base: { linked: RefBuilder }, + head: { linked: NonNullUserRefBuilder }, + base: { linked: NonNullUserRefBuilder }, draft: { default: false }, merged: { default: false }, mergeable: { default: true }, diff --git a/src/test/builders/rest/refBuilder.ts b/src/test/builders/rest/refBuilder.ts index 6796e9a19f..8e4f070616 100644 --- a/src/test/builders/rest/refBuilder.ts +++ b/src/test/builders/rest/refBuilder.ts @@ -1,3 +1,8 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + import { UserBuilder } from './userBuilder'; import { RepositoryBuilder } from './repoBuilder'; import { createBuilderClass } from '../base'; @@ -5,7 +10,7 @@ import { OctokitCommon } from '../../../github/common'; type RefUnion = OctokitCommon.PullsListResponseItemHead & OctokitCommon.PullsListResponseItemBase; -export const RefBuilder = createBuilderClass()({ +export const RefBuilder = createBuilderClass>()({ label: { default: 'octocat:new-feature' }, ref: { default: 'new-feature' }, user: { linked: UserBuilder }, @@ -14,4 +19,15 @@ export const RefBuilder = createBuilderClass()({ repo: { linked: RepositoryBuilder }, }); +// Variant where user is guaranteed non-null. +type NonNullUserRef = Omit & { user: NonNullable }; + +export const NonNullUserRefBuilder = createBuilderClass()({ + label: { default: 'octocat:new-feature' }, + ref: { default: 'new-feature' }, + user: { linked: UserBuilder }, // non-null guarantee + sha: { default: '0000000000000000000000000000000000000000' }, + repo: { linked: RepositoryBuilder }, +}); + export type RefBuilder = InstanceType; diff --git a/src/test/builders/rest/repoBuilder.ts b/src/test/builders/rest/repoBuilder.ts index 2fc8038731..3f05aaf494 100644 --- a/src/test/builders/rest/repoBuilder.ts +++ b/src/test/builders/rest/repoBuilder.ts @@ -16,7 +16,7 @@ type License = RepoUnion['license']; type Permissions = RepoUnion['permissions']; type CodeOfConduct = RepoUnion['code_of_conduct']; -export const RepositoryBuilder = createBuilderClass()({ +export const RepositoryBuilder = createBuilderClass>()({ id: { default: 0 }, node_id: { default: 'node0' }, name: { default: 'reponame' }, @@ -123,9 +123,9 @@ export const RepositoryBuilder = createBuilderClass()({ name: { default: 'name' }, url: { default: 'https://github.com/octocat/reponame' }, }), - forks: { default: null }, - open_issues: { default: null }, - watchers: { default: null }, + forks: { default: 0 }, + open_issues: { default: 0 }, + watchers: { default: 0 }, }); export type RepositoryBuilder = InstanceType; diff --git a/src/test/builders/rest/userBuilder.ts b/src/test/builders/rest/userBuilder.ts index c1846eab71..412b21548d 100644 --- a/src/test/builders/rest/userBuilder.ts +++ b/src/test/builders/rest/userBuilder.ts @@ -17,7 +17,9 @@ type UserUnion = | OctokitCommon.PullsListResponseItemHeadRepoOwner | OctokitCommon.IssuesListEventsForTimelineResponseItemActor; -export const UserBuilder = createBuilderClass>()({ +type NonNullUser = NonNullable; + +export const UserBuilder = createBuilderClass()({ id: { default: 0 }, node_id: { default: 'node0' }, login: { default: 'octocat' }, diff --git a/src/test/index.ts b/src/test/index.ts index ed90fdeebd..8644c6edc2 100644 --- a/src/test/index.ts +++ b/src/test/index.ts @@ -3,6 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +// @ts-nocheck + // This file is providing the test runner to use when running extension tests. import * as path from 'path'; import * as vscode from 'vscode'; diff --git a/src/test/mocks/mockGitHubRepository.ts b/src/test/mocks/mockGitHubRepository.ts index 08f0cad6a6..4bff09d7e9 100644 --- a/src/test/mocks/mockGitHubRepository.ts +++ b/src/test/mocks/mockGitHubRepository.ts @@ -21,8 +21,7 @@ import { MockTelemetry } from './mockTelemetry'; import { Uri } from 'vscode'; import { LoggingOctokit, RateLogger } from '../../github/loggingOctokit'; import { mergeQuerySchemaWithShared } from '../../github/common'; -import { PullRequestModel } from '../../github/pullRequestModel'; -import { TimelineEvent } from '../../common/timelineEvent'; + const queries = mergeQuerySchemaWithShared(require('../../github/queries.gql'), require('../../github/queriesShared.gql')) as any; export class MockGitHubRepository extends GitHubRepository { @@ -35,7 +34,7 @@ export class MockGitHubRepository extends GitHubRepository { this._hub = { octokit: new LoggingOctokit(this.queryProvider.octokit, new RateLogger(new MockTelemetry(), true)), - graphql: null, + graphql: {} as any, }; this._metadata = Promise.resolve({ @@ -73,8 +72,8 @@ export class MockGitHubRepository extends GitHubRepository { block(builder); const responses = builder.build(); - const prNumber = responses.pullRequest.repository.pullRequest.number; - const headRef = responses.pullRequest.repository.pullRequest.headRef; + const prNumber = responses.pullRequest.repository!.pullRequest.number; + const headRef = responses.pullRequest.repository?.pullRequest.headRef; this.queryProvider.expectGraphQLQuery( { diff --git a/src/test/view/prsTree.test.ts b/src/test/view/prsTree.test.ts index a3d9c339c6..1f72287dd1 100644 --- a/src/test/view/prsTree.test.ts +++ b/src/test/view/prsTree.test.ts @@ -74,7 +74,7 @@ describe('GitHub Pull Requests view', function () { userAgent: 'GitHub VSCode Pull Requests', previews: ['shadow-cat-preview'], }), new RateLogger(telemetry, true)), - graphql: null, + graphql: {} as any, }; return github; @@ -160,7 +160,7 @@ describe('GitHub Pull Requests view', function () { ); }); }).pullRequest; - const prItem0 = await parseGraphQLPullRequest(pr0.repository.pullRequest, gitHubRepository); + const prItem0 = await parseGraphQLPullRequest(pr0.repository!.pullRequest, gitHubRepository); const pullRequest0 = new PullRequestModel(credentialStore, telemetry, gitHubRepository, remote, prItem0); const pr1 = gitHubRepository.addGraphQLPullRequest(builder => { @@ -177,7 +177,7 @@ describe('GitHub Pull Requests view', function () { ); }); }).pullRequest; - const prItem1 = await parseGraphQLPullRequest(pr1.repository.pullRequest, gitHubRepository); + const prItem1 = await parseGraphQLPullRequest(pr1.repository!.pullRequest, gitHubRepository); const pullRequest1 = new PullRequestModel(credentialStore, telemetry, gitHubRepository, remote, prItem1); const repository = new MockRepository(); diff --git a/tsconfig.json b/tsconfig.json index b60d27e54d..c84f7118b4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,7 +12,6 @@ }, "exclude": [ "node_modules", - "src/test", "webviews" ] } \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js index a369f627ef..2fee9f7763 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -157,26 +157,6 @@ async function getWebviewConfig(mode, env, entry) { async function getExtensionConfig(target, mode, env) { const basePath = path.join(__dirname, 'src'); - const entry = { - extension: './src/extension.ts', - }; - if (target === 'webworker') { - entry['test/index'] = './src/test/browser/index.ts'; - } else if (target === 'node') { - entry['test/index'] = './src/test/index.ts'; - } - - // Determine if we're building tests and choose the appropriate TypeScript config - const hasTestEntry = 'test/index' in entry; - let tsConfigFile; - if (hasTestEntry) { - // When building tests, use a config that includes test files - tsConfigFile = target === 'webworker' ? 'tsconfig.browser.json' : 'tsconfig.test.json'; - } else { - // When building only extension, use standard configs - tsConfigFile = target === 'webworker' ? 'tsconfig.browser.json' : 'tsconfig.json'; - } - /** * @type WebpackConfig['plugins'] | any */ @@ -188,8 +168,7 @@ async function getExtensionConfig(target, mode, env) { async: false, formatter: 'basic', typescript: { - configFile: path.join(__dirname, tsConfigFile), - mode: 'write-tsbuildinfo' + configFile: path.join(__dirname, target === 'webworker' ? 'tsconfig.browser.json' : 'tsconfig.json'), }, }), new webpack.ContextReplacementPlugin(/mocha/, /^$/) @@ -208,6 +187,15 @@ async function getExtensionConfig(target, mode, env) { })); } + const entry = { + extension: './src/extension.ts', + }; + if (target === 'webworker') { + entry['test/index'] = './src/test/browser/index.ts'; + } else if (target === 'node') { + entry['test/index'] = './src/test/index.ts'; + } + return { name: `extension:${target}`, entry, From c089c26a85fc89f84b049ba9661f4c931ce189f0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 26 Sep 2025 13:58:13 +0000 Subject: [PATCH 06/15] Generate individual *.test.js files from webpack compilation Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> --- src/test/index.ts | 37 +++++++++++++++++++------------------ webpack.config.js | 22 +++++++++++++++++++--- 2 files changed, 38 insertions(+), 21 deletions(-) diff --git a/src/test/index.ts b/src/test/index.ts index 8644c6edc2..b3f198406d 100644 --- a/src/test/index.ts +++ b/src/test/index.ts @@ -3,15 +3,29 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -// @ts-nocheck - // This file is providing the test runner to use when running extension tests. import * as path from 'path'; import * as vscode from 'vscode'; +import glob from 'glob'; import Mocha from 'mocha'; import { mockWebviewEnvironment } from './mocks/mockWebviewEnvironment'; import { EXTENSION_ID } from '../constants'; +function addTests(mocha: Mocha, root: string): Promise { + return new Promise((resolve, reject) => { + glob('**/**.test.js', { cwd: root }, (error, files) => { + if (error) { + return reject(error); + } + + for (const testFile of files) { + mocha.addFile(path.join(root, testFile)); + } + resolve(); + }); + }); +} + async function runAllExtensionTests(testsRoot: string, clb: (error: Error | null, failures?: number) => void): Promise { // Ensure the dev-mode extension is activated await vscode.extensions.getExtension(EXTENSION_ID)!.activate(); @@ -22,23 +36,10 @@ async function runAllExtensionTests(testsRoot: string, clb: (error: Error | null ui: 'bdd', color: true }); + mocha.addFile(path.resolve(testsRoot, 'globalHooks.js')); - // Load globalHooks if it exists - try { - mocha.addFile(path.resolve(testsRoot, 'globalHooks.js')); - } catch (e) { - // globalHooks might not exist in webpack bundle, ignore - } - - // Import all test files using webpack's require.context - try { - // Load tests from src/test directory only - // Webview tests are compiled separately with the webview configuration - const importAll = (r: __WebpackModuleApi.RequireContext) => r.keys().forEach(r); - importAll(require.context('./', true, /\.test$/)); - } catch (e) { - console.log('Error loading tests:', e); - } + await addTests(mocha, testsRoot); + await addTests(mocha, path.resolve(testsRoot, '../../../webviews/')); if (process.env.TEST_JUNIT_XML_PATH) { mocha.reporter('mocha-multi-reporters', { diff --git a/webpack.config.js b/webpack.config.js index 2fee9f7763..c822a12ab0 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -156,14 +156,12 @@ async function getWebviewConfig(mode, env, entry) { */ async function getExtensionConfig(target, mode, env) { const basePath = path.join(__dirname, 'src'); + const glob = require('glob'); /** * @type WebpackConfig['plugins'] | any */ const plugins = [ - new webpack.optimize.LimitChunkCountPlugin({ - maxChunks: 1 - }), new ForkTsCheckerPlugin({ async: false, formatter: 'basic', @@ -190,10 +188,28 @@ async function getExtensionConfig(target, mode, env) { const entry = { extension: './src/extension.ts', }; + + // Add test entry points if (target === 'webworker') { entry['test/index'] = './src/test/browser/index.ts'; } else if (target === 'node') { + // Add main test runner entry['test/index'] = './src/test/index.ts'; + + // Add individual test files as separate entry points + const testFiles = glob.sync('src/test/**/*.test.ts', { cwd: __dirname }); + testFiles.forEach(testFile => { + // Convert src/test/github/utils.test.ts -> test/github/utils.test + const entryName = testFile.replace('src/', '').replace('.ts', ''); + entry[entryName] = `./${testFile}`; + }); + } + + // Don't limit chunks for node target when we have individual test files + if (target !== 'node' || !('test/index' in entry && Object.keys(entry).some(key => key.endsWith('.test')))) { + plugins.unshift(new webpack.optimize.LimitChunkCountPlugin({ + maxChunks: 1 + })); } return { From adf79e1860b7bfab841eff0d5e11c8e4cdbbbd86 Mon Sep 17 00:00:00 2001 From: Alex Ross <38270282+alexr00@users.noreply.github.com> Date: Fri, 26 Sep 2025 17:56:06 +0200 Subject: [PATCH 07/15] Update test files --- .vscode-test.mjs | 2 +- src/test/index.ts | 33 ++++++++++++++++----------------- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/.vscode-test.mjs b/.vscode-test.mjs index 586b08d4e5..c3406cbc00 100644 --- a/.vscode-test.mjs +++ b/.vscode-test.mjs @@ -8,7 +8,7 @@ function generateConfig(label) { /** @type {import('@vscode/test-cli').TestConfiguration} */ let config = { label, - files: ["out/**/*.test.js"], + files: ["dist/**/*.test.js"], version: "insiders", srcDir: "src", launchArgs: [ diff --git a/src/test/index.ts b/src/test/index.ts index b3f198406d..05ef9f9859 100644 --- a/src/test/index.ts +++ b/src/test/index.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +// @ts-nocheck // This file is providing the test runner to use when running extension tests. import * as path from 'path'; import * as vscode from 'vscode'; @@ -11,20 +12,6 @@ import Mocha from 'mocha'; import { mockWebviewEnvironment } from './mocks/mockWebviewEnvironment'; import { EXTENSION_ID } from '../constants'; -function addTests(mocha: Mocha, root: string): Promise { - return new Promise((resolve, reject) => { - glob('**/**.test.js', { cwd: root }, (error, files) => { - if (error) { - return reject(error); - } - - for (const testFile of files) { - mocha.addFile(path.join(root, testFile)); - } - resolve(); - }); - }); -} async function runAllExtensionTests(testsRoot: string, clb: (error: Error | null, failures?: number) => void): Promise { // Ensure the dev-mode extension is activated @@ -36,10 +23,22 @@ async function runAllExtensionTests(testsRoot: string, clb: (error: Error | null ui: 'bdd', color: true }); - mocha.addFile(path.resolve(testsRoot, 'globalHooks.js')); + // Load globalHooks if it exists + try { + mocha.addFile(path.resolve(testsRoot, 'globalHooks.js')); + } catch (e) { + // globalHooks might not exist in webpack bundle, ignore + } - await addTests(mocha, testsRoot); - await addTests(mocha, path.resolve(testsRoot, '../../../webviews/')); + // Import all test files using webpack's require.context + try { + // Load tests from src/test directory only + // Webview tests are compiled separately with the webview configuration + const importAll = (r: __WebpackModuleApi.RequireContext) => r.keys().forEach(r); + importAll(require.context('./', true, /\.test$/)); + } catch (e) { + console.log('Error loading tests:', e); + } if (process.env.TEST_JUNIT_XML_PATH) { mocha.reporter('mocha-multi-reporters', { From 3bd528c3092f85b13908ec4c3ef193422e482504 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 26 Sep 2025 16:10:59 +0000 Subject: [PATCH 08/15] Fix: Remove mocha import to use global functions in test files Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> --- src/test/github/markdownUtils.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/github/markdownUtils.test.ts b/src/test/github/markdownUtils.test.ts index 99b8801603..3f0be975f1 100644 --- a/src/test/github/markdownUtils.test.ts +++ b/src/test/github/markdownUtils.test.ts @@ -6,7 +6,6 @@ import * as assert from 'assert'; import * as marked from 'marked'; import { PlainTextRenderer } from '../../github/markdownUtils'; -import { describe, it } from 'mocha'; describe('PlainTextRenderer', () => { it('should escape inline code by default', () => { From 7d50c90b59df8ae4a44cf457e4a941ede1a3f8e8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 29 Sep 2025 11:11:34 +0000 Subject: [PATCH 09/15] Fix: Copy test fixtures to dist directory during webpack builds Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> --- package.json | 8 ++++---- src/@types/vscode.proposed.chatParticipantAdditions.d.ts | 1 + src/@types/vscode.proposed.chatParticipantPrivate.d.ts | 4 ++++ 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index e034e81f55..3950f8afdd 100644 --- a/package.json +++ b/package.json @@ -4172,14 +4172,14 @@ }, "scripts": { "postinstall": "yarn update-dts", - "bundle": "webpack --mode production --env esbuild", - "bundle:node": "webpack --mode production --config-name extension:node --config-name webviews", + "bundle": "webpack --mode production --env esbuild && node scripts/preprocess-fixtures --in src --out dist", + "bundle:node": "webpack --mode production --config-name extension:node --config-name webviews && node scripts/preprocess-fixtures --in src --out dist", "bundle:web": "webpack --mode production --config-name extension:webworker --config-name webviews", "clean": "rm -r dist/", - "compile": "webpack --mode development --env esbuild", + "compile": "webpack --mode development --env esbuild && node scripts/preprocess-fixtures --in src --out dist", "compile:test": "tsc -p tsconfig.test.json", "watch:test": "tsc -w -p tsconfig.test.json", - "compile:node": "webpack --mode development --config-name extension:node --config-name webviews", + "compile:node": "webpack --mode development --config-name extension:node --config-name webviews && node scripts/preprocess-fixtures --in src --out dist", "compile:web": "webpack --mode development --config-name extension:webworker --config-name webviews", "lint": "eslint --fix --cache --config .eslintrc.js --ignore-pattern src/env/browser/**/* \"{src,webviews}/**/*.{ts,tsx}\"", "lint:browser": "eslint --fix --cache --cache-location .eslintcache.browser --config .eslintrc.browser.json --ignore-pattern src/env/node/**/* \"{src,webviews}/**/*.{ts,tsx}\"", diff --git a/src/@types/vscode.proposed.chatParticipantAdditions.d.ts b/src/@types/vscode.proposed.chatParticipantAdditions.d.ts index 73c8c948cc..f5b8bd0072 100644 --- a/src/@types/vscode.proposed.chatParticipantAdditions.d.ts +++ b/src/@types/vscode.proposed.chatParticipantAdditions.d.ts @@ -103,6 +103,7 @@ declare module 'vscode' { isConfirmed?: boolean; isComplete?: boolean; toolSpecificData?: ChatTerminalToolInvocationData; + fromSubAgent?: boolean; constructor(toolName: string, toolCallId: string, isError?: boolean); } diff --git a/src/@types/vscode.proposed.chatParticipantPrivate.d.ts b/src/@types/vscode.proposed.chatParticipantPrivate.d.ts index 9c444026ad..ade1cf62e8 100644 --- a/src/@types/vscode.proposed.chatParticipantPrivate.d.ts +++ b/src/@types/vscode.proposed.chatParticipantPrivate.d.ts @@ -221,6 +221,10 @@ declare module 'vscode' { chatSessionId?: string; chatInteractionId?: string; terminalCommand?: string; + /** + * Lets us add some nicer UI to toolcalls that came from a sub-agent, but in the long run, this should probably just be rendered in a similar way to thinking text + tool call groups + */ + fromSubAgent?: boolean; } export interface LanguageModelToolInvocationPrepareOptions { From fac13c5372c7852fe175cc8cde0e7b2beff02705 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 29 Sep 2025 13:07:17 +0000 Subject: [PATCH 10/15] Fix: Update watch task to copy test fixtures before starting webpack watch Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3950f8afdd..cca5a27848 100644 --- a/package.json +++ b/package.json @@ -4192,7 +4192,7 @@ "test:preprocess-svg": "node scripts/preprocess-svg --in ../resources/ --out out/resources", "test:preprocess-fixtures": "node scripts/preprocess-fixtures --in src --out out", "update-dts": "cd \"src/@types\" && npx vscode-dts main && npx vscode-dts dev", - "watch": "webpack --watch --mode development --env esbuild", + "watch": "node scripts/preprocess-fixtures --in src --out dist && webpack --watch --mode development --env esbuild", "watch:web": "webpack --watch --mode development --config-name extension:webworker --config-name webviews", "hygiene": "node ./build/hygiene.js", "prepare": "husky install" From 37d31069ec6a8d8c98b4664ee27fd6ef103d7be0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 29 Sep 2025 13:46:34 +0000 Subject: [PATCH 11/15] Replace preprocess-fixtures script with webpack plugin for automatic fixture copying Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> --- package.json | 10 +++++----- webpack.config.js | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index cca5a27848..e034e81f55 100644 --- a/package.json +++ b/package.json @@ -4172,14 +4172,14 @@ }, "scripts": { "postinstall": "yarn update-dts", - "bundle": "webpack --mode production --env esbuild && node scripts/preprocess-fixtures --in src --out dist", - "bundle:node": "webpack --mode production --config-name extension:node --config-name webviews && node scripts/preprocess-fixtures --in src --out dist", + "bundle": "webpack --mode production --env esbuild", + "bundle:node": "webpack --mode production --config-name extension:node --config-name webviews", "bundle:web": "webpack --mode production --config-name extension:webworker --config-name webviews", "clean": "rm -r dist/", - "compile": "webpack --mode development --env esbuild && node scripts/preprocess-fixtures --in src --out dist", + "compile": "webpack --mode development --env esbuild", "compile:test": "tsc -p tsconfig.test.json", "watch:test": "tsc -w -p tsconfig.test.json", - "compile:node": "webpack --mode development --config-name extension:node --config-name webviews && node scripts/preprocess-fixtures --in src --out dist", + "compile:node": "webpack --mode development --config-name extension:node --config-name webviews", "compile:web": "webpack --mode development --config-name extension:webworker --config-name webviews", "lint": "eslint --fix --cache --config .eslintrc.js --ignore-pattern src/env/browser/**/* \"{src,webviews}/**/*.{ts,tsx}\"", "lint:browser": "eslint --fix --cache --cache-location .eslintcache.browser --config .eslintrc.browser.json --ignore-pattern src/env/node/**/* \"{src,webviews}/**/*.{ts,tsx}\"", @@ -4192,7 +4192,7 @@ "test:preprocess-svg": "node scripts/preprocess-svg --in ../resources/ --out out/resources", "test:preprocess-fixtures": "node scripts/preprocess-fixtures --in src --out out", "update-dts": "cd \"src/@types\" && npx vscode-dts main && npx vscode-dts dev", - "watch": "node scripts/preprocess-fixtures --in src --out dist && webpack --watch --mode development --env esbuild", + "watch": "webpack --watch --mode development --env esbuild", "watch:web": "webpack --watch --mode development --config-name extension:webworker --config-name webviews", "hygiene": "node ./build/hygiene.js", "prepare": "husky install" diff --git a/webpack.config.js b/webpack.config.js index c822a12ab0..d35b6b9c57 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -172,6 +172,43 @@ async function getExtensionConfig(target, mode, env) { new webpack.ContextReplacementPlugin(/mocha/, /^$/) ]; + // Add fixtures copying plugin for node target (which has individual test files) + if (target === 'node') { + const fs = require('fs'); + + class CopyFixturesPlugin { + apply(compiler) { + compiler.hooks.afterEmit.tap('CopyFixturesPlugin', () => { + this.copyFixtures('src', compiler.options.output.path); + }); + } + + copyFixtures(inputDir, outputDir) { + try { + const files = fs.readdirSync(inputDir); + for (const file of files) { + const filePath = path.join(inputDir, file); + const stats = fs.statSync(filePath); + if (stats.isDirectory()) { + if (file === 'fixtures') { + const outputFilePath = path.join(outputDir, inputDir, file); + const inputFilePath = path.join(inputDir, file); + fs.cpSync(inputFilePath, outputFilePath, { recursive: true, force: true }); + } else { + this.copyFixtures(filePath, outputDir); + } + } + } + } catch (error) { + // Ignore errors during fixtures copying to not break the build + console.warn('Warning: Could not copy fixtures:', error.message); + } + } + } + + plugins.push(new CopyFixturesPlugin()); + } + if (target === 'webworker') { plugins.push(new webpack.ProvidePlugin({ process: path.join( From 842ac8fbef43e1ff91703b7f1eb0f6aba9baea96 Mon Sep 17 00:00:00 2001 From: Alex Ross <38270282+alexr00@users.noreply.github.com> Date: Mon, 29 Sep 2025 16:41:27 +0200 Subject: [PATCH 12/15] Get fixtures copied correctly --- webpack.config.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/webpack.config.js b/webpack.config.js index d35b6b9c57..0dab06968a 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -175,14 +175,14 @@ async function getExtensionConfig(target, mode, env) { // Add fixtures copying plugin for node target (which has individual test files) if (target === 'node') { const fs = require('fs'); - + const srcRoot = 'src'; class CopyFixturesPlugin { apply(compiler) { compiler.hooks.afterEmit.tap('CopyFixturesPlugin', () => { - this.copyFixtures('src', compiler.options.output.path); + this.copyFixtures(srcRoot, compiler.options.output.path); }); } - + copyFixtures(inputDir, outputDir) { try { const files = fs.readdirSync(inputDir); @@ -191,7 +191,7 @@ async function getExtensionConfig(target, mode, env) { const stats = fs.statSync(filePath); if (stats.isDirectory()) { if (file === 'fixtures') { - const outputFilePath = path.join(outputDir, inputDir, file); + const outputFilePath = path.join(outputDir, inputDir.substring(srcRoot.length), file); const inputFilePath = path.join(inputDir, file); fs.cpSync(inputFilePath, outputFilePath, { recursive: true, force: true }); } else { @@ -205,7 +205,7 @@ async function getExtensionConfig(target, mode, env) { } } } - + plugins.push(new CopyFixturesPlugin()); } @@ -232,7 +232,7 @@ async function getExtensionConfig(target, mode, env) { } else if (target === 'node') { // Add main test runner entry['test/index'] = './src/test/index.ts'; - + // Add individual test files as separate entry points const testFiles = glob.sync('src/test/**/*.test.ts', { cwd: __dirname }); testFiles.forEach(testFile => { From c2b32d5aa24872dc9fbbbf02cb4e5260881e9591 Mon Sep 17 00:00:00 2001 From: Alex Ross <38270282+alexr00@users.noreply.github.com> Date: Thu, 2 Oct 2025 11:20:50 +0200 Subject: [PATCH 13/15] Last step! Remove the webpack scheme from the source maps --- webpack.config.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/webpack.config.js b/webpack.config.js index 0dab06968a..250d4933f8 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -74,6 +74,9 @@ async function getWebviewConfig(mode, env, entry) { output: { filename: '[name].js', path: path.resolve(__dirname, 'dist'), + // Use absolute paths (file:///) in source maps instead of the default webpack:// scheme + devtoolModuleFilenameTemplate: info => 'file:///' + info.absoluteResourcePath.replace(/\\/g, '/'), + devtoolFallbackModuleFilenameTemplate: 'file:///[absolute-resource-path]' }, optimization: { minimizer: [ @@ -233,7 +236,7 @@ async function getExtensionConfig(target, mode, env) { // Add main test runner entry['test/index'] = './src/test/index.ts'; - // Add individual test files as separate entry points + // Add individual test files as separate entry points const testFiles = glob.sync('src/test/**/*.test.ts', { cwd: __dirname }); testFiles.forEach(testFile => { // Convert src/test/github/utils.test.ts -> test/github/utils.test @@ -260,6 +263,9 @@ async function getExtensionConfig(target, mode, env) { libraryTarget: 'commonjs2', filename: '[name].js', chunkFilename: 'feature-[name].js', + // Use absolute paths (file:///) in source maps for easier debugging of tests & sources + devtoolModuleFilenameTemplate: info => 'file:///' + info.absoluteResourcePath.replace(/\\/g, '/'), + devtoolFallbackModuleFilenameTemplate: 'file:///[absolute-resource-path]', }, optimization: { minimizer: [ From 87e2b82132b5a13b8b7fe6db9f12e9f741e71576 Mon Sep 17 00:00:00 2001 From: Alex Ross <38270282+alexr00@users.noreply.github.com> Date: Thu, 2 Oct 2025 11:28:37 +0200 Subject: [PATCH 14/15] Exclude tests from package --- .vscodeignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.vscodeignore b/.vscodeignore index 906a576e03..3c9c0e53af 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -12,6 +12,7 @@ node_modules/** scripts/** src/** webviews/** +**/test/** .eslintcache* .eslintignore .eslintrc* From d0dad4d33900a62168109de123a57d402a0f2198 Mon Sep 17 00:00:00 2001 From: Alex Ross <38270282+alexr00@users.noreply.github.com> Date: Thu, 2 Oct 2025 11:30:19 +0200 Subject: [PATCH 15/15] Clean up --- .../vscode.proposed.chatParticipantAdditions.d.ts | 11 ++--------- .../vscode.proposed.chatParticipantPrivate.d.ts | 7 ------- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/src/@types/vscode.proposed.chatParticipantAdditions.d.ts b/src/@types/vscode.proposed.chatParticipantAdditions.d.ts index f5b8bd0072..2cb4168b43 100644 --- a/src/@types/vscode.proposed.chatParticipantAdditions.d.ts +++ b/src/@types/vscode.proposed.chatParticipantAdditions.d.ts @@ -103,7 +103,6 @@ declare module 'vscode' { isConfirmed?: boolean; isComplete?: boolean; toolSpecificData?: ChatTerminalToolInvocationData; - fromSubAgent?: boolean; constructor(toolName: string, toolCallId: string, isError?: boolean); } @@ -647,13 +646,7 @@ declare module 'vscode' { } export interface ChatRequest { - readonly modeInstructions?: string; - readonly modeInstructions2?: ChatRequestModeInstructions; - } - - export interface ChatRequestModeInstructions { - readonly content: string; - readonly toolReferences?: readonly ChatLanguageModelToolReference[]; - readonly metadata?: Record; + modeInstructions?: string; + modeInstructionsToolReferences?: readonly ChatLanguageModelToolReference[]; } } diff --git a/src/@types/vscode.proposed.chatParticipantPrivate.d.ts b/src/@types/vscode.proposed.chatParticipantPrivate.d.ts index ade1cf62e8..eb42d52c92 100644 --- a/src/@types/vscode.proposed.chatParticipantPrivate.d.ts +++ b/src/@types/vscode.proposed.chatParticipantPrivate.d.ts @@ -187,8 +187,6 @@ declare module 'vscode' { isQuotaExceeded?: boolean; - isRateLimited?: boolean; - level?: ChatErrorLevel; code?: string; @@ -221,10 +219,6 @@ declare module 'vscode' { chatSessionId?: string; chatInteractionId?: string; terminalCommand?: string; - /** - * Lets us add some nicer UI to toolcalls that came from a sub-agent, but in the long run, this should probably just be rendered in a similar way to thinking text + tool call groups - */ - fromSubAgent?: boolean; } export interface LanguageModelToolInvocationPrepareOptions { @@ -245,7 +239,6 @@ declare module 'vscode' { export class ExtendedLanguageModelToolResult extends LanguageModelToolResult { toolResultMessage?: string | MarkdownString; toolResultDetails?: Array; - toolMetadata?: unknown; } // #region Chat participant detection