Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: improve CLI messages and move them into separate file #40

Merged
merged 18 commits into from
Aug 11, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions src/components/Context/context.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ import React from 'react';
import { render } from 'ink-testing-library';
import { ListContexts, ShowCurrentContext, AddContext, SetCurrent } from './Context';
import { ContextTestingHelper } from '../../constants';
import * as messages from '../../messages';

const testing = new ContextTestingHelper();

describe('listing contexts', () => {
test('should render error when no context file found', () => {
testing.deleteDummyContextFile();
const { lastFrame } = render(<ListContexts />);
expect(lastFrame()).toMatch('No contexts saved yet.');
expect(lastFrame()).toMatch(messages.NO_CONTEXTS_SAVED);
});

test('Should render the context list', () => {
Expand All @@ -27,7 +28,7 @@ describe('rendering current context', () => {
testing.deleteDummyContextFile();
const { lastFrame } = render(<ShowCurrentContext />);
const message = lastFrame();
expect(message).toMatch('No contexts saved yet.');
expect(message).toMatch(messages.NO_CONTEXTS_SAVED);
});

test('showing current context ', () => {
Expand All @@ -41,15 +42,15 @@ describe('AddContext ', () => {
test('should return message', () => {
testing.createDummyContextFile();
const { lastFrame } = render(<AddContext options={{}} args={['home', './test/specification.yml']} />);
expect(lastFrame()).toMatch('New context added');
expect(lastFrame()).toMatch(messages.NEW_CONTEXT_ADDED('home'));
});
});

describe('SetContext ', () => {
test('Should render error message is key is not in store', () => {
testing.createDummyContextFile();
const { lastFrame } = render(<SetCurrent args={['name']} options={{}} />);
expect(lastFrame()).toMatch('The context you are trying to use is not present');
expect(lastFrame()).toMatch(messages.CONTEXT_NOT_FOUND('name'));
});

test('Should render the update context', () => {
Expand Down
14 changes: 0 additions & 14 deletions src/components/Context/contexterror.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,7 @@
import React from 'react';
import { Text } from 'ink';

import { ContextFileNotFoundError, DeletingCurrentContextError, KeyNotFoundError } from '../../hooks/context';

const ContextError: React.FunctionComponent<{ error: Error }> = ({ error }) => {
if (error instanceof ContextFileNotFoundError) {
return <Text>No contexts saved yet.</Text>;
}

if (error instanceof KeyNotFoundError) {
return <Text>The context you are trying to use is not present</Text>;
}

if (error instanceof DeletingCurrentContextError) {
return <Text>You are trying to delete a context that is set as current.</Text>;
}

return <Text>{error.message}</Text>;
};

Expand Down
30 changes: 30 additions & 0 deletions src/help-message.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { HelpMessageBuilder } from './help-message';

let helpBuilder: HelpMessageBuilder;

describe('HelpMessageBuilder should', () => {
beforeAll(() => {
helpBuilder = new HelpMessageBuilder();
});
it('return root Help message', () => {
expect(typeof helpBuilder.showHelp()).toMatch('string');
expect(helpBuilder.showHelp()).toMatch(
'usage: asyncapi [options] [command]\n\n'+
'flags:\n'+
' -h, --help display help for command\n'+
' -v, --version output the version number\n'+
'\n'+
'commands:\n'+
' validate [options] [command] Validate asyncapi file\n'+
' context [options] [command] Manage context\n'
);
});

it('return validate help message', () => {
expect(typeof helpBuilder.showCommandHelp('validate')).toMatch('string');
});

it('return context help message', () => {
expect(typeof helpBuilder.showCommandHelp('context')).toMatch('string');
});
});
99 changes: 99 additions & 0 deletions src/help-message.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { injectable, container } from 'tsyringe';

export type CommandName = 'validate' | 'context';

export type Command = {
[name in CommandName]: {
usage: string;
shortDescription: string;
longDescription?: string;
flags: string[];
subCommands?: string[];
};
};

@injectable()
export class HelpMessage {
private helpFlag = '-h, --help display help for command';

readonly usage: string = 'asyncapi [options] [command]';

readonly flags = [
this.helpFlag,
'-v, --version output the version number',
];

readonly commands: Command = {
validate: {
usage: 'asyncapi validate [options]',
shortDescription: 'Validate asyncapi file',
flags: [
this.helpFlag,
'-f, --file <spec-file-path> Path of the AsyncAPI file',
'-c, --context <saved-context-name> Context to use',
'-w, --watch Watch mode'
]
},
context: {
usage: 'asyncapi context [command] [options]',
shortDescription: 'Manage context',
longDescription: 'Context is what makes it easier for you to work with multiple AsyncAPI files.\nYou can add multiple different files to a context.\nThis way you do not have to pass --file flag with path to the file every time but just --context flag with reference name.\nYou can also set a default context, so neither --file nor --context flags are needed',
flags: [this.helpFlag],
subCommands: [
'list list all saved contexts',
'current see current context',
'use <context-name> set given context as default/current',
'add <context-name> <spec-file-path> add/update context',
'remove <context-name> remove a context'
]
}
}
}

export class HelpMessageBuilder {
private helpMessage: HelpMessage = container.resolve(HelpMessage);

showHelp() {
let helpText = '';
helpText += `usage: ${this.helpMessage.usage}\n\n`;
helpText += 'flags:\n';
for (const flag of this.helpMessage.flags) {
helpText += ` ${flag}\n`;
}
helpText += '\n';

if (this.helpMessage.commands) {
helpText += 'commands:\n';
for (const [name, obj] of Object.entries(this.helpMessage.commands)) {
helpText += ` ${name} [options] [command] ${obj.shortDescription}\n`;
}
}

return helpText;
}

showCommandHelp(command: CommandName) {
let helpText = '';
const commandHelpObject = this.helpMessage.commands[command as CommandName];
helpText += `usage: ${commandHelpObject.usage}\n\n`;

if (commandHelpObject.longDescription) {
helpText += `${commandHelpObject.longDescription}\n\n`;
}

helpText += 'flags: \n';
for (const flag of commandHelpObject.flags) {
helpText += ` ${flag}\n`;
}

if (commandHelpObject.subCommands) {
helpText += '\n';
helpText += 'commands:\n';
for (const command of commandHelpObject.subCommands) {
helpText += ` ${command}\n`;
}
}

return helpText;
}
}
6 changes: 3 additions & 3 deletions src/hooks/context/contextService.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { injectable } from 'tsyringe';
import { Context, ContextFileNotFoundError,KeyNotFoundError, SpecFileNotFoundError } from './models';
import { Context, ContextFileNotFoundError, ContextNotFoundError, SpecFileNotFoundError } from './models';
import { CONTEXTFILE_PATH } from '../../constants';
import * as fs from 'fs';
import * as path from 'path';
Expand All @@ -26,7 +26,7 @@ export class ContextService {
}

addContext(context: Context, key: string, specFile: SpecificationFile): Context {
if (specFile.isNotValid()) {throw new SpecFileNotFoundError();}
if (specFile.isNotValid()) {throw new SpecFileNotFoundError(specFile.getSpecificationName());}
context.store[String(key)] = specFile.getSpecificationName();
return context;
}
Expand All @@ -38,7 +38,7 @@ export class ContextService {
}

updateCurrent(context: Context, key: string): Context {
if (!context.store[String(key)]) {throw new KeyNotFoundError();}
if (!context.store[String(key)]) {throw new ContextNotFoundError(key);}
context.current = key;
return context;
}
Expand Down
15 changes: 8 additions & 7 deletions src/hooks/context/hook.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { useContextFile, useSpecfile } from './hooks';
import { ContextFileNotFoundError, KeyNotFoundError, ContextNotFoundError } from './models';
import { ContextFileNotFoundError, ContextNotFoundError } from './models';
import { ContextTestingHelper } from '../../constants';
import { SpecificationFile } from '../validation';
import * as messages from '../../messages';

const testingVariables = new ContextTestingHelper();

Expand Down Expand Up @@ -42,22 +43,22 @@ describe('useContextFile().addContext ', () => {
testingVariables.deleteDummyContextFile();
const { response, error } = useContextFile().addContext('home', new SpecificationFile('./test/specification.yml'));
expect(error).toBeUndefined();
expect(response).toMatch('New context added');
expect(response).toMatch(messages.NEW_CONTEXT_ADDED('home'));
testingVariables.deleteDummyContextFile();
});

test('should save when context file is present', () => {
testingVariables.createDummyContextFile();
const { response, error } = useContextFile().addContext('home', new SpecificationFile('./test/specification.yml'));
expect(error).toBeUndefined();
expect(response).toMatch('New context added');
expect(response).toMatch(messages.NEW_CONTEXT_ADDED('home'));
});

test('Auto set current when when adding context for the fist time', () => {
testingVariables.deleteDummyContextFile();
const { response, error } = useContextFile().addContext('home', new SpecificationFile('./test/specification.yml'));
expect(error).toBeUndefined();
expect(response).toMatch('New context added');
expect(response).toMatch(messages.NEW_CONTEXT_ADDED('home'));
const { response: res, error: err } = useContextFile().current();
expect(err).toBeUndefined();
expect(res?.key).toMatch('home');
Expand All @@ -77,7 +78,7 @@ describe('useContextFile.updateCurrent ', () => {
testingVariables.createDummyContextFile();
const { response, error } = useContextFile().setCurrent('name');
expect(response).toBeUndefined();
expect(error instanceof KeyNotFoundError).toBeTruthy();
expect(error instanceof ContextNotFoundError).toBeTruthy();
});

test('Should update the current context', () => {
Expand All @@ -93,13 +94,13 @@ describe('useContextFile().deleteContext ', () => {
testingVariables.createDummyContextFile();
const { response, error } = useContextFile().deleteContext('code');
expect(error).toBeUndefined();
expect(response).toMatch('context deleted successfully');
expect(response).toMatch(messages.CONTEXT_DELETED);
});

test('return error if deleting current context', () => {
testingVariables.createDummyContextFile();
const { response, error } = useContextFile().deleteContext('home');
expect(response).toMatch('context deleted successfully');
expect(response).toMatch(messages.CONTEXT_DELETED);
expect(error).toBeUndefined();
});
});
Expand Down
13 changes: 7 additions & 6 deletions src/hooks/context/hooks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Context, ContextFileNotFoundError, ContextNotFoundError, MissingCurrent
import { ContextService } from './contextService';
import { container } from 'tsyringe';
import { SpecificationFile } from '../validation';
import * as messages from '../../messages';

export type Result = {
response?: any,
Expand Down Expand Up @@ -36,15 +37,15 @@ export const useContextFile = (): any => {
const ctx = contextService.loadContextFile();
const updatedContext = contextService.addContext(ctx, key, specFile);
contextService.save(updatedContext);
const response = 'New context added';
const response = messages.NEW_CONTEXT_ADDED(key);
return { response };
} catch (error) {
if (error instanceof ContextFileNotFoundError) {
const context: Context = { current: '', store: {} };
try {
const newContext = contextService.addContext(context, key, specFile);
contextService.save(contextService.updateCurrent(newContext, key));
const response = 'New context added';
const response = messages.NEW_CONTEXT_ADDED(key);
return { response };
} catch (error) {
return { error };
Expand All @@ -69,11 +70,11 @@ export const useContextFile = (): any => {
const ctx = contextService.loadContextFile();
if (Object.keys(ctx.store).length === 1) {
contextService.deleteContextFile();
return { response: 'context deleted successfully' };
return { response: messages.CONTEXT_DELETED };
}
const updatedContext = contextService.deleteContext(ctx, key);
contextService.save(updatedContext);
const response = 'context deleted successfully';
const response = messages.CONTEXT_DELETED;
return { response };
} catch (error) {
return { error };
Expand Down Expand Up @@ -102,7 +103,7 @@ export const useContextFile = (): any => {
try {
const ctx = contextService.loadContextFile();
const ctxValue = ctx.store[String(key)];
if (!ctxValue) { throw new ContextNotFoundError(); }
if (!ctxValue) { throw new ContextNotFoundError(key); }
const response = new SpecificationFile(ctxValue);
return { response };
} catch (error) {
Expand Down Expand Up @@ -136,7 +137,7 @@ export const useSpecfile = (flags: useSpecFileInput): useSpecFileOutput => {

if (flags.context) {
const ctxFile = ctx.store[flags.context];
if (!ctxFile) { throw new ContextNotFoundError(); }
if (!ctxFile) { throw new ContextNotFoundError(flags.context); }
const specFile = new SpecificationFile(ctxFile);
return { specFile };
}
Expand Down
Loading