Tests for channels and notifications
Restructured the notifications and channels as services.
This commit is contained in:
Родитель
f205358162
Коммит
9a31f80fe6
|
@ -1,14 +1,19 @@
|
|||
// Place your settings in this file to overwrite default and user settings.
|
||||
{
|
||||
"files.exclude": {
|
||||
"out": false // set this to true to hide the "out" folder with the compiled JS files
|
||||
},
|
||||
"search.exclude": {
|
||||
"out": true // set this to false to include "out" folder in search results
|
||||
},
|
||||
"typescript.tsdk": "./node_modules/typescript/lib",
|
||||
"editor.tabSize": 2,
|
||||
"editor.formatOnSave": true,
|
||||
"prettier.singleQuote": true,
|
||||
"rewrap.wrappingColumn": 80
|
||||
}
|
||||
"files.exclude": {
|
||||
"**/.git": true,
|
||||
"**/.svn": true,
|
||||
"**/.hg": true,
|
||||
"**/CVS": true,
|
||||
"**/.DS_Store": true,
|
||||
"**/.vscode-test": true
|
||||
},
|
||||
"search.exclude": {
|
||||
"**/out": true
|
||||
},
|
||||
"typescript.tsdk": "./node_modules/typescript/lib",
|
||||
"editor.tabSize": 2,
|
||||
"editor.formatOnSave": true,
|
||||
"prettier.singleQuote": true,
|
||||
"rewrap.wrappingColumn": 80,
|
||||
"typescript.check.tscVersion": false
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ export class CliCommandExecutor {
|
|||
export class CommandExecution {
|
||||
public readonly command: Command;
|
||||
public readonly cancellationToken?: CancellationToken;
|
||||
public readonly processExitSubject: Observable<number | string>;
|
||||
public readonly processExitSubject: Observable<number | string | null>;
|
||||
public readonly stdoutSubject: Observable<Buffer | string>;
|
||||
public readonly stderrSubject: Observable<Buffer | string>;
|
||||
|
||||
|
@ -50,7 +50,10 @@ export class CommandExecution {
|
|||
let timerSubscriber: Subscription | null;
|
||||
|
||||
// Process
|
||||
this.processExitSubject = Observable.fromEvent(childProcess, 'exit');
|
||||
this.processExitSubject = Observable.fromEvent(
|
||||
childProcess,
|
||||
'exit'
|
||||
) as Observable<number | string | null>;
|
||||
this.processExitSubject.subscribe(next => {
|
||||
if (timerSubscriber) {
|
||||
timerSubscriber.unsubscribe();
|
||||
|
|
|
@ -17,7 +17,7 @@ describe('CommandExecutor tests', () => {
|
|||
const exitCode = await new Promise<string>((resolve, reject) => {
|
||||
execution.processExitSubject.subscribe(
|
||||
data => {
|
||||
resolve(data.toString());
|
||||
resolve(data !== null ? data.toString() : '');
|
||||
},
|
||||
err => {
|
||||
reject(err);
|
||||
|
@ -45,7 +45,7 @@ describe('CommandExecutor tests', () => {
|
|||
const exitCode = await new Promise<string>((resolve, reject) => {
|
||||
execution.processExitSubject.subscribe(
|
||||
data => {
|
||||
resolve(data.toString());
|
||||
resolve(data !== null ? data.toString() : '');
|
||||
},
|
||||
err => {
|
||||
reject(err);
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
"mocha": "3.2.0",
|
||||
"nyc": "^11.0.2",
|
||||
"typescript": "2.4.0",
|
||||
"vscode": "1.1.0"
|
||||
"vscode": "1.1.2"
|
||||
},
|
||||
"scripts": {
|
||||
"vscode:prepublish": "tsc -p ./",
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
"categories": ["Other"],
|
||||
"dependencies": {
|
||||
"@salesforce/salesforcedx-utils-vscode": "0.1.0",
|
||||
"rxjs": "^5.4.1",
|
||||
"vscode-nls": "^2.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -24,11 +25,13 @@
|
|||
"@types/mocha": "2.2.38",
|
||||
"@types/node": "^6.0.40",
|
||||
"@types/path-exists": "^1.0.29",
|
||||
"@types/sinon": "^2.3.2",
|
||||
"chai": "^4.0.2",
|
||||
"mocha": "3.2.0",
|
||||
"nyc": "^11.0.2",
|
||||
"sinon": "^2.3.6",
|
||||
"typescript": "2.4.0",
|
||||
"vscode": "1.1.0"
|
||||
"vscode": "1.1.2"
|
||||
},
|
||||
"scripts": {
|
||||
"vscode:prepublish": "tsc -p ./",
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
import * as vscode from 'vscode';
|
||||
import { localize } from '../messages';
|
||||
|
||||
import { CommandExecution } from '@salesforce/salesforcedx-utils-vscode/out/src/cli';
|
||||
|
||||
export const DEFAULT_SFDX_CHANNEL = vscode.window.createOutputChannel(
|
||||
localize('channel_name')
|
||||
);
|
||||
|
||||
export class ChannelService {
|
||||
private readonly channel: vscode.OutputChannel;
|
||||
private static instance: ChannelService;
|
||||
|
||||
public constructor(channel?: vscode.OutputChannel) {
|
||||
this.channel = channel || DEFAULT_SFDX_CHANNEL;
|
||||
}
|
||||
|
||||
public static getInstance(channel?: vscode.OutputChannel) {
|
||||
if (!ChannelService.instance) {
|
||||
ChannelService.instance = new ChannelService(channel);
|
||||
}
|
||||
return ChannelService.instance;
|
||||
}
|
||||
|
||||
public streamCommandOutput(execution: CommandExecution) {
|
||||
this.channel.append(localize('channel_starting_message'));
|
||||
this.channel.appendLine(execution.command.toString());
|
||||
this.channel.appendLine('');
|
||||
|
||||
execution.stderrSubject.subscribe(data =>
|
||||
this.channel.append(data.toString())
|
||||
);
|
||||
execution.stdoutSubject.subscribe(data =>
|
||||
this.channel.append(data.toString())
|
||||
);
|
||||
|
||||
execution.processExitSubject.subscribe(data => {
|
||||
this.channel.append(execution.command.toString());
|
||||
this.channel.append(' ');
|
||||
if (data !== null) {
|
||||
this.channel.appendLine(
|
||||
localize('channel_end_with_exit_code', data.toString())
|
||||
);
|
||||
} else {
|
||||
this.channel.appendLine(localize('channel_end'));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,32 +1,4 @@
|
|||
import * as vscode from 'vscode';
|
||||
import { get } from '../messages';
|
||||
import { ChannelService } from './channelService';
|
||||
|
||||
import { CommandExecution } from '@salesforce/salesforcedx-utils-vscode/out/src/cli';
|
||||
|
||||
export const DEFAULT_SFDX_CHANNEL = vscode.window.createOutputChannel(
|
||||
get('channel_name')
|
||||
);
|
||||
|
||||
export function streamCommandOutput(execution: CommandExecution) {
|
||||
DEFAULT_SFDX_CHANNEL.append(get('channel_starting_message'));
|
||||
DEFAULT_SFDX_CHANNEL.appendLine(execution.command.toString());
|
||||
DEFAULT_SFDX_CHANNEL.appendLine('');
|
||||
|
||||
execution.stderrSubject.subscribe(data =>
|
||||
DEFAULT_SFDX_CHANNEL.append(data.toString())
|
||||
);
|
||||
execution.stdoutSubject.subscribe(data =>
|
||||
DEFAULT_SFDX_CHANNEL.append(data.toString())
|
||||
);
|
||||
|
||||
execution.processExitSubject.subscribe(data => {
|
||||
DEFAULT_SFDX_CHANNEL.append(execution.command.toString());
|
||||
if (data) {
|
||||
DEFAULT_SFDX_CHANNEL.appendLine(
|
||||
get('channel_end_with_exit_code', data.toString())
|
||||
);
|
||||
} else {
|
||||
DEFAULT_SFDX_CHANNEL.appendLine(get('channel_end'));
|
||||
}
|
||||
});
|
||||
}
|
||||
export const channelService = ChannelService.getInstance();
|
||||
export { DEFAULT_SFDX_CHANNEL } from './channelService';
|
||||
|
|
|
@ -4,8 +4,8 @@ import {
|
|||
CliCommandExecutor,
|
||||
SfdxCommandBuilder
|
||||
} from '@salesforce/salesforcedx-utils-vscode/out/src/cli';
|
||||
import { streamCommandOutput } from '../channels';
|
||||
import { reportExecutionStatus } from '../notifications';
|
||||
import { channelService } from '../channels';
|
||||
import { notificationService } from '../notifications';
|
||||
import { CancellableStatusBar } from '../statuses';
|
||||
|
||||
export function forceApexTestRun(testClass?: string) {
|
||||
|
@ -52,8 +52,11 @@ function runTestClass(testClass: string) {
|
|||
{ cwd: vscode.workspace.rootPath }
|
||||
).execute(cancellationToken);
|
||||
|
||||
streamCommandOutput(execution);
|
||||
reportExecutionStatus(execution, cancellationToken);
|
||||
channelService.streamCommandOutput(execution);
|
||||
notificationService.reportCommandExecutionStatus(
|
||||
execution,
|
||||
cancellationToken
|
||||
);
|
||||
CancellableStatusBar.show(execution, cancellationTokenSource);
|
||||
}
|
||||
|
||||
|
@ -68,8 +71,11 @@ function runAllTests() {
|
|||
{ cwd: vscode.workspace.rootPath }
|
||||
).execute(cancellationToken);
|
||||
|
||||
streamCommandOutput(execution);
|
||||
reportExecutionStatus(execution, cancellationToken);
|
||||
channelService.streamCommandOutput(execution);
|
||||
notificationService.reportCommandExecutionStatus(
|
||||
execution,
|
||||
cancellationToken
|
||||
);
|
||||
CancellableStatusBar.show(execution, cancellationTokenSource);
|
||||
}
|
||||
|
||||
|
@ -85,7 +91,10 @@ function runTestSuite(testSuiteName: string) {
|
|||
{ cwd: vscode.workspace.rootPath }
|
||||
).execute(cancellationToken);
|
||||
|
||||
streamCommandOutput(execution);
|
||||
reportExecutionStatus(execution, cancellationToken);
|
||||
channelService.streamCommandOutput(execution);
|
||||
notificationService.reportCommandExecutionStatus(
|
||||
execution,
|
||||
cancellationToken
|
||||
);
|
||||
CancellableStatusBar.show(execution, cancellationTokenSource);
|
||||
}
|
||||
|
|
|
@ -3,8 +3,8 @@ import {
|
|||
CliCommandExecutor,
|
||||
SfdxCommandBuilder
|
||||
} from '@salesforce/salesforcedx-utils-vscode/out/src/cli';
|
||||
import { streamCommandOutput } from '../channels';
|
||||
import { reportExecutionStatus } from '../notifications';
|
||||
import { channelService } from '../channels';
|
||||
import { notificationService } from '../notifications';
|
||||
import { CancellableStatusBar } from '../statuses';
|
||||
|
||||
export function forceAuthWebLogin() {
|
||||
|
@ -19,7 +19,10 @@ export function forceAuthWebLogin() {
|
|||
{ cwd: vscode.workspace.rootPath }
|
||||
).execute(cancellationTokenSource.token);
|
||||
|
||||
streamCommandOutput(execution);
|
||||
reportExecutionStatus(execution, cancellationToken);
|
||||
channelService.streamCommandOutput(execution);
|
||||
notificationService.reportCommandExecutionStatus(
|
||||
execution,
|
||||
cancellationToken
|
||||
);
|
||||
CancellableStatusBar.show(execution, cancellationTokenSource);
|
||||
}
|
||||
|
|
|
@ -4,8 +4,8 @@ import {
|
|||
CliCommandExecutor,
|
||||
SfdxCommandBuilder
|
||||
} from '@salesforce/salesforcedx-utils-vscode/out/src/cli';
|
||||
import { streamCommandOutput } from '../channels';
|
||||
import { reportExecutionStatus } from '../notifications';
|
||||
import { channelService } from '../channels';
|
||||
import { notificationService } from '../notifications';
|
||||
import { CancellableStatusBar } from '../statuses';
|
||||
|
||||
export function forceOrgCreate() {
|
||||
|
@ -35,8 +35,11 @@ export function forceOrgCreate() {
|
|||
{ cwd: rootPath }
|
||||
).execute(cancellationToken);
|
||||
|
||||
streamCommandOutput(execution);
|
||||
reportExecutionStatus(execution, cancellationToken);
|
||||
channelService.streamCommandOutput(execution);
|
||||
notificationService.reportCommandExecutionStatus(
|
||||
execution,
|
||||
cancellationToken
|
||||
);
|
||||
CancellableStatusBar.show(execution, cancellationTokenSource);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -3,8 +3,8 @@ import {
|
|||
CliCommandExecutor,
|
||||
SfdxCommandBuilder
|
||||
} from '@salesforce/salesforcedx-utils-vscode/out/src/cli';
|
||||
import { streamCommandOutput } from '../channels';
|
||||
import { reportExecutionStatus } from '../notifications';
|
||||
import { channelService } from '../channels';
|
||||
import { notificationService } from '../notifications';
|
||||
import { CancellableStatusBar } from '../statuses';
|
||||
|
||||
export function forceOrgOpen() {
|
||||
|
@ -16,7 +16,10 @@ export function forceOrgOpen() {
|
|||
{ cwd: vscode.workspace.rootPath }
|
||||
).execute(cancellationToken);
|
||||
|
||||
streamCommandOutput(execution);
|
||||
reportExecutionStatus(execution, cancellationToken);
|
||||
channelService.streamCommandOutput(execution);
|
||||
notificationService.reportCommandExecutionStatus(
|
||||
execution,
|
||||
cancellationToken
|
||||
);
|
||||
CancellableStatusBar.show(execution, cancellationTokenSource);
|
||||
}
|
||||
|
|
|
@ -3,8 +3,8 @@ import {
|
|||
CliCommandExecutor,
|
||||
SfdxCommandBuilder
|
||||
} from '@salesforce/salesforcedx-utils-vscode/out/src/cli';
|
||||
import { streamCommandOutput } from '../channels';
|
||||
import { reportExecutionStatus } from '../notifications';
|
||||
import { channelService } from '../channels';
|
||||
import { notificationService } from '../notifications';
|
||||
import { CancellableStatusBar } from '../statuses';
|
||||
|
||||
export function forceSourcePull() {
|
||||
|
@ -16,7 +16,10 @@ export function forceSourcePull() {
|
|||
{ cwd: vscode.workspace.rootPath }
|
||||
).execute(cancellationToken);
|
||||
|
||||
streamCommandOutput(execution);
|
||||
reportExecutionStatus(execution, cancellationToken);
|
||||
channelService.streamCommandOutput(execution);
|
||||
notificationService.reportCommandExecutionStatus(
|
||||
execution,
|
||||
cancellationToken
|
||||
);
|
||||
CancellableStatusBar.show(execution, cancellationTokenSource);
|
||||
}
|
||||
|
|
|
@ -3,8 +3,8 @@ import {
|
|||
CliCommandExecutor,
|
||||
SfdxCommandBuilder
|
||||
} from '@salesforce/salesforcedx-utils-vscode/out/src/cli';
|
||||
import { streamCommandOutput } from '../channels';
|
||||
import { reportExecutionStatus } from '../notifications';
|
||||
import { channelService } from '../channels';
|
||||
import { notificationService } from '../notifications';
|
||||
import { CancellableStatusBar } from '../statuses';
|
||||
|
||||
export function forceSourcePush() {
|
||||
|
@ -16,7 +16,10 @@ export function forceSourcePush() {
|
|||
{ cwd: vscode.workspace.rootPath }
|
||||
).execute(cancellationToken);
|
||||
|
||||
streamCommandOutput(execution);
|
||||
reportExecutionStatus(execution, cancellationToken);
|
||||
channelService.streamCommandOutput(execution);
|
||||
notificationService.reportCommandExecutionStatus(
|
||||
execution,
|
||||
cancellationToken
|
||||
);
|
||||
CancellableStatusBar.show(execution, cancellationTokenSource);
|
||||
}
|
||||
|
|
|
@ -3,8 +3,8 @@ import {
|
|||
CliCommandExecutor,
|
||||
SfdxCommandBuilder
|
||||
} from '@salesforce/salesforcedx-utils-vscode/out/src/cli';
|
||||
import { streamCommandOutput } from '../channels';
|
||||
import { reportExecutionStatus } from '../notifications';
|
||||
import { channelService } from '../channels';
|
||||
import { notificationService } from '../notifications';
|
||||
import { CancellableStatusBar } from '../statuses';
|
||||
|
||||
export function forceSourceStatus() {
|
||||
|
@ -16,7 +16,10 @@ export function forceSourceStatus() {
|
|||
{ cwd: vscode.workspace.rootPath }
|
||||
).execute(cancellationToken);
|
||||
|
||||
streamCommandOutput(execution);
|
||||
reportExecutionStatus(execution, cancellationToken);
|
||||
channelService.streamCommandOutput(execution);
|
||||
notificationService.reportCommandExecutionStatus(
|
||||
execution,
|
||||
cancellationToken
|
||||
);
|
||||
CancellableStatusBar.show(execution, cancellationTokenSource);
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ import util = require('util');
|
|||
|
||||
const locale = 'en_US';
|
||||
|
||||
export let get = function(label: string, ...args: any[]): string {
|
||||
export let localize = function(label: string, ...args: any[]): string {
|
||||
if (!messages[locale]) {
|
||||
throw new Error("Locale '" + locale + "' doesn't exist");
|
||||
}
|
||||
|
@ -39,6 +39,11 @@ const messages: Messages = {
|
|||
channel_name: 'SalesforceDX CLI',
|
||||
channel_starting_message: 'Starting ',
|
||||
channel_end_with_exit_code: 'ended with exit code %s',
|
||||
channel_end: 'ended'
|
||||
channel_end: 'ended',
|
||||
|
||||
notification_successful_execution_message: 'Successfully executed %s',
|
||||
notification_canceled_execution_message: '%s canceled',
|
||||
notification_unsuccessful_execution_message: 'Failed to execute %s',
|
||||
notification_show_button_text: 'Show'
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,30 +1,3 @@
|
|||
import * as vscode from 'vscode';
|
||||
import { CommandExecution } from '@salesforce/salesforcedx-utils-vscode/out/src/cli';
|
||||
import { DEFAULT_SFDX_CHANNEL } from '../channels';
|
||||
import { NotificationService } from './notificationService';
|
||||
|
||||
export function reportExecutionStatus(
|
||||
execution: CommandExecution,
|
||||
cancellationToken?: vscode.CancellationToken
|
||||
) {
|
||||
execution.processExitSubject.subscribe(async data => {
|
||||
if (data !== null && data !== 'undefined' && data.toString() === '0') {
|
||||
const selection = await vscode.window.showInformationMessage(
|
||||
`Successfully executed ${execution.command}`,
|
||||
'Show'
|
||||
);
|
||||
if (selection && selection === 'Show') {
|
||||
DEFAULT_SFDX_CHANNEL.show();
|
||||
}
|
||||
} else {
|
||||
if (cancellationToken && cancellationToken.isCancellationRequested) {
|
||||
vscode.window.showWarningMessage(`${execution.command} canceled`);
|
||||
DEFAULT_SFDX_CHANNEL.show();
|
||||
} else {
|
||||
vscode.window.showErrorMessage(
|
||||
`Failed to execute ${execution.command}`
|
||||
);
|
||||
DEFAULT_SFDX_CHANNEL.show();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
export const notificationService = NotificationService.getInstance();
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
import * as vscode from 'vscode';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { CommandExecution } from '@salesforce/salesforcedx-utils-vscode/out/src/cli';
|
||||
import { DEFAULT_SFDX_CHANNEL } from '../channels';
|
||||
import { localize } from '../messages';
|
||||
|
||||
export class NotificationService {
|
||||
private readonly channel: vscode.OutputChannel;
|
||||
private static instance: NotificationService;
|
||||
|
||||
public constructor(channel?: vscode.OutputChannel) {
|
||||
this.channel = channel || DEFAULT_SFDX_CHANNEL;
|
||||
}
|
||||
|
||||
public static getInstance(channel?: vscode.OutputChannel) {
|
||||
if (!NotificationService.instance) {
|
||||
NotificationService.instance = new NotificationService(channel);
|
||||
}
|
||||
return NotificationService.instance;
|
||||
}
|
||||
|
||||
public reportCommandExecutionStatus(
|
||||
execution: CommandExecution,
|
||||
cancellationToken?: vscode.CancellationToken
|
||||
) {
|
||||
// https://stackoverflow.com/questions/38168581/observablet-is-not-a-class-derived-from-observablet
|
||||
this.reportExecutionStatus(
|
||||
execution.command.command.toString(),
|
||||
(execution.processExitSubject as any) as Observable<
|
||||
number | string | null
|
||||
>,
|
||||
cancellationToken
|
||||
);
|
||||
}
|
||||
|
||||
public reportExecutionStatus(
|
||||
executionName: string,
|
||||
observable: Observable<number | string | null>,
|
||||
cancellationToken?: vscode.CancellationToken
|
||||
) {
|
||||
observable.subscribe(async data => {
|
||||
if (data !== null && data.toString() === '0') {
|
||||
const showButtonText = localize('notification_show_button_text');
|
||||
const selection = await vscode.window.showInformationMessage(
|
||||
localize('notification_successful_execution_message', executionName),
|
||||
showButtonText
|
||||
);
|
||||
if (selection && selection === showButtonText) {
|
||||
this.channel.show();
|
||||
}
|
||||
} else {
|
||||
if (cancellationToken && cancellationToken.isCancellationRequested) {
|
||||
vscode.window.showWarningMessage(
|
||||
localize('notification_canceled_execution_message', executionName)
|
||||
);
|
||||
this.channel.show();
|
||||
} else {
|
||||
vscode.window.showErrorMessage(
|
||||
localize(
|
||||
'notification_unsuccessful_execution_message',
|
||||
executionName
|
||||
)
|
||||
);
|
||||
this.channel.show();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,6 +1,101 @@
|
|||
export {
|
||||
CANCEL_EXECUTION_COMMAND,
|
||||
PassiveStatusBar,
|
||||
CancellableStatusBar,
|
||||
cancelCommandExecution
|
||||
} from './status';
|
||||
import {
|
||||
CancellationTokenSource,
|
||||
StatusBarItem,
|
||||
StatusBarAlignment,
|
||||
window
|
||||
} from 'vscode';
|
||||
|
||||
import { CommandExecution } from '@salesforce/salesforcedx-utils-vscode/out/src/cli';
|
||||
|
||||
export const CANCEL_EXECUTION_COMMAND = 'internal.cancel.execution.command';
|
||||
const ALIGNMENT = StatusBarAlignment.Left;
|
||||
const PRIORITY = -10;
|
||||
|
||||
const statusBarItem: StatusBarItem = window.createStatusBarItem(
|
||||
ALIGNMENT,
|
||||
PRIORITY
|
||||
);
|
||||
let statusTimer: NodeJS.Timer | null;
|
||||
let cancellationTokenSource: CancellationTokenSource | null;
|
||||
|
||||
export class PassiveStatusBar {
|
||||
public static show(execution: CommandExecution) {
|
||||
return new PassiveStatusBar(execution);
|
||||
}
|
||||
|
||||
private constructor(execution: CommandExecution) {
|
||||
resetStatusBarItem();
|
||||
|
||||
statusBarItem.text = `$(clock) ${execution.command}`;
|
||||
|
||||
if (statusTimer) {
|
||||
clearInterval(statusTimer);
|
||||
}
|
||||
statusTimer = setInterval(
|
||||
() => (statusBarItem.text = statusBarItem.text + '.'),
|
||||
1000
|
||||
);
|
||||
execution.processExitSubject.subscribe(data => {
|
||||
if (statusTimer) {
|
||||
clearInterval(statusTimer);
|
||||
}
|
||||
statusBarItem.hide();
|
||||
});
|
||||
|
||||
statusBarItem.show();
|
||||
}
|
||||
}
|
||||
|
||||
export class CancellableStatusBar {
|
||||
public static show(
|
||||
execution: CommandExecution,
|
||||
token: CancellationTokenSource
|
||||
) {
|
||||
return new CancellableStatusBar(execution, token);
|
||||
}
|
||||
|
||||
private constructor(
|
||||
execution: CommandExecution,
|
||||
token: CancellationTokenSource
|
||||
) {
|
||||
resetStatusBarItem();
|
||||
|
||||
statusBarItem.text = `$(x) ${execution.command}`;
|
||||
statusBarItem.tooltip = 'Click to cancel the command';
|
||||
statusBarItem.command = CANCEL_EXECUTION_COMMAND;
|
||||
|
||||
cancellationTokenSource = token;
|
||||
|
||||
if (statusTimer) {
|
||||
clearInterval(statusTimer);
|
||||
}
|
||||
statusTimer = setInterval(
|
||||
() => (statusBarItem.text = statusBarItem.text + '.'),
|
||||
1000
|
||||
);
|
||||
execution.processExitSubject.subscribe(data => {
|
||||
if (statusTimer) {
|
||||
clearInterval(statusTimer);
|
||||
}
|
||||
statusBarItem.hide();
|
||||
});
|
||||
|
||||
statusBarItem.show();
|
||||
}
|
||||
}
|
||||
|
||||
function resetStatusBarItem() {
|
||||
statusBarItem.text = '';
|
||||
statusBarItem.tooltip = '';
|
||||
statusBarItem.command = undefined;
|
||||
}
|
||||
|
||||
export function cancelCommandExecution() {
|
||||
if (cancellationTokenSource) {
|
||||
cancellationTokenSource.cancel();
|
||||
resetStatusBarItem();
|
||||
if (statusTimer) {
|
||||
clearInterval(statusTimer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,101 +0,0 @@
|
|||
import {
|
||||
CancellationTokenSource,
|
||||
StatusBarItem,
|
||||
StatusBarAlignment,
|
||||
window
|
||||
} from 'vscode';
|
||||
|
||||
import { CommandExecution } from '@salesforce/salesforcedx-utils-vscode/out/src/cli';
|
||||
|
||||
export const CANCEL_EXECUTION_COMMAND = 'internal.cancel.execution.command';
|
||||
const ALIGNMENT = StatusBarAlignment.Left;
|
||||
const PRIORITY = -10;
|
||||
|
||||
const statusBarItem: StatusBarItem = window.createStatusBarItem(
|
||||
ALIGNMENT,
|
||||
PRIORITY
|
||||
);
|
||||
let statusTimer: NodeJS.Timer | null;
|
||||
let cancellationTokenSource: CancellationTokenSource | null;
|
||||
|
||||
export class PassiveStatusBar {
|
||||
public static show(execution: CommandExecution) {
|
||||
return new PassiveStatusBar(execution);
|
||||
}
|
||||
|
||||
private constructor(execution: CommandExecution) {
|
||||
resetStatusBarItem();
|
||||
|
||||
statusBarItem.text = `$(clock) ${execution.command}`;
|
||||
|
||||
if (statusTimer) {
|
||||
clearInterval(statusTimer);
|
||||
}
|
||||
statusTimer = setInterval(
|
||||
() => (statusBarItem.text = statusBarItem.text + '.'),
|
||||
1000
|
||||
);
|
||||
execution.processExitSubject.subscribe(data => {
|
||||
if (statusTimer) {
|
||||
clearInterval(statusTimer);
|
||||
}
|
||||
statusBarItem.hide();
|
||||
});
|
||||
|
||||
statusBarItem.show();
|
||||
}
|
||||
}
|
||||
|
||||
export class CancellableStatusBar {
|
||||
public static show(
|
||||
execution: CommandExecution,
|
||||
token: CancellationTokenSource
|
||||
) {
|
||||
return new CancellableStatusBar(execution, token);
|
||||
}
|
||||
|
||||
private constructor(
|
||||
execution: CommandExecution,
|
||||
token: CancellationTokenSource
|
||||
) {
|
||||
resetStatusBarItem();
|
||||
|
||||
statusBarItem.text = `$(x) ${execution.command}`;
|
||||
statusBarItem.tooltip = 'Click to cancel the command';
|
||||
statusBarItem.command = CANCEL_EXECUTION_COMMAND;
|
||||
|
||||
cancellationTokenSource = token;
|
||||
|
||||
if (statusTimer) {
|
||||
clearInterval(statusTimer);
|
||||
}
|
||||
statusTimer = setInterval(
|
||||
() => (statusBarItem.text = statusBarItem.text + '.'),
|
||||
1000
|
||||
);
|
||||
execution.processExitSubject.subscribe(data => {
|
||||
if (statusTimer) {
|
||||
clearInterval(statusTimer);
|
||||
}
|
||||
statusBarItem.hide();
|
||||
});
|
||||
|
||||
statusBarItem.show();
|
||||
}
|
||||
}
|
||||
|
||||
function resetStatusBarItem() {
|
||||
statusBarItem.text = '';
|
||||
statusBarItem.tooltip = '';
|
||||
statusBarItem.command = undefined;
|
||||
}
|
||||
|
||||
export function cancelCommandExecution() {
|
||||
if (cancellationTokenSource) {
|
||||
cancellationTokenSource.cancel();
|
||||
resetStatusBarItem();
|
||||
if (statusTimer) {
|
||||
clearInterval(statusTimer);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,11 +1,90 @@
|
|||
import { expect } from 'chai';
|
||||
import { OutputChannel, ViewColumn } from 'vscode';
|
||||
import { EOL } from 'os';
|
||||
import {
|
||||
CliCommandExecutor,
|
||||
SfdxCommandBuilder
|
||||
} from '@salesforce/salesforcedx-utils-vscode/out/src/cli';
|
||||
import {
|
||||
DEFAULT_SFDX_CHANNEL,
|
||||
ChannelService
|
||||
} from '../../src/channels/channelService';
|
||||
|
||||
import { DEFAULT_SFDX_CHANNEL } from '../../src/channels';
|
||||
class MockChannel implements OutputChannel {
|
||||
public readonly name = 'MockChannel';
|
||||
public value = '';
|
||||
|
||||
describe('Channel tests', () => {
|
||||
public append(value: string): void {
|
||||
this.value += value;
|
||||
}
|
||||
|
||||
public appendLine(value: string): void {
|
||||
this.value += value;
|
||||
this.value += EOL;
|
||||
}
|
||||
|
||||
// These methods are not mocked or needed
|
||||
// tslint:disable:no-empty
|
||||
public clear(): void {}
|
||||
public show(preserveFocus?: boolean | undefined): void;
|
||||
public show(
|
||||
column?: ViewColumn | undefined,
|
||||
preserveFocus?: boolean | undefined
|
||||
): void;
|
||||
public show(column?: any, preserveFocus?: any) {}
|
||||
public hide(): void {}
|
||||
public dispose(): void {}
|
||||
// tslint:enable:no-empty
|
||||
}
|
||||
|
||||
describe('Channel', () => {
|
||||
describe('Default SFDX channel', () => {
|
||||
let mChannel: MockChannel;
|
||||
let channelService: ChannelService;
|
||||
|
||||
beforeEach(() => {
|
||||
mChannel = new MockChannel();
|
||||
channelService = new ChannelService(mChannel);
|
||||
});
|
||||
|
||||
it('Should have proper name', () => {
|
||||
expect(DEFAULT_SFDX_CHANNEL.name).to.equal('SalesforceDX CLI');
|
||||
});
|
||||
|
||||
it('Should pipe stdout on successful command execution', async () => {
|
||||
const execution = new CliCommandExecutor(
|
||||
new SfdxCommandBuilder().withArg('force').withArg('--help').build(),
|
||||
{}
|
||||
).execute();
|
||||
|
||||
channelService.streamCommandOutput(execution);
|
||||
|
||||
await new Promise<string>((resolve, reject) => {
|
||||
execution.processExitSubject.subscribe(data => {
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
expect(mChannel.value).to.contain(
|
||||
'Usage: sfdx COMMAND [command-specific-options]'
|
||||
);
|
||||
expect(mChannel.value).to.contain('ended with exit code 0');
|
||||
});
|
||||
|
||||
it('Should pipe stderr on unsuccessful command execution', async () => {
|
||||
const execution = new CliCommandExecutor(
|
||||
new SfdxCommandBuilder().withArg('force').withArg('--unknown').build(),
|
||||
{}
|
||||
).execute();
|
||||
|
||||
channelService.streamCommandOutput(execution);
|
||||
|
||||
await new Promise<string>((resolve, reject) => {
|
||||
execution.processExitSubject.subscribe(data => {
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
expect(mChannel.value).to.contain('Error: Unexpected flag --unknown');
|
||||
expect(mChannel.value).to.contain('ended with exit code 2');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
import { expect } from 'chai';
|
||||
|
||||
// Defines a Mocha test suite to group tests of similar kind together
|
||||
describe('Extension Tests', () => {
|
||||
// Defines a Mocha unit test
|
||||
it('Something 1', () => {
|
||||
expect([1, 2, 3].indexOf(5)).to.equal(-1);
|
||||
expect([1, 2, 3].indexOf(1)).to.equal(0);
|
||||
});
|
||||
it('Something 1', () => {
|
||||
expect([1, 2, 3].indexOf(5)).to.equal(-1);
|
||||
expect([1, 2, 3].indexOf(1)).to.equal(0);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,108 @@
|
|||
import { SinonStub, assert, stub } from 'sinon';
|
||||
import { ReplaySubject } from 'rxjs/ReplaySubject';
|
||||
import { CancellationTokenSource, window } from 'vscode';
|
||||
import { NotificationService } from '../../src/notifications/notificationService';
|
||||
import { DEFAULT_SFDX_CHANNEL } from '../../src/channels/channelService';
|
||||
import { localize } from '../../src/messages';
|
||||
|
||||
const SHOW_BUTTON_TEXT = localize('notification_show_button_text');
|
||||
|
||||
// tslint:disable:no-empty
|
||||
describe('Notifications', () => {
|
||||
let mShowInformation: SinonStub;
|
||||
let mShowWarningMessage: SinonStub;
|
||||
let mShowErrorMessage: SinonStub;
|
||||
let mShow: SinonStub;
|
||||
|
||||
beforeEach(() => {
|
||||
mShow = stub(DEFAULT_SFDX_CHANNEL, 'show');
|
||||
mShowInformation = stub(window, 'showInformationMessage').returns(
|
||||
Promise.resolve(null)
|
||||
);
|
||||
mShowWarningMessage = stub(window, 'showWarningMessage').returns(
|
||||
Promise.resolve(null)
|
||||
);
|
||||
mShowErrorMessage = stub(window, 'showErrorMessage').returns(
|
||||
Promise.resolve(null)
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
mShow.restore();
|
||||
mShowInformation.restore();
|
||||
mShowWarningMessage.restore();
|
||||
mShowErrorMessage.restore();
|
||||
});
|
||||
|
||||
it('Should notify successful execution', async () => {
|
||||
const observable = new ReplaySubject<number | string | null>();
|
||||
observable.next(0);
|
||||
|
||||
const notificationService = NotificationService.getInstance();
|
||||
await notificationService.reportExecutionStatus('mock command', observable);
|
||||
|
||||
assert.notCalled(mShow);
|
||||
assert.calledWith(
|
||||
mShowInformation,
|
||||
'Successfully executed mock command',
|
||||
SHOW_BUTTON_TEXT
|
||||
);
|
||||
assert.notCalled(mShowWarningMessage);
|
||||
assert.notCalled(mShowErrorMessage);
|
||||
});
|
||||
|
||||
it('Should notify successful and show channel as requested', async () => {
|
||||
// For this particular test, we need it to return a different value
|
||||
mShowInformation.restore();
|
||||
mShowInformation = stub(window, 'showInformationMessage').returns(
|
||||
Promise.resolve(SHOW_BUTTON_TEXT)
|
||||
);
|
||||
const observable = new ReplaySubject<number | string | null>();
|
||||
observable.next(0);
|
||||
|
||||
const notificationService = NotificationService.getInstance();
|
||||
await notificationService.reportExecutionStatus('mock command', observable);
|
||||
|
||||
assert.calledOnce(mShow);
|
||||
assert.calledWith(
|
||||
mShowInformation,
|
||||
'Successfully executed mock command',
|
||||
SHOW_BUTTON_TEXT
|
||||
);
|
||||
assert.notCalled(mShowWarningMessage);
|
||||
assert.notCalled(mShowErrorMessage);
|
||||
});
|
||||
|
||||
it('Should notify cancellation', async () => {
|
||||
const observable = new ReplaySubject<number | string | null>();
|
||||
observable.next(null);
|
||||
const cancellationTokenSource = new CancellationTokenSource();
|
||||
cancellationTokenSource.cancel();
|
||||
|
||||
const notificationService = NotificationService.getInstance();
|
||||
await notificationService.reportExecutionStatus(
|
||||
'mock command',
|
||||
observable,
|
||||
cancellationTokenSource.token
|
||||
);
|
||||
|
||||
assert.calledOnce(mShow);
|
||||
assert.notCalled(mShowInformation);
|
||||
assert.calledWith(mShowWarningMessage, 'mock command canceled');
|
||||
assert.notCalled(mShowErrorMessage);
|
||||
});
|
||||
|
||||
it('Should notify unsuccessful execution', async () => {
|
||||
const ABNORMAL_EXIT = -1;
|
||||
const observable = new ReplaySubject<number | string | null>();
|
||||
observable.next(ABNORMAL_EXIT);
|
||||
|
||||
const notificationService = NotificationService.getInstance();
|
||||
await notificationService.reportExecutionStatus('mock command', observable);
|
||||
|
||||
assert.calledOnce(mShow);
|
||||
assert.notCalled(mShowInformation);
|
||||
assert.notCalled(mShowWarningMessage);
|
||||
assert.calledWith(mShowErrorMessage, 'Failed to execute mock command');
|
||||
});
|
||||
});
|
|
@ -1,23 +1,16 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es6",
|
||||
"lib": [
|
||||
"dom",
|
||||
"es6"
|
||||
],
|
||||
"sourceMap": true,
|
||||
"declaration": true,
|
||||
"moduleResolution": "node",
|
||||
"noImplicitAny": true,
|
||||
"rootDir": ".",
|
||||
"outDir": "out",
|
||||
"preserveConstEnums": true,
|
||||
"strict": true
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
".vscode-test",
|
||||
"out"
|
||||
]
|
||||
}
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es6",
|
||||
"lib": ["dom", "es6"],
|
||||
"sourceMap": true,
|
||||
"declaration": true,
|
||||
"moduleResolution": "node",
|
||||
"noImplicitAny": true,
|
||||
"rootDir": ".",
|
||||
"outDir": "out",
|
||||
"preserveConstEnums": true,
|
||||
"strict": true
|
||||
},
|
||||
"exclude": ["node_modules", ".vscode-test", "out"]
|
||||
}
|
||||
|
|
|
@ -19,10 +19,12 @@
|
|||
"@types/chai": "^4.0.0",
|
||||
"@types/mocha": "2.2.38",
|
||||
"@types/node": "^6.0.40",
|
||||
"@types/sinon": "^2.3.2",
|
||||
"chai": "^4.0.2",
|
||||
"mocha": "3.2.0",
|
||||
"typescript": "2.3.4",
|
||||
"vscode": "1.1.0"
|
||||
"sinon": "^2.3.6",
|
||||
"typescript": "2.4.0",
|
||||
"vscode": "1.1.2"
|
||||
},
|
||||
"scripts": {
|
||||
"vscode:prepublish": "tsc -p ./",
|
||||
|
|
|
@ -19,10 +19,12 @@
|
|||
"@types/chai": "^4.0.0",
|
||||
"@types/mocha": "2.2.38",
|
||||
"@types/node": "^6.0.40",
|
||||
"@types/sinon": "^2.3.2",
|
||||
"chai": "^4.0.2",
|
||||
"mocha": "3.2.0",
|
||||
"typescript": "2.3.4",
|
||||
"vscode": "1.1.0"
|
||||
"sinon": "^2.3.6",
|
||||
"typescript": "2.4.0",
|
||||
"vscode": "1.1.2"
|
||||
},
|
||||
"scripts": {
|
||||
"vscode:prepublish": "tsc -p ./",
|
||||
|
|
Загрузка…
Ссылка в новой задаче