2980b14e7b
Add ~60 new behave scenarios across output rendering, tool router, file ops, and skill search features targeting uncovered lines and branches. Key additions: - ElementHandle validation guards (empty id, type, negative index, None args) - Handle close/context-manager edge cases - Table list-row and batch-row coverage - Color/box-draw/JSON/YAML materializer edge cases - Format selection paths (detect capabilities, explicit flag, empty format) - Session ColumnDef, double-close guard, force-close open handles - Tool router schema export and provider format scenarios - File ops edge cases and search stat-failure/glob-include tests All nox checks pass: lint, typecheck (0 errors), unit_tests (4730 scenarios), coverage_report (97.0% >= 97% threshold).
832 lines
34 KiB
Gherkin
832 lines
34 KiB
Gherkin
Feature: Output Rendering Framework
|
|
The output rendering framework decouples command output data from visual
|
|
presentation through a session/handle/materialiser pipeline.
|
|
|
|
# --- OutputSession lifecycle ---
|
|
|
|
Scenario: OutputSession opens and closes cleanly
|
|
Given an OutputSession with format "plain"
|
|
When the session is closed
|
|
Then the session state is "closed"
|
|
|
|
Scenario: OutputSession works as a context manager
|
|
When I use an OutputSession context manager with format "plain"
|
|
Then the session was closed automatically
|
|
|
|
Scenario: OutputSession context manager sets exit_code on exception
|
|
When I use an OutputSession context manager that raises an exception
|
|
Then the session exit code is 1
|
|
|
|
# --- PanelHandle ---
|
|
|
|
Scenario: PanelHandle set_entry adds key-value pairs
|
|
Given an OutputSession with format "plain"
|
|
When I create a panel handle with title "Details"
|
|
And I set entry "Name" to "test-project"
|
|
And I set entry "Status" to "active"
|
|
And I close the panel handle
|
|
Then the panel has 2 entries
|
|
And the panel entry "Name" has value "test-project"
|
|
|
|
Scenario: PanelHandle set_entry updates existing key
|
|
Given an OutputSession with format "plain"
|
|
When I create a panel handle with title "Details"
|
|
And I set entry "Name" to "old-value"
|
|
And I set entry "Name" to "new-value"
|
|
Then the panel entry "Name" has value "new-value"
|
|
And the panel has 1 entries
|
|
|
|
Scenario: PanelHandle set_entries batch update
|
|
Given an OutputSession with format "plain"
|
|
When I create a panel handle with title "Details"
|
|
And I batch set entries with keys "A,B,C" and values "1,2,3"
|
|
Then the panel has 3 entries
|
|
|
|
Scenario: PanelHandle remove_entry removes a key
|
|
Given an OutputSession with format "plain"
|
|
When I create a panel handle with title "Details"
|
|
And I set entry "Name" to "value"
|
|
And I set entry "Extra" to "remove-me"
|
|
And I remove entry "Extra"
|
|
Then the panel has 1 entries
|
|
|
|
Scenario: PanelHandle add_line convenience alias
|
|
Given an OutputSession with format "plain"
|
|
When I create a panel handle with title "Details"
|
|
And I add line "Key" with value "Val"
|
|
Then the panel entry "Key" has value "Val"
|
|
|
|
Scenario: Writing to a closed PanelHandle raises ElementClosedError
|
|
Given an OutputSession with format "plain"
|
|
When I create a panel handle with title "Details"
|
|
And I close the panel handle
|
|
Then setting entry "X" to "Y" raises ElementClosedError
|
|
|
|
# --- TableHandle ---
|
|
|
|
Scenario: TableHandle add_row with dict
|
|
Given an OutputSession with format "plain"
|
|
When I create a table handle with columns "Name,Status"
|
|
And I add a dict row with Name "foo" and Status "active"
|
|
Then the table has 1 rows
|
|
|
|
Scenario: TableHandle add_row with list
|
|
Given an OutputSession with format "plain"
|
|
When I create a table handle with columns "Name,Status"
|
|
And I add a list row with values "bar,inactive"
|
|
Then the table has 1 rows
|
|
|
|
Scenario: TableHandle add_rows batch
|
|
Given an OutputSession with format "plain"
|
|
When I create a table handle with columns "Name,Status"
|
|
And I add 3 batch rows
|
|
Then the table has 3 rows
|
|
|
|
Scenario: TableHandle set_summary
|
|
Given an OutputSession with format "plain"
|
|
When I create a table handle with columns "Name,Count"
|
|
And I add a dict row with Name "a" and Status "1"
|
|
And I set summary with total "1"
|
|
Then the table summary is set
|
|
|
|
Scenario: TableHandle set_sort_key
|
|
Given an OutputSession with format "plain"
|
|
When I create a table handle with columns "Name,Status"
|
|
And I set sort key to "Name"
|
|
Then the table sort key is "Name"
|
|
|
|
Scenario: Writing to a closed TableHandle raises ElementClosedError
|
|
Given an OutputSession with format "plain"
|
|
When I create a table handle with columns "Name,Status"
|
|
And I close the table handle
|
|
Then adding a row raises ElementClosedError
|
|
|
|
# --- StatusHandle ---
|
|
|
|
Scenario: StatusHandle creation and update
|
|
Given an OutputSession with format "plain"
|
|
When I create a status handle with message "Working..."
|
|
And I update the status message to "Done"
|
|
And I update the status level to "ok"
|
|
And I set the status detail to "All tasks complete"
|
|
Then the status message is "Done"
|
|
And the status level is "ok"
|
|
And the status detail is "All tasks complete"
|
|
|
|
Scenario: StatusHandle rejects invalid level
|
|
Given an OutputSession with format "plain"
|
|
When I create a status handle with message "Test"
|
|
Then setting status level to "invalid" raises ValueError
|
|
|
|
# --- ProgressHandle ---
|
|
|
|
Scenario: ProgressHandle set_progress updates current and total
|
|
Given an OutputSession with format "plain"
|
|
When I create a progress handle labelled "Uploading"
|
|
And I set progress to 50 of 100
|
|
Then the progress current is 50
|
|
And the progress total is 100
|
|
|
|
Scenario: ProgressHandle tick increments by 1
|
|
Given an OutputSession with format "plain"
|
|
When I create a progress handle labelled "Processing"
|
|
And I tick the progress 3 times
|
|
Then the progress current is 3
|
|
|
|
Scenario: ProgressHandle increment by delta
|
|
Given an OutputSession with format "plain"
|
|
When I create a progress handle labelled "Processing"
|
|
And I increment progress by 5
|
|
Then the progress current is 5
|
|
|
|
Scenario: ProgressHandle set_label changes label
|
|
Given an OutputSession with format "plain"
|
|
When I create a progress handle labelled "Old Label"
|
|
And I set progress label to "New Label"
|
|
Then the progress label is "New Label"
|
|
|
|
Scenario: ProgressHandle set_indeterminate enables spinner mode
|
|
Given an OutputSession with format "plain"
|
|
When I create a progress handle labelled "Loading"
|
|
And I set progress to indeterminate mode
|
|
Then the progress is indeterminate
|
|
|
|
Scenario: ProgressHandle set_step_status tracks steps
|
|
Given an OutputSession with format "plain"
|
|
When I create a progress handle labelled "Pipeline" with steps "A,B,C"
|
|
And I set step "A" status to "done"
|
|
And I set step "B" status to "active"
|
|
Then step "A" has status "done"
|
|
And step "B" has status "active"
|
|
And step "C" has status "pending"
|
|
|
|
Scenario: ProgressHandle rejects negative current
|
|
Given an OutputSession with format "plain"
|
|
When I create a progress handle labelled "Test"
|
|
Then setting progress to -1 raises ValueError
|
|
|
|
Scenario: ProgressHandle rejects invalid step status
|
|
Given an OutputSession with format "plain"
|
|
When I create a progress handle labelled "Test"
|
|
Then setting step "X" to status "bogus" raises ValueError
|
|
|
|
# --- PlainMaterializer ---
|
|
|
|
Scenario: PlainMaterializer renders panel as plain ASCII
|
|
Given an OutputSession with format "plain" using PlainMaterializer
|
|
When I create a panel handle with title "Info"
|
|
And I set entry "Name" to "test"
|
|
And I set entry "Value" to "42"
|
|
And I close the panel handle
|
|
And the session is closed
|
|
Then the plain output contains "Info"
|
|
And the plain output contains "Name: test"
|
|
And the plain output contains "Value: 42"
|
|
And the plain output does not contain ANSI codes
|
|
|
|
Scenario: PlainMaterializer renders table as plain ASCII
|
|
Given an OutputSession with format "plain" using PlainMaterializer
|
|
When I create a table handle with columns "Name,Status"
|
|
And I add a dict row with Name "foo" and Status "active"
|
|
And I close the table handle
|
|
And the session is closed
|
|
Then the plain output contains "Name"
|
|
And the plain output contains "foo"
|
|
And the plain output does not contain ANSI codes
|
|
|
|
Scenario: PlainMaterializer renders status as plain ASCII
|
|
Given an OutputSession with format "plain" using PlainMaterializer
|
|
When I create a status handle with message "All good"
|
|
And I update the status level to "ok"
|
|
And I close the status handle
|
|
And the session is closed
|
|
Then the plain output contains "[OK]"
|
|
And the plain output contains "All good"
|
|
|
|
Scenario: PlainMaterializer renders progress as plain ASCII
|
|
Given an OutputSession with format "plain" using PlainMaterializer
|
|
When I create a progress handle labelled "Downloading" with total 100
|
|
And I set progress to 75 of 100
|
|
And I close the progress handle
|
|
And the session is closed
|
|
Then the plain output contains "Downloading"
|
|
And the plain output contains "75/100"
|
|
|
|
# --- ColorMaterializer ---
|
|
|
|
Scenario: ColorMaterializer renders panel with ANSI codes
|
|
Given an OutputSession with format "color" using ColorMaterializer
|
|
When I create a panel handle with title "Coloured"
|
|
And I set entry "Key" to "val"
|
|
And I close the panel handle
|
|
And the session is closed
|
|
Then the color output contains ANSI codes
|
|
And the color output contains "Coloured"
|
|
|
|
Scenario: ColorMaterializer renders status with ANSI codes
|
|
Given an OutputSession with format "color" using ColorMaterializer
|
|
When I create a status handle with message "Warning text"
|
|
And I update the status level to "warn"
|
|
And I close the status handle
|
|
And the session is closed
|
|
Then the color output contains "[WARN]"
|
|
And the color output contains ANSI codes
|
|
|
|
# --- TableMaterializer ---
|
|
|
|
Scenario: TableMaterializer renders table with box-drawing
|
|
Given an OutputSession with format "table" using TableMaterializer
|
|
When I create a table handle with columns "Name,Status"
|
|
And I add a dict row with Name "svc" and Status "up"
|
|
And I close the table handle
|
|
And the session is closed
|
|
Then the table output contains box-drawing characters
|
|
And the table output contains "svc"
|
|
|
|
# --- RichMaterializer ---
|
|
|
|
Scenario: RichMaterializer renders output
|
|
Given an OutputSession with format "rich" using RichMaterializer
|
|
When I create a panel handle with title "Rich Panel"
|
|
And I set entry "Hello" to "World"
|
|
And I close the panel handle
|
|
And the session is closed
|
|
Then the rich output contains "Rich Panel"
|
|
|
|
# --- JsonMaterializer ---
|
|
|
|
Scenario: JsonMaterializer serialises all elements as JSON
|
|
Given an OutputSession with format "json" using JsonMaterializer
|
|
When I create a panel handle with title "JSON Panel"
|
|
And I set entry "Key1" to "Val1"
|
|
And I close the panel handle
|
|
And the session is closed
|
|
Then the json output is valid JSON
|
|
And the json output contains element type "panel"
|
|
And the json output contains "Key1"
|
|
|
|
Scenario: JsonMaterializer includes tables in output
|
|
Given an OutputSession with format "json" using JsonMaterializer
|
|
When I create a table handle with columns "Col1,Col2"
|
|
And I add a dict row with Name "Col1" and Status "Val1"
|
|
And I close the table handle
|
|
And the session is closed
|
|
Then the json output is valid JSON
|
|
And the json output contains element type "table"
|
|
|
|
# --- YamlMaterializer ---
|
|
|
|
Scenario: YamlMaterializer serialises all elements as YAML
|
|
Given an OutputSession with format "yaml" using YamlMaterializer
|
|
When I create a panel handle with title "YAML Panel"
|
|
And I set entry "Alpha" to "Beta"
|
|
And I close the panel handle
|
|
And the session is closed
|
|
Then the yaml output is valid YAML
|
|
And the yaml output contains "Alpha"
|
|
|
|
# --- Error envelope ---
|
|
|
|
Scenario: JsonMaterializer error envelope
|
|
Given a JsonMaterializer
|
|
When I generate an error envelope with code "NOT_FOUND" and message "Resource not found"
|
|
Then the json error output contains "NOT_FOUND"
|
|
And the json error output contains "Resource not found"
|
|
|
|
Scenario: YamlMaterializer error envelope
|
|
Given a YamlMaterializer
|
|
When I generate a yaml error envelope with code "FORBIDDEN" and message "Access denied"
|
|
Then the yaml error output contains "FORBIDDEN"
|
|
And the yaml error output contains "Access denied"
|
|
|
|
# --- Concurrent producers ---
|
|
|
|
Scenario: Concurrent producers do not interleave in plain mode
|
|
Given an OutputSession with format "plain" using PlainMaterializer
|
|
When I create two panels concurrently and close them out of order
|
|
And the session is closed
|
|
Then the plain output has panel A before panel B
|
|
|
|
# --- Format fallback ---
|
|
|
|
Scenario: Rich format falls back to plain when no TTY
|
|
Given a non-TTY terminal environment
|
|
When I select a materializer for format "rich"
|
|
Then the selected materializer is PlainMaterializer
|
|
|
|
Scenario: Color format falls back to plain when no ANSI support
|
|
Given a terminal without ANSI support
|
|
When I select a materializer for format "color"
|
|
Then the selected materializer is PlainMaterializer
|
|
|
|
Scenario: Plain format always works
|
|
Given a non-TTY terminal environment
|
|
When I select a materializer for format "plain"
|
|
Then the selected materializer is PlainMaterializer
|
|
|
|
Scenario: JSON format ignores terminal capabilities
|
|
Given a non-TTY terminal environment
|
|
When I select a materializer for format "json"
|
|
Then the selected materializer is JsonMaterializer
|
|
|
|
Scenario: Invalid format raises ValueError
|
|
When I attempt to select a materializer for format "invalid"
|
|
Then a ValueError is raised
|
|
|
|
# --- Backward compatibility ---
|
|
|
|
Scenario: format_output still works with plain format
|
|
When I call format_output with a dict and format "plain"
|
|
Then the output contains key-value pairs
|
|
|
|
Scenario: format_output still works with json format
|
|
When I call format_output with a dict and format "json"
|
|
Then the output is valid JSON
|
|
|
|
Scenario: format_output_session renders via OutputSession
|
|
When I call format_output_session with a dict and format "plain"
|
|
Then the session output contains the dict keys
|
|
|
|
# --- Handle context manager ---
|
|
|
|
Scenario: Element handle works as context manager
|
|
Given an OutputSession with format "plain"
|
|
When I use a panel handle as a context manager
|
|
Then the handle is closed after the context exits
|
|
|
|
# --- Snapshot ---
|
|
|
|
Scenario: Session snapshot captures all elements
|
|
Given an OutputSession with format "plain"
|
|
When I create a panel handle with title "P1"
|
|
And I set entry "K" to "V"
|
|
And I create a table handle with columns "A,B"
|
|
And I add a dict row with Name "A" and Status "B"
|
|
Then the session snapshot has 2 elements
|
|
|
|
# --- Materializer selection with explicit flag ---
|
|
|
|
Scenario: Explicit format overrides fallback
|
|
Given a non-TTY terminal environment
|
|
When I select a materializer for format "rich" with explicit flag
|
|
Then the selected materializer is RichMaterializer
|
|
|
|
# ================================================================
|
|
# Coverage boost: handle validation guards
|
|
# ================================================================
|
|
|
|
Scenario: PanelHandle rejects empty key in set_entry
|
|
Given an OutputSession with format "plain"
|
|
When I create a panel handle with title "V"
|
|
Then setting entry with empty key raises ValueError
|
|
|
|
Scenario: PanelHandle rejects None entries in set_entries
|
|
Given an OutputSession with format "plain"
|
|
When I create a panel handle with title "V"
|
|
Then batch setting None entries raises ValueError
|
|
|
|
Scenario: PanelHandle rejects empty key in remove_entry
|
|
Given an OutputSession with format "plain"
|
|
When I create a panel handle with title "V"
|
|
Then removing entry with empty key raises ValueError
|
|
|
|
Scenario: TableHandle rejects None row in add_row
|
|
Given an OutputSession with format "plain"
|
|
When I create a table handle with columns "A,B"
|
|
Then adding a None row raises ValueError
|
|
|
|
Scenario: TableHandle rejects None rows in add_rows
|
|
Given an OutputSession with format "plain"
|
|
When I create a table handle with columns "A,B"
|
|
Then adding None rows raises ValueError
|
|
|
|
Scenario: TableHandle rejects None summary
|
|
Given an OutputSession with format "plain"
|
|
When I create a table handle with columns "A,B"
|
|
Then setting None summary raises ValueError
|
|
|
|
Scenario: TableHandle rejects empty sort key
|
|
Given an OutputSession with format "plain"
|
|
When I create a table handle with columns "A,B"
|
|
Then setting empty sort key raises ValueError
|
|
|
|
Scenario: StatusHandle rejects empty message
|
|
Given an OutputSession with format "plain"
|
|
When I create a status handle with message "ok"
|
|
Then setting empty status message raises ValueError
|
|
|
|
Scenario: StatusHandle rejects empty level
|
|
Given an OutputSession with format "plain"
|
|
When I create a status handle with message "ok"
|
|
Then setting empty status level raises ValueError
|
|
|
|
Scenario: ProgressHandle rejects empty label
|
|
Given an OutputSession with format "plain"
|
|
When I create a progress handle labelled "Loading"
|
|
Then setting empty progress label raises ValueError
|
|
|
|
Scenario: ProgressHandle rejects negative delta
|
|
Given an OutputSession with format "plain"
|
|
When I create a progress handle labelled "Loading" with total 10
|
|
Then incrementing by negative delta raises ValueError
|
|
|
|
Scenario: ProgressHandle rejects empty step label
|
|
Given an OutputSession with format "plain"
|
|
When I create a progress handle labelled "Loading" with steps "A,B"
|
|
Then setting step status with empty label raises ValueError
|
|
|
|
# ================================================================
|
|
# Coverage boost: handle data paths
|
|
# ================================================================
|
|
|
|
Scenario: PanelHandle set_entries updates existing entry
|
|
Given an OutputSession with format "plain"
|
|
When I create a panel handle with title "UP"
|
|
And I set entry "K" to "old"
|
|
And I batch set entries with keys "K" and values "new"
|
|
Then the panel entry "K" has value "new"
|
|
|
|
Scenario: TableHandle add_rows with list-type rows
|
|
Given an OutputSession with format "plain"
|
|
When I create a table handle with columns "A,B"
|
|
And I batch add list rows with values "x,y" and "m,n"
|
|
Then the table has 2 rows
|
|
|
|
Scenario: ProgressHandle increment adds to current
|
|
Given an OutputSession with format "plain"
|
|
When I create a progress handle labelled "Down" with total 100
|
|
And I set progress to 10 of 100
|
|
And I increment progress by 5
|
|
Then the progress current is 15
|
|
|
|
Scenario: ProgressHandle set_step_status creates steps list
|
|
Given an OutputSession with format "plain"
|
|
When I create a progress handle labelled "Steps"
|
|
And I set step "Alpha" status to "done"
|
|
Then step "Alpha" has status "done"
|
|
|
|
# ================================================================
|
|
# Coverage boost: color materializer rendering
|
|
# ================================================================
|
|
|
|
Scenario: Color materializer renders table with ANSI codes
|
|
Given an OutputSession with format "color" using ColorMaterializer
|
|
When I create a table handle with columns "Name,Status"
|
|
And I add a dict row with Name "foo" and Status "ok"
|
|
And I close the table handle
|
|
And the session is closed
|
|
Then the color output contains ANSI codes
|
|
And the color output contains "foo"
|
|
|
|
Scenario: Color materializer renders empty table
|
|
Given an OutputSession with format "color" using ColorMaterializer
|
|
When I create a table with no columns
|
|
And I close the table handle
|
|
And the session is closed
|
|
Then the color output contains "empty table"
|
|
|
|
Scenario: Color materializer renders status with detail
|
|
Given an OutputSession with format "color" using ColorMaterializer
|
|
When I create a status handle with message "Building"
|
|
And I set the status detail to "step 3 of 10"
|
|
And I close the status handle
|
|
And the session is closed
|
|
Then the color output contains ANSI codes
|
|
And the color output contains "Building"
|
|
|
|
Scenario: Color materializer renders progress indicator
|
|
Given an OutputSession with format "color" using ColorMaterializer
|
|
When I create a progress handle labelled "Downloading" with total 50
|
|
And I set progress to 25 of 50
|
|
And I close the progress handle
|
|
And the session is closed
|
|
Then the color output contains ANSI codes
|
|
And the color output contains "Downloading"
|
|
|
|
Scenario: Color materializer renders panel with style hints
|
|
Given an OutputSession with format "color" using ColorMaterializer
|
|
When I create a panel handle with title "Results"
|
|
And I set entry with key "passed" value "yes" and style hint "success"
|
|
And I set entry with key "warnings" value "3" and style hint "warning"
|
|
And I set entry with key "errors" value "1" and style hint "error"
|
|
And I close the panel handle
|
|
And the session is closed
|
|
Then the color output contains ANSI codes
|
|
And the color output contains "passed"
|
|
|
|
# ================================================================
|
|
# Coverage boost: plain materializer edge cases
|
|
# ================================================================
|
|
|
|
Scenario: Plain materializer renders empty table
|
|
Given an OutputSession with format "plain" using PlainMaterializer
|
|
When I create a table with no columns
|
|
And I close the table handle
|
|
And the session is closed
|
|
Then the plain output contains "empty table"
|
|
|
|
Scenario: Plain materializer renders table with title and summary
|
|
Given an OutputSession with format "plain" using PlainMaterializer
|
|
When I create a table handle with columns "Name,Count"
|
|
And I set table title to "Report"
|
|
And I add a dict row with Name "A" and Status "10"
|
|
And I set summary with total "10"
|
|
And I close the table handle
|
|
And the session is closed
|
|
Then the plain output contains "Report"
|
|
|
|
Scenario: Plain materializer renders status with detail
|
|
Given an OutputSession with format "plain" using PlainMaterializer
|
|
When I create a status handle with message "Processing"
|
|
And I set the status detail to "file 3 of 10"
|
|
And I close the status handle
|
|
And the session is closed
|
|
Then the plain output contains "Processing"
|
|
And the plain output contains "file 3 of 10"
|
|
|
|
Scenario: Plain materializer renders indeterminate progress
|
|
Given an OutputSession with format "plain" using PlainMaterializer
|
|
When I create a progress handle labelled "Scanning"
|
|
And I set progress to indeterminate mode
|
|
And I close the progress handle
|
|
And the session is closed
|
|
Then the plain output contains "Scanning"
|
|
|
|
Scenario: Plain materializer renders current-only progress
|
|
Given an OutputSession with format "plain" using PlainMaterializer
|
|
When I create a progress handle labelled "Items"
|
|
And I set progress current to 42
|
|
And I close the progress handle
|
|
And the session is closed
|
|
Then the plain output contains "Items"
|
|
And the plain output contains "42"
|
|
|
|
Scenario: Plain materializer renders progress with steps
|
|
Given an OutputSession with format "plain" using PlainMaterializer
|
|
When I create a progress handle labelled "Deploy" with steps "Build,Test,Ship"
|
|
And I set step "Build" status to "done"
|
|
And I set step "Test" status to "active"
|
|
And I close the progress handle
|
|
And the session is closed
|
|
Then the plain output contains "Build"
|
|
And the plain output contains "Test"
|
|
|
|
# ================================================================
|
|
# Coverage boost: serialization helpers (JSON/YAML)
|
|
# ================================================================
|
|
|
|
Scenario: JSON materializer serializes status element
|
|
Given an OutputSession with format "json" using JsonMaterializer
|
|
When I create a status handle with message "Done"
|
|
And I set the status detail to "All passed"
|
|
And I close the status handle
|
|
And the session is closed
|
|
Then the json output is valid JSON
|
|
And the json output contains element type "status"
|
|
And the json output contains "Done"
|
|
|
|
Scenario: JSON materializer serializes progress with steps
|
|
Given an OutputSession with format "json" using JsonMaterializer
|
|
When I create a progress handle labelled "Pipeline" with steps "A,B"
|
|
And I set step "A" status to "done"
|
|
And I close the progress handle
|
|
And the session is closed
|
|
Then the json output is valid JSON
|
|
And the json output contains element type "progress"
|
|
And the json output contains "Pipeline"
|
|
|
|
Scenario: JSON materializer serializes panel with style hint and icon
|
|
Given an OutputSession with format "json" using JsonMaterializer
|
|
When I create a panel handle with title "Info"
|
|
And I set entry with key "status" value "ok" and style hint "success"
|
|
And I set entry with icon key "icon_entry" value "check" and icon "check_mark"
|
|
And I close the panel handle
|
|
And the session is closed
|
|
Then the json output is valid JSON
|
|
And the json output contains "style_hint"
|
|
And the json output contains "icon"
|
|
|
|
Scenario: JSON error envelope includes details
|
|
Given a JsonMaterializer
|
|
When I generate an error envelope with code "ERR" message "fail" and details "traceback"
|
|
Then the json error output contains "traceback"
|
|
|
|
# ================================================================
|
|
# Coverage boost: box-drawing materializer edge cases
|
|
# ================================================================
|
|
|
|
Scenario: Box-drawing materializer renders empty table
|
|
Given an OutputSession with format "table" using TableMaterializer
|
|
When I create a table with no columns
|
|
And I close the table handle
|
|
And the session is closed
|
|
Then the strategy output contains "empty table"
|
|
|
|
Scenario: Box-drawing materializer renders table with title
|
|
Given an OutputSession with format "table" using TableMaterializer
|
|
When I create a table handle with columns "X,Y"
|
|
And I set table title to "Summary"
|
|
And I add a dict row with Name "X" and Status "1"
|
|
And I close the table handle
|
|
And the session is closed
|
|
Then the strategy output contains "Summary"
|
|
|
|
# ================================================================
|
|
# Coverage boost: format selection paths
|
|
# ================================================================
|
|
|
|
Scenario: Select yaml materializer
|
|
When I select a materializer for format "yaml"
|
|
Then the selected materializer is YamlMaterializer
|
|
|
|
Scenario: Select materializer rejects empty format
|
|
When I attempt to select a materializer with empty format
|
|
Then a ValueError is raised
|
|
|
|
Scenario: Rich format falls back to color when no cursor support
|
|
Given a terminal with ANSI but no cursor support
|
|
When I select a materializer for format "rich"
|
|
Then the selected materializer is ColorMaterializer
|
|
|
|
Scenario: Color format uses color materializer with ANSI support
|
|
Given a terminal with ANSI support
|
|
When I select a materializer for format "color"
|
|
Then the selected materializer is ColorMaterializer
|
|
|
|
Scenario: Table format uses table materializer on TTY
|
|
Given a TTY terminal with ANSI support
|
|
When I select a materializer for format "table"
|
|
Then the selected materializer is TableMaterializer
|
|
|
|
Scenario: Table format falls back to plain on non-TTY
|
|
Given a non-TTY terminal environment
|
|
When I select a materializer for format "table"
|
|
Then the selected materializer is PlainMaterializer
|
|
|
|
# ================================================================
|
|
# Coverage boost: session edge cases
|
|
# ================================================================
|
|
|
|
Scenario: Session rejects creating panel with empty title
|
|
Given an OutputSession with format "plain"
|
|
Then creating a panel with empty title raises ValueError
|
|
|
|
Scenario: Session rejects creating status with empty message
|
|
Given an OutputSession with format "plain"
|
|
Then creating a status with empty message raises ValueError
|
|
|
|
Scenario: Session rejects creating progress with empty label
|
|
Given an OutputSession with format "plain"
|
|
Then creating a progress with empty label raises ValueError
|
|
|
|
Scenario: Closed session rejects new handles
|
|
Given an OutputSession with format "plain"
|
|
When the session is closed
|
|
Then creating a panel on closed session raises RuntimeError
|
|
|
|
Scenario: Session close force-closes open handles
|
|
Given an OutputSession with format "plain" using PlainMaterializer
|
|
When I create a panel handle with title "Unclosed"
|
|
And I set entry "A" to "B"
|
|
And the session is closed
|
|
Then the plain output contains "Unclosed"
|
|
|
|
# ================================================================
|
|
# Coverage boost: ElementHandle validation guards
|
|
# ================================================================
|
|
|
|
Scenario: ElementHandle rejects empty handle_id
|
|
Given an OutputSession with format "plain"
|
|
Then constructing a handle with empty handle_id raises ValueError
|
|
|
|
Scenario: ElementHandle rejects empty element_type
|
|
Given an OutputSession with format "plain"
|
|
Then constructing a handle with empty element_type raises ValueError
|
|
|
|
Scenario: ElementHandle rejects negative declaration_index
|
|
Given an OutputSession with format "plain"
|
|
Then constructing a handle with negative index raises ValueError
|
|
|
|
Scenario: ElementHandle rejects None session
|
|
Given an OutputSession with format "plain"
|
|
Then constructing a handle with None session raises ValueError
|
|
|
|
Scenario: ElementHandle rejects None element
|
|
Given an OutputSession with format "plain"
|
|
Then constructing a handle with None element raises ValueError
|
|
|
|
# ================================================================
|
|
# Coverage boost: handle close and __exit__ edge cases
|
|
# ================================================================
|
|
|
|
Scenario: Closing an already-closed handle is a no-op
|
|
Given an OutputSession with format "plain" using PlainMaterializer
|
|
When I create a panel handle with title "CloseMe"
|
|
And I close the panel handle
|
|
And I close the panel handle again
|
|
Then the handle state is "closed"
|
|
|
|
Scenario: Handle used as context manager auto-closes
|
|
Given an OutputSession with format "plain" using PlainMaterializer
|
|
When I use a panel handle as context manager with title "AutoClose"
|
|
Then the handle state is "closed"
|
|
And the plain output contains "AutoClose"
|
|
|
|
# ================================================================
|
|
# Coverage boost: table rows as list (not dict)
|
|
# ================================================================
|
|
|
|
Scenario: TableHandle accepts list rows
|
|
Given an OutputSession with format "plain" using PlainMaterializer
|
|
When I create a table handle with columns "A,B"
|
|
And I add a list row with values "x,y"
|
|
And I close the table handle
|
|
And the session is closed
|
|
Then the plain output contains "x"
|
|
|
|
Scenario: TableHandle batch add accepts list rows
|
|
Given an OutputSession with format "plain" using PlainMaterializer
|
|
When I create a table handle with columns "A,B"
|
|
And I batch add list rows with values "1,2" and "3,4"
|
|
And I close the table handle
|
|
And the session is closed
|
|
Then the plain output contains "1"
|
|
|
|
# ================================================================
|
|
# Coverage boost: color materializer table with title
|
|
# ================================================================
|
|
|
|
Scenario: Color materializer renders table title
|
|
Given an OutputSession with format "color" using ColorMaterializer
|
|
When I create a table handle with columns "Col1,Col2"
|
|
And I set table title to "Colored Title"
|
|
And I add a dict row with Name "Col1" and Status "val"
|
|
And I close the table handle
|
|
And the session is closed
|
|
Then the color output contains "Colored Title"
|
|
|
|
# ================================================================
|
|
# Coverage boost: box-draw table dispatch for non-table
|
|
# ================================================================
|
|
|
|
Scenario: Table materializer renders panel as plain text
|
|
Given an OutputSession with format "table" using TableMaterializer
|
|
When I create a panel handle with title "PanelInTable"
|
|
And I set entry "info" to "details"
|
|
And I close the panel handle
|
|
And the session is closed
|
|
Then the strategy output contains "PanelInTable"
|
|
|
|
# ================================================================
|
|
# Coverage boost: session creates table with ColumnDef objects
|
|
# ================================================================
|
|
|
|
Scenario: Session table accepts ColumnDef objects
|
|
Given an OutputSession with format "plain" using PlainMaterializer
|
|
When I create a table with ColumnDef objects "Alpha,Beta"
|
|
And I add a dict row with Name "Alpha" and Status "val1"
|
|
And I close the table handle
|
|
And the session is closed
|
|
Then the plain output contains "Alpha"
|
|
|
|
# ================================================================
|
|
# Coverage boost: detect_capabilities function
|
|
# ================================================================
|
|
|
|
Scenario: Detect terminal capabilities returns TerminalCapabilities
|
|
When I detect terminal capabilities
|
|
Then the capabilities object has is_tty field
|
|
And the capabilities object has supports_ansi field
|
|
|
|
# ================================================================
|
|
# Coverage boost: explicit format flag
|
|
# ================================================================
|
|
|
|
Scenario: Explicit format flag creates strategy without fallback
|
|
When I select a materializer for format "rich" with explicit flag
|
|
Then the selected materializer is RichMaterializer
|
|
|
|
# ================================================================
|
|
# Coverage boost: serializer on_session_end with buffered content
|
|
# ================================================================
|
|
|
|
Scenario: Plain materializer flushes remaining buffers on session end
|
|
Given an OutputSession with format "plain" using PlainMaterializer
|
|
When I create a panel handle with title "First"
|
|
And I create a second panel handle with title "Second"
|
|
And I set entry "a" to "1" on handle 2
|
|
And I close handle 2
|
|
And I set entry "b" to "2"
|
|
And I close the panel handle
|
|
And the session is closed
|
|
Then the plain output contains "First"
|
|
And the plain output contains "Second"
|
|
|
|
# ================================================================
|
|
# Coverage boost: serializer get_output with None session
|
|
# ================================================================
|
|
|
|
Scenario: JSON serializer returns empty when session is None
|
|
Given a JsonMaterializer with no session
|
|
Then the serializer get_output returns empty string
|