Skip to content

Commit

Permalink
Breaking out commands to make testing easier
Browse files Browse the repository at this point in the history
  • Loading branch information
shaunburdick committed Aug 23, 2024
1 parent ee52d69 commit a26c6f5
Show file tree
Hide file tree
Showing 4 changed files with 449 additions and 143 deletions.
214 changes: 214 additions & 0 deletions src/Command.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
import { CommandContext, commandsWithContext } from './Command';
import { displayUser, User } from './Users';

describe('Command', () => {
function buildContext(): CommandContext {
return {
commandHistory: [],
environment: new Map(),
setConsoleLines: jest.fn(),
setEnvironment: jest.fn(),
setLastCommand: jest.fn(),
users: new Map([['test', { name: 'test' }]]),
workingDir: '',
};
}

test('Get a command map', () => {
expect(commandsWithContext(buildContext())).toBeDefined();
});

test('clear', (done) => {
const ctx = buildContext();
const commands = commandsWithContext(ctx);

const response = commands.get('clear')?.run();

expect(response).toEqual([]);

setTimeout(() => {
expect(ctx.setConsoleLines).toHaveBeenCalledTimes(1);
expect(ctx.setLastCommand).toHaveBeenCalledTimes(1);
expect(ctx.setLastCommand).toHaveBeenCalledWith(undefined);

done();
});
});

test('env', () => {
const ctx = buildContext();
ctx.environment.set('foo', 'bar');
ctx.environment.set('fizz', 'buzz');
const commands = commandsWithContext(ctx);

const response = commands.get('env')?.run();

expect(response).toEqual([
['foo=bar'],
['fizz=buzz']
]);
});

test('export', () => {
const ctx = buildContext();
ctx.environment.set('foo', 'bar');
ctx.environment.set('fizz', 'buzz');
const commands = commandsWithContext(ctx);

const response = commands.get('export')?.run('key', 'value');

expect(response).toEqual([
['key=value']
]);
});

test('help (no args)', () => {
const ctx = buildContext();
const commands = commandsWithContext(ctx);

const response = commands.get('help')?.run();

expect(Array.isArray(response)).toBe(true);

// make sure no secret commands are listed
expect(response?.filter(line => (line[0] as string).startsWith('secret'))).toEqual([]);
});

test('help (with args)', () => {
const ctx = buildContext();
const commands = commandsWithContext(ctx);

const help = commands.get('help');

expect(help?.run('clear')).toEqual([[commands.get('clear')?.description]]);
expect(help?.run('secret')).toEqual([['I\'m not helping you. It\'s a secret!']]);
expect(help?.run('doesnotexist')).toEqual([['Unknown command: doesnotexist']]);
});

test('history', () => {
const ctx = buildContext();
ctx.commandHistory.push('foo', 'bar');
const commands = commandsWithContext(ctx);

const response = commands.get('history')?.run();

expect(response).toEqual([
['1: foo'],
['2: bar']
]);
});

test('open', () => {
const ctx = buildContext();
const commands = commandsWithContext(ctx);

const open = commands.get('open');

expect(open?.run('http://foo.com')).toEqual([
['Opening http://foo.com...']
]);

expect(open?.run('ftp://foo.com')).toEqual([
['Unknown protocol: ftp:']
]);

expect(open?.run('floopity/ss/s/s/s')).toEqual([
['Cannot open: floopity/ss/s/s/s']
]);
});

test('pwd', () => {
const ctx = buildContext();
ctx.workingDir = 'test';
const commands = commandsWithContext(ctx);

const response = commands.get('pwd')?.run();

expect(response).toEqual([
['test']
]);
});

test('rm', () => {
const ctx = buildContext();
const commands = commandsWithContext(ctx);

const response = commands.get('rm')?.run();

expect(response).toEqual([
['rm never gonna give you up!']
]);
});

test('secret', () => {
const ctx = buildContext();
const commands = commandsWithContext(ctx);

const response = commands.get('secret')?.run();

expect(response).toEqual([
['You found it!']
]);
});

test('users', () => {
const ctx = buildContext();
ctx.users.set('test', { name: 'test' });
const commands = commandsWithContext(ctx);

const response = commands.get('users')?.run();

expect(response).toEqual([
['test']
]);
});

test('view-source', () => {
const ctx = buildContext();
const commands = commandsWithContext(ctx);

const response = commands.get('view-source')?.run();

expect(response).toEqual([
['Opening GH Page...']
]);
});

test('whoami', () => {
const ctx = buildContext();
const commands = commandsWithContext(ctx);

const response = commands.get('whoami')?.run();

expect(response).toEqual([
['You\'re you, silly']
]);
});

test('whois', () => {
const ctx = buildContext();
ctx.users.set('test', { name: 'test' });
const commands = commandsWithContext(ctx);

const whois = commands.get('whois');

expect(whois?.run('test')).toEqual(
displayUser(ctx.users.get('test') as User)
);

expect(whois?.run('miki')).toEqual([
['Hello, miki']
]);

const gfResponse = whois?.run('gamefront');
expect(Array.isArray(gfResponse)).toBe(true);

expect(whois?.run('unknown_user')).toEqual([
['Unknown user: unknown_user']
]);

expect(whois?.run()).toEqual([
['Unknown user: ']
]);
});
});
167 changes: 167 additions & 0 deletions src/Command.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
import { CommandResult, ConsoleLine } from './ConsoleOutput';
import { displayUser, User } from './Users';

export interface Command {
description: string;
secret?: boolean;
run: (...args: string[]) => ConsoleLine[];
}

export interface CommandContext {
commandHistory: string[];
environment: Map<string, string>;
setConsoleLines: React.Dispatch<React.SetStateAction<CommandResult[]>>;
setEnvironment: React.Dispatch<React.SetStateAction<Map<string, string>>>;
setLastCommand: React.Dispatch<React.SetStateAction<CommandResult | undefined>>;
users: Map<string, User>;
workingDir: string;
}

export const commandsWithContext = ({
commandHistory,
environment,
setConsoleLines,
setEnvironment,
setLastCommand,
users,
workingDir,
}: CommandContext): Map<string, Command> => {
/**
* A map of commands available to run
*/
const COMMANDS = new Map<string, Command>();

COMMANDS.set('clear', {
description: 'Clears the screen',
run: () => {
// use setTimeout to clear screen after this loop is finished
setTimeout(() => {
setConsoleLines([]);
setLastCommand(undefined);
});

return [];
}
});

COMMANDS.set('env', {
description: 'Print Environment',
run: () => {
const response: ConsoleLine[] = [];

environment.forEach((v, k) => {
response.push([`${k}=${v}`]);
});

return response;
}
});

COMMANDS.set('export', {
description: 'Set an environment variable',
run: (key, value) => {
setEnvironment({ ...environment, [key]: value });
return [[`${key}=${value}`]];
}
});

COMMANDS.set('help', {
description: 'Provides a list of commands. Usage: `help [command]`',
run: (command?: string) => {
if (command) {
if (COMMANDS.get(command)?.secret) {
return [['I\'m not helping you. It\'s a secret!']];
} else {
return [[COMMANDS.get(command)?.description || `Unknown command: ${command}`]];
}
} else {
return [
['List of Commands:'],
...[...COMMANDS]
.filter(cmd => !cmd[1].secret)
.map(([commandName, commandInfo]) => [`${commandName}:`, commandInfo.description])
];
}
}
});

COMMANDS.set('history', {
description: 'Show previous commands',
run: () => commandHistory.map((command, index) => [`${index + 1}: ${command}`])
});

COMMANDS.set('open', {
description: 'Open a file or URL',
run: (target) => {
try {
const url = new URL(target);
if (['http:', 'https:'].includes(url.protocol)) {
window.open(target);
return [[`Opening ${target}...`]];
} else {
return [[`Unknown protocol: ${url.protocol}`]];
}
} catch {
return [[`Cannot open: ${target}`]];
}
}
});

COMMANDS.set('pwd', {
description: 'Return the working directory',
run: () => [[workingDir]]
});

COMMANDS.set('rm', {
description: 'Remove directory entries',
run: () => {
window.open('https://www.youtube.com/watch?v=dQw4w9WgXcQ');
return [['rm never gonna give you up!']];
}
});

COMMANDS.set('secret', {
description: 'A secret command',
secret: true,
run: () => [['You found it!']]
});

COMMANDS.set('users', {
description: 'List users',
run: () => [...users.keys()].map(userName => [userName])
});

COMMANDS.set('view-source', {
description: 'View the source of this app',
run: () => {
window.open('https://github.com/shaunburdick/shaunburdick.com');
return [['Opening GH Page...']];
}
});

COMMANDS.set('whoami', {
description: 'Tell you a little about yourself',
run: () => [
['You\'re you, silly']
]
});

COMMANDS.set('whois', {
description: 'Tell you a little about a user. Usage: `whois <username>`',
run: (username: string) => {
const user = users.get(username);
if (user) {
return displayUser(user);
} else if(/miki|mikey|faktrl/.test(username)) {
window.open('https://www.youtube.com/watch?v=YjyUIwKPAxA');
return [[`Hello, ${username}`]];
} else if (username === 'gamefront') {
return [[<a href='https://gamefront.com'>Gamefront</a>, 'is just FilesNetwork with a better skin']];
} else {
return [[`Unknown user: ${username || ''}`]];
}
}
});

return COMMANDS;
};
Loading

0 comments on commit a26c6f5

Please sign in to comment.