test(cli): boost coverage for registry.py render dispatch and edge cases
CI / lint (pull_request) Successful in 51s
CI / typecheck (pull_request) Successful in 58s
CI / quality (pull_request) Successful in 51s
CI / build (pull_request) Successful in 27s
CI / helm (pull_request) Successful in 34s
CI / push-validation (pull_request) Successful in 21s
CI / unit_tests (pull_request) Successful in 4m53s
CI / integration_tests (pull_request) Successful in 9m1s
CI / security (pull_request) Failing after 14m51s
CI / coverage (pull_request) Has been cancelled
CI / docker (pull_request) Has been cancelled
CI / status-check (pull_request) Has been cancelled

Add 16 BDD scenarios covering:
- PlainElementRenderer.render_element dispatch for all 9 element types
- _BaseElementRenderer.serialize() via PlainElementRenderer
- ColorElementRenderer.render_element dispatch for all element types
- TableElementRenderer can_render (True/False) and render_element dispatch
- RichElementRenderer.render_element dispatch + can_render True path
- JsonElementRenderer.render_element dispatch (all render_* return "")
- YamlElementRenderer.render_element dispatch (all render_* return "")
- RendererRegistry.resolve() unknown format fallback to plain
- RendererRegistry.resolve() ValueError when no plain fallback registered

Raises cli/output/registry.py coverage from 60.9% to pass the 96.5%
overall threshold.

ISSUES CLOSED: #917
This commit is contained in:
2026-05-29 23:52:28 -04:00
committed by drew
parent 14ab4610c5
commit 63cf1a7872
2 changed files with 243 additions and 0 deletions
@@ -154,3 +154,69 @@ Feature: Output Rendering Framework — Registry, Renderer, and Capabilities
Given a JsonMaterializer instance
When I call bind on the json materializer
Then no error is raised
# ================================================================
# Render dispatch — concrete renderer methods coverage
# ================================================================
Scenario: PlainElementRenderer dispatches all element types via render_element
Given a PlainElementRenderer
When I call render_element with each element type
Then no render dispatch error occurred
Scenario: PlainElementRenderer serialize assembles rendered text
Given a PlainElementRenderer
When I call base serialize with a text element
Then the base serialized output is a string
Scenario: ColorElementRenderer dispatches all element types via render_element
Given a ColorElementRenderer
When I call render_element with each element type
Then no render dispatch error occurred
Scenario: ColorElementRenderer can_render returns True when ANSI is supported
Given a ColorElementRenderer
Then color renderer can_render is True with ANSI support
Scenario: TableElementRenderer can_render checks is_tty flag
Given a TableElementRenderer
Then table renderer can_render is True on a TTY terminal
And table renderer can_render is False on a non-TTY terminal
Scenario: TableElementRenderer dispatches all element types via render_element
Given a TableElementRenderer
When I call render_element with each element type
Then no render dispatch error occurred
Scenario: RichElementRenderer dispatches all element types via render_element
Given a RichElementRenderer
When I call render_element with each element type
Then no render dispatch error occurred
Scenario: RichElementRenderer can_render returns True with cursor movement
Given a RichElementRenderer
Then rich renderer can_render is True with cursor movement
Scenario: JsonElementRenderer dispatches all element types returning empty strings
Given a JsonElementRenderer
When I call render_element with each element type
Then no render dispatch error occurred
Scenario: YamlElementRenderer dispatches all element types returning empty strings
Given a YamlElementRenderer
When I call render_element with each element type
Then no render dispatch error occurred
# ================================================================
# RendererRegistry edge-case paths
# ================================================================
Scenario: RendererRegistry resolve unknown format falls back to plain
Given the default renderer registry
When I resolve format "unregistered_format" from the registry
Then the resolved renderer format name is "plain"
Scenario: RendererRegistry resolve raises ValueError when no plain fallback exists
Given a registry with no plain and a non-renderable format
When I try to resolve the non-renderable format
Then a ValueError is raised from resolve
@@ -414,3 +414,180 @@ def step_json_coldef_type_key(context: Context) -> None:
def step_yaml_not_contains(context: Context, text: str) -> None:
output = context.strategy.get_output()
assert text not in output, f"Found {text!r} in YAML output: {output[:200]}"
# ===========================================================================
# TableElementRenderer given
# ===========================================================================
@given("a TableElementRenderer")
def step_table_renderer(context: Context) -> None:
from cleveragents.cli.output.registry import TableElementRenderer
context.element_renderer = TableElementRenderer()
# ===========================================================================
# Generic render_element dispatch — covers all render_* methods
# ===========================================================================
@when("I call render_element with each element type")
def step_render_all_element_types(context: Context) -> None:
from cleveragents.cli.output.handles import (
ActionHint,
CodeBlock,
DiffBlock,
ProgressIndicator,
Separator,
StatusMessage,
Table,
TextBlock,
Tree,
TreeNode,
)
elements = [
TextBlock(content="hello world"),
StatusMessage(message="all ok"),
CodeBlock(content="x = 1"),
DiffBlock(),
Separator(),
ActionHint(commands=["ls", "pwd"]),
Table(title="data"),
Tree(root=TreeNode(label="root")),
ProgressIndicator(label="loading"),
]
context.render_dispatch_error = None
try:
for elem in elements:
context.element_renderer.render_element(elem)
except Exception as exc:
context.render_dispatch_error = exc
@then("no render dispatch error occurred")
def step_no_dispatch_error(context: Context) -> None:
err = getattr(context, "render_dispatch_error", None)
assert err is None, f"Unexpected render error: {err}"
# ===========================================================================
# _BaseElementRenderer.serialize() via PlainElementRenderer
# ===========================================================================
@when("I call base serialize with a text element")
def step_base_serialize_text(context: Context) -> None:
from cleveragents.cli.output.handles import TextBlock
from cleveragents.cli.output.session import StructuredOutput
output = StructuredOutput(
command="test-base",
session_id="ses-base-test",
elements=[TextBlock(content="hello from base serialize")],
exit_code=0,
)
context.base_serialized = context.element_renderer.serialize(output)
@then("the base serialized output is a string")
def step_base_serialized_is_string(context: Context) -> None:
assert isinstance(context.base_serialized, str)
# ===========================================================================
# can_render True paths
# ===========================================================================
@then("color renderer can_render is True with ANSI support")
def step_color_can_render_true(context: Context) -> None:
caps = TerminalCapabilities(
is_tty=True,
supports_ansi=True,
supports_cursor_movement=True,
term_program="xterm-256color",
)
assert context.element_renderer.can_render(caps) is True
@then("table renderer can_render is True on a TTY terminal")
def step_table_can_render_true(context: Context) -> None:
caps = TerminalCapabilities(
is_tty=True,
supports_ansi=True,
supports_cursor_movement=True,
term_program="xterm",
)
assert context.element_renderer.can_render(caps) is True
@then("table renderer can_render is False on a non-TTY terminal")
def step_table_can_render_false(context: Context) -> None:
caps = TerminalCapabilities(
is_tty=False,
supports_ansi=False,
supports_cursor_movement=False,
term_program="",
)
assert context.element_renderer.can_render(caps) is False
@then("rich renderer can_render is True with cursor movement")
def step_rich_can_render_true(context: Context) -> None:
caps = TerminalCapabilities(
is_tty=True,
supports_ansi=True,
supports_cursor_movement=True,
term_program="xterm",
)
assert context.element_renderer.can_render(caps) is True
# ===========================================================================
# RendererRegistry error paths
# ===========================================================================
@given("a registry with no plain and a non-renderable format")
def step_registry_no_plain_non_renderable(context: Context) -> None:
from cleveragents.cli.output.materializers import PlainMaterializer
from cleveragents.cli.output.registry import RendererRegistry
class _NeverRenders:
format_name = "norender"
def can_render(self, terminal_caps: TerminalCapabilities) -> bool:
return False
context.registry = RendererRegistry()
context.registry.register(
"norender",
strategy_factory=lambda caps: PlainMaterializer(),
renderer_factory=lambda caps: _NeverRenders(),
fallback=None,
)
context.no_plain_format = "norender"
@when("I try to resolve the non-renderable format")
def step_resolve_non_renderable(context: Context) -> None:
caps = TerminalCapabilities(
is_tty=False,
supports_ansi=False,
supports_cursor_movement=False,
term_program="",
)
context.resolve_error = None
try:
context.registry.resolve(context.no_plain_format, caps)
except ValueError as exc:
context.resolve_error = exc
@then("a ValueError is raised from resolve")
def step_valueerror_raised(context: Context) -> None:
assert context.resolve_error is not None, "Expected ValueError but none was raised"
assert isinstance(context.resolve_error, ValueError)