Skip to content

Commit f6e3a72

Browse files
committed
update tests + assertions [skip publish]
1 parent 827d5dc commit f6e3a72

3 files changed

Lines changed: 117 additions & 152 deletions

File tree

src/assertions.ts

Lines changed: 78 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
1-
import chalk from "chalk"
2-
import { CommandDef, ExampleDef, HelpDef, MainDef, OptionDef } from "./options"
3-
import { asArray } from "./utils"
1+
import { CommandDef, ExampleDef, HelpDef, MainDef, Named, OptionDef } from "./options"
2+
import { ArrayOr, asArray } from "./utils"
43

5-
export function assert(condition: any, message?: string): void {
4+
// prettier-ignore
5+
export const colorList = [
6+
"reset", "bold", "dim", "italic", "underline", "inverse", "hidden", "strikethrough", "black", "red", "green",
7+
"yellow", "blue", "magenta", "cyan", "white", "gray", "bgBlack", "bgRed", "bgGreen", "bgYellow", "bgBlue",
8+
"bgMagenta", "bgCyan", "bgWhite",
9+
]
10+
11+
function assert(condition: any, message?: string): void {
612
if (!condition) {
713
throw new Error(message)
814
}
@@ -12,146 +18,93 @@ function nullOr(condition: any, condition2: any): boolean {
1218
return [null, undefined].includes(condition) || condition2
1319
}
1420

15-
export const colorList = [
16-
"reset",
17-
"bold",
18-
"dim",
19-
"italic",
20-
"underline",
21-
"inverse",
22-
"hidden",
23-
"strikethrough",
24-
"black",
25-
"red",
26-
"green",
27-
"yellow",
28-
"blue",
29-
"magenta",
30-
"cyan",
31-
"white",
32-
"gray",
33-
"bgBlack",
34-
"bgRed",
35-
"bgGreen",
36-
"bgYellow",
37-
"bgBlue",
38-
"bgMagenta",
39-
"bgCyan",
40-
"bgWhite",
41-
]
21+
function assertType(obj: any, prefix: string, name: string, options: { type: string; required?: boolean }): void {
22+
if (options.required) {
23+
assert(![undefined, null].includes(obj), `${prefix}: ${name} must be provided`)
24+
} else if ([null, undefined].includes(obj)) {
25+
return
26+
}
4227

43-
export function assertHelp(help: HelpDef) {
44-
assert(nullOr(help.binName, typeof help.binName === "string"), "Help: Binary Name must be string")
45-
assert(
46-
nullOr(
47-
help.normalColors,
48-
asArray(help.normalColors).every((c) => colorList.includes(c!))
49-
),
50-
"Help: Normal colors must be string or array of strings. Accepted values: " + colorList.join(", ")
51-
)
52-
assert(
53-
nullOr(
54-
help.bodyColors,
55-
asArray(help.bodyColors).every((c) => colorList.includes(c!))
56-
),
57-
"Help: Body colors must be string or array of strings. Accepted values: " + colorList.join(", ")
58-
)
59-
assert(
60-
nullOr(
61-
help.titleColors,
62-
asArray(help.titleColors).every((c) => colorList.includes(c!))
63-
),
64-
"Help: Title colors must be string or array of strings. Accepted values: " + colorList.join(", ")
65-
)
66-
assert(
67-
nullOr(
68-
help.subtitleColors,
69-
asArray(help.subtitleColors).every((c) => colorList.includes(c!))
70-
),
71-
"Help: Subtitle colors must be string or array of strings. Accepted values: " + colorList.join(", ")
72-
)
28+
assert(typeof obj === options.type, `${prefix}: ${name} must be ${options.type}`)
29+
}
30+
31+
function assertNumber(
32+
obj: number | null | undefined,
33+
prefix: string,
34+
name: string,
35+
options: { min?: number; max?: number; required?: boolean }
36+
): void {
37+
assertType(obj, prefix, name, { required: options.required, type: "number" })
38+
39+
if (!options.required && [null, undefined].includes(obj as any)) {
40+
return
41+
}
42+
43+
if (typeof options.max === "number") {
44+
assert(obj! <= options.max, `${prefix}: ${name} must be ≤ ${options.max}`)
45+
}
46+
if (typeof options.min === "number") {
47+
assert(obj! >= options.min, `${prefix}: ${name} must be ≥ ${options.min}`)
48+
}
49+
}
50+
51+
function assertColor(color: ArrayOr<string> | undefined, prefix: string, name: string) {
7352
assert(
7453
nullOr(
75-
help.highlightColors,
76-
asArray(help.highlightColors).every((c) => colorList.includes(c!))
54+
color,
55+
asArray(color).every((c) => colorList.includes(c!))
7756
),
78-
"Help: Highlight colors must be string or array of strings. Accepted values: " + colorList.join(", ")
79-
)
80-
assert(nullOr(help.footer, typeof help.footer === "string"), "Help: Footer must be string")
81-
assert(nullOr(help.header, typeof help.header === "string"), "Help: Header must be string")
82-
assert(
83-
nullOr(help.optionNameSeparator, typeof help.optionNameSeparator === "string"),
84-
"Help: Option Name Separator must be string"
85-
)
86-
assert(
87-
nullOr(help.commandNameSeparator, typeof help.commandNameSeparator === "string"),
88-
"Help: Command Name Separator must be string"
89-
)
90-
assert(
91-
nullOr(help.printWidth, typeof help.printWidth === "number" && help.printWidth >= 0),
92-
"Help: Print Width must be a number ≥ 0"
93-
)
94-
assert(
95-
nullOr(help.exampleInputPrefix, typeof help.exampleInputPrefix === "string"),
96-
"Help: Example Input Prefix must be string"
97-
)
98-
assert(
99-
nullOr(help.exampleOutputPrefix, typeof help.exampleOutputPrefix === "string"),
100-
"Help: Example Output Prefix must be string"
101-
)
102-
assert(nullOr(help.usageExample, typeof help.usageExample === "string"), "Help: Usage Example must be string")
103-
assert(
104-
nullOr(help.useGlobalColumns, typeof help.useGlobalColumns === "boolean"),
105-
"Help: Use Global Columns must be boolean"
57+
`${prefix}: ${name} must be string or array of strings. Accepted values: ` + colorList.join(", ")
10658
)
59+
}
60+
61+
function assertAliases(def: Named, allDefs: Named[], prefix: string) {
10762
assert(
108-
nullOr(help.includeDefaults, typeof help.includeDefaults === "boolean"),
109-
"Help: Include Defaults must be boolean"
63+
!def.aliases || def.aliases.every((a) => !allDefs.find((opt) => [opt.name, ...(opt.aliases ?? [])].includes(a))),
64+
`${prefix}: Aliases must be unique`
11065
)
111-
assert(nullOr(help.useColors, typeof help.useColors === "boolean"), "Help: Use Colors must be boolean")
66+
}
67+
68+
export function assertHelp(help: HelpDef) {
69+
assertType(help.binName, "Help", "Binary Name", { type: "string" })
70+
assertColor(help.normalColors, "Help", "Normal colors")
71+
assertColor(help.bodyColors, "Help", "Body colors")
72+
assertColor(help.titleColors, "Help", "Title colors")
73+
assertColor(help.subtitleColors, "Help", "Subtitle colors")
74+
assertColor(help.highlightColors, "Help", "Highlight colors")
75+
assertType(help.footer, "Help", "Footer", { type: "string" })
76+
assertType(help.header, "Help", "Header", { type: "string" })
77+
assertType(help.optionNameSeparator, "Help", "Option Name Separator", { type: "string" })
78+
assertType(help.commandNameSeparator, "Help", "Command Name Separator", { type: "string" })
79+
assertNumber(help.printWidth, "Help", "Print Width", { min: 0 })
80+
assertType(help.exampleInputPrefix, "Help", "Example Input Prefix", { type: "string" })
81+
assertType(help.exampleOutputPrefix, "Help", "Example Output Prefix", { type: "string" })
82+
assertType(help.usageExample, "Help", "Usage Example", { type: "string" })
83+
assertType(help.useGlobalColumns, "Help", "Use Global Columns", { type: "boolean" })
84+
assertType(help.includeDefaults, "Help", "Include Defaults", { type: "boolean" })
85+
assertType(help.useColors, "Help", "Use Colors", { type: "boolean" })
11286
}
11387

11488
export function assertCommand(command: CommandDef<any>, allCommands: CommandDef<any>[]): void {
115-
assert(command.name, "Command: Name must be provided")
116-
assert(typeof command.name === "string", "Command: Name must be string")
117-
assert(
118-
!command.aliases ||
119-
command.aliases.every((a) => !allCommands.find((cmd) => cmd.name === a || cmd.aliases?.includes(a))),
120-
"Command: Aliases must be unique"
121-
)
122-
assert(command.run, "Command: Run function must be provided")
123-
assert(typeof command.run === "function", "Command: Run must be a function")
89+
assertType(command.name, "Command", "Name", { required: true, type: "string" })
90+
assertAliases(command, allCommands, "Command")
91+
assertType(command.run, "Command", "Run", { required: true, type: "function" })
12492
}
12593

12694
export function assertOption(option: OptionDef<any, any>, allOptions: OptionDef<any, any>[]): void {
12795
assert(option.name, "Option: Name must be provided")
12896
assert(typeof option.name === "string", "Option: Name must be string")
129-
130-
assert(
131-
nullOr(
132-
option.aliases,
133-
option.aliases?.every((a) => typeof a === "string")
134-
),
135-
"Option: Aliases must an array of strings"
136-
)
137-
assert(
138-
nullOr(
139-
option.aliases,
140-
option.aliases?.every((a) => !allOptions.find((opt) => opt.name === a || opt.aliases?.includes(a)))
141-
),
142-
"Option: Aliases must be unique"
143-
)
144-
assert(nullOr(option.boolean, typeof ["undefined", "boolean"].includes(typeof option.defaultValue)))
145-
146-
assert(typeof option.parse === "function", "Option: Parse must be a function")
97+
assertAliases(option, allOptions, "Option")
98+
assertType(option.boolean, "Option", "Default Value", { type: "boolean" })
99+
assertType(option.parse, "Option", "Parse", { type: "function" })
147100
}
148101

149102
export function assertExample(example: ExampleDef) {
150-
assert(example.input, "Example: Input must be provided")
151-
assert(typeof example.output === "string", 'Example: Output must be provided. To signify empty output, use ""')
103+
assertType(example.input, "Example", "Input", { required: true, type: "string" })
104+
assertType(example.output, "Example", "Output", { required: true, type: "string" })
105+
assertType(example.description, "Example", "Description", { type: "string" })
152106
}
153107

154108
export function assertMain(run: MainDef<any>) {
155-
assert(run, "Main: Main function must be provided")
156-
assert(typeof run === "function", "Main: Main must be a function")
109+
assertType(run, "Main", "Main", { required: true, type: "function" })
157110
}

src/options.d.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,12 @@ export type OptionsBase = {
1111

1212
export type MainDef<Options> = (options: Options) => void
1313

14-
export interface OptionDef<Options, Value> {
14+
export interface Named {
15+
name: string
16+
aliases?: string[]
17+
}
18+
19+
export interface OptionDef<Options, Value> extends Named {
1520
/** Option name. When using in CLI, you will use `--name`, e.g. `--my-option`. */
1621
name: string
1722

@@ -76,7 +81,7 @@ export interface OptionDef<Options, Value> {
7681
parse?(value: string, options: Options): Value
7782
}
7883

79-
export interface CommandDef<T> {
84+
export interface CommandDef<T> extends Named {
8085
/**
8186
* Command name. When using in CLI, you will use `name` without any prefixes, unlike options.
8287
* Also, the first command that was parsed will run, and the others will be skipped.

tests/assertions.test.ts

Lines changed: 32 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import { HelpDef } from "../src/options"
55

66
describe("Assertions", () => {
77
test("should assert main command input", () => {
8-
expect(() => massarg().main(undefined as any)).toThrow("Main: Main function must be provided")
9-
expect(() => massarg().main(1 as any)).toThrow("Main: Main must be a function")
8+
expect(() => massarg().main(undefined as any)).toThrow("Main: Main must be provided")
9+
expect(() => massarg().main(1 as any)).toThrow("Main: Main must be function")
1010
})
1111

1212
test("should assert command input", () => {
@@ -29,14 +29,14 @@ describe("Assertions", () => {
2929
name: "cmd",
3030
run: undefined as any,
3131
})
32-
).toThrow("Command: Run function must be provided")
32+
).toThrow("Command: Run must be provided")
3333

3434
expect(() =>
3535
massarg().command({
3636
name: "cmd",
3737
run: 1 as any,
3838
})
39-
).toThrow("Command: Run must be a function")
39+
).toThrow("Command: Run must be function")
4040

4141
expect(() =>
4242
massarg()
@@ -70,7 +70,7 @@ describe("Assertions", () => {
7070
name: "opt",
7171
parse: 1 as any,
7272
})
73-
).toThrow("Option: Parse must be a function")
73+
).toThrow("Option: Parse must be function")
7474

7575
expect(() =>
7676
massarg()
@@ -84,18 +84,7 @@ describe("Assertions", () => {
8484
).toThrow("Option: Aliases must be unique")
8585
})
8686

87-
test("should assert help input", () => {
88-
const colors = ["normal", "body", "title", "subtitle", "highlight"]
89-
for (const color of colors) {
90-
expect(() =>
91-
massarg().help({
92-
[color + "Colors"]: [1],
93-
})
94-
).toThrow(
95-
`Help: ${capitalize(color)} colors must be string or array of strings. Accepted values: ` + colorList.join(", ")
96-
)
97-
}
98-
87+
describe("help input assertions", () => {
9988
const strings: Partial<Record<keyof HelpDef, string>> = {
10089
binName: "Binary Name",
10190
optionNameSeparator: "Option Name Separator",
@@ -114,18 +103,36 @@ describe("Assertions", () => {
114103
}
115104

116105
for (const k of Object.getOwnPropertyNames(strings)) {
117-
expect(() => massarg().help({ [k as keyof HelpDef]: 1 as any })).toThrow(
118-
`Help: ${strings[k as keyof HelpDef]} must be string`
119-
)
106+
test(`should assert string ${k}`, () => {
107+
expect(() => massarg().help({ [k as keyof HelpDef]: 1 as any })).toThrow(
108+
`Help: ${strings[k as keyof HelpDef]} must be string`
109+
)
110+
})
120111
}
121112

122113
for (const k of Object.getOwnPropertyNames(bools)) {
123-
expect(() => massarg().help({ [k as keyof HelpDef]: 1 as any })).toThrow(
124-
`Help: ${bools[k as keyof HelpDef]} must be bool`
125-
)
114+
test(`should assert bool ${k}`, () => {
115+
expect(() => massarg().help({ [k as keyof HelpDef]: 1 as any })).toThrow(
116+
`Help: ${bools[k as keyof HelpDef]} must be bool`
117+
)
118+
})
126119
}
127120

128-
expect(() => massarg().help({ printWidth: -1 })).toThrow("Help: Print Width must be a number ≥ 0")
129-
expect(() => massarg().help({ printWidth: "a" as any })).toThrow("Help: Print Width must be a number ≥ 0")
121+
test("should assert other help input params", () => {
122+
const colors = ["normal", "body", "title", "subtitle", "highlight"]
123+
for (const color of colors) {
124+
expect(() =>
125+
massarg().help({
126+
[color + "Colors"]: [1],
127+
})
128+
).toThrow(
129+
`Help: ${capitalize(color)} colors must be string or array of strings. Accepted values: ` +
130+
colorList.join(", ")
131+
)
132+
}
133+
134+
expect(() => massarg().help({ printWidth: -1 })).toThrow("Help: Print Width must be ≥ 0")
135+
expect(() => massarg().help({ printWidth: "a" as any })).toThrow("Help: Print Width must be number")
136+
})
130137
})
131138
})

0 commit comments

Comments
 (0)