mirror of
https://github.com/chenasraf/massarg.git
synced 2026-05-18 01:39:05 +00:00
test: tests & bug fixes
This commit is contained in:
@@ -23,7 +23,7 @@ export default {
|
||||
// collectCoverageFrom: undefined,
|
||||
|
||||
// The directory where Jest should output its coverage files
|
||||
coverageDirectory: "coverage",
|
||||
coverageDirectory: 'coverage',
|
||||
|
||||
// An array of regexp pattern strings used to skip coverage collection
|
||||
// coveragePathIgnorePatterns: [
|
||||
@@ -31,10 +31,10 @@ export default {
|
||||
// ],
|
||||
|
||||
// Indicates which provider should be used to instrument code for coverage
|
||||
coverageProvider: "v8",
|
||||
coverageProvider: 'v8',
|
||||
|
||||
// A list of reporter names that Jest uses when writing coverage reports
|
||||
coverageReporters: ["json-summary", "json", "text", "lcov", "clover"],
|
||||
coverageReporters: ['json-summary', 'json', 'text', 'lcov', 'clover'],
|
||||
|
||||
// An object that configures minimum threshold enforcement for coverage results
|
||||
// coverageThreshold: undefined,
|
||||
@@ -88,7 +88,7 @@ export default {
|
||||
// notifyMode: "failure-change",
|
||||
|
||||
// A preset that is used as a base for Jest's configuration
|
||||
preset: "ts-jest",
|
||||
preset: 'ts-jest',
|
||||
|
||||
// Run tests from one or more projects
|
||||
// projects: undefined,
|
||||
@@ -147,9 +147,7 @@ export default {
|
||||
// ],
|
||||
|
||||
// An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
|
||||
// testPathIgnorePatterns: [
|
||||
// "/node_modules/"
|
||||
// ],
|
||||
testPathIgnorePatterns: ['/node_modules/', '/_old/'],
|
||||
|
||||
// The regexp pattern or array of patterns that Jest uses to detect test files
|
||||
// testRegex: [],
|
||||
|
||||
17
src/color.ts
17
src/color.ts
@@ -1,3 +1,6 @@
|
||||
import z from 'zod'
|
||||
import { zodEnumFromObjKeys } from './utils'
|
||||
|
||||
export const ansiStyles = {
|
||||
reset: '\x1b[0m',
|
||||
bold: '\x1b[1m',
|
||||
@@ -24,12 +27,14 @@ export const ansiColors = {
|
||||
brightWhite: '\x1b[97m',
|
||||
}
|
||||
|
||||
export type StringStyle = {
|
||||
color?: keyof typeof ansiColors
|
||||
bold?: boolean
|
||||
underline?: boolean
|
||||
reset?: boolean
|
||||
}
|
||||
export const StringStyle = z.object({
|
||||
bold: z.boolean().optional(),
|
||||
underline: z.boolean().optional(),
|
||||
color: zodEnumFromObjKeys(ansiColors).optional(),
|
||||
reset: z.boolean().optional(),
|
||||
})
|
||||
|
||||
export type StringStyle = z.infer<typeof StringStyle>
|
||||
|
||||
export function format(string: string, style: StringStyle = {}): string {
|
||||
const { color, bold, underline, reset } = style
|
||||
|
||||
101
src/command.ts
101
src/command.ts
@@ -1,13 +1,13 @@
|
||||
import { z } from 'zod'
|
||||
import { isZodError, ParseError, ValidationError } from './error'
|
||||
import { HelpGenerator } from './help'
|
||||
import { defaultHelpConfig, HelpConfig, HelpGenerator } from './help'
|
||||
import MassargOption, {
|
||||
MassargFlag,
|
||||
OptionConfig,
|
||||
TypedOptionConfig,
|
||||
MassargHelpFlag,
|
||||
} from './option'
|
||||
import { setOrPush } from './utils'
|
||||
import { setOrPush, deepMerge } from './utils'
|
||||
import MassargExample, { ExampleConfig } from './example'
|
||||
|
||||
export const CommandConfig = <RunArgs extends z.ZodType>(args: RunArgs) =>
|
||||
@@ -26,18 +26,7 @@ export const CommandConfig = <RunArgs extends z.ZodType>(args: RunArgs) =>
|
||||
.function()
|
||||
.args(args, z.any())
|
||||
.returns(z.union([z.promise(z.void()), z.void()])) as z.ZodType<Runner<z.infer<RunArgs>>>,
|
||||
/**
|
||||
* Whether to bind the help command to this command
|
||||
*
|
||||
* Set this to `true` to automatically add a `help` command to this command's subcommands.
|
||||
*/
|
||||
bindHelpCommand: z.boolean().optional(),
|
||||
/**
|
||||
* Whether to bind the help option to this command
|
||||
*
|
||||
* Set this to `true` to automatically add a `--help` option to this command's options.
|
||||
*/
|
||||
bindHelpOption: z.boolean().optional(),
|
||||
helpConfig: HelpConfig.optional(),
|
||||
// argsHint: z.string().optional(),
|
||||
})
|
||||
|
||||
@@ -59,6 +48,7 @@ export default class MassargCommand<Args extends ArgsObject = ArgsObject> {
|
||||
options: MassargOption[] = []
|
||||
examples: MassargExample[] = []
|
||||
args: Partial<Args> = {}
|
||||
helpConfig: Required<HelpConfig>
|
||||
|
||||
constructor(options: CommandConfig<Args>) {
|
||||
CommandConfig(z.any()).parse(options)
|
||||
@@ -66,12 +56,7 @@ export default class MassargCommand<Args extends ArgsObject = ArgsObject> {
|
||||
this.description = options.description
|
||||
this.aliases = options.aliases ?? []
|
||||
this._run = options.run
|
||||
if (options.bindHelpCommand) {
|
||||
this.command(new MassargHelpCommand())
|
||||
}
|
||||
if (options.bindHelpOption) {
|
||||
this.option(new MassargHelpFlag())
|
||||
}
|
||||
this.helpConfig = HelpConfig.required().parse(defaultHelpConfig)
|
||||
}
|
||||
|
||||
command<A extends ArgsObject = Args>(config: CommandConfig<A>): MassargCommand<Args>
|
||||
@@ -110,15 +95,13 @@ export default class MassargCommand<Args extends ArgsObject = ArgsObject> {
|
||||
): MassargCommand<Args> {
|
||||
try {
|
||||
const flag = config instanceof MassargFlag ? config : new MassargFlag(config)
|
||||
if (flag.isDefault) {
|
||||
const defaultOption = this.options.find((o) => o.isDefault)
|
||||
if (defaultOption) {
|
||||
throw new ValidationError({
|
||||
code: 'duplicate_default_option',
|
||||
message: `Option "${flag.name}" cannot be set as default because option "${defaultOption.name}" is already set as default`,
|
||||
path: [this.name, flag.name],
|
||||
})
|
||||
}
|
||||
const existing = this.options.find((c) => c.name === flag.name)
|
||||
if (existing) {
|
||||
throw new ValidationError({
|
||||
code: 'duplicate_flag',
|
||||
message: `Flag "${flag.name}" already exists`,
|
||||
path: [this.name, flag.name],
|
||||
})
|
||||
}
|
||||
this.options.push(flag as MassargOption)
|
||||
return this
|
||||
@@ -140,6 +123,14 @@ export default class MassargCommand<Args extends ArgsObject = ArgsObject> {
|
||||
try {
|
||||
const option =
|
||||
config instanceof MassargOption ? config : MassargOption.fromTypedConfig(config)
|
||||
const existing = this.options.find((c) => c.name === option.name)
|
||||
if (existing) {
|
||||
throw new ValidationError({
|
||||
code: 'duplicate_option',
|
||||
message: `Option "${option.name}" already exists`,
|
||||
path: [this.name, option.name],
|
||||
})
|
||||
}
|
||||
if (option.isDefault) {
|
||||
const defaultOption = this.options.find((o) => o.isDefault)
|
||||
if (defaultOption) {
|
||||
@@ -169,6 +160,20 @@ export default class MassargCommand<Args extends ArgsObject = ArgsObject> {
|
||||
return this
|
||||
}
|
||||
|
||||
help(config: HelpConfig): MassargCommand<Args> {
|
||||
this.helpConfig = HelpConfig.required().parse(
|
||||
deepMerge(defaultHelpConfig, config) as HelpConfig,
|
||||
)
|
||||
|
||||
if (this.helpConfig.bindCommand) {
|
||||
this.command(new MassargHelpCommand())
|
||||
}
|
||||
if (this.helpConfig.bindOption) {
|
||||
this.option(new MassargHelpFlag())
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
main<A extends ArgsObject = Args>(run: Runner<A>): MassargCommand<Args> {
|
||||
this._run = run
|
||||
return this
|
||||
@@ -217,46 +222,50 @@ export default class MassargCommand<Args extends ArgsObject = ArgsObject> {
|
||||
let _args: Args = { ...this.args, ...args } as Args
|
||||
let _argv = [...argv]
|
||||
const _a = this.args as Record<string, string[]>
|
||||
|
||||
// fill defaults
|
||||
for (const option of this.options) {
|
||||
if (option.defaultValue !== undefined && _a[option.name] === undefined) {
|
||||
_args[option.name as keyof Args] = option.defaultValue as Args[keyof Args]
|
||||
}
|
||||
}
|
||||
|
||||
// parse options
|
||||
while (_argv.length) {
|
||||
const arg = _argv.shift()!
|
||||
const found = this.options.some((o) => o._isOption(arg))
|
||||
if (found) {
|
||||
const option = this.options.find((o) => o._match(arg))
|
||||
if (!option) {
|
||||
throw new ValidationError({
|
||||
path: [MassargOption.getName(arg)],
|
||||
code: 'unknown_option',
|
||||
message: 'Unknown option',
|
||||
})
|
||||
}
|
||||
const res = option._parseDetails(argv)
|
||||
_args[res.key as keyof Args] = setOrPush<Args[keyof Args]>(
|
||||
res.value,
|
||||
_args[res.key as keyof Args],
|
||||
option.isArray,
|
||||
)
|
||||
_argv = this.parseOption(arg, _argv)
|
||||
_args = { ..._args, ...this.args }
|
||||
continue
|
||||
}
|
||||
|
||||
const command = this.commands.find((c) => c.name === arg || c.aliases.includes(arg))
|
||||
if (command) {
|
||||
// this is dry run, just exit
|
||||
if (!parseCommands) {
|
||||
break
|
||||
}
|
||||
// this is real run, parse command, pass unparsed args
|
||||
return command.parse(_argv, this.args, parent ?? this)
|
||||
}
|
||||
// default option - passes arg value even without flag name
|
||||
const defaultOption = this.options.find((o) => o.isDefault)
|
||||
if (defaultOption) {
|
||||
_argv = this.parseOption(`--${defaultOption.name}`, [arg, ..._argv])
|
||||
continue
|
||||
}
|
||||
// not parsed by any step, add to extra key
|
||||
_a.extra ??= []
|
||||
_a.extra.push(arg)
|
||||
}
|
||||
if (!parseCommands) {
|
||||
return _args
|
||||
}
|
||||
this.args = { ...this.args, ..._args }
|
||||
// dry run, just exit
|
||||
if (!parseCommands) {
|
||||
return this.args as Args
|
||||
}
|
||||
|
||||
// no sub command found, run main command
|
||||
if (this._run) {
|
||||
this._run(this.args, parent ?? this)
|
||||
}
|
||||
|
||||
226
src/help.ts
226
src/help.ts
@@ -1,36 +1,102 @@
|
||||
import z from 'zod'
|
||||
import { format, StringStyle, stripColors } from './color'
|
||||
import MassargCommand from './command'
|
||||
import { chainStr, indent } from './utils'
|
||||
|
||||
export type GenerateTableCommandConfig = {
|
||||
maxRowLength?: number
|
||||
namePrefix?: string
|
||||
aliasPrefix?: string
|
||||
compact?: boolean
|
||||
nameStyle?: StringStyle
|
||||
descriptionStyle?: StringStyle
|
||||
}
|
||||
export const GenerateTableCommandConfig = z.object({
|
||||
maxRowLength: z.number().optional(),
|
||||
namePrefix: z.string().optional(),
|
||||
aliasPrefix: z.string().optional(),
|
||||
compact: z.boolean().optional(),
|
||||
nameStyle: StringStyle.optional(),
|
||||
descriptionStyle: StringStyle.optional(),
|
||||
})
|
||||
export type GenerateTableCommandConfig = z.infer<typeof GenerateTableCommandConfig>
|
||||
|
||||
export type GenerateTableOptionConfig = GenerateTableCommandConfig & {
|
||||
typeStyle?: StringStyle
|
||||
defaultStyle?: StringStyle
|
||||
}
|
||||
export const GenerateTableOptionConfig = GenerateTableCommandConfig
|
||||
export type GenerateTableOptionConfig = z.infer<typeof GenerateTableOptionConfig>
|
||||
|
||||
export type GenerateHelpOptions = {
|
||||
// sub-styles
|
||||
commandOptions?: GenerateTableCommandConfig
|
||||
optionOptions?: GenerateTableOptionConfig
|
||||
export const HelpConfig = z.object({
|
||||
/**
|
||||
* Whether to bind the help command to this command
|
||||
*
|
||||
* Set this to `true` to automatically add a `help` command to this command's subcommands.
|
||||
*/
|
||||
bindCommand: z.boolean().optional(),
|
||||
/**
|
||||
* Whether to bind the help option to this command
|
||||
*
|
||||
* Set this to `true` to automatically add a `--help` option to this command's options.
|
||||
*/
|
||||
bindOption: z.boolean().optional(),
|
||||
|
||||
// global styles
|
||||
titleStyle?: StringStyle
|
||||
descriptionStyle?: StringStyle
|
||||
subtitleStyle?: StringStyle
|
||||
usageStyle?: StringStyle
|
||||
exampleStyles?: {
|
||||
description?: StringStyle
|
||||
input?: StringStyle
|
||||
output?: StringStyle
|
||||
}
|
||||
commandOptions: GenerateTableCommandConfig.optional(),
|
||||
optionOptions: GenerateTableOptionConfig.optional(),
|
||||
titleStyle: StringStyle.optional(),
|
||||
descriptionStyle: StringStyle.optional(),
|
||||
subtitleStyle: StringStyle.optional(),
|
||||
usageStyle: StringStyle.optional(),
|
||||
maxRowLength: z.number().optional(),
|
||||
exampleStyles: z
|
||||
.object({
|
||||
description: StringStyle.optional(),
|
||||
input: StringStyle.optional(),
|
||||
output: StringStyle.optional(),
|
||||
})
|
||||
.optional(),
|
||||
})
|
||||
|
||||
export type HelpConfig = z.infer<typeof HelpConfig>
|
||||
|
||||
export const defaultHelpConfig: HelpConfig = {
|
||||
maxRowLength: 80,
|
||||
commandOptions: {
|
||||
namePrefix: '',
|
||||
aliasPrefix: '',
|
||||
nameStyle: {
|
||||
color: 'yellow',
|
||||
},
|
||||
descriptionStyle: {
|
||||
color: 'gray',
|
||||
},
|
||||
},
|
||||
optionOptions: {
|
||||
namePrefix: '--',
|
||||
aliasPrefix: '-',
|
||||
nameStyle: {
|
||||
color: 'yellow',
|
||||
},
|
||||
descriptionStyle: {
|
||||
color: 'gray',
|
||||
},
|
||||
},
|
||||
descriptionStyle: {},
|
||||
exampleStyles: {
|
||||
description: {
|
||||
bold: true,
|
||||
},
|
||||
input: {
|
||||
color: 'yellow',
|
||||
},
|
||||
output: {
|
||||
color: 'brightWhite',
|
||||
},
|
||||
},
|
||||
bindCommand: false,
|
||||
bindOption: false,
|
||||
titleStyle: {
|
||||
bold: true,
|
||||
color: 'yellow',
|
||||
},
|
||||
usageStyle: {
|
||||
bold: true,
|
||||
color: 'yellow',
|
||||
},
|
||||
subtitleStyle: {
|
||||
bold: true,
|
||||
color: 'brightWhite',
|
||||
underline: true,
|
||||
},
|
||||
}
|
||||
|
||||
export type HelpItem = {
|
||||
@@ -41,105 +107,56 @@ export type HelpItem = {
|
||||
|
||||
export class HelpGenerator {
|
||||
entry: MassargCommand<any>
|
||||
config: GenerateHelpOptions
|
||||
config: HelpConfig
|
||||
|
||||
constructor(entry: MassargCommand<any>, config?: GenerateHelpOptions) {
|
||||
constructor(entry: MassargCommand<any>, config?: HelpConfig) {
|
||||
this.entry = entry
|
||||
this.config = {
|
||||
this.config = HelpConfig.parse({
|
||||
commandOptions: {
|
||||
...config?.commandOptions,
|
||||
},
|
||||
optionOptions: {
|
||||
...config?.optionOptions,
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
generate(): string {
|
||||
const entry = this.entry
|
||||
const options = generateHelpTable(entry.options, {
|
||||
namePrefix: '--',
|
||||
aliasPrefix: '-',
|
||||
...this.config.optionOptions,
|
||||
})
|
||||
const commands = generateHelpTable(entry.commands, {
|
||||
namePrefix: '',
|
||||
aliasPrefix: '',
|
||||
...this.config.commandOptions,
|
||||
})
|
||||
const options = generateHelpTable(entry.options, this.config.optionOptions)
|
||||
const commands = generateHelpTable(entry.commands, this.config.commandOptions)
|
||||
const examples = entry.examples
|
||||
.map((example) => {
|
||||
const { description, input, output } = example
|
||||
return chainStr(
|
||||
description && [
|
||||
format(description, {
|
||||
reset: true,
|
||||
bold: true,
|
||||
...this.config.exampleStyles?.description,
|
||||
}),
|
||||
'',
|
||||
],
|
||||
input &&
|
||||
format(input, { reset: true, color: 'yellow', ...this.config.exampleStyles?.input }),
|
||||
output &&
|
||||
format(output, {
|
||||
reset: true,
|
||||
color: 'brightWhite',
|
||||
...this.config.exampleStyles?.output,
|
||||
}),
|
||||
description && [format(description, this.config.exampleStyles?.description), ''],
|
||||
input && format(input, this.config.exampleStyles?.input),
|
||||
output && format(output, this.config.exampleStyles?.output),
|
||||
)
|
||||
})
|
||||
.join('\n')
|
||||
|
||||
return (
|
||||
chainStr(
|
||||
format(`Usage: ${entry.name} [...options]`, {
|
||||
bold: true,
|
||||
color: 'yellow',
|
||||
reset: true,
|
||||
...this.config.titleStyle,
|
||||
}),
|
||||
format(`Usage: ${entry.name} [...options]`, this.config.usageStyle),
|
||||
'',
|
||||
format(entry.description, { reset: true, ...this.config.descriptionStyle }),
|
||||
format(entry.description, this.config.descriptionStyle),
|
||||
commands.length &&
|
||||
indent([
|
||||
'',
|
||||
format(`Commands for ${entry.name}:`, {
|
||||
bold: true,
|
||||
reset: true,
|
||||
color: 'brightWhite',
|
||||
underline: true,
|
||||
...this.config.subtitleStyle,
|
||||
}),
|
||||
'',
|
||||
indent(commands),
|
||||
]),
|
||||
indent([
|
||||
'',
|
||||
format(`Commands for ${entry.name}:`, this.config.subtitleStyle),
|
||||
'',
|
||||
indent(commands),
|
||||
]),
|
||||
options.length &&
|
||||
indent([
|
||||
'',
|
||||
format(`Options for ${entry.name}:`, {
|
||||
bold: true,
|
||||
reset: true,
|
||||
color: 'brightWhite',
|
||||
underline: true,
|
||||
...this.config.subtitleStyle,
|
||||
}),
|
||||
'',
|
||||
indent(options),
|
||||
]),
|
||||
indent([
|
||||
'',
|
||||
format(`Options for ${entry.name}:`, this.config.subtitleStyle),
|
||||
'',
|
||||
indent(options),
|
||||
]),
|
||||
examples.length &&
|
||||
indent([
|
||||
'',
|
||||
format('Examples:', {
|
||||
bold: true,
|
||||
reset: true,
|
||||
color: 'brightWhite',
|
||||
underline: true,
|
||||
...this.config.subtitleStyle,
|
||||
}),
|
||||
'',
|
||||
indent(examples),
|
||||
]),
|
||||
indent(['', format('Examples:', this.config.subtitleStyle), '', indent(examples)]),
|
||||
) + '\n'
|
||||
)
|
||||
}
|
||||
@@ -160,17 +177,14 @@ function generateHelpTable<T extends Partial<GenerateTableCommandConfig>>(
|
||||
}: Partial<T> = {},
|
||||
): string {
|
||||
const rows = items.map((o) => {
|
||||
const name = `${namePrefix}${o.name}${
|
||||
o.aliases.length ? ` | ${aliasPrefix}${o.aliases.join(`|${aliasPrefix}`)}` : ''
|
||||
}`
|
||||
const name = `${namePrefix}${o.name}${o.aliases.length ? ` | ${aliasPrefix}${o.aliases.join(`|${aliasPrefix}`)}` : ''
|
||||
}`
|
||||
const description = o.description
|
||||
return { name, description }
|
||||
})
|
||||
const maxNameLength = Math.max(...rows.map((o) => o.name.length))
|
||||
const nameStyle = (name: string) =>
|
||||
format(name, { color: 'yellow', reset: true, ...config.nameStyle })
|
||||
const descStyle = (desc: string) =>
|
||||
format(desc, { color: 'gray', reset: true, ...config.descriptionStyle })
|
||||
const nameStyle = (name: string) => format(name, config.nameStyle)
|
||||
const descStyle = (desc: string) => format(desc, config.descriptionStyle)
|
||||
const table = rows.map((row) => {
|
||||
const name = nameStyle(row.name.padEnd(maxNameLength + 2))
|
||||
const description = descStyle(row.description)
|
||||
|
||||
21
src/utils.ts
21
src/utils.ts
@@ -1,3 +1,5 @@
|
||||
import z from 'zod'
|
||||
|
||||
export function setOrPush<T>(
|
||||
newValue: unknown,
|
||||
currentValue: T[] | T | undefined,
|
||||
@@ -46,3 +48,22 @@ export function indent(str: Parseable | Parseable[], indent = 2): string {
|
||||
.map((s) => ' '.repeat(indent) + s)
|
||||
.join('\n')
|
||||
}
|
||||
|
||||
export function zodEnumFromObjKeys<K extends string>(obj: Record<K, any>): z.ZodEnum<[K, ...K[]]> {
|
||||
const [firstKey, ...otherKeys] = Object.keys(obj) as K[]
|
||||
return z.enum([firstKey, ...otherKeys])
|
||||
}
|
||||
|
||||
export function deepMerge<T1, T2>(obj1: T1, obj2: T2): NonNullable<T1> & NonNullable<T2> {
|
||||
const res = { ...obj1 } as any
|
||||
if (obj1 == null) return obj2 as any
|
||||
if (obj2 == null) return obj1 as any
|
||||
for (const [key, value] of Object.entries(obj2 as never)) {
|
||||
if (typeof value === 'object' && typeof res[key] === 'object') {
|
||||
res[key] = deepMerge(res[key], value)
|
||||
} else {
|
||||
res[key] = value
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
348
test/command.test.ts
Normal file
348
test/command.test.ts
Normal file
@@ -0,0 +1,348 @@
|
||||
import { MassargCommand } from '../src/command'
|
||||
import { defaultHelpConfig } from '../src/help'
|
||||
import { massarg } from '../src/index'
|
||||
|
||||
const opts = {
|
||||
name: 'test',
|
||||
description: 'test',
|
||||
}
|
||||
test('constructor', () => {
|
||||
expect(massarg(opts)).toBeInstanceOf(MassargCommand)
|
||||
})
|
||||
|
||||
describe('command', () => {
|
||||
test('add', () => {
|
||||
const command = massarg(opts)
|
||||
expect(command.command).toBeInstanceOf(Function)
|
||||
expect(command.command({ name: 'test2', description: 'test2', run: jest.fn() })).toBeInstanceOf(
|
||||
MassargCommand,
|
||||
)
|
||||
})
|
||||
test('add duplicate', () => {
|
||||
expect(() =>
|
||||
massarg(opts)
|
||||
.command({ name: 'test2', description: 'test2', run: jest.fn() })
|
||||
.command({ name: 'test2', description: 'test2', run: jest.fn() }),
|
||||
).toThrow('Command "test2" already exists')
|
||||
})
|
||||
test('validate', () => {
|
||||
expect(() =>
|
||||
massarg(opts).command({
|
||||
name: 'test2',
|
||||
description: 123 as any,
|
||||
run: jest.fn(),
|
||||
}),
|
||||
).toThrow('Expected string, received number')
|
||||
})
|
||||
})
|
||||
|
||||
describe('option', () => {
|
||||
test('add', () => {
|
||||
const command = massarg(opts)
|
||||
expect(command.option).toBeInstanceOf(Function)
|
||||
expect(
|
||||
command.option({ name: 'test2', description: 'test2', aliases: [], defaultValue: '' }),
|
||||
).toBeInstanceOf(MassargCommand)
|
||||
})
|
||||
test('validate', () => {
|
||||
expect(() =>
|
||||
massarg(opts).option({
|
||||
name: 'test2',
|
||||
description: 123 as any,
|
||||
aliases: [],
|
||||
defaultValue: '',
|
||||
}),
|
||||
).toThrow('Expected string, received number')
|
||||
})
|
||||
test('add duplicate', () => {
|
||||
expect(() =>
|
||||
massarg(opts)
|
||||
.option({
|
||||
name: 'test2',
|
||||
description: 'test2',
|
||||
aliases: [],
|
||||
defaultValue: '',
|
||||
})
|
||||
.option({
|
||||
name: 'test2',
|
||||
description: 'test2',
|
||||
aliases: [],
|
||||
defaultValue: '',
|
||||
}),
|
||||
).toThrow('Option "test2" already exists')
|
||||
})
|
||||
test('add 2 defaults', () => {
|
||||
expect(() =>
|
||||
massarg(opts)
|
||||
.option({
|
||||
name: 'test',
|
||||
description: 'test2',
|
||||
aliases: [],
|
||||
isDefault: true,
|
||||
})
|
||||
.option({
|
||||
name: 'test2',
|
||||
description: 'test2',
|
||||
aliases: [],
|
||||
isDefault: true,
|
||||
}),
|
||||
).toThrow(
|
||||
'Option "test2" cannot be set as default because option "test" is already set as default',
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('flag', () => {
|
||||
test('add', () => {
|
||||
const command = massarg(opts)
|
||||
expect(command.flag).toBeInstanceOf(Function)
|
||||
expect(command.flag({ name: 'test2', description: 'test2', aliases: [] })).toBeInstanceOf(
|
||||
MassargCommand,
|
||||
)
|
||||
})
|
||||
test('add duplicate', () => {
|
||||
expect(() =>
|
||||
massarg(opts)
|
||||
.flag({ name: 'test2', description: 'test2', aliases: [] })
|
||||
.flag({ name: 'test2', description: 'test2', aliases: [] }),
|
||||
).toThrow('Flag "test2" already exists')
|
||||
})
|
||||
test('validate', () => {
|
||||
expect(() =>
|
||||
massarg(opts).flag({
|
||||
name: 'test2',
|
||||
description: 123 as any,
|
||||
aliases: [],
|
||||
}),
|
||||
).toThrow('Expected string, received number')
|
||||
})
|
||||
})
|
||||
|
||||
describe('example', () => {
|
||||
test('example', () => {
|
||||
const command = massarg(opts)
|
||||
expect(command.example).toBeInstanceOf(Function)
|
||||
expect(command.example({ description: 'test', input: '', output: '' })).toBeInstanceOf(
|
||||
MassargCommand,
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('help', () => {
|
||||
test('default value', () => {
|
||||
const command = massarg(opts)
|
||||
expect(command.helpConfig).toEqual(defaultHelpConfig)
|
||||
})
|
||||
|
||||
test('init', () => {
|
||||
const command = massarg(opts).help({
|
||||
bindOption: true,
|
||||
optionOptions: {
|
||||
namePrefix: '__',
|
||||
},
|
||||
})
|
||||
expect(command.help).toBeInstanceOf(Function)
|
||||
expect(command.helpConfig).toHaveProperty('bindOption', true)
|
||||
expect(command.helpConfig).toHaveProperty('optionOptions.namePrefix', '__')
|
||||
expect(command.helpConfig).toHaveProperty('optionOptions.aliasPrefix', '-')
|
||||
expect(command.helpConfig).toHaveProperty('optionOptions.nameStyle.color', 'yellow')
|
||||
})
|
||||
|
||||
test('binds command', () => {
|
||||
const command = massarg(opts).help({
|
||||
bindCommand: true,
|
||||
})
|
||||
expect(command.help).toBeInstanceOf(Function)
|
||||
expect(command.helpConfig).toHaveProperty('bindCommand', true)
|
||||
expect(command.commands.find((o) => o.name === 'help')).toBeTruthy()
|
||||
})
|
||||
|
||||
test('binds option', () => {
|
||||
const command = massarg(opts).help({
|
||||
bindOption: true,
|
||||
})
|
||||
expect(command.help).toBeInstanceOf(Function)
|
||||
expect(command.helpConfig).toHaveProperty('bindOption', true)
|
||||
expect(command.options.find((o) => o.name === 'help')).toBeTruthy()
|
||||
})
|
||||
|
||||
test('help string', () => {
|
||||
const command = massarg(opts)
|
||||
expect(command.helpString()).toContain(`Usage:`)
|
||||
})
|
||||
|
||||
test('print help', () => {
|
||||
const log = jest.spyOn(console, 'log').mockImplementation(() => {})
|
||||
const command = massarg(opts)
|
||||
command.printHelp()
|
||||
expect(log).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('getArgs', () => {
|
||||
test('basic', () => {
|
||||
expect(massarg(opts).getArgs([])).toEqual({})
|
||||
expect(
|
||||
massarg(opts)
|
||||
.option({
|
||||
name: 'test',
|
||||
description: 'test',
|
||||
aliases: [],
|
||||
})
|
||||
.getArgs(['--test', 'test']),
|
||||
).toEqual({ test: 'test' })
|
||||
})
|
||||
|
||||
test('stops after command', () => {
|
||||
expect(
|
||||
massarg(opts)
|
||||
.command({ name: 'test', description: 'test', run: jest.fn() })
|
||||
.getArgs(['test', '--test', 'test']),
|
||||
).toEqual({})
|
||||
})
|
||||
|
||||
test('alias', () => {
|
||||
expect(
|
||||
massarg(opts)
|
||||
.option({
|
||||
name: 'test',
|
||||
description: 'test',
|
||||
aliases: ['t'],
|
||||
})
|
||||
.getArgs(['-t', 'test']),
|
||||
).toEqual({ test: 'test' })
|
||||
})
|
||||
|
||||
test('default value', () => {
|
||||
expect(
|
||||
massarg(opts)
|
||||
.option({
|
||||
name: 'test',
|
||||
description: 'test',
|
||||
aliases: [],
|
||||
defaultValue: 'test',
|
||||
})
|
||||
.getArgs([]),
|
||||
).toEqual({ test: 'test' })
|
||||
})
|
||||
|
||||
test('override default', () => {
|
||||
expect(
|
||||
massarg(opts)
|
||||
.option({
|
||||
name: 'test',
|
||||
description: 'test',
|
||||
aliases: [],
|
||||
defaultValue: 'test',
|
||||
})
|
||||
.getArgs(['--test', 'test2']),
|
||||
).toEqual({ test: 'test2' })
|
||||
})
|
||||
|
||||
test('override duplicate option', () => {
|
||||
expect(
|
||||
massarg(opts)
|
||||
.option({
|
||||
name: 'test',
|
||||
description: 'test',
|
||||
aliases: [],
|
||||
defaultValue: 'test',
|
||||
})
|
||||
.getArgs(['--test', 'test2', '--test', 'test3']),
|
||||
).toEqual({ test: 'test3' })
|
||||
})
|
||||
|
||||
test('default option', () => {
|
||||
expect(
|
||||
massarg(opts)
|
||||
.option({
|
||||
name: 'test',
|
||||
description: 'test',
|
||||
aliases: [],
|
||||
isDefault: true,
|
||||
})
|
||||
.getArgs(['test3']),
|
||||
).toEqual({ test: 'test3' })
|
||||
})
|
||||
test('prefers command over default', () => {
|
||||
expect(
|
||||
massarg(opts)
|
||||
.command({
|
||||
name: 'test3',
|
||||
description: 'test3',
|
||||
run: jest.fn(),
|
||||
})
|
||||
.option({
|
||||
name: 'test',
|
||||
description: 'test',
|
||||
aliases: [],
|
||||
isDefault: true,
|
||||
})
|
||||
.getArgs(['test3']),
|
||||
).toEqual({})
|
||||
})
|
||||
test.skip('extra values', () => {
|
||||
expect(
|
||||
massarg(opts)
|
||||
.command({
|
||||
name: 'test3',
|
||||
description: 'test3',
|
||||
run: jest.fn(),
|
||||
})
|
||||
.option({
|
||||
name: 'test',
|
||||
description: 'test',
|
||||
aliases: [],
|
||||
})
|
||||
.getArgs(['test3', 'test2']),
|
||||
).toEqual({ extra: ['test2'] })
|
||||
})
|
||||
})
|
||||
|
||||
describe('parse', () => {
|
||||
test('runs command', () => {
|
||||
const fn = jest.fn()
|
||||
const command = massarg(opts).command({
|
||||
name: 'test',
|
||||
description: 'test',
|
||||
run: fn,
|
||||
})
|
||||
expect(command).toBeInstanceOf(MassargCommand)
|
||||
expect(command.parse(['test'])).toBeUndefined()
|
||||
expect(fn).toHaveBeenCalled()
|
||||
})
|
||||
test('runs main', () => {
|
||||
const fn = jest.fn()
|
||||
const command = massarg(opts).main(fn)
|
||||
expect(command).toBeInstanceOf(MassargCommand)
|
||||
expect(command.parse([])).toBeUndefined()
|
||||
expect(fn).toHaveBeenCalledWith({}, command)
|
||||
})
|
||||
test('runs main with args', () => {
|
||||
const fn = jest.fn()
|
||||
const command = massarg(opts)
|
||||
.option({ name: 'test', description: 'test', aliases: [] })
|
||||
.main(fn)
|
||||
expect(command).toBeInstanceOf(MassargCommand)
|
||||
expect(command.parse(['--test', 'test'])).toBeUndefined()
|
||||
expect(fn).toHaveBeenCalledWith({ test: 'test' }, command)
|
||||
})
|
||||
// test('throws with unknown option', () => {
|
||||
// expect(() => massarg(opts).parse(['--test', 'test'])).toThrow('Unknown option "test"')
|
||||
// })
|
||||
// test('throws with unknown command', () => {
|
||||
// expect(() => massarg(opts).parse(['test'])).toThrow('Unknown command "test"')
|
||||
// })
|
||||
// test('throws without main command', () => {
|
||||
// expect(() => massarg(opts).parse([])).toThrow('No main command')
|
||||
// })
|
||||
})
|
||||
|
||||
describe('main', () => {
|
||||
test('add', () => {
|
||||
const fn = jest.fn()
|
||||
const command = massarg(opts)
|
||||
expect(command.main).toBeInstanceOf(Function)
|
||||
expect(command.main(fn)).toBeInstanceOf(MassargCommand)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user