Skip to content

Commit

Permalink
feat: Collapse All Apex Tests functionality @W-16273375@ (#5684)
Browse files Browse the repository at this point in the history
* feat: adds collapse all apex test functionality to testing side panel

* fix: update to type guard via @peternhale code review feedback

* fix: update to type guard via @peternhale code review feedback

* Revert "fix: update to type guard via @peternhale code review feedback"

This reverts commit 7e69e45.

* Updates initial state to expanded for test groups by default

* Adds Collapse All functionality to LWC test pane as well

* fix: updates typo in new packages\salesforcedx-vscode-lwc\test\jest\testExplorer\testOutlineProvider.test.ts test file

* fix: standardize ordering of LWC/Apex test pane action buttons

---------

Co-authored-by: peternhale <[email protected]>
  • Loading branch information
jamessimone and peternhale authored Jul 19, 2024
1 parent 1a92295 commit 2f88e43
Show file tree
Hide file tree
Showing 17 changed files with 248 additions and 64 deletions.
17 changes: 15 additions & 2 deletions packages/salesforcedx-vscode-apex/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -140,12 +140,17 @@
{
"command": "sf.test.view.run",
"when": "view == sf.test.view",
"group": "navigation"
"group": "navigation@1"
},
{
"command": "sf.test.view.refresh",
"when": "view == sf.test.view",
"group": "navigation"
"group": "navigation@2"
},
{
"command": "sf.test.view.collapseAll",
"when": "view == sf.test.view",
"group": "navigation@3"
}
],
"view/item/context": [
Expand Down Expand Up @@ -276,6 +281,14 @@
"dark": "resources/dark/play-button.svg"
}
},
{
"command": "sf.test.view.collapseAll",
"title": "%collapse_tests_title%",
"icon": {
"light": "resources/light/collapse-all.svg",
"dark": "resources/dark/collapse-all.svg"
}
},
{
"command": "sf.test.view.refresh",
"title": "%refresh_test_title%",
Expand Down
1 change: 1 addition & 0 deletions packages/salesforcedx-vscode-apex/package.nls.ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"apex_trace_server_description": "Controls the level of detail for trace logs in the Apex Language Server",
"apex_verbose_level_trace_description": "Output everything, including details about notifications and responses received by the client, and requests sent by the server.",
"configuration_title": "Salesforce Apex Configuration",
"collapse_tests_title": "SFDX: Apex テストを隠す",
"go_to_definition_title": "定義に移動",
"java_home_description": "Apex 言語サーバの起動に使用される Java 8 または Java 11 ランタイムのフォルダパスを指定します。(例: /Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home)",
"java_memory_description": "Apex 言語サーバへのメモリ割り当て量を MB 単位で指定します。空の場合はシステムのデフォルト値を使用します。",
Expand Down
1 change: 1 addition & 0 deletions packages/salesforcedx-vscode-apex/package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"apex_trace_server_description": "Controls the level of detail for trace logs in the Apex Language Server",
"apex_verbose_level_trace_description": "Output everything, including details about notifications and responses received by the client, and requests sent by the server.",
"configuration_title": "Salesforce Apex Configuration",
"collapse_tests_title": "SFDX: Collapse All Apex Tests",
"go_to_definition_title": "Go to Definition",
"java_home_description": "Specifies the folder path to the Java 11 or Java 17 runtime used to launch the Apex Language Server. Note on Windows the backslashes must be escaped.\n\nMac Example: `/Library/Java/JavaVirtualMachines/openjdk-11.jdk/Contents/Home`\n\nWindows Example: `C:\\\\Program Files\\\\Zulu\\\\zulu-17`\n\nLinux Example: `/usr/lib/jvm/java-17-openjdk-amd64`",
"java_memory_description": "Specifies the amount of memory allocation to the Apex Language Server in MB, or null to use the system default value.",
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
85 changes: 56 additions & 29 deletions packages/salesforcedx-vscode-apex/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ import {
import { nls } from './messages';
import { retrieveEnableSyncInitJobs } from './settings';
import { telemetryService } from './telemetry';
import { getTestOutlineProvider } from './views/testOutlineProvider';
import { getTestOutlineProvider, TestNode } from './views/testOutlineProvider';
import { ApexTestRunner, TestRunType } from './views/testRunner';

export const activate = async (extensionContext: vscode.ExtensionContext) => {
Expand Down Expand Up @@ -229,48 +229,64 @@ const registerTestView = (): vscode.Disposable => {
const testViewItems = new Array<vscode.Disposable>();

const testProvider = vscode.window.registerTreeDataProvider(
'sf.test.view',
testOutlineProvider.getId(),
testOutlineProvider
);
testViewItems.push(testProvider);

// Run Test Button on Test View command
testViewItems.push(
vscode.commands.registerCommand('sf.test.view.run', () =>
vscode.commands.registerCommand(`${testOutlineProvider.getId()}.run`, () =>
testRunner.runAllApexTests()
)
);
// Show Error Message command
testViewItems.push(
vscode.commands.registerCommand('sf.test.view.showError', test =>
testRunner.showErrorMessage(test)
vscode.commands.registerCommand(
`${testOutlineProvider.getId()}.showError`,
(test: TestNode) => testRunner.showErrorMessage(test)
)
);
// Show Definition command
testViewItems.push(
vscode.commands.registerCommand('sf.test.view.goToDefinition', test =>
testRunner.showErrorMessage(test)
vscode.commands.registerCommand(
`${testOutlineProvider.getId()}.goToDefinition`,
(test: TestNode) => testRunner.showErrorMessage(test)
)
);
// Run Class Tests command
testViewItems.push(
vscode.commands.registerCommand('sf.test.view.runClassTests', test =>
testRunner.runApexTests([test.name], TestRunType.Class)
vscode.commands.registerCommand(
`${testOutlineProvider.getId()}.runClassTests`,
(test: TestNode) =>
testRunner.runApexTests([test.name], TestRunType.Class)
)
);
// Run Single Test command
testViewItems.push(
vscode.commands.registerCommand('sf.test.view.runSingleTest', test =>
testRunner.runApexTests([test.name], TestRunType.Method)
vscode.commands.registerCommand(
`${testOutlineProvider.getId()}.runSingleTest`,
(test: TestNode) =>
testRunner.runApexTests([test.name], TestRunType.Method)
)
);
// Refresh Test View command
testViewItems.push(
vscode.commands.registerCommand('sf.test.view.refresh', () => {
if (languageClientUtils.getStatus().isReady()) {
return testOutlineProvider.refresh();
vscode.commands.registerCommand(
`${testOutlineProvider.getId()}.refresh`,
() => {
if (languageClientUtils.getStatus().isReady()) {
return testOutlineProvider.refresh();
}
}
})
)
);
// Collapse All Apex Tests command
testViewItems.push(
vscode.commands.registerCommand(
`${testOutlineProvider.getId()}.collapseAll`,
() => testOutlineProvider.collapseAll()
)
);

return vscode.Disposable.from(...testViewItems);
Expand All @@ -297,16 +313,19 @@ const createLanguageClient = async (
const languageClient = languageClientUtils.getClientInstance();

if (languageClient) {
languageClient.errorHandler?.addListener('error', message => {
languageClient.errorHandler?.addListener('error', (message: string) => {
languageServerStatusBarItem.error(message);
});
languageClient.errorHandler?.addListener('restarting', count => {
languageServerStatusBarItem.error(
nls
.localize('apex_language_server_quit_and_restarting')
.replace('$N', count)
);
});
languageClient.errorHandler?.addListener(
'restarting',
(count: number) => {
languageServerStatusBarItem.error(
nls
.localize('apex_language_server_quit_and_restarting')
.replace('$N', `${count}`)
);
}
);
languageClient.errorHandler?.addListener('startFailed', () => {
languageServerStatusBarItem.error(
nls.localize('apex_language_server_failed_activate')
Expand Down Expand Up @@ -343,17 +362,25 @@ const createLanguageClient = async (
);
}
} catch (e) {
languageClientUtils.setStatus(ClientStatus.Error, e);
let eMsg =
typeof e === 'string' ? e : e.message ?? nls.localize('unknown_error');
let errorMessage = '';
if (typeof e === 'string') {
errorMessage = e;
} else if (e instanceof Error) {
errorMessage = e.message ?? nls.localize('unknown_error');
}
if (
eMsg.includes(nls.localize('wrong_java_version_text', SET_JAVA_DOC_LINK))
errorMessage.includes(
nls.localize('wrong_java_version_text', SET_JAVA_DOC_LINK)
)
) {
eMsg = nls.localize('wrong_java_version_short');
errorMessage = nls.localize('wrong_java_version_short');
}
languageClientUtils.setStatus(ClientStatus.Error, errorMessage);
languageServerStatusBarItem.error(
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
`${nls.localize('apex_language_server_failed_activate')} - ${eMsg}`
`${nls.localize(
'apex_language_server_failed_activate'
)} - ${errorMessage}`
);
}
};
Expand Down
61 changes: 33 additions & 28 deletions packages/salesforcedx-vscode-apex/src/views/testOutlineProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,17 @@ import { nls } from '../messages';
import { IconsEnum, iconHelpers } from './icons';
import { ApexTestMethod } from './lspConverter';

// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
const safeLocalize = (val: string) => nls.localize(val);

// Message
const LOADING_MESSAGE = nls.localize('test_view_loading_message');
const NO_TESTS_MESSAGE = nls.localize('test_view_no_tests_message');
const NO_TESTS_DESCRIPTION = nls.localize('test_view_no_tests_description');
const LOADING_MESSAGE = safeLocalize('test_view_loading_message');
const NO_TESTS_MESSAGE = safeLocalize('test_view_no_tests_message');
const NO_TESTS_DESCRIPTION = safeLocalize('test_view_no_tests_description');

const TEST_RUN_ID_FILE = 'test-run-id.txt';
const TEST_RESULT_JSON_FILE = 'test-result.json';
const BASE_ID = 'sf.test.view';

export class ApexTestOutlineProvider
implements vscode.TreeDataProvider<TestNode>
Expand Down Expand Up @@ -56,6 +60,10 @@ export class ApexTestOutlineProvider
}
}

public getId(): string {
return BASE_ID;
}

public getChildren(element: TestNode): TestNode[] {
if (element) {
return element.children;
Expand All @@ -68,7 +76,7 @@ export class ApexTestOutlineProvider
const languageClientStatus = languageClientUtils.getStatus();
if (!languageClientStatus.isReady()) {
if (languageClientStatus.failedToInitialize()) {
vscode.window.showInformationMessage(
void vscode.window.showInformationMessage(
languageClientStatus.getStatusMessage()
);
return new Array<ApexTestNode>();
Expand Down Expand Up @@ -110,30 +118,29 @@ export class ApexTestOutlineProvider
this.rootNode = null; // Reset tests
this.apexTestMap.clear();
this.testStrings.clear();
this.apexTestInfo = null;
if (languageClientUtils.getStatus().isReady()) {
this.apexTestInfo = await getApexTests();
this.createTestIndex();
}
this.apexTestInfo = await getApexTests();
this.createTestIndex();
this.getAllApexTests();
this.onDidChangeTestData.fire(undefined);
}

public async collapseAll(): Promise<void> {
return vscode.commands.executeCommand(
`workbench.actions.treeView.${this.getId()}.collapseAll`
);
}

public async onResultFileCreate(
apexTestPath: string,
testResultFile: string
) {
const testRunIdFile = path.join(apexTestPath, TEST_RUN_ID_FILE);
const testRunId = readFileSync(testRunIdFile);
let testResultFilePath;
if (testRunId.toString() === '') {
testResultFilePath = path.join(apexTestPath, TEST_RESULT_JSON_FILE);
} else {
testResultFilePath = path.join(
apexTestPath,
`test-result-${testRunId.toString()}.json`
);
}
const testRunId = readFileSync(testRunIdFile).toString();
const testResultFilePath = path.join(
apexTestPath,
!testRunId ? TEST_RESULT_JSON_FILE : `test-result-${testRunId}.json`
);

if (testResultFile === testResultFilePath) {
await this.refresh();
this.updateTestResults(testResultFile);
Expand All @@ -157,7 +164,7 @@ export class ApexTestOutlineProvider
}

private getAllApexTests(): TestNode {
if (this.rootNode == null) {
if (this.rootNode === null) {
// Starting Out
this.rootNode = new ApexTestGroupNode(APEX_TESTS, null);
}
Expand Down Expand Up @@ -254,8 +261,8 @@ export abstract class TestNode extends vscode.TreeItem {
this.description = label;
this.name = label;
this.command = {
command: 'sf.test.view.showError',
title: nls.localize('test_view_show_error_title'),
command: `${BASE_ID}.showError`,
title: safeLocalize('test_view_show_error_title'),
arguments: [this]
};
}
Expand Down Expand Up @@ -316,12 +323,10 @@ export class ApexTestGroupNode extends TestNode {
this.failing = 0;
this.skipping = 0;
this.children.forEach(child => {
if ((child as ApexTestNode).outcome === PASS_RESULT) {
this.passing++;
} else if ((child as ApexTestNode).outcome === FAIL_RESULT) {
this.failing++;
} else if ((child as ApexTestNode).outcome === SKIP_RESULT) {
this.skipping++;
if (child instanceof ApexTestNode) {
this.passing += child.outcome === PASS_RESULT ? 1 : 0;
this.failing += child.outcome === FAIL_RESULT ? 1 : 0;
this.skipping += child.outcome === SKIP_RESULT ? 1 : 0;
}
});

Expand Down
4 changes: 3 additions & 1 deletion packages/salesforcedx-vscode-apex/test/jest/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,9 @@ describe('deactivate', () => {
let stopSpy: jest.SpyInstance;
beforeEach(() => {
stopSpy = jest.fn();
jest.spyOn(languageClientUtils, 'getClientInstance').mockReturnValue({ stop: stopSpy } as unknown as ApexLanguageClient);
jest
.spyOn(languageClientUtils, 'getClientInstance')
.mockReturnValue({ stop: stopSpy } as unknown as ApexLanguageClient);
});

it('should call stop on the language client', async () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright (c) 2024, salesforce.com, inc.
* All rights reserved.
* Licensed under the BSD 3-Clause license.
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/

import * as vscode from 'vscode';
import { iconHelpers } from '../../../src/views/icons/iconHelpers';

import { getTestOutlineProvider } from '../../../src/views/testOutlineProvider';

describe('testOutlineProvider Unit Tests.', () => {
const vscodeMocked = jest.mocked(vscode);
let commandMock: jest.SpyInstance;

beforeEach(() => {
// testOutlineProvider has a hidden dependency on iconHelpers.getIconPath that needs to be mocked
// for our purposes, the return value has no bearing on what we're testing
jest
.spyOn(iconHelpers, 'getIconPath')
.mockReturnValue(vscode.Uri.parse('https://salesforce.com'));
commandMock = jest.spyOn(vscodeMocked.commands, 'executeCommand');
});

it('sets test outline provider id', () => {
const provider = getTestOutlineProvider();
expect(provider.getId()).toBe('sf.test.view');
});

it('calls collapse all apex tests', () => {
const provider = getTestOutlineProvider();

void provider.collapseAll();

expect(commandMock.mock.calls.length).toBe(1);
expect(commandMock.mock.calls[0].length).toBe(1);
expect(commandMock.mock.calls[0][0]).toBe(
`workbench.actions.treeView.${provider.getId()}.collapseAll`
);
});
});
Loading

0 comments on commit 2f88e43

Please sign in to comment.