Skip to content
Merged
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
1 change: 1 addition & 0 deletions docs/COMMANDS.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Run `adata <command> --help` for the authoritative flag list.
- [subset](#subset)
- [split](#split)
- [concat](#concat)
- [convert](#convert)
- [create](#create)
- [export](#export)
- [import](#import)
Expand Down
2 changes: 1 addition & 1 deletion src/adata/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@

app = typer.Typer(
help="Streaming CLI for huge AnnData .h5ad and .zarr stores "
"(view, ls, subset, split, concat, export, import)."
"(view, ls, create, subset, split, concat, convert, export, import)."
)
# Use stderr for status/progress to keep stdout clean for data output
# force_terminal=True ensures Rich output is visible even in non-TTY environments
Expand Down
53 changes: 53 additions & 0 deletions tests/test_docs_are_accurate.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,3 +265,56 @@ def test_every_relative_link_resolves_inside_the_published_site(page):
+ "\nUse an absolute https://github.com/... URL for anything outside "
"docs/."
)


# ---------------------------------------------------------------------------
# every command is documented
#
# The tests above check one direction: that everything the docs claim really
# exists. They say nothing about the reverse, so `convert` shipped in 0.6.0
# with a section in COMMANDS.md that the table of contents never listed, and
# an app help string that named neither it nor `create`. Both were invisible
# to a suite that only walks from the docs to the code.
#
# Derived from the Click tree rather than a hardcoded list, so a command
# added tomorrow is covered without anyone remembering to add it here.


def _visible_commands():
import typer

command = typer.main.get_command(app)
return sorted(
name for name, sub in command.commands.items() if not sub.hidden
)


COMMANDS_MD = (REPO / "docs" / "COMMANDS.md").read_text()


@pytest.mark.parametrize("name", _visible_commands())
def test_every_command_has_a_section_in_the_reference(name):
assert f"## `{name}`" in COMMANDS_MD, (
f"`adata {name}` has no `## \\`{name}\\`` section in docs/COMMANDS.md"
)


@pytest.mark.parametrize("name", _visible_commands())
def test_every_command_is_in_the_table_of_contents(name):
contents = COMMANDS_MD.split("---", 1)[0]
assert f"](#{name})" in contents, (
f"`adata {name}` is missing from the table of contents in "
"docs/COMMANDS.md. The link text has no backticks: `- [name](#name)`."
)


@pytest.mark.parametrize("name", _visible_commands())
def test_every_command_is_named_in_the_app_help(name):
"""`adata --help` opens with a list; a command absent from it is hidden
in plain sight, since that line is the first thing anyone reads."""
import typer

help_text = typer.main.get_command(app).help or ""
assert name in help_text, (

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Match complete command names in the help list

When a future visible command has a name that is already a substring elsewhere in the prose, this test passes even if the command is omitted from the parenthesized list. For example, adding a store command without updating the help succeeds because store matches stores, contradicting the stated guarantee that commands added tomorrow are covered. Parse the listed commands or use delimiter-aware whole-name matching instead.

Useful? React with 👍 / 👎.

f"`{name}` is missing from the app help string in cli.py: {help_text!r}"
)
Loading