package ui import ( "strings" "testing" "github.com/gdamore/tcell/v2" "codeberg.org/turbo-editors/turbo-core/theme" ) // newTestScreen returns a simulated terminal of the given size, already // initialised, together with a painter covering it. // // tcell's SimulationScreen is a real Screen that draws into memory, so every // widget below is exercised through exactly the code path a terminal takes — // no rendering is stubbed out. func newTestScreen(t *testing.T, width, height int) (tcell.SimulationScreen, *Painter) { t.Helper() screen := tcell.NewSimulationScreen("UTF-8") if err := screen.Init(); err != nil { t.Fatalf("initialising the simulation screen: %v", err) } t.Cleanup(screen.Fini) screen.SetSize(width, height) return screen, NewPainter(screen) } // screenLines returns what the simulated screen currently shows, one string // per row, so a test can assert on the picture rather than on cells. func screenLines(t *testing.T, screen tcell.SimulationScreen) []string { t.Helper() cells, width, height := screen.GetContents() lines := make([]string, height) for y := range height { var row strings.Builder for x := range width { runes := cells[y*width+x].Runes if len(runes) == 0 { row.WriteRune(' ') continue } row.WriteRune(runes[0]) } lines[y] = row.String() } return lines } // styleAt returns the style of one cell of the simulated screen. func styleAt(t *testing.T, screen tcell.SimulationScreen, x, y int) tcell.Style { t.Helper() cells, width, _ := screen.GetContents() return cells[y*width+x].Style } // testTheme is a theme whose every key has a distinct, easily recognised // colour, so a test can prove which style was used. func testTheme(t *testing.T) *theme.Theme { t.Helper() th, err := theme.Parse([]byte(` [colors] default = { fg = "white", bg = "black" } desktop = { fg = "blue", bg = "navy" } shadow = { fg = "gray", bg = "black" } "menu.bar" = { fg = "black", bg = "silver" } "menu.item" = { fg = "black", bg = "silver" } "menu.selected" = { fg = "black", bg = "green" } "menu.shortcut" = { fg = "maroon", bg = "silver" } "menu.disabled" = { fg = "gray", bg = "silver" } "window.frame.active" = { fg = "white", bg = "navy" } "window.frame.inactive" = { fg = "silver", bg = "navy" } "window.title.active" = { fg = "yellow", bg = "navy" } "window.title.inactive" = { fg = "silver", bg = "navy" } "window.body" = { fg = "silver", bg = "navy" } statusbar = { fg = "black", bg = "silver" } "statusbar.key" = { fg = "maroon", bg = "silver" } "statusbar.hint" = { fg = "navy", bg = "silver" } scrollbar = { fg = "navy", bg = "teal" } "scrollbar.thumb" = { fg = "aqua", bg = "teal" } "dialog.frame" = { fg = "white", bg = "silver" } "dialog.body" = { fg = "black", bg = "silver" } "dialog.title" = { fg = "black", bg = "silver" } "dialog.label" = { fg = "black", bg = "silver" } button = { fg = "black", bg = "green" } "button.focused" = { fg = "white", bg = "green" } "button.shortcut" = { fg = "yellow", bg = "green" } input = { fg = "black", bg = "teal" } "input.focused" = { fg = "white", bg = "teal" } list = { fg = "black", bg = "silver" } "list.selected" = { fg = "white", bg = "navy" } "list.unfocused" = { fg = "black", bg = "gray" } checkbox = { fg = "black", bg = "silver" } "checkbox.focused" = { fg = "white", bg = "navy" } `), "") if err != nil { t.Fatalf("parsing the test theme: %v", err) } return th } // keyEvent builds a key press. func keyEvent(key tcell.Key, r rune, mods tcell.ModMask) *tcell.EventKey { return tcell.NewEventKey(key, r, mods) } // runeEvent builds the press of a printable character. func runeEvent(r rune) *tcell.EventKey { return tcell.NewEventKey(tcell.KeyRune, r, tcell.ModNone) } // clickEvent builds a left-button press at a screen position. func clickEvent(x, y int) *tcell.EventMouse { return tcell.NewEventMouse(x, y, tcell.Button1, tcell.ModNone) } // releaseEvent builds the release of every mouse button at a position. func releaseEvent(x, y int) *tcell.EventMouse { return tcell.NewEventMouse(x, y, tcell.ButtonNone, tcell.ModNone) }