Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: usage #161

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions src/usage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,21 @@
import type { ArgsDef, CommandDef } from "./types";
import { resolveArgs } from "./args";

export async function showUsage<T extends ArgsDef = ArgsDef>(
cmd: CommandDef<T>,
parent?: CommandDef<T>,
) {
export async function showUsage<
T extends ArgsDef = ArgsDef,
U extends ArgsDef = ArgsDef,
>(cmd: CommandDef<T>, parent?: CommandDef<U>) {

Check warning on line 10 in src/usage.ts

View check run for this annotation

Codecov / codecov/patch

src/usage.ts#L7-L10

Added lines #L7 - L10 were not covered by tests
try {
consola.log((await renderUsage(cmd, parent)) + "\n");
} catch (error) {
consola.error(error);
}
}

export async function renderUsage<T extends ArgsDef = ArgsDef>(
cmd: CommandDef<T>,
parent?: CommandDef<T>,
) {
export async function renderUsage<
T extends ArgsDef = ArgsDef,
U extends ArgsDef = ArgsDef,
>(cmd: CommandDef<T>, parent?: CommandDef<U>) {
const cmdMeta = await resolveValue(cmd.meta || {});
const cmdArgs = resolveArgs(await resolveValue(cmd.args || {}));
const parentMeta = await resolveValue(parent?.meta || {});
Expand Down
306 changes: 306 additions & 0 deletions test/usage.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,306 @@
import { expect, it, describe } from "vitest";
import { renderUsage } from "../src/usage";
import { defineCommand } from "../src";

describe("usage", () => {
it("renders arguments", async () => {
const command = defineCommand({
meta: {
name: "Commander",
description: "A command",
},
args: {
foo: {
type: "string",
required: true,
description: "A foo",
},
bar: {
alias: ["b"],
type: "string",
description: "A bar",
},
pos: {
type: "positional",
name: "pos",
description: "A pos",
},
enum: {
type: "enum",
name: "enum",
options: ["a", "b"],
description: "An enum",
},
boolean: {
type: "boolean",
name: "boolean",
description: "A boolean",
},
},
});

const usage = await renderUsage(command);

expect(usage).toMatchInlineSnapshot(`
"A command (Commander)

USAGE \`Commander [OPTIONS] --foo <POS>\`

ARGUMENTS

\`POS\` A pos

OPTIONS

\`--foo (required)\` A foo
\`-b, --bar\` A bar
\`--enum=<a|b>\` An enum
\`--boolean\` A boolean
"
`);
});

it("renders the negative description when a boolean default is true", async () => {
const command = defineCommand({
meta: {
name: "Commander",
description: "A command",
},
args: {
boolean: {
type: "boolean",
name: "boolean",
default: true,
description: "A boolean",
negativeDescription: "A negative boolean",
},
},
});

const usage = await renderUsage(command);

expect(usage).toMatchInlineSnapshot(`
"A command (Commander)

USAGE \`Commander [OPTIONS] \`

OPTIONS

\`--no-boolean\` A negative boolean
"
`);
});

it('renders arguments hints when "valueHint" is provided', async () => {
const command = defineCommand({
meta: {
name: "Commander",
description: "A command",
},
args: {
foo: {
type: "string",
description: "A foo",
valueHint: "FOO",
},
},
});

const usage = await renderUsage(command);

expect(usage).toMatchInlineSnapshot(`
"A command (Commander)

USAGE \`Commander [OPTIONS] \`

OPTIONS

\`--foo=<FOO>\` A foo
"
`);
});

it("renders the default value when provided", async () => {
const command = defineCommand({
meta: {
name: "Commander",
description: "A command",
},
args: {
foo: {
type: "string",
description: "A foo",
default: "bar",
},
},
});

const usage = await renderUsage(command);

expect(usage).toMatchInlineSnapshot(`
"A command (Commander)

USAGE \`Commander [OPTIONS] \`

OPTIONS

\`--foo="bar"\` A foo
"
`);
});

it("renders subcommands", async () => {
const command = defineCommand({
meta: {
name: "Commander",
description: "A command",
},
subCommands: {
sub: defineCommand({
meta: {
name: "Subcommander",
description: "A subcommand",
},
}),
},
});

const usage = await renderUsage(command);

expect(usage).toMatchInlineSnapshot(`
"A command (Commander)

USAGE \`Commander sub\`

COMMANDS

\`sub\` A subcommand

Use \`Commander <command> --help\` for more information about a command."
`);
});

it("renders both arguments and subcommands", async () => {
const command = defineCommand({
meta: {
name: "Commander",
description: "A command",
},
args: {
foo: {
required: true,
description: "A foo",
},
},
subCommands: {
sub: defineCommand({
meta: {
name: "Subcommander",
description: "A subcommand",
},
}),
},
});

const usage = await renderUsage(command);

expect(usage).toMatchInlineSnapshot(`
"A command (Commander)

USAGE \`Commander [OPTIONS] --foo sub\`

OPTIONS

\`--foo (required)\` A foo

COMMANDS

\`sub\` A subcommand

Use \`Commander <command> --help\` for more information about a command."
`);
});

it("does not render hidden subcommands", async () => {
const command = defineCommand({
meta: {
name: "Commander",
description: "A command",
},
subCommands: {
sub: defineCommand({
meta: {
name: "Subcommander",
description: "A subcommand",
hidden: true,
},
}),
start: defineCommand({
meta: {
name: "Start",
description: "A start",
},
}),
},
});

const usage = await renderUsage(command);

expect(usage).toMatchInlineSnapshot(`
"A command (Commander)

USAGE \`Commander start\`

COMMANDS

\`start\` A start

Use \`Commander <command> --help\` for more information about a command."
`);
});

it("uses parents meta to explain how to run sub commands", async () => {
const childCommand = defineCommand({
meta: {
name: "child-command",
description: "A child command",
},
args: {
foo: {
type: "string",
description: "A foo",
},
},
subCommands: {
"sub-command": defineCommand({}),
},
});

const parentCommand = defineCommand({
meta: {
name: "parent-command",
},
subCommands: {
sub: childCommand,
},
});

const usage = await renderUsage(childCommand, parentCommand);

expect(usage).toMatchInlineSnapshot(`
"A child command (parent-command child-command)

USAGE \`parent-command child-command [OPTIONS] sub-command\`

OPTIONS

\`--foo\` A foo

COMMANDS

\`sub-command\`

Use \`parent-command child-command <command> --help\` for more information about a command."
`);
});
});