416 lines
12 KiB
Go
416 lines
12 KiB
Go
package tui
|
|
|
|
import (
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/gdamore/tcell"
|
|
|
|
"b612.me/apps/b612/bed/event"
|
|
"b612.me/apps/b612/bed/key"
|
|
"b612.me/apps/b612/bed/layout"
|
|
"b612.me/apps/b612/bed/mode"
|
|
"b612.me/apps/b612/bed/state"
|
|
)
|
|
|
|
func (ui *Tui) initForTest(eventCh chan<- event.Event, screen tcell.SimulationScreen) (err error) {
|
|
ui.eventCh = eventCh
|
|
ui.mode = mode.Normal
|
|
ui.screen = screen
|
|
ui.waitCh = make(chan struct{})
|
|
return ui.screen.Init()
|
|
}
|
|
|
|
func mockKeyManager() map[mode.Mode]*key.Manager {
|
|
kms := make(map[mode.Mode]*key.Manager)
|
|
km := key.NewManager(true)
|
|
km.Register(event.Quit, "Z", "Q")
|
|
km.Register(event.CursorDown, "j")
|
|
kms[mode.Normal] = km
|
|
return kms
|
|
}
|
|
|
|
func getContents(screen tcell.SimulationScreen) string {
|
|
width, _ := screen.Size()
|
|
cells, _, _ := screen.GetContents()
|
|
var runes []rune
|
|
for i, cell := range cells {
|
|
runes = append(runes, cell.Runes...)
|
|
if (i+1)%width == 0 {
|
|
runes = append(runes, '\n')
|
|
}
|
|
}
|
|
return string(runes)
|
|
}
|
|
|
|
func shouldContain(t *testing.T, screen tcell.SimulationScreen, expected []string) {
|
|
got := getContents(screen)
|
|
for _, str := range expected {
|
|
if !strings.Contains(got, str) {
|
|
t.Errorf("screen should contain %q but got\n%v", str, got)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestTuiRun(t *testing.T) {
|
|
ui := NewTui()
|
|
eventCh := make(chan event.Event)
|
|
screen := tcell.NewSimulationScreen("")
|
|
if err := ui.initForTest(eventCh, screen); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
screen.SetSize(90, 20)
|
|
go ui.Run(mockKeyManager())
|
|
|
|
screen.InjectKey(tcell.KeyRune, 'Z', tcell.ModNone)
|
|
screen.InjectKey(tcell.KeyRune, 'Q', tcell.ModNone)
|
|
e := <-eventCh
|
|
if e.Type != event.Rune {
|
|
t.Errorf("pressing Z should emit event.Rune but got: %+v", e)
|
|
}
|
|
e = <-eventCh
|
|
if e.Type != event.Quit {
|
|
t.Errorf("pressing ZQ should emit event.Quit but got: %+v", e)
|
|
}
|
|
screen.InjectKey(tcell.KeyRune, '7', tcell.ModNone)
|
|
screen.InjectKey(tcell.KeyRune, '0', tcell.ModNone)
|
|
screen.InjectKey(tcell.KeyRune, '9', tcell.ModNone)
|
|
screen.InjectKey(tcell.KeyRune, 'j', tcell.ModNone)
|
|
e = <-eventCh
|
|
e = <-eventCh
|
|
e = <-eventCh
|
|
e = <-eventCh
|
|
if e.Type != event.CursorDown {
|
|
t.Errorf("pressing 709j should emit event.CursorDown but got: %+v", e)
|
|
}
|
|
if e.Count != 709 {
|
|
t.Errorf("pressing 709j should emit event with count %d but got: %+v", 709, e)
|
|
}
|
|
if err := ui.Close(); err != nil {
|
|
t.Errorf("ui.Close should return nil but got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestTuiEmpty(t *testing.T) {
|
|
ui := NewTui()
|
|
eventCh := make(chan event.Event)
|
|
screen := tcell.NewSimulationScreen("")
|
|
if err := ui.initForTest(eventCh, screen); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
screen.SetSize(90, 20)
|
|
width, height := screen.Size()
|
|
go ui.Run(mockKeyManager())
|
|
|
|
s := state.State{
|
|
WindowStates: map[int]*state.WindowState{
|
|
0: {
|
|
Name: "",
|
|
Modified: false,
|
|
Width: 16,
|
|
Offset: 0,
|
|
Cursor: 0,
|
|
Bytes: []byte(strings.Repeat("\x00", 16*(height-1))),
|
|
Size: 16 * (height - 1),
|
|
Length: 0,
|
|
Mode: mode.Normal,
|
|
},
|
|
},
|
|
Layout: layout.NewLayout(0).Resize(0, 0, width, height-1),
|
|
}
|
|
if err := ui.Redraw(s); err != nil {
|
|
t.Errorf("ui.Redraw should return nil but got: %v", err)
|
|
}
|
|
|
|
shouldContain(t, screen, []string{
|
|
" | 0 1 2 3 4 5 6 7 8 9 a b c d e f | ",
|
|
" 000000 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ #",
|
|
" 000010 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ #",
|
|
" 000020 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ #",
|
|
" 000100 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ #",
|
|
" [No name] : 0x00 : '\\x00' 0/0 : 0x000000/0x000000 : 0.00%",
|
|
})
|
|
|
|
x, y, visible := screen.GetCursor()
|
|
if x != 10 || y != 1 {
|
|
t.Errorf("cursor position should be (%d, %d) but got (%d, %d)", 10, 1, x, y)
|
|
}
|
|
if visible != true {
|
|
t.Errorf("cursor should be visible but got %v", visible)
|
|
}
|
|
if err := ui.Close(); err != nil {
|
|
t.Errorf("ui.Close should return nil but got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestTuiScrollBar(t *testing.T) {
|
|
ui := NewTui()
|
|
eventCh := make(chan event.Event)
|
|
screen := tcell.NewSimulationScreen("")
|
|
if err := ui.initForTest(eventCh, screen); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
screen.SetSize(90, 20)
|
|
width, height := screen.Size()
|
|
go ui.Run(mockKeyManager())
|
|
|
|
s := state.State{
|
|
WindowStates: map[int]*state.WindowState{
|
|
0: {
|
|
Name: "",
|
|
Modified: true,
|
|
Width: 16,
|
|
Offset: 0,
|
|
Cursor: 0,
|
|
Bytes: []byte(strings.Repeat("a", 16*(height-1))),
|
|
Size: 16 * (height - 1),
|
|
Length: int64(16 * (height - 1) * 3),
|
|
Mode: mode.Normal,
|
|
},
|
|
},
|
|
Layout: layout.NewLayout(0).Resize(0, 0, width, height-1),
|
|
}
|
|
if err := ui.Redraw(s); err != nil {
|
|
t.Errorf("ui.Redraw should return nil but got: %v", err)
|
|
}
|
|
|
|
shouldContain(t, screen, []string{
|
|
" | 0 1 2 3 4 5 6 7 8 9 a b c d e f | ",
|
|
" 000000 | 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 | aaaaaaaaaaaaaaaa # ",
|
|
" 000050 | 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 | aaaaaaaaaaaaaaaa # ",
|
|
" 000060 | 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 | aaaaaaaaaaaaaaaa | ",
|
|
" 000100 | 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 | aaaaaaaaaaaaaaaa | ",
|
|
" [No name] : + : 0x61 : 'a' 0/912 : 0x000000/0x000390 : 0.00%",
|
|
})
|
|
|
|
x, y, visible := screen.GetCursor()
|
|
if x != 10 || y != 1 {
|
|
t.Errorf("cursor position should be (%d, %d) but got (%d, %d)", 10, 1, x, y)
|
|
}
|
|
if visible != true {
|
|
t.Errorf("cursor should be visible but got %v", visible)
|
|
}
|
|
if err := ui.Close(); err != nil {
|
|
t.Errorf("ui.Close should return nil but got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestTuiHorizontalSplit(t *testing.T) {
|
|
ui := NewTui()
|
|
eventCh := make(chan event.Event)
|
|
screen := tcell.NewSimulationScreen("")
|
|
if err := ui.initForTest(eventCh, screen); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
screen.SetSize(110, 20)
|
|
width, height := screen.Size()
|
|
go ui.Run(mockKeyManager())
|
|
|
|
s := state.State{
|
|
WindowStates: map[int]*state.WindowState{
|
|
0: {
|
|
Name: "test0",
|
|
Modified: false,
|
|
Width: 16,
|
|
Offset: 0,
|
|
Cursor: 0,
|
|
Bytes: []byte("Test window 0." + strings.Repeat("\x00", 110*10)),
|
|
Size: 110 * 10,
|
|
Length: 600,
|
|
Mode: mode.Normal,
|
|
},
|
|
1: {
|
|
Name: "test1",
|
|
Modified: false,
|
|
Width: 16,
|
|
Offset: 0,
|
|
Cursor: 0,
|
|
Bytes: []byte("Test window 1." + strings.Repeat(" ", 110*10)),
|
|
Size: 110 * 10,
|
|
Length: 800,
|
|
Mode: mode.Normal,
|
|
},
|
|
},
|
|
Layout: layout.NewLayout(0).SplitBottom(1).Resize(0, 0, width, height-1),
|
|
}
|
|
if err := ui.Redraw(s); err != nil {
|
|
t.Errorf("ui.Redraw should return nil but got: %v", err)
|
|
}
|
|
|
|
shouldContain(t, screen, []string{
|
|
" 000000 | 54 65 73 74 20 77 69 6e 64 6f 77 20 30 2e 00 00 | Test window 0... #",
|
|
" 000010 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ #",
|
|
" test0 : 0x54 : 'T' 0/600 : 0x000000/0x000258 : 0.00%",
|
|
" | 0 1 2 3 4 5 6 7 8 9 a b c d e f | ",
|
|
" 000000 | 54 65 73 74 20 77 69 6e 64 6f 77 20 31 2e 20 20 | Test window 1. #",
|
|
" 000010 | 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 | #",
|
|
" test1 : 0x54 : 'T' 0/800 : 0x000000/0x000320 : 0.00%",
|
|
})
|
|
|
|
x, y, visible := screen.GetCursor()
|
|
if x != 10 || y != 10 {
|
|
t.Errorf("cursor position should be (%d, %d) but got (%d, %d)", 10, 10, x, y)
|
|
}
|
|
if visible != true {
|
|
t.Errorf("cursor should be visible but got %v", visible)
|
|
}
|
|
if err := ui.Close(); err != nil {
|
|
t.Errorf("ui.Close should return nil but got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestTuiVerticalSplit(t *testing.T) {
|
|
ui := NewTui()
|
|
eventCh := make(chan event.Event)
|
|
screen := tcell.NewSimulationScreen("")
|
|
if err := ui.initForTest(eventCh, screen); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
screen.SetSize(110, 20)
|
|
width, height := screen.Size()
|
|
go ui.Run(mockKeyManager())
|
|
|
|
s := state.State{
|
|
WindowStates: map[int]*state.WindowState{
|
|
0: {
|
|
Name: "test0",
|
|
Modified: false,
|
|
Width: 8,
|
|
Offset: 0,
|
|
Cursor: 0,
|
|
Bytes: []byte("Test window 0." + strings.Repeat("\x00", 55*19)),
|
|
Size: 55 * 19,
|
|
Length: 600,
|
|
Mode: mode.Normal,
|
|
},
|
|
1: {
|
|
Name: "test1",
|
|
Modified: false,
|
|
Width: 8,
|
|
Offset: 0,
|
|
Cursor: 0,
|
|
Bytes: []byte("Test window 1." + strings.Repeat(" ", 54*19)),
|
|
Size: 54 * 19,
|
|
Length: 800,
|
|
Mode: mode.Normal,
|
|
},
|
|
},
|
|
Layout: layout.NewLayout(0).SplitRight(1).Resize(0, 0, width, height-1),
|
|
}
|
|
if err := ui.Redraw(s); err != nil {
|
|
t.Errorf("ui.Redraw should return nil but got: %v", err)
|
|
}
|
|
|
|
shouldContain(t, screen, []string{
|
|
" | 0 1 2 3 4 5 6 7 | | | 0 1 2 3 4 5 6 7 |",
|
|
" 000000 | 54 65 73 74 20 77 69 6e | Test win # | 000000 | 54 65 73 74 20 77 69 6e | Test win #",
|
|
" 000008 | 64 6f 77 20 30 2e 00 00 | dow 0... # | 000008 | 64 6f 77 20 31 2e 20 20 | dow 1. #",
|
|
" 000010 | 00 00 00 00 00 00 00 00 | ........ # | 000010 | 20 20 20 20 20 20 20 20 | #",
|
|
" test0 : 0x54 : 'T' 0/600 : 0x000000/0x000258 : 0.00% | test1 : 0x54 : 'T' 0/800 : 0x000000/0x000320 : 0.00",
|
|
})
|
|
|
|
x, y, visible := screen.GetCursor()
|
|
if x != 66 || y != 1 {
|
|
t.Errorf("cursor position should be (%d, %d) but got (%d, %d)", 66, 1, x, y)
|
|
}
|
|
if visible != true {
|
|
t.Errorf("cursor should be visible but got %v", visible)
|
|
}
|
|
if err := ui.Close(); err != nil {
|
|
t.Errorf("ui.Close should return nil but got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestTuiCmdline(t *testing.T) {
|
|
ui := NewTui()
|
|
eventCh := make(chan event.Event)
|
|
screen := tcell.NewSimulationScreen("")
|
|
if err := ui.initForTest(eventCh, screen); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
screen.SetSize(20, 15)
|
|
getCmdline := func() string {
|
|
cells, _, _ := screen.GetContents()
|
|
var runes []rune
|
|
for _, cell := range cells[20*14:] {
|
|
runes = append(runes, cell.Runes...)
|
|
}
|
|
return string(runes)
|
|
}
|
|
go ui.Run(mockKeyManager())
|
|
|
|
s := state.State{
|
|
Mode: mode.Cmdline,
|
|
Cmdline: []rune("vnew test"),
|
|
CmdlineCursor: 9,
|
|
}
|
|
if err := ui.Redraw(s); err != nil {
|
|
t.Errorf("ui.Redraw should return nil but got: %v", err)
|
|
}
|
|
|
|
got, expected := getCmdline(), ":vnew test "
|
|
if !strings.HasPrefix(got, expected) {
|
|
t.Errorf("cmdline should start with %q but got %q", expected, got)
|
|
}
|
|
|
|
s = state.State{
|
|
Mode: mode.Normal,
|
|
Error: errors.New("error"),
|
|
Cmdline: []rune("vnew test"),
|
|
CmdlineCursor: 9,
|
|
}
|
|
if err := ui.Redraw(s); err != nil {
|
|
t.Errorf("ui.Redraw should return nil but got: %v", err)
|
|
}
|
|
|
|
got, expected = getCmdline(), "error "
|
|
if !strings.HasPrefix(got, expected) {
|
|
t.Errorf("cmdline should start with %q but got %q", expected, got)
|
|
}
|
|
if err := ui.Close(); err != nil {
|
|
t.Errorf("ui.Close should return nil but got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestTuiCmdlineCompletionCandidates(t *testing.T) {
|
|
ui := NewTui()
|
|
eventCh := make(chan event.Event)
|
|
screen := tcell.NewSimulationScreen("")
|
|
if err := ui.initForTest(eventCh, screen); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
screen.SetSize(20, 15)
|
|
go ui.Run(mockKeyManager())
|
|
|
|
s := state.State{
|
|
Mode: mode.Cmdline,
|
|
Cmdline: []rune("new test2"),
|
|
CmdlineCursor: 9,
|
|
CompletionResults: []string{"test1", "test2", "test3", "test9/", "/bin/ls"},
|
|
CompletionIndex: 1,
|
|
}
|
|
if err := ui.Redraw(s); err != nil {
|
|
t.Errorf("ui.Redraw should return nil but got: %v", err)
|
|
}
|
|
|
|
shouldContain(t, screen, []string{
|
|
" test1 test2 test3",
|
|
":new test2",
|
|
})
|
|
|
|
s.CompletionIndex += 2
|
|
s.Cmdline = []rune("new test9/")
|
|
if err := ui.Redraw(s); err != nil {
|
|
t.Errorf("ui.Redraw should return nil but got: %v", err)
|
|
}
|
|
|
|
shouldContain(t, screen, []string{
|
|
" test3 test9/ /bin",
|
|
":new test9/",
|
|
})
|
|
if err := ui.Close(); err != nil {
|
|
t.Errorf("ui.Close should return nil but got %v", err)
|
|
}
|
|
}
|