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
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed

- A test now holds the README's command tables to the command registry. The
twenty missing options below were not the defect — they were the symptom. The
defect is that a table is a hand-maintained view of something derivable, and
nothing recomputed it: `--help` is generated by Click and stays complete, so
the gap never hurt enough to be noticed, and every later check *read* the
table, which looks complete when you read it.

The test compares instead: every option in the registry must appear in the
README in one of its forms, every command must have a row, and the section
counters must sum to the number of commands. It found one more defect on its
first run — `### Edit` claimed 11 where there are 8 commands, because
`insert-block` occupies five rows. Corrected.

Same shape as the `--dry-run` coverage test added earlier in this release,
and for the same reason: the source is the registry, the document is a view,
and a view must not be able to disagree with its source.

- The README documented 20 options that the CLI accepts but never named —
among them `--min-refs`, `--min-shared`, `--upsert-heading`, `--no-backlinks`
and the `--date` of the three journal writers. Some of them decide what a
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ logseq-cli get-page --name "My Page" # equivalent
| `add-journal-content --content TEXT [--date DATE]` | Add hierarchical content to journal (`--under-heading`, `--top-level`, `--dry-run`). `--date` defaults to today |
| `add-note-content --page NAME --content TEXT [--under-heading "## X"] [--no-create] [--property K=V] [--dry-run]` | Add content to any page; optionally under a heading (created if missing). The page is created when missing unless `--no-create` is given. `--property` sets `key:: value` on the root block, repeatable. `--dry-run` reports the target, the block count and whether page or heading would be created |

### Edit (11)
### Edit (8)

| Command | Description |
|---------|-------------|
Expand Down
109 changes: 109 additions & 0 deletions tests/test_readme_documents_options.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
"""The README's command tables must not fall behind the command registry.

Twenty options went undocumented for the life of the project — `--min-refs`,
`--min-shared`, `--upsert-heading`, `--no-backlinks` and the `--date` of the
three journal writers among them. Nineteen of those came in with the initial
import, which wrote the code and the first README in one go; the table was a
selection from the start and was read as a reference afterwards.

Nothing caught it, for a reason worth naming: ``--help`` is generated by Click
and is always complete, so the gap never hurt anyone enough to be noticed, and
every later check *read* the table — which looks complete when you read it.
Only a comparison shows what is not in it.

So this compares. The registry is the source, the README a view on it, and a
view must not be able to disagree with its source.
"""
import pathlib
import re

import pytest

from logseq_cli.cli import cli


README = pathlib.Path(__file__).resolve().parent.parent / "README.md"

# Present on nearly every command and documented once, in prose, rather than
# repeated in forty table rows.
UBIQUITOUS = {"--json", "--help"}


def _readme_text():
return README.read_text(encoding="utf-8")


def _documented_forms(param):
"""Every spelling of an option a caller could type.

A boolean flag has a positive and a negative form (``--create`` /
``--no-create``), and the README lists the one a caller actually passes —
which for a default-on flag is the negative. Either spelling counts as
documented; requiring a specific one would document a flag nobody uses.
"""
forms = list(getattr(param, "opts", ())) + list(getattr(param, "secondary_opts", ()))
return [f for f in forms if f.startswith("--") and f not in UBIQUITOUS]


def _all_options():
for name, command in sorted(cli.commands.items()):
for param in command.params:
forms = _documented_forms(param)
if forms:
yield name, forms


class TestEveryOptionIsDocumented:
def test_the_scan_sees_the_options(self):
"""Guards the guard: a scan that finds nothing would pass silently."""
found = {opt for _, forms in _all_options() for opt in forms}
for expected in ("--dry-run", "--min-refs", "--resolve-refs", "--limit"):
assert expected in found, (
f"{expected} exists but the scan missed it — the detection is "
"broken, not the README"
)

def test_every_option_appears_in_the_readme(self):
text = _readme_text()
missing = [
(command, "/".join(forms))
for command, forms in _all_options()
if not any(form in text for form in forms)
]
assert not missing, (
"these options exist but the README never names them, in any of "
f"their forms: {missing}"
)

def test_the_ubiquitous_options_are_explained_somewhere(self):
"""--json and --help are exempt above, so they must be covered in prose."""
text = _readme_text()
assert "--json" in text, "--json is exempt from the table check and must be documented in prose"


class TestSectionCountersMatchTheRegistry:
"""The `### Read (14)` counters are a hand-maintained view of the same list.

They are the other half of the same defect: a number beside a table that
nothing recomputes. `delete-block` is a documented alias of `remove-block`
and shares its row, so it is not counted twice.
"""

ALIASES = {"delete-block"}

def test_counters_sum_to_the_number_of_commands(self):
counters = [int(n) for n in re.findall(r"^### [A-Za-z][A-Za-z ]* \((\d+)\)", _readme_text(), re.M)]
assert counters, "no section counters found — the README layout changed"
expected = len(set(cli.commands) - self.ALIASES)
assert sum(counters) == expected, (
f"section counters sum to {sum(counters)} but the registry has "
f"{expected} commands (aliases excluded: {sorted(self.ALIASES)})"
)

def test_every_command_has_a_row(self):
text = _readme_text()
missing = [
name for name in sorted(cli.commands)
if name not in self.ALIASES and f"`{name}" not in text
]
assert not missing, f"commands with no README row: {missing}"
Loading