Testing Helpers

Rendering

urwid.testing.render_text(widget: AbstractWidget, size: _Size, *, focus: bool = False, strip: bool = False) → list[str]

Render widget and return the text of each row.

>>> from urwid import Pile, Text
>>> render_text(Text("ab cd"), (3,))
['ab ', 'cd ']
>>> render_text(Text("ab cd"), (3,), strip=True)
['ab', 'cd']
>>> render_text(Pile([Text("x")]), ())
['x']
Parameters:
  • widget – widget to render

  • size – size to render widget with: (), (cols,) or (cols, rows)

  • focus – whether widget is rendered in focus

  • strip – remove trailing whitespace from every row

Returns:

one string per canvas row, decoded with the target encoding.

urwid.testing.render_runs(widget: AbstractWidget, size: _Size, *, focus: bool = False) → list[list[tuple[Hashable, str]]]

Render widget and return each row as runs of display attribute and text.

Adjacent runs with the same attribute are merged, so the result does not depend on where the canvas splits runs for character sets or run-length encoding.

>>> from urwid import Text
>>> render_runs(Text([("bold", "Hi"), "!"]), (3,))
[[('bold', 'Hi'), (None, '!')]]
Parameters:
  • widget – widget to render

  • size – size to render widget with: (), (cols,) or (cols, rows)

  • focus – whether widget is rendered in focus

Returns:

one list of (attr, text) pairs per canvas row, the text decoded with the target encoding.

urwid.testing.resolve_attr(attr: Hashable, palette: Iterable[PaletteEntry], *, colors: int = 256) → AttrSpec

Return the AttrSpec a screen with palette draws display attribute attr with.

>>> resolve_attr("bold", [("bold", "dark red,bold", "default")], colors=16)
AttrSpec('dark red,bold', 'default')
Parameters:
  • attr – display attribute of a canvas run, such as one returned by render_runs()

  • palette – palette entries, as passed to MainLoop

  • colors – number of colors of the screen: 1, 16, 88, 256 or 2**24

Returns:

the attribute the screen resolves attr to; see urwid.BaseScreen.resolve_attr().

Raises:

ValueError – colors is not one of the supported numbers of colors.

urwid.testing.resolve_runs(runs: Iterable[Iterable[tuple[Hashable, str]]], palette: Iterable[PaletteEntry], *, colors: int = 256) → list[list[tuple[AttrSpec, str]]]

Resolve the attribute of every run returned by render_runs(), keeping the text.

Runs are not merged again, although two attributes may resolve to the same AttrSpec.

>>> from urwid import Text
>>> runs = render_runs(Text([("bold", "Hi"), "!"]), (3,))
>>> resolve_runs(runs, [("bold", "dark red,bold", "default")], colors=16)
[[(AttrSpec('dark red,bold', 'default'), 'Hi'), (AttrSpec('default', 'default'), '!')]]
Parameters:
  • runs – rows of (attr, text) pairs

  • palette – palette entries, as passed to MainLoop

  • colors – number of colors of the screen: 1, 16, 88, 256 or 2**24

Returns:

rows of (AttrSpec, text) pairs, in the shape of runs.

Raises:

ValueError – colors is not one of the supported numbers of colors.

Sizing checks

urwid.testing.check_sizing(widget: AbstractWidget, *, cols: int = 80, rows: int = 24) → None

Run the sizing check for each sizing mode in widget.sizing().

Warning

The check empties the canvas cache before every rendered case, so it is for tests, not for a running application.

>>> from urwid import Pile, Text
>>> check_sizing(Pile([Text("x")]))
Parameters:
Raises:

AssertionError – widget.sizing() is empty, or one of the checks fails.

urwid.testing.check_box_sizing(widget: AbstractWidget, *, cols: int = 80, rows: int = 24) → None

Check that the box widget widget renders canvases of the requested size, including degenerate sizes.

The sizes (cols, 0), (cols, 1), (0, rows), (1, rows) and (cols, rows) are rendered with focus off and on. Each canvas must have the requested columns and rows, and a cursor, when set, must lie inside it.

Warning

The check empties the canvas cache before every rendered case, so it is for tests, not for a running application.

>>> from urwid import Filler, Text
>>> check_box_sizing(Filler(Text("x")))
Parameters:
  • widget – box widget to check

  • cols – number of columns of the full size

  • rows – number of rows of the full size

Raises:

AssertionError – a canvas has the wrong size or its cursor lies outside it.

urwid.testing.check_flow_sizing(widget: AbstractFlowWidget, *, cols: int = 80) → None

Check that the flow widget widget renders as many rows as its rows() reports.

The widths 0, 1 and cols are rendered with focus off and on. Each canvas must have the requested columns and the rows widget.rows() returned before the render; in focus, its cursor must equal widget.get_cursor_coords() when the widget has that method; a cursor, when set, must lie inside it.

Warning

The check empties the canvas cache before every rendered case, so it is for tests, not for a running application.

>>> from urwid import Edit
>>> check_flow_sizing(Edit("> ", "abc"))
Parameters:
  • widget – flow widget to check

  • cols – number of columns of the full width

Raises:

AssertionError – a canvas has the wrong size or a cursor that does not match the widget.

urwid.testing.check_fixed_sizing(widget: AbstractWidget) → None

Check that the fixed widget widget renders a canvas of the size its pack(()) reports.

The widget is rendered with focus off and on; a cursor, when set, must lie inside the canvas.

Warning

The check empties the canvas cache before every rendered case, so it is for tests, not for a running application.

>>> from urwid import Text
>>> check_fixed_sizing(Text("x"))
Parameters:

widget – fixed widget to check

Raises:

AssertionError – a canvas size differs from pack(()) or its cursor lies outside it.

Input

urwid.testing.press(widget: AbstractWidget, size: _Size, *keys: str) → list[str]

Send keys to widget.keypress() in order and return the keys it did not handle.

The returned key is collected, not the sent one, since a widget may translate a key.

>>> from urwid import Edit
>>> edit = Edit("> ")
>>> press(edit, (10,), "a", "b", "esc")
['esc']
>>> edit.edit_text
'ab'
Parameters:
  • widget – widget to send the keys to

  • size – size passed to keypress()

  • keys – key names, such as "a" or "enter"

Returns:

the keys keypress() returned, in order.

urwid.testing.click(widget: AbstractWidget, size: _Size, col: int, row: int, *, button: int = 1, event: str = 'mouse press', focus: bool = True) → bool

Send one mouse event to widget.mouse_event() and return whether it was handled.

>>> from urwid import Button
>>> click(Button("OK"), (10,), 1, 0)
True
Parameters:
  • widget – widget to send the event to

  • size – size passed to mouse_event()

  • col – column of the event, relative to the widget

  • row – row of the event, relative to the widget

  • button – mouse button number

  • event – event name, such as "mouse press" or "mouse release"

  • focus – whether widget is in focus

Returns:

True when mouse_event() returned a true value.

Harness screen

class urwid.testing.ScriptedScreen(*, size: tuple[int, int] = (80, 24), inputs: Iterable[Iterable[_Input]] = ())

Screen without a terminal that records what is drawn and feeds scripted input to MainLoop.

MainLoop(widget, palette, screen=ScriptedScreen(...)) works with run(), which feeds one batch of inputs after every redraw and stops with ExitMainLoop once they are used up, and with process_input() plus draw_screen() called by hand.

A draw whose size, content (text and attributes) and cursor equal those of the last recorded frame is not recorded again, so canvases is the same sequence of distinct screens under every event loop, while the number of draws differs between them. Assert the frames and last_text, not a count of draws.

The input batches are consumed, so one screen scripts one run(). Alarms still pending after the last batch never fire.

>>> from urwid import Edit, Filler, MainLoop
>>> screen = ScriptedScreen(size=(6, 1), inputs=[["a", "b"], ["backspace"]])
>>> MainLoop(Filler(Edit("> ")), screen=screen).run()
>>> [frame.decoded_text for frame in screen.canvases]
[('>     ',), ('> ab  ',), ('> a   ',)]
>>> screen.last_text
['> a   ']

Create an unstarted screen.

Parameters:
  • size – (cols, rows) returned by get_cols_rows(); assign size to resize, then script a "window resize" key

  • inputs – batches of keys and mouse event tuples, one batch returned by each get_input()

Raises:

TypeError – a batch is not a sequence of keys and mouse event tuples.

canvases: list[Canvas]

distinct frames drawn, oldest first; canvases[-1] is the screen shown last

draw_count

number of draw_screen() calls, recorded or not

draw_screen(size: tuple[int, int], canvas: Canvas) → None

Record canvas unless it shows the same frame as the last one, then schedule the next input batch.

Parameters:
  • size – (cols, rows) the canvas was rendered for

  • canvas – canvas to show

get_cols_rows() → tuple[int, int]

Return size.

get_input(raw_keys: Literal[False] = False) → list[_Input]
get_input(raw_keys: Literal[True]) → tuple[list[_Input], list[int]]

Return the next input batch.

Parameters:

raw_keys – also return the raw key codes, which are always empty

Returns:

the batch, or (batch, []) when raw_keys is true.

Raises:

ExitMainLoop – the scripted batches are used up.

hook_event_loop(event_loop: EventLoop, callback: Callable[[list[_Input], list[int]], Any]) → None

Feed the input batches to callback through alarms of event_loop, one after every draw.

Parameters:
  • event_loop – event loop to schedule the batches on

  • callback – function taking the keys and the raw key codes of one batch

property last_text: list[str]

Return the text of each row of the frame drawn last.

Raises:

IndexError – nothing has been drawn yet.

size

(cols, rows) returned by get_cols_rows()

unhook_event_loop(event_loop: EventLoop) → None

Cancel a pending input batch and forget the event loop.

Parameters:

event_loop – event loop passed to hook_event_loop()