Skip to content

Commit

Permalink
feat: export Spinner and remove console.error (#223)
Browse files Browse the repository at this point in the history
* feat: export Spinner and remove console.error

* feat: add stubUx

* fix: use logJson

* chore: test fixes

* fix: add prompter stubs

* chore: remove ts-types for external nuts

* chore: revert changelog

* chore: fix logJson
  • Loading branch information
mdonnalley committed Feb 20, 2023
1 parent aa71ec7 commit 04475a0
Show file tree
Hide file tree
Showing 7 changed files with 609 additions and 494 deletions.
545 changes: 119 additions & 426 deletions CHANGELOG.md

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion src/exported.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@ import { Flags as OclifFlags } from '@oclif/core';
export { toHelpSection, parseVarArgs } from './util';
export { Deployable, Deployer, DeployerResult } from './deployer';
export { Deauthorizer } from './deauthorizer';
export { Progress, Prompter, generateTableChoices, Ux } from './ux';
export { Progress, Prompter, generateTableChoices, Ux, Spinner } from './ux';
export { SfHook } from './hooks';
export * from './types';
export { SfCommand, SfCommandInterface, StandardColors } from './sfCommand';
export * from './compatibility';
export * from './stubUx';
// custom flags
import { requiredOrgFlag, requiredHubFlag, optionalOrgFlag, optionalHubFlag } from './flags/orgFlags';
import { salesforceIdFlag } from './flags/salesforceId';
Expand Down
13 changes: 10 additions & 3 deletions src/sfCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,14 @@ export abstract class SfCommand<T> extends Command {
};
}

// eslint-disable-next-line class-methods-use-this
protected logJson(json: AnyJson | unknown): void {
// If `--json` is enabled, then the ux instance on the class will disable output, which
// means that the logJson method will not output anything. So, we need to create a new
// instance of the ux class that does not have output disabled in order to log the json.
new Ux().styledJSON(json as AnyJson);
}

// eslint-disable-next-line class-methods-use-this
protected async assignProject(): Promise<SfProject> {
try {
Expand Down Expand Up @@ -442,10 +450,9 @@ export abstract class SfCommand<T> extends Command {
};

if (this.jsonEnabled()) {
ux.styledJSON(this.toErrorJson(sfCommandError));
this.logJson(this.toErrorJson(sfCommandError));
} else {
// eslint-disable-next-line no-console
console.error(this.formatError(sfCommandError));
this.logToStderr(this.formatError(sfCommandError));
}
return sfCommandError;
}
Expand Down
53 changes: 53 additions & 0 deletions src/stubUx.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Copyright (c) 2023, 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 { SinonSandbox } from 'sinon';
import { SfCommand } from './sfCommand';
import { Prompter, Spinner, Ux } from './ux';

export function stubUx(sandbox: SinonSandbox) {
return {
log: sandbox.stub(Ux.prototype, 'log'),
warn: sandbox.stub(Ux.prototype, 'warn'),
table: sandbox.stub(Ux.prototype, 'table'),
url: sandbox.stub(Ux.prototype, 'url'),
styledHeader: sandbox.stub(Ux.prototype, 'styledHeader'),
styledObject: sandbox.stub(Ux.prototype, 'styledObject'),
styledJSON: sandbox.stub(Ux.prototype, 'styledJSON'),
};
}

export function stubSfCommandUx(sandbox: SinonSandbox) {
return {
log: sandbox.stub(SfCommand.prototype, 'log'),
logToStderr: sandbox.stub(SfCommand.prototype, 'logToStderr'),
logSuccess: sandbox.stub(SfCommand.prototype, 'logSuccess'),
logSensitive: sandbox.stub(SfCommand.prototype, 'logSensitive'),
info: sandbox.stub(SfCommand.prototype, 'info'),
warn: sandbox.stub(SfCommand.prototype, 'warn'),
table: sandbox.stub(SfCommand.prototype, 'table'),
url: sandbox.stub(SfCommand.prototype, 'url'),
styledHeader: sandbox.stub(SfCommand.prototype, 'styledHeader'),
styledObject: sandbox.stub(SfCommand.prototype, 'styledObject'),
styledJSON: sandbox.stub(SfCommand.prototype, 'styledJSON'),
};
}

export function stubSpinner(sandbox: SinonSandbox) {
return {
start: sandbox.stub(Spinner.prototype, 'start'),
stop: sandbox.stub(Spinner.prototype, 'stop'),
};
}

export function stubPrompter(sandbox: SinonSandbox) {
return {
prompt: sandbox.stub(Prompter.prototype, 'prompt'),
confirm: sandbox.stub(Prompter.prototype, 'confirm'),
timedPrompt: sandbox.stub(Prompter.prototype, 'timedPrompt'),
};
}
2 changes: 1 addition & 1 deletion test/unit/flags/apiVersion.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ const messages = Messages.loadMessages('@salesforce/sf-plugins-core', 'messages'

describe('fs flags', () => {
const sandbox = sinon.createSandbox();
sandbox.stub(Lifecycle, 'getInstance').returns(Lifecycle.prototype);
let warnStub: sinon.SinonStub;

beforeEach(() => {
sandbox.stub(Lifecycle, 'getInstance').returns(Lifecycle.prototype);
warnStub = sandbox.stub(Lifecycle.prototype, 'emitWarning');
});

Expand Down
148 changes: 85 additions & 63 deletions test/unit/sfCommand.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,79 +4,101 @@
* 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 os from 'os';
import { test } from '@oclif/test';
import { Config } from '@oclif/core';
import { Flags } from '@oclif/core';
import { Lifecycle } from '@salesforce/core';
import { TestContext } from '@salesforce/core/lib/testSetup';
import { stubMethod } from '@salesforce/ts-sinon';
import { expect } from 'chai';
import { SfError } from '@salesforce/core';
import { SfCommand } from '../../src/sfCommand';

class TestCommand extends SfCommand<string> {
// eslint-disable-next-line class-methods-use-this
public async run(): Promise<string> {
return Promise.resolve('');
class TestCommand extends SfCommand<void> {
public static readonly flags = {
actions: Flags.boolean({ char: 'a', description: 'show actions' }),
error: Flags.boolean({ char: 'e', description: 'throw an error' }),
warn: Flags.boolean({ char: 'w', description: 'throw a warning' }),
};

public async run(): Promise<void> {
const { flags } = await this.parse(TestCommand);

if (flags.error && !flags.warn) {
const infoError = new SfError('foo bar baz', 'FooError', flags.actions ? ['this', 'is an', 'action'] : null);
this.info(infoError);
} else if (flags.warn) {
if (flags.error) {
const warnError = new SfError('foo bar baz', 'FooError', flags.actions ? ['this', 'is an', 'action'] : null);
this.warn(warnError);
} else {
this.warn('foo bar baz');
}
} else {
this.info('foo bar baz');
}
}
}

describe('info messages', () => {
test
.stdout()
.do(() => {
const testCommand = new TestCommand([], {} as Config);
testCommand.info('foo bar baz');
})
.it('should show a info message from a string', (ctx) => {
expect(ctx.stdout).to.include('foo bar baz');
});
test
.stdout()
.do(() => {
const testCommand = new TestCommand([], {} as Config);
testCommand.info(new Error('foo bar baz') as SfCommand.Info);
})
.it('should show a info message from Error, no actions', (ctx) => {
expect(ctx.stdout).to.include('foo bar baz');
});
test
.stdout()
.do(() => {
const testCommand = new TestCommand([], {} as Config);
const infoError = new SfError('foo bar baz', 'foo', ['this', 'is an', 'action']) as Error;
testCommand.info(infoError as SfCommand.Info);
})
.it('should show a info message, with actions', (ctx) => {
expect(ctx.stdout).to.include('foo bar baz');
expect(ctx.stdout).to.include(['this', 'is an', 'action'].join(os.EOL));
const $$ = new TestContext();
beforeEach(() => {
stubMethod($$.SANDBOX, Lifecycle, 'getInstance').returns({
on: $$.SANDBOX.stub(),
onWarning: $$.SANDBOX.stub(),
});
});

it('should show a info message from a string', async () => {
const infoStub = stubMethod($$.SANDBOX, SfCommand.prototype, 'info');
await TestCommand.run([]);
expect(infoStub.calledWith('foo bar baz')).to.be.true;
});

it('should show a info message from Error, no actions', async () => {
const logStub = stubMethod($$.SANDBOX, SfCommand.prototype, 'log');
await TestCommand.run(['--error']);
expect(logStub.firstCall.firstArg).to.include('foo bar baz');
});

it('should show a info message, with actions', async () => {
const logStub = stubMethod($$.SANDBOX, SfCommand.prototype, 'log');
await TestCommand.run(['--error', '--actions']);
expect(logStub.firstCall.firstArg)
.to.include('foo bar baz')
.and.to.include('this')
.and.to.include('is an')
.and.to.include('action');
});
});

describe('warning messages', () => {
test
.stderr()
.do(() => {
const testCommand = new TestCommand([], {} as Config);
testCommand.warn('foo bar baz');
})
.it('should show a info message from a string', (ctx) => {
expect(ctx.stderr).to.include('Warning: foo bar baz');
});
test
.stderr()
.do(() => {
const testCommand = new TestCommand([], {} as Config);
testCommand.warn(new Error('foo bar baz') as SfCommand.Warning);
})
.it('should show a warning message from Error, no actions', (ctx) => {
expect(ctx.stderr).to.include('Warning: foo bar baz');
});
test
.stderr()
.do(() => {
const testCommand = new TestCommand([], {} as Config);
const infoError = new SfError('foo bar baz', 'foo', ['this', 'is an', 'action']) as Error;
testCommand.warn(infoError as SfCommand.Info);
})
.it('should show a info message from Error, with actions', (ctx) => {
expect(ctx.stderr).to.include('Warning: foo bar baz');
expect(ctx.stderr).to.include(['this', 'is an', 'action'].join(os.EOL));
const $$ = new TestContext();
beforeEach(() => {
stubMethod($$.SANDBOX, Lifecycle, 'getInstance').returns({
on: $$.SANDBOX.stub(),
onWarning: $$.SANDBOX.stub(),
});
});

it('should show a info message from a string', async () => {
const logToStderrStub = stubMethod($$.SANDBOX, SfCommand.prototype, 'logToStderr');
await TestCommand.run(['--warn']);
expect(logToStderrStub.firstCall.firstArg).to.include('Warning').and.to.include('foo bar baz');
});

it('should show a warning message from Error, no actions', async () => {
const logToStderrStub = stubMethod($$.SANDBOX, SfCommand.prototype, 'logToStderr');
await TestCommand.run(['--warn', '--error']);
expect(logToStderrStub.firstCall.firstArg).to.include('Warning').and.to.include('foo bar baz');
});

it('should show a info message from Error, with actions', async () => {
const logToStderrStub = stubMethod($$.SANDBOX, SfCommand.prototype, 'logToStderr');
await TestCommand.run(['--warn', '--error', '--actions']);
expect(logToStderrStub.firstCall.firstArg)
.to.include('Warning')
.and.to.include('foo bar baz')
.and.to.include('this')
.and.to.include('is an')
.and.to.include('action');
});
});
Loading

0 comments on commit 04475a0

Please sign in to comment.