tui-testing
GitHub指导如何为 Warp 的无头 TUI 前端编写和运行单元测试。通过渲染元素树到文本行进行断言,区分了组件库与视图层的测试辅助函数使用场景,不涉及真实 GUI 集成测试。
Trigger Scenarios
Install
npx skills add warpdotdev/warp --skill tui-testing -g -y
SKILL.md
Frontmatter
{
"name": "tui-testing",
"description": "Write and run unit tests for Warp TUI (crates\/warp_tui) elements\/screens by rendering to text lines. Use for TUI test work."
}
tui-testing
How to write and run unit tests for Warp's headless TUI front-end (crates/warp_tui and the element library at crates/warpui_core/src/elements/tui). This complements rust-unit-tests (general Rust test conventions) and parallels gui-integration-test for the TUI.
TUI tests are plain, fast unit tests: they render an element tree to a fixed cell grid and assert on the resulting text lines. They do not use the GUI real-display / integration / computer-use framework (that's gui-integration-test / gui-integration-test-video, which are GUI-only).
Two test locations, two harnesses
TUI tests live in two crates, and which render helper you use depends on where the test is:
Element-library tests (in warpui_core)
Tests for the shared cell-grid elements live in crates/warpui_core/src/elements/tui/*_tests.rs and use the crate-internal test_support helpers from crates/warpui_core/src/elements/tui/mod.rs:
test_support::render_to_lines(element: &dyn TuiElement, size: TuiSize) -> Vec<String>— one-call harness: buildsarea = TuiRect::new(0, 0, size.width, size.height)andTuiBuffer::empty(area), callselement.render(area, &mut buffer, ctx)inside a paint context, and returnsbuffer.to_lines(). It only callsrender, notlayout— fine for a leaf likeTuiText, but composite elements (e.g.TuiFlex) populate child sizes duringlayout, so lay the element out first (see thelayout_athelper inflex_tests.rs) or it renders empty/stale.test_support::with_paint_context(|ctx| ...)— runs a closure with aTuiPaintContextover a fresh, empty view map. Use it when you need theTuiBufferafterward to assert on individualCells.
These helpers are pub(crate) to warpui_core, so they are only callable from that crate's own tests. Simplest leaf assertion (see text_tests.rs, flex_tests.rs):
assert_eq!(
render_to_lines(&TuiText::new("hello"), TuiSize::new(10, 1)),
vec!["hello "],
);
View/screen tests (in warp_tui)
warp_tui tests (crates/warp_tui/src/*_tests.rs) can NOT use test_support — render directly instead, under an App::test read/update so an AppContext is available. layout must run before render so child sizes are populated. This local helper mirrors render_element in transcript_view_tests.rs and render_lines in editor_element_tests.rs:
fn render_lines(app_ctx: &AppContext, mut element: impl TuiElement, w: u16, h: u16) -> Vec<String> {
let mut rendered_views = EntityIdMap::default();
let mut lctx = TuiLayoutContext { rendered_views: &mut rendered_views };
let size = element.layout(TuiConstraint::loose(TuiSize::new(w, h)), &mut lctx, app_ctx);
let area = TuiRect::new(0, 0, size.width, size.height);
let mut buffer = TuiBuffer::empty(area);
let mut paint_ctx = TuiPaintContext::new(&mut rendered_views);
element.render(area, &mut buffer, &mut paint_ctx);
buffer.to_lines()
}
Views that resolve theme styles need an Appearance singleton (ctx.add_singleton_model(|_| Appearance::mock())). To exercise a whole view through the real draw path, drive the presenter: TuiPresenter::new(), presenter.invalidate(&invalidation, ctx, window_id), then presenter.present(ctx, &view, area) and assert on frame.buffer.to_lines() (see transcript_view_tests.rs).
Asserting on styles, cursor, and events
- Styles/colors: paint into a buffer yourself and index cells.
to_lines()only carries glyphs, so style assertions readCellfields:
let mut buffer = TuiBuffer::empty(TuiRect::new(0, 0, 1, 1));
with_paint_context(|ctx| text.render(TuiRect::new(0, 0, 1, 1), &mut buffer, ctx));
let cell = &buffer[(0, 0)];
assert_eq!(cell.symbol(), "a");
assert_eq!(cell.fg, Color::Red);
assert!(cell.modifier.contains(Modifier::BOLD));
(with_paint_context is the warpui_core-only helper; in a warp_tui test construct the paint context directly with TuiPaintContext::new(&mut rendered_views) as in the render_lines helper above.)
- Cursor: call
element.cursor_position(area, ctx)and assert on the returnedOption<(u16, u16)>. - Events: build a
TuiEvent(e.g.TuiEvent::KeyDown { keystroke, chars, details, is_composing }orTuiEvent::ScrollWheel { .. }), then callelement.dispatch_event(&event, area, &mut event_ctx, &mut layout_ctx, app_ctx)and assert on the returnedbooland on the re-rendered lines/cursor. Layout must run first. Seerender_element/dispatch_event/dispatch_scrollhelpers incrates/warp_tui/src/transcript_view_tests.rs.
Keep test areas at a stable, small width/height so golden line vectors stay readable and deterministic; trailing padding is spaces (e.g. "hello ").
Where tests live
Follow the repo convention: put tests in a sibling *_tests.rs file included at the end of the source module:
#[cfg(test)]
#[path = "foo_tests.rs"]
mod tests;
Real examples to model:
- Element library:
crates/warpui_core/src/elements/tui/text_tests.rs,flex_tests.rs,container_tests.rs,constrained_box_tests.rs,buffer_tests.rs. - Views/screens:
crates/warp_tui/src/transcript_view_tests.rs,crates/warp_tui/src/input/view_tests.rs.
Appearance in view tests
Views that resolve theme styles (via TuiUiBuilder::from_app) need an Appearance singleton. Install the mock in the test with app.add_singleton_model(|_| Appearance::mock()); (as in transcript_view_tests.rs). Appearance::mock() comes from warp_core's test-util feature, wired as a dev-dependency of the TUI crates.
Process-level tests (no integration harness)
The TUI has no GUI-style integration harness: the real-display, synthetic-event framework in crates/integration (see gui-integration-test) is GUI-only and does not drive the TUI. Besides render-to-lines unit tests, binary-level behavior is covered by a process-level test that spawns the built binary and asserts on its output/exit — see crates/warp_tui/tests/worker_dispatch.rs (it runs CARGO_BIN_EXE_warp-tui-oss and checks that a worker invocation dispatches without launching the TUI frontend). Use that pattern for process/CLI-level behavior, and render-to-lines unit tests for element/screen rendering. There is no separate TUI integration-test skill because there is no such framework today.
Running
- Whole crates:
cargo nextest run -p warp_tuiandcargo nextest run -p warpui_core. - The TUI element library is behind the
tuifeature; if a test needs it explicitly, add--features tui. - A single test by substring:
cargo nextest run -p warp_tui -E 'test(<substring>)'. - Before opening a PR, run
./script/formatandcargo clippyper repo conventions.
Version History
- 726c1b6 Current 2026-07-24 20:21


