DEV Community

chovy
chovy

Posted on Originally published at dev.profullstack.com

A terminal UI you can write a test against

DiskPush's two-pane browser was about 940 lines of hand-rolled ANSI. Its own escape codes, its own key parser, its own box drawing, and a full-screen repaint on every keystroke. It worked. The problem was that the only way to know what it drew was to run it in a terminal and look at it.

I rewrote it on HQTUI and split the result three ways:

  • model.ts, state and the pure functions over it. No terminal, no ssh, no rsync.
  • view.ts, the whole screen as one pure function of a snapshot.
  • app.ts, state and effects only.

That second file is the point. A frame is now a value:

const text = renderToText(({ ui, theme }) => draw(ui, theme, 100, 30, state), {
  width: 100,
  height: 30,
})
expect(text).toContain('Local -> prod')
Enter fullscreen mode Exit fullscreen mode

Three defects fell out of that in the first hour, none of which I caught reading the code I had just written.

One pane lost its title, the other lost its path

A panel's title and subtitle share the top border row, and the box gives the subtitle priority when the two collide. Sizing the path against the full pane width put it right on that threshold, so a one-column rounding difference between two side-by-side panes decided which half of the header survived. Two panes pointed at the very same directory rendered differently. Budgeting the path against what the title leaves fixes it.

Paths were shortened from the wrong end

HQTUI's truncate keeps the head of a string, which is right for a label. For a path it means every worktree under one repo renders as the identical string. Shortening from the left costs nine lines and is the difference between a header that names a directory and one that does not.

A finished transfer showed 80 percent

The progress bar was reading rsync's last printed progress line, which is whatever it happened to emit before exiting. A preview that had completed sat at 80 percent forever.

All three are the kind of thing a screenshot review catches and a code review does not. The difference is that a test catches them on every commit, and I did not have to remember to look.

The rewrite also bought the things you get once something else owns the terminal: a live transfer panel with a progress bar, the rate, and the files as rsync reports them, cancellable with esc. A filter on / that narrows a listing as you type. Sorting on o. Dotfiles on .. Mouse click and wheel. The TUI test count went from 15 to 59.

DiskPush 0.6.0 is out. Run diskpush tui and press ?.

Drafted with AI assistance, reviewed and edited by a human.

Top comments (0)