-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
19 changed files
with
582 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
{ | ||
"extends": ["../../../.eslintrc.json"], | ||
"ignorePatterns": ["!**/*"], | ||
"overrides": [ | ||
{ | ||
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"], | ||
"rules": {} | ||
}, | ||
{ | ||
"files": ["*.ts", "*.tsx"], | ||
"rules": {} | ||
}, | ||
{ | ||
"files": ["*.js", "*.jsx"], | ||
"rules": {} | ||
} | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
# packages-cli-testing | ||
|
||
This library was generated with [Nx](https://nx.dev). | ||
|
||
## Building | ||
|
||
Run `nx build packages-cli-testing` to build the library. | ||
|
||
## Running unit tests | ||
|
||
Run `nx test packages-cli-testing` to execute the unit tests via [Jest](https://jestjs.io). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
{ | ||
"$schema": "../../../../node_modules/ng-packagr/package.schema.json", | ||
"lib": { | ||
"entryFile": "src/index.ts", | ||
"flatModuleFile": "cli-testing-cli-project" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export * from './lib/types'; | ||
export * from './lib/utils'; | ||
export * from './lib/cli'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,194 @@ | ||
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'fs'; | ||
import { dirname, join } from 'path'; | ||
import { CliProcess, FileOrFolderMap, ProcessParams, ProcessTestOptions, ProjectConfig } from './types'; | ||
import { ProcessOptions, PromptTestOptions, testProcessE2e, TestResult } from '../../../process/src/index'; | ||
import { deleteFileOrFolder, processParamsToParamsArray } from './utils'; | ||
|
||
/** | ||
* A closure for the testProcessE2e function to seperate process configuration and testing config from test data. | ||
* | ||
* @param processOptions: passed directly to execa as options | ||
*/ | ||
export function getCliProcess(processOptions: ProcessOptions, promptTestOptions: PromptTestOptions & ProcessTestOptions): CliProcess { | ||
return { | ||
exec: (processParams: ProcessParams = {}, userInput: string[] = []): Promise<TestResult> => { | ||
const processOpts = [promptTestOptions.bin, ...processParamsToParamsArray(processParams)]; | ||
return testProcessE2e(processOpts, userInput, processOptions, promptTestOptions); | ||
} | ||
}; | ||
} | ||
|
||
/** | ||
* A helper class to manage an project structure for a yargs based CLI | ||
*/ | ||
export class CliProject<RcConfig extends {}> { | ||
|
||
/** | ||
* A flag to add more detailed information as logs | ||
*/ | ||
protected verbose: boolean = false; | ||
|
||
/** | ||
* The folder in which to execute the process | ||
*/ | ||
public root: string = ''; | ||
|
||
/** | ||
* The the binary to execute | ||
*/ | ||
protected bin: string = ''; | ||
|
||
/** | ||
* The process executing the CLI bin | ||
*/ | ||
protected process: CliProcess = undefined as unknown as CliProcess; | ||
|
||
/** | ||
* Filenames to delete e.g. in project teardown | ||
* All files are located from root | ||
*/ | ||
protected deleteFiles: string[] = []; | ||
/** | ||
* Filenames to create e.g. in project setup | ||
* All files are located from root | ||
*/ | ||
protected createFiles: FileOrFolderMap = {}; | ||
/** | ||
* Filenames to create e.g. in project setup | ||
*/ | ||
protected rcFile: Record<string, RcConfig> = {}; | ||
|
||
constructor() { | ||
} | ||
|
||
/** | ||
* freezes jest so we can read logs :) | ||
* @param ms | ||
*/ | ||
async wait(ms: number = 30000) { | ||
await new Promise(r => setTimeout(r, ms)); | ||
} | ||
|
||
logVerbose(...args: any): void { | ||
this.verbose && console.log(...args); | ||
} | ||
|
||
tableVerbose(...args: any): void { | ||
this.verbose && console.table(...args); | ||
} | ||
|
||
async _setup(cfg: ProjectConfig<RcConfig>): Promise<void> { | ||
// global settings | ||
this.verbose = Boolean(cfg.verbose); | ||
|
||
// use configurations | ||
this.root = cfg.root; | ||
this.bin = cfg.bin; | ||
cfg.delete && (this.deleteFiles = cfg.delete); | ||
cfg.create && (this.createFiles = cfg.create); | ||
cfg.rcFile && (this.rcFile = cfg.rcFile); | ||
|
||
// handle default rcPath | ||
if (Object.keys(this.rcFile).length > 0) { | ||
Object.entries(this.rcFile).forEach(([rcName, rcContent]) => { | ||
this.deleteFiles.push(rcName); | ||
this.createFiles[rcName] = rcContent; | ||
}); | ||
} | ||
|
||
// remove duplicated paths | ||
this.deleteFiles = Array.from(new Set(this.deleteFiles)); | ||
|
||
this.process = getCliProcess({ | ||
cwd: this.root, | ||
env: cfg.env | ||
}, { bin: this.bin }); | ||
} | ||
|
||
/** | ||
* @internal | ||
* @protected | ||
* | ||
* Method to delete files generated during the CLI run | ||
* Notice all files will get located from the project root | ||
*/ | ||
deleteGeneratedFiles(): void { | ||
deleteFileOrFolder((this.deleteFiles || []) | ||
.map(file => join(this.root, file)) | ||
); | ||
} | ||
|
||
/** | ||
* @internal | ||
* @protected | ||
* | ||
* Method to create files needed during the CLI run | ||
* Notice all files will get located from the project root | ||
*/ | ||
createInitialFiles(): void { | ||
const preparedPaths = Object.entries(this?.createFiles || {}) | ||
.map(entry => { | ||
entry[0] = join(this.root, entry[0]); | ||
return entry; | ||
}); | ||
preparedPaths | ||
.forEach(([file, content]) => { | ||
const exists = existsSync(file); | ||
if (exists) { | ||
if (content !== undefined) { | ||
rmSync(file); | ||
this.logVerbose(`File ${file} got deleted as it already exists`); | ||
} | ||
} | ||
if (content === undefined) { | ||
!exists && mkdirSync(file, { recursive: true }); | ||
} else { | ||
const dir = dirname(file); | ||
if (!existsSync(dir)) { | ||
this.logVerbose(`Created dir ${dir} to save ${file}`); | ||
mkdirSync(dir); | ||
} | ||
|
||
switch (true) { | ||
case file.endsWith('.png'): | ||
case file.endsWith('.ico'): | ||
content = Buffer.from(content + '', 'base64') as any; | ||
break; | ||
case file.endsWith('.json'): | ||
content = JSON.stringify(content) as any; | ||
break; | ||
} | ||
|
||
writeFileSync(file, content as any, 'utf8'); | ||
} | ||
this.logVerbose(`File ${file} created`); | ||
}); | ||
} | ||
|
||
/** | ||
* Set up the project. e.g. create files, start processes | ||
*/ | ||
async setup(): Promise<void> { | ||
this.createInitialFiles(); | ||
} | ||
|
||
|
||
/** | ||
* Teardown the project. e.g delete files, stop processes | ||
*/ | ||
async teardown(): Promise<void> { | ||
this.deleteGeneratedFiles(); | ||
} | ||
|
||
/** | ||
* Executes the CLI with given parameters and user input. | ||
* See getCliProcess for details | ||
* | ||
* @param processParams | ||
* @param userInput | ||
*/ | ||
exec(processParams?: ProcessParams, userInput?: string[]): Promise<TestResult> { | ||
return this.process.exec(processParams, userInput); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import { PromptTestOptions, TestResult } from '../../../process/src/index'; | ||
|
||
export type ProcessTestOptions = { | ||
bin: string | ||
} | ||
|
||
export type ProcessParams = { | ||
// command placeholder | ||
_?: string | ||
} & Record<string, any> | ||
|
||
export type ExecFn<T extends ProcessParams = ProcessParams> = (processParams?: T, userInput?: string[], promptOptions?: PromptTestOptions) => Promise<TestResult>; | ||
|
||
export type CliProcess = { | ||
exec: ExecFn | ||
} | ||
|
||
export type CliCommand = Record<`\$${string}`, ExecFn>; | ||
|
||
export type Project = { | ||
deleteGeneratedFiles: () => void, | ||
createInitialFiles: () => void, | ||
setup: () => void, | ||
teardown: () => void, | ||
exec: ExecFn, | ||
} & | ||
{ | ||
[value in keyof CliCommand]: ExecFn<any> | ||
} | ||
|
||
export type FileOrFolderMap = Record<string, string | {} | undefined>; | ||
export type ProjectConfig<RcConfig extends {}> = { | ||
verbose?: boolean, | ||
root: string, | ||
bin: string, | ||
// the process env of the created process | ||
env?: Record<string, string>, | ||
// files | ||
rcFile?: Record<string, RcConfig>, | ||
delete?: string[], | ||
create?: FileOrFolderMap; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import * as fs from 'fs'; | ||
import { ProcessParams } from './types'; | ||
import * as path from 'path'; | ||
|
||
export function getFolderContent(folders: string[]): string[] { | ||
return folders.flatMap((d) => { | ||
if (fs.existsSync(d)) { | ||
const files = fs.readdirSync(d); | ||
return files.map((f) => path.join(d, f)); | ||
} | ||
return [d]; | ||
}); | ||
} | ||
|
||
export function deleteFileOrFolder(files: string | string[]): void { | ||
(typeof files === 'string' ? [files] : files).forEach((p) => { | ||
if (fs.existsSync(p)) { | ||
const stats = fs.lstatSync(p); | ||
if(stats.isDirectory()) { | ||
fs.rmdirSync(p, { recursive: true }); | ||
} else { | ||
fs.rmSync(p); | ||
} | ||
// console.info(`Deleted file ${file}`); | ||
} else { | ||
// console.error(`File ${file} does not exist`); | ||
} | ||
}); | ||
} | ||
|
||
export function processParamsToParamsArray(params: ProcessParams): string[] { | ||
return Object.entries(params).flatMap(([key, value]) => { | ||
if (key === '_') { | ||
return value.toString(); | ||
} else if (Array.isArray(value)) { | ||
return value.map(v => `--${key}=${v.toString()}`); | ||
} else { | ||
if (typeof value === 'string') { | ||
return [`--${key}=${value + ''}`]; | ||
} else if (typeof value === 'boolean') { | ||
return [`--${value ? '' : 'no-'}${key}`]; | ||
} | ||
return [`--${key}=${value + ''}`]; | ||
} | ||
}) as string[]; | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
/* eslint-disable */ | ||
export default { | ||
displayName: 'packages-cli-testing', | ||
preset: '../../../jest.preset.js', | ||
globals: { | ||
'ts-jest': { | ||
tsconfig: '<rootDir>/tsconfig.spec.json', | ||
}, | ||
}, | ||
transform: { | ||
'^.+\\.[tj]s$': 'ts-jest', | ||
}, | ||
moduleFileExtensions: ['ts', 'js', 'html'], | ||
coverageDirectory: '../../../coverage/packages/cli-testing', | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
{ | ||
"name": "@push-based/node-cli-testing", | ||
"version": "0.0.0", | ||
"type": "commonjs", | ||
"main": "src/index.js", | ||
"license": "MIT", | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/push-based/node-cli-testing.git" | ||
}, | ||
"keywords": [ | ||
"testing", | ||
"cli", | ||
"node" | ||
], | ||
"peerDdependencies": {}, | ||
"dependencies": {} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
{ | ||
"$schema": "../../../../node_modules/ng-packagr/package.schema.json", | ||
"lib": { | ||
"entryFile": "src/index.ts", | ||
"flatModuleFile": "cli-testing-process" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export * from './lib/types'; | ||
export * from './lib/test-process-e2e'; | ||
export * from './lib/keyboard'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
export const DOWN = '\x1B\x5B\x42'; | ||
export const UP = '\x1B\x5B\x41'; | ||
export const ENTER = '\x0D'; | ||
export const SPACE = '\x20'; | ||
export const DECLINE_BOOLEAN = 'n'; | ||
export const ACCEPT_BOOLEAN = 'Y'; |
Oops, something went wrong.