Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Write and run unit tests for Warp TUI (crates/warp_tui) elements/screens by rendering to text lines. Use for TUI test work.
.claude/skills/warpdotdev-tui-testing/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 43% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 32% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 82% | 0% |
| case-06 | ✗→✓ | ▲ Improved | 8% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 38% | 0% |
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).
TUI tests live in two crates, and which render helper you use depends on where the test is:
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: builds area = TuiRect::new(0, 0, size.width, size.height) and TuiBuffer::empty(area), calls element.render(area, &mut buffer, ctx) inside a paint context, and returns buffer.to_lines(). It only calls render, not layout — fine for a leaf like TuiText, but composite elements (e.g. TuiFlex) populate child sizes during layout, so lay the element out first (see the layout_at helper in flex_tests.rs) or it renders empty/stale.test_support::with_paint_context(|ctx| ...) — runs a closure with a TuiPaintContext over a fresh, empty view map. Use it when you need the TuiBuffer afterward to assert on individual Cells.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):
rust path=null start=nullassert_eq!( render_to_lines(&TuiText::new("hello"), TuiSize::new(10, 1)), vec!["hello "], );
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:
rust path=null start=nullfn 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).
to_lines() only carries glyphs, so style assertions read Cell fields:rust path=null start=nulllet 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.)
element.cursor_position(area, ctx) and assert on the returned Option<(u16, u16)>.TuiEvent (e.g. TuiEvent::KeyDown { keystroke, chars, details, is_composing } or TuiEvent::ScrollWheel { .. }), then call element.dispatch_event(&event, area, &mut event_ctx, &mut layout_ctx, app_ctx) and assert on the returned bool and on the re-rendered lines/cursor. Layout must run first. See render_element / dispatch_event / dispatch_scroll helpers in crates/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 ").
Follow the repo convention: put tests in a sibling *_tests.rs file included at the end of the source module:
rust path=null start=null#[cfg(test)] #[path = "foo_tests.rs"] mod tests;
Real examples to model:
crates/warpui_core/src/elements/tui/text_tests.rs, flex_tests.rs, container_tests.rs, constrained_box_tests.rs, buffer_tests.rs.crates/warp_tui/src/transcript_view_tests.rs, crates/warp_tui/src/input/view_tests.rs.Appearance in view testsViews 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.
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.
cargo nextest run -p warp_tui and cargo nextest run -p warpui_core.tui feature; if a test needs it explicitly, add --features tui.cargo nextest run -p warp_tui -E 'test(<substring>)'../script/format once. Do not add a full presubmit or rerun earlier checks after formatting unless explicitly required; follow AGENTS.md.| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→pass | 17,537 | 12,415 | -29% | 1 | 1 | 0% | 3,313 | 4,750 | +43% | 0 | 0 | — |
case-02 | fail→pass | 20,486 | 15,421 | -25% | 1 | 1 | 0% | 4,125 | 5,443 | +32% | 0 | 0 | — |
case-03 | fail→pass | 8,873 | 4,587 | -48% | 1 | 1 | 0% | 1,525 | 2,779 | +82% | 0 | 0 | — |
case-04 | fail→fail | 18,310 | 5,645 | -69% | 1 | 1 | 0% | 2,558 | 2,901 | +13% | 0 | 0 | — |
case-05 | pass→pass | 9,763 | 4,814 | -51% | 1 | 1 | 0% | 1,531 | 2,880 | +88% | 0 | 0 | — |
case-06 | fail→pass | 20,159 | 7,886 | -61% | 1 | 1 | 0% | 3,232 | 3,483 | +8% | 0 | 0 | — |
case-07 | fail→pass | 18,002 | 9,911 | -45% | 1 | 1 | 0% | 2,652 | 3,648 | +38% | 0 | 0 | — |
case-08 | fail→pass | 14,918 | 9,187 | -38% | 1 | 1 | 0% | 2,640 | 3,802 | +44% | 0 | 0 | — |
case-09 | fail→pass | 13,363 | 5,172 | -61% | 1 | 1 | 0% | 1,955 | 2,872 | +47% | 0 | 0 | — |
case-10 | fail→pass | 14,820 | 10,451 | -29% | 1 | 1 | 0% | 2,319 | 3,702 | +60% | 0 | 0 | — |
case-11 | pass→pass | 13,114 | 7,550 | -42% | 1 | 1 | 0% | 2,173 | 3,240 | +49% | 0 | 0 | — |
case-12 | fail→pass | 17,169 | 4,728 | -72% | 1 | 1 | 0% | 2,530 | 2,668 | +5% | 0 | 0 | — |
case-13 | fail→pass | 12,820 | 12,774 | -0% | 1 | 1 | 0% | 2,083 | 4,284 | +106% | 0 | 0 | — |
case-14 | fail→pass | 6,398 | 5,095 | -20% | 1 | 1 | 0% | 1,041 | 2,865 | +175% | 0 | 0 | — |
case-15 | pass→pass | 10,708 | 2,663 | -75% | 1 | 1 | 0% | 1,776 | 2,332 | +31% | 0 | 0 | — |
case-16 | fail→pass | 15,749 | 7,975 | -49% | 1 | 1 | 0% | 2,218 | 3,424 | +54% | 0 | 0 | — |
case-17 | fail→pass | 8,028 | 2,853 | -64% | 1 | 1 | 0% | 1,163 | 2,379 | +105% | 0 | 0 | — |
case-18 | fail→fail | 9,852 | 2,658 | -73% | 1 | 1 | 0% | 1,581 | 2,341 | +48% | 0 | 0 | — |
case-19 | fail→pass | 18,465 | 4,514 | -76% | 1 | 1 | 0% | 2,845 | 2,686 | -6% | 0 | 0 | — |
case-20 | fail→pass | 9,891 | 3,439 | -65% | 1 | 1 | 0% | 1,604 | 2,562 | +60% | 0 | 0 | — |
case-21 | fail→pass | 11,887 | 7,557 | -36% | 1 | 1 | 0% | 1,796 | 2,774 | +54% | 0 | 0 | — |
case-22 | fail→pass | 12,668 | 4,403 | -65% | 1 | 1 | 0% | 2,004 | 2,808 | +40% | 0 | 0 | — |
case-23 | pass→pass | 34,797 | 2,905 | -92% | 1 | 1 | 0% | 6,025 | 2,362 | -61% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 23 cases were attempted. The headline lift of +74 percentage points is the difference between those two pass rates over the 23 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.