mAAdhaTTah/brookjs

View on GitHub
packages/brookjs-cli/features/support/world.ts

Summary

Maintainability
A
2 hrs
Test Coverage
import * as path from 'path';
import * as os from 'os';
import { spawn, IPty } from 'node-pty';
import * as fs from 'fs-extra';
import { setWorldConstructor, World } from 'cucumber';
 
const keypresses = {
ENTER: '\r',
DOWN: '\x1B\x5B\x42',
UP: '\x1B\x5B\x41'
};
 
declare module 'cucumber' {
interface World {
cwd: string;
 
snapshot: {
testname: string;
filename: string;
};
 
process: {
stdout: string;
closed: boolean;
code: number | null;
};
 
defaultRc: File;
 
spawned?: IPty;
 
createProject(type: 'js' | 'ts'): Promise<void>;
 
outputFile(file: File): Promise<void>;
 
appendFile(file: File): Promise<void>;
 
getFile(file: string, barrel: string): Promise<string>;
 
run(command: string): void;
 
spawn(bin: string, command: string): void;
 
respondTo(questions: Question[]): Promise<void>;
 
untilOutputContains(matches: string): Promise<void>;
 
outputContains(contains: string): boolean;
 
untilEnded(): Promise<void>;
 
wait(time: number): Promise<void>;
}
}
 
type File = {
path: string;
contents: string;
};
 
export type Question = {
text: string;
response: string;
};
 
class CliWorld implements World {
cwd: string = fs.mkdtempSync(path.join(os.tmpdir(), 'cli-'));
 
snapshot: {
testname: string;
filename: string;
} = {
testname: '',
filename: ''
};
 
process: {
stdout: string;
closed: boolean;
code: number | null;
} = {
stdout: '',
closed: false,
code: null
};
 
defaultRc: File = {
path: '.beaverrc.js',
contents: `export const dir = 'src';`
};
 
spawned?: IPty;
 
async createProject(type: 'js' | 'ts') {
this.spawn(
'tar',
`-C ${this.cwd} -zxvf ${path.join(__dirname, `test-app-${type}.tar.gz`)}`
);
this.cwd = path.join(this.cwd, `test-app-${type}`);
 
await this.untilEnded();
}
 
Similar blocks of code found in 2 locations. Consider refactoring.
outputFile(file: File) {
return fs.outputFile(path.join(this.cwd, file.path), file.contents);
}
 
Similar blocks of code found in 2 locations. Consider refactoring.
appendFile(file: File) {
return fs.appendFile(path.join(this.cwd, file.path), file.contents);
}
 
async prependFile(file: File) {
const targetPath = path.join(this.cwd, file.path);
const contents = await fs.readFile(targetPath, 'utf-8');
return fs.outputFile(targetPath, file.contents + contents);
}
 
getFile(file: string, barrel: string) {
return fs.readFile(path.join(this.cwd, 'src', barrel, file), 'utf-8');
}
 
run(command: string) {
this.spawn('npx', `beaver ${command}`);
}
 
Function `spawn` has 29 lines of code (exceeds 25 allowed). Consider refactoring.
spawn(bin: string, command: string) {
this.process = {
stdout: '',
closed: false,
code: null
};
 
// Remove constants that indicate it's in CI.
// This is because Ink will only emit the last frame in CI.
const {
CI,
CONTINUOUS_INTEGRATION,
TRAVIS,
BUILD_NUMBER,
RUN_ID,
TRAVIS_PULL_REQUEST,
...env
} = process.env as any;
 
const spawned = (this.spawned = spawn(bin, command.split(' '), {
name: 'xterm-color',
cols: 80,
rows: 30,
encoding: 'utf-8',
cwd: this.cwd,
env
}));
 
spawned.on('data', data => {
this.process.stdout += data;
});
 
spawned.on('exit', code => {
this.process.closed = true;
this.process.code = code;
});
}
 
async respondTo(questions: Question[]) {
if (!this.spawned) {
throw new Error('Attempted to respond to questions before spawning cli.');
}
 
for (const { text, response } of questions) {
await this.untilOutputContains(text);
const keys = response.replace(
/KEY:(.*)/,
val => keypresses[val.split(':')[1] as keyof typeof keypresses]
);
 
for (const key of keys) {
this.spawned.write(key);
await this.wait(10);
}
 
this.spawned.write('\r');
}
}
 
async untilOutputContains(matches: string) {
while (!this.outputContains(matches) && !this.process.closed) {
await this.wait(200);
}
 
if (!this.outputContains(matches)) {
throw new Error(`Output closed before match found.`);
}
}
 
outputContains(contains: string) {
return this.process.stdout.trim().includes(contains);
}
 
async untilEnded() {
while (this.process.closed !== true) {
await this.wait(200);
}
}
 
wait(time: number) {
return new Promise<void>(resolve => {
setTimeout(resolve, time);
});
}
}
 
setWorldConstructor(CliWorld);