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
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:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user