diff --git a/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/model/JavaTestItem.java b/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/model/JavaTestItem.java index 00e594c8..6dababc3 100644 --- a/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/model/JavaTestItem.java +++ b/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/model/JavaTestItem.java @@ -37,6 +37,11 @@ public class JavaTestItem { private String jdtHandler; + /** + * Optional field for project item. + */ + private String[] natureIds; + public JavaTestItem() {} public JavaTestItem(String displayName, String fullName, String project, String uri, @@ -132,6 +137,14 @@ public void setProjectName(String projectName) { this.projectName = projectName; } + public String[] getNatureIds() { + return natureIds; + } + + public void setNatureIds(String[] natureIds) { + this.natureIds = natureIds; + } + public void addChild(JavaTestItem child) { if (this.children == null) { this.children = new ArrayList<>(); diff --git a/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/util/TestItemUtils.java b/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/util/TestItemUtils.java index 0f9fdbef..2915ffd4 100644 --- a/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/util/TestItemUtils.java +++ b/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/util/TestItemUtils.java @@ -16,6 +16,7 @@ import com.microsoft.java.test.plugin.model.TestLevel; import org.eclipse.core.resources.IProject; +import org.eclipse.core.runtime.IPath; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.IMethod; @@ -38,13 +39,16 @@ public class TestItemUtils { public static JavaTestItem constructJavaTestItem(IJavaElement element, TestLevel level, TestKind kind) throws JavaModelException { final String displayName; + String uri = null; if (element instanceof IJavaProject) { final IJavaProject javaProject = (IJavaProject) element; final IProject project = javaProject.getProject(); if (ProjectUtils.isVisibleProject(project)) { displayName = project.getName(); } else { - displayName = ProjectUtils.getProjectRealFolder(project).lastSegment(); + final IPath realPath = ProjectUtils.getProjectRealFolder(project); + displayName = realPath.lastSegment(); + uri = realPath.toFile().toURI().toString(); } } else if (element instanceof IPackageFragment && ((IPackageFragment) element).isDefaultPackage()) { displayName = DEFAULT_PACKAGE_NAME; @@ -52,7 +56,9 @@ public static JavaTestItem constructJavaTestItem(IJavaElement element, TestLevel displayName = JavaElementLabels.getElementLabel(element, JavaElementLabels.ALL_DEFAULT); } final String fullName = parseFullName(element, level); - final String uri = JDTUtils.getFileURI(element.getResource()); + if (uri == null) { + uri = JDTUtils.getFileURI(element.getResource()); + } Range range = null; if (level == TestLevel.CLASS || level == TestLevel.METHOD) { range = parseTestItemRange(element); diff --git a/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/util/TestSearchUtils.java b/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/util/TestSearchUtils.java index 92220a71..1bc18608 100644 --- a/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/util/TestSearchUtils.java +++ b/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/util/TestSearchUtils.java @@ -104,9 +104,19 @@ public static List findJavaProjects(List arguments, IProgr continue; } + final TestKind testKind; + final List testKinds = TestKindProvider.getTestKindsFromCache(project); + if (testKinds.isEmpty()) { + testKind = TestKind.None; + } else { + testKind = testKinds.get(0); + } + try { - resultList.add(TestItemUtils.constructJavaTestItem(project, TestLevel.PROJECT, TestKind.None)); - } catch (JavaModelException e) { + final JavaTestItem item = TestItemUtils.constructJavaTestItem(project, TestLevel.PROJECT, testKind); + item.setNatureIds(project.getProject().getDescription().getNatureIds()); + resultList.add(item); + } catch (CoreException e) { JUnitPlugin.logError("Failed to parse project item: " + project.getElementName()); } } @@ -423,13 +433,21 @@ public static List resolvePath(List arguments, IProgressMo if (project == null) { return Collections.emptyList(); } - result.add(TestItemUtils.constructJavaTestItem(project, TestLevel.PROJECT, TestKind.None)); + + final TestKind testKind; + final List testKinds = TestKindProvider.getTestKindsFromCache(project); + if (testKinds.isEmpty()) { + return Collections.emptyList(); + } else { + testKind = testKinds.get(0); + } + result.add(TestItemUtils.constructJavaTestItem(project, TestLevel.PROJECT, testKind)); final IPackageFragment packageFragment = (IPackageFragment) unit.getParent(); if (packageFragment == null || !(packageFragment instanceof IPackageFragment)) { return Collections.emptyList(); } - result.add(TestItemUtils.constructJavaTestItem(packageFragment, TestLevel.PACKAGE, TestKind.None)); + result.add(TestItemUtils.constructJavaTestItem(packageFragment, TestLevel.PACKAGE, testKind)); } return result; diff --git a/package.json b/package.json index 6f7f0d66..fb6894cd 100644 --- a/package.json +++ b/package.json @@ -69,14 +69,9 @@ "when": "java:serverMode == LightWeight" }, { - "view": "testExplorer", - "contents": "%contributes.viewsWelcome.noProjectWithProjectManagerInstalled%", - "when": "workspaceFolderCount == 0 && java:projectManagerActivated && java:serverMode != LightWeight" - }, - { - "view": "testExplorer", - "contents": "%contributes.viewsWelcome.noProjectWithOutProjectManagerInstalled%", - "when": "workspaceFolderCount == 0 && !java:projectManagerActivated && java:serverMode != LightWeight" + "view": "testing", + "contents": "Click below button to configure a test framework for your project.\n[Enable Java Tests](command:_java.test.enableTests)", + "when": "java:needSetupTests" } ], "menus": { diff --git a/package.nls.json b/package.nls.json index 2df58a73..6893e0e7 100644 --- a/package.nls.json +++ b/package.nls.json @@ -29,6 +29,5 @@ "configuration.java.test.config.sourcePaths.description": "Specify extra source paths when debugging the tests", "configuration.java.test.config.preLaunchTask.description": "Specify the label of a task specified in tasks.json (in the workspace's .vscode folder). The task will be launched before the start of testing.", "contributes.viewsWelcome.inLightWeightMode": "No test cases are listed because the Java Language Server is currently running in [LightWeight Mode](https://aka.ms/vscode-java-lightweight). To show test cases, click on the button to switch to Standard Mode.\n[Switch to Standard Mode](command:java.server.mode.switch?%5B%22Standard%22,true%5D)", - "contributes.viewsWelcome.noProjectWithProjectManagerInstalled": "No folder opened in Visual Studio Code. You can [open a Java project](command:_java.project.open), or create a new Java project by clicking the button below.\n[Create Java Project](command:java.project.create)", - "contributes.viewsWelcome.noProjectWithOutProjectManagerInstalled": "No folder opened in Visual Studio Code." + "contributes.viewsWelcome.enableTests": "Click below button to configure a test framework for your project.\n[Enable Java Tests](command:_java.test.enableTests)" } diff --git a/package.nls.zh.json b/package.nls.zh.json index c73e2f90..daa19ecd 100644 --- a/package.nls.zh.json +++ b/package.nls.zh.json @@ -29,6 +29,5 @@ "configuration.java.test.config.sourcePaths.description": "设定调试测试用例时的源代码路径", "configuration.java.test.config.preLaunchTask.description": "在 tasks.json(在工作空间的.vscode文件夹中)中某个任务的名称。该任务会在启动测试之前被执行", "contributes.viewsWelcome.inLightWeightMode": "由于 Java 语言服务正运行在 [LightWeight 模式](https://aka.ms/vscode-java-lightweight)下,因此测试用例将不会展示在该视图中。如果您需要展示测试用例,可以点击下方按钮将 Java 语言服务切换至 Standard 模式。\n[切换至 Standard 模式](command:java.server.mode.switch?%5B%22Standard%22,true%5D)", - "contributes.viewsWelcome.noProjectWithProjectManagerInstalled": "当前没有已打开的文件夹,您可以[打开一个 Java 项目](command:_java.project.open),或点击下方按钮创建一个新的 Java 项目。\n[创建 Java 项目](command:java.project.create)", - "contributes.viewsWelcome.noProjectWithOutProjectManagerInstalled": "当前没有已打开的文件夹。" + "contributes.viewsWelcome.enableTests": "点击下方按钮为你的项目添加一个测试框架\n[启用 Java 测试](command:_java.test.enableTests)" } diff --git a/src/commands/testDependenciesCommands.ts b/src/commands/testDependenciesCommands.ts new file mode 100644 index 00000000..2cc32b37 --- /dev/null +++ b/src/commands/testDependenciesCommands.ts @@ -0,0 +1,324 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { CancellationToken, Progress, ProgressLocation, Uri, window, workspace, WorkspaceConfiguration } from 'vscode'; +import * as path from 'path'; +import * as http from 'http'; +import * as https from 'https'; +import * as fse from 'fs-extra'; +import * as _ from 'lodash'; +import * as os from 'os'; +import { getJavaProjects, getProjectType } from '../controller/utils'; +import { IJavaTestItem, ProjectType, TestKind } from '../types'; +import { createWriteStream, WriteStream } from 'fs'; +import { URL } from 'url'; +import { ClientRequest, IncomingMessage } from 'http'; +import { sendError } from 'vscode-extension-telemetry-wrapper'; + +export async function enableTests(): Promise { + const project: IJavaTestItem | undefined = await getTargetProject(); + if (!project) { + return; + } + + const projectType: ProjectType = await getProjectType(project); + switch (projectType) { + case ProjectType.UnmanagedFolder: + await setupUnmanagedFolder(Uri.parse(project.uri!)); + return; + default: + // currently other typed projects are not supported. + break; + } + return; +} + +async function getTargetProject(): Promise { + let testProjects: IJavaTestItem[] = []; + for (const workspaceFolder of workspace.workspaceFolders || [] ) { + testProjects.push(...await getJavaProjects(workspaceFolder)); + } + + testProjects = testProjects.filter((project: IJavaTestItem) => { + return project.testKind === TestKind.None; + }); + + if (testProjects.length === 0) { + sendError(new Error('Failed to find a project to enable tests.')); + return undefined; + } + + // currently this feature will only be enabled when workspace contains one unmanaged folder without test dependencies. + return testProjects[0]; +} + +async function setupUnmanagedFolder(projectUri: Uri): Promise { + const testKind: TestKind | undefined = await getTestKind(); + if (testKind === undefined) { + return; + } + const libFolder: string = await getLibFolder(projectUri); + const libFolderExists: boolean = await fse.pathExists(libFolder); + if (!libFolderExists) { + await fse.ensureDir(libFolder); + } + + try { + await window.withProgress({ + location: ProgressLocation.Notification, + cancellable: true + }, async (progress: Progress<{message?: string; increment?: number}>, token: CancellationToken) => { + const metadata: IArtifactMetadata[] = getJarIds(testKind); + for (const jar of metadata) { + if (token.isCancellationRequested) { + throw new Error('User cancelled'); + } + progress.report({ + message: `Downloading ${jar.artifactId}.jar...`, + }); + if (!jar.version) { + jar.version = await getLatestVersion(jar.groupId, jar.artifactId) || jar.defaultVersion; + } + await downloadJar(libFolder, jar.groupId, jar.artifactId, jar.version, metadata.length, progress, token); + } + }); + } catch (e) { + if (e?.message !== 'User cancelled') { + sendError(e); + } + if (!libFolderExists) { + fse.remove(libFolder); + } + return; + } + + updateProjectSettings(projectUri, libFolder); +} + +async function getTestKind(): Promise { + const framework: any = await window.showQuickPick([{ + label: 'JUnit Jupiter', + value: TestKind.JUnit5, + }, { + label: 'JUnit', + value: TestKind.JUnit, + }, { + label: 'TestNG', + value: TestKind.TestNG, + }], { + placeHolder: 'Select the test framework to be enabled.' + }); + return framework?.value; +} + +async function getLibFolder(projectUri: Uri): Promise { + const referencedLibraries: any = workspace.getConfiguration('java', projectUri).get('project.referencedLibraries'); + if (_.isArray(referencedLibraries)) { + // do a simple check if the project uses default lib location. + if (referencedLibraries.includes('lib/**/*.jar')) { + return path.join(projectUri.fsPath, 'lib'); + } + } + + for (let i: number = 0; i < 100; i++) { + const folderPath: string = path.join(projectUri.fsPath, `test-lib${i > 0 ? i : ''}`); + if (await fse.pathExists(folderPath)) { + continue; + } + return folderPath; + } + + return path.join(projectUri.fsPath, 'test-lib'); +} + +function getJarIds(testKind: TestKind): IArtifactMetadata[] { + switch (testKind) { + case TestKind.JUnit5: + return [{ + groupId: 'org.junit.platform', + artifactId: 'junit-platform-console-standalone', + defaultVersion: '1.8.2', + }]; + case TestKind.JUnit: + return [{ + groupId: 'junit', + artifactId: 'junit', + defaultVersion: '4.13.2', + }, { + groupId: 'org.hamcrest', + artifactId: 'hamcrest-core', + version: '1.3', + defaultVersion: '1.3', + }]; + case TestKind.TestNG: + return [{ + groupId: 'org.testng', + artifactId: 'testng', + defaultVersion: '7.5', + }, { + groupId: 'com.beust', + artifactId: 'jcommander', + defaultVersion: '1.81', + }]; + default: + return []; + } +} + +async function getLatestVersion(groupId: string, artifactId: string): Promise { + try { + const response: any = await getHttpsAsJSON(getQueryLink(groupId, artifactId)); + + if (!response.response?.docs?.[0]?.latestVersion) { + sendError(new Error(`Invalid format for the latest version response`)); + return undefined; + } + return response.response.docs[0].latestVersion; + } catch (e) { + sendError(new Error(`Failed to fetch the latest version for ${groupId}:${artifactId}`)); + } + + return undefined; +} + +async function downloadJar( + libFolder: string, + groupId: string, + artifactId: string, + version: string, + totalJars: number, + progress: Progress<{message?: string; increment?: number}>, + token: CancellationToken + ): Promise { + // tslint:disable-next-line: typedef + await new Promise(async (resolve, reject) => { + progress.report({ + message: `Downloading ${artifactId}-${version}.jar...`, + }); + const tempFilePath: string = path.join(os.tmpdir(), `${artifactId}-${version}.jar`); + const writer: WriteStream = createWriteStream(tempFilePath); + + const url: string = getDownloadLink(groupId, artifactId, version); + const totalSize: number = await getTotalBytes(url); + if (token.isCancellationRequested) { + writer.close(); + return reject(new Error('User cancelled')); + } + const req: ClientRequest = https.get(url, (res: IncomingMessage) => { + res.pipe(writer); + res.on('data', (chunk: any) => { + progress.report({ + message: `Downloading ${artifactId}-${version}.jar...`, + increment: chunk.length / totalSize / totalJars * 100, + }); + }); + }); + + token.onCancellationRequested(() => { + req.destroy(); + writer.close(); + fse.unlink(tempFilePath); + reject(new Error('User cancelled')); + }); + + req.on('error', (err: any) => { + writer.close(); + fse.unlink(tempFilePath); + reject(err); + }); + + writer.on('finish', () => { + writer.close(); + const filePath: string = path.join(libFolder, `${artifactId}-${version}.jar`); + fse.move(tempFilePath, filePath, { overwrite: false }); + return resolve(); + }); + + writer.on('error', () => { + writer.close(); + fse.unlink(tempFilePath); + reject(new Error('Failed to write jar file.')); + }); + }); +} + +async function updateProjectSettings(projectUri: Uri, libFolder: string): Promise { + // if 'referenced libraries' is already set to 'lib/**/*.jar' + if (path.basename(libFolder) === 'lib') { + window.showInformationMessage("Test libraries have been downloaded into 'lib/'."); + return; + } + + const relativePath: string = path.relative(projectUri.fsPath, libFolder); + const testDependencies: string = path.join(relativePath, '**', '*.jar'); + const configuration: WorkspaceConfiguration = workspace.getConfiguration('java', projectUri); + let referencedLibraries: any = configuration.get('project.referencedLibraries'); + if (_.isArray(referencedLibraries)) { + referencedLibraries.push(testDependencies); + } else if (_.isObject(referencedLibraries)) { + referencedLibraries = referencedLibraries as {include: string[]}; + referencedLibraries.include.push(testDependencies); + if (!referencedLibraries.exclude && !referencedLibraries.sources) { + referencedLibraries = referencedLibraries.include; + } + } + + configuration.update('project.referencedLibraries', referencedLibraries); + window.showInformationMessage(`Test libraries have been downloaded into '${relativePath}/'.`); +} + +async function getHttpsAsJSON(link: string): Promise { + // tslint:disable-next-line: typedef + const response: string = await new Promise((resolve, reject) => { + let result: string = ''; + https.get(link, { + headers: { + 'User-Agent': 'vscode-JavaTestRunner/0.1', + }, + }, (res: http.IncomingMessage) => { + if (res.statusCode !== 200) { + return reject(new Error(`Request failed with status code: ${res.statusCode}`)); + } + res.on('data', (chunk: any) => { + result = result.concat(chunk.toString()); + }); + res.on('end', () => { + resolve(result); + }); + res.on('error', reject); + }); + }); + return JSON.parse(response); +} + +async function getTotalBytes(url: string): Promise { + // tslint:disable-next-line: typedef + return new Promise((resolve, reject) => { + const link: URL = new URL(url); + const req: ClientRequest = https.request({ + host: link.host, + path: link.pathname, + method: 'HEAD' + }, (res: http.IncomingMessage) => { + const num: number = parseInt(res.headers['content-length'] as string, 10); + resolve(num); + }); + req.on('error', reject); + req.end(); + }); +} + +function getQueryLink(groupId: string, artifactId: string): string { + return `https://search.maven.org/solrsearch/select?q=id:%22${groupId}:${artifactId}%22&rows=1&wt=json`; +} + +function getDownloadLink(groupId: string, artifactId: string, version: string): string { + return `https://repo1.maven.org/maven2/${groupId.split('.').join('/')}/${artifactId}/${version}/${artifactId}-${version}.jar` +} + +interface IArtifactMetadata { + groupId: string; + artifactId: string; + version?: string; + defaultVersion: string; +} diff --git a/src/constants.ts b/src/constants.ts index 01a20934..a26c802e 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -30,6 +30,7 @@ export namespace JavaTestRunnerCommands { export const FIND_TEST_LOCATION: string = 'vscode.java.test.findTestLocation'; export const GO_TO_TEST: string = 'java.test.goToTest'; export const GO_TO_TEST_SUBJECT: string = 'java.test.goToTestSubject'; + export const ENABLE_TESTS: string = '_java.test.enableTests'; export const JAVA_TEST_OPEN_STACKTRACE: string = '_java.test.openStackTrace'; export const ASK_CLIENT_FOR_CHOICE: string = '_java.test.askClientForChoice'; export const ASK_CLIENT_FOR_INPUT: string = '_java.test.askClientForInput'; diff --git a/src/controller/utils.ts b/src/controller/utils.ts index 4dedeaa1..52d73bfb 100644 --- a/src/controller/utils.ts +++ b/src/controller/utils.ts @@ -3,11 +3,12 @@ import * as _ from 'lodash'; import * as path from 'path'; +import * as fse from 'fs-extra'; import { performance } from 'perf_hooks'; -import { CancellationToken, Range, TestItem, Uri, workspace, WorkspaceFolder } from 'vscode'; +import { CancellationToken, commands, Range, TestItem, Uri, workspace, WorkspaceFolder } from 'vscode'; import { sendError } from 'vscode-extension-telemetry-wrapper'; import { INVOCATION_PREFIX, JavaTestRunnerDelegateCommands } from '../constants'; -import { IJavaTestItem, TestLevel } from '../types'; +import { IJavaTestItem, ProjectType, TestKind, TestLevel } from '../types'; import { executeJavaLanguageServerCommand } from '../utils/commandUtils'; import { getRequestDelay, lruCache, MovingAverage } from './debouncing'; import { runnableTag, testController } from './testController'; @@ -17,17 +18,62 @@ import { dataCache } from './testItemDataCache'; * Load the Java projects, which are the root nodes of the test explorer */ export async function loadJavaProjects(): Promise { + let testProjects: IJavaTestItem[] = []; for (const workspaceFolder of workspace.workspaceFolders || [] ) { - const testProjects: IJavaTestItem[] = await getJavaProjects(workspaceFolder); - for (const project of testProjects) { - if (testController?.items.get(project.id)) { - continue; - } - const projectItem: TestItem = createTestItem(project); - projectItem.canResolveChildren = true; - testController?.items.add(projectItem); + testProjects.push(...await getJavaProjects(workspaceFolder)); + } + + if (testProjects.length === 1 && testProjects[0].testKind === TestKind.None && + await getProjectType(testProjects[0]) === ProjectType.UnmanagedFolder) { + commands.executeCommand('setContext', 'java:needSetupTests', true); + return; + } + + testProjects = testProjects.filter((project: IJavaTestItem) => { + return project.testKind !== TestKind.None; + }); + + for (const project of testProjects) { + if (testController?.items.get(project.id)) { + continue; } + + const projectItem: TestItem = createTestItem(project); + projectItem.canResolveChildren = true; + testController?.items.add(projectItem); + } +} + +export async function getProjectType(item: IJavaTestItem): Promise { + if (item.testLevel !== TestLevel.Project) { + throw new Error('The test item is not a project'); } + + if (!item.natureIds) { + return ProjectType.Other; + } + + const hasClasspathFile: boolean = await fse.pathExists(path.join(Uri.parse(item.uri!).fsPath, '.classpath')); + let hasJavaNature: boolean = false; + for (const id of item.natureIds) { + if (id.includes('maven2Nature')) { + return ProjectType.Maven; + } + + if (id.includes('gradleprojectnature')) { + return ProjectType.Gradle; + } + + if (id.includes('javanature')) { + hasJavaNature = true; + } + } + + if (hasJavaNature && !hasClasspathFile) { + return ProjectType.UnmanagedFolder; + } + + return ProjectType.Other; } /** diff --git a/src/extension.ts b/src/extension.ts index 27178e1c..ccac4df8 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -17,6 +17,7 @@ import { initExpService } from './experimentationService'; import { disposeCodeActionProvider, registerTestCodeActionProvider } from './provider/codeActionProvider'; import { testSourceProvider } from './provider/testSourceProvider'; import { registerAskForChoiceCommand, registerAdvanceAskForChoice, registerAskForInputCommand } from './commands/askForOptionCommands'; +import { enableTests } from './commands/testDependenciesCommands'; export let extensionContext: ExtensionContext; @@ -50,8 +51,11 @@ async function doActivate(_operationId: string, context: ExtensionContext): Prom if (extensionApi.onDidClasspathUpdate) { const onDidClasspathUpdate: Event = extensionApi.onDidClasspathUpdate; context.subscriptions.push(onDidClasspathUpdate(async () => { - testSourceProvider.clear(); - commands.executeCommand(JavaTestRunnerCommands.REFRESH_TEST_EXPLORER); + // workaround: wait more time to make sure Language Server has updated all caches + setTimeout(() => { + testSourceProvider.clear(); + commands.executeCommand(JavaTestRunnerCommands.REFRESH_TEST_EXPLORER); + }, 1000 /*ms*/); })); } @@ -102,6 +106,7 @@ async function doActivate(_operationId: string, context: ExtensionContext): Prom instrumentOperationAsVsCodeCommand(JavaTestRunnerCommands.DEBUG_TEST_FROM_JAVA_PROJECT_EXPLORER, async (node: any) => await runTestsFromJavaProjectExplorer(node, true /* isDebug */)), instrumentOperationAsVsCodeCommand(JavaTestRunnerCommands.GO_TO_TEST, async () => await navigateToTestOrTarget(true)), instrumentOperationAsVsCodeCommand(JavaTestRunnerCommands.GO_TO_TEST_SUBJECT, async () => await navigateToTestOrTarget(false)), + instrumentOperationAsVsCodeCommand(JavaTestRunnerCommands.ENABLE_TESTS, async () => await enableTests()), window.onDidChangeActiveTextEditor(async (e: TextEditor | undefined) => { if (await isTestJavaFile(e?.document)) { await updateItemForDocumentWithDebounce(e!.document.uri); diff --git a/src/types.ts b/src/types.ts index 367364b4..7a6e7547 100644 --- a/src/types.ts +++ b/src/types.ts @@ -14,6 +14,10 @@ export interface IJavaTestItem { projectName: string; testKind: TestKind; testLevel: TestLevel; + /** + * Optional fields for projects + */ + natureIds?: string[]; } export enum TestKind { @@ -42,3 +46,10 @@ export interface IRunTestContext { testRun: TestRun; workspaceFolder: WorkspaceFolder; } + +export enum ProjectType { + Gradle, + Maven, + UnmanagedFolder, + Other, +}