package editor import ( "testing" "time" "git.gophernest.net/azpect/TextEditor/internal/core" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/x/exp/teatest" ) // NOTE: Lots of this actually sucks ass, but it works for now... // TODO: Refactor this to implement the builder pattern // sendKeys sends a sequence of keys to the test model func sendKeys(tm *teatest.TestModel, keys ...string) { for _, key := range keys { switch key { case "esc": tm.Send(tea.KeyMsg{Type: tea.KeyEscape}) case "enter": tm.Send(tea.KeyMsg{Type: tea.KeyEnter}) case "backspace": tm.Send(tea.KeyMsg{Type: tea.KeyBackspace}) case "ctrl+c": tm.Send(tea.KeyMsg{Type: tea.KeyCtrlC}) case "ctrl+d": tm.Send(tea.KeyMsg{Type: tea.KeyCtrlD}) case "ctrl+u": tm.Send(tea.KeyMsg{Type: tea.KeyCtrlU}) case "ctrl+v": tm.Send(tea.KeyMsg{Type: tea.KeyCtrlV}) case "ctrl+r": tm.Send(tea.KeyMsg{Type: tea.KeyCtrlR}) case "ctrl+w": tm.Send(tea.KeyMsg{Type: tea.KeyCtrlW}) case "tab": tm.Send(tea.KeyMsg{Type: tea.KeyTab}) case "left": tm.Send(tea.KeyMsg{Type: tea.KeyLeft}) case "right": tm.Send(tea.KeyMsg{Type: tea.KeyRight}) case "up": tm.Send(tea.KeyMsg{Type: tea.KeyUp}) case "down": tm.Send(tea.KeyMsg{Type: tea.KeyDown}) default: tm.Send(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune(key)}) } } } // sendKeyString is a convenience function for sending many keys. func sendKeyString(tm *teatest.TestModel, keyString string) { for _, key := range keyString { sendKeys(tm, string(key)) } } // TestModelOption is a functional option for configuring test models type TestModelOption func(*testModelConfig) type testModelConfig struct { lines []string pos core.Position width int height int regName rune regType core.RegisterType regContent []string } // WithLines sets the initial buffer lines func WithLines(lines []string) TestModelOption { return func(c *testModelConfig) { c.lines = lines } } // WithCursorPos sets the initial cursor position func WithCursorPos(pos core.Position) TestModelOption { return func(c *testModelConfig) { c.pos = pos } } // WithTermSize sets the terminal dimensions func WithTermSize(width, height int) TestModelOption { return func(c *testModelConfig) { c.width = width c.height = height } } // WithRegister sets a register's content func WithRegister(name rune, regType core.RegisterType, content []string) TestModelOption { return func(c *testModelConfig) { c.regName = name c.regType = regType c.regContent = content } } // newTestModel creates a test model with optional configuration func newTestModel(t *testing.T, opts ...TestModelOption) *teatest.TestModel { // Default configuration cfg := testModelConfig{ lines: []string{"line 1", "line 2", "line 3", "line 4", "line 5", "line 6"}, pos: core.Position{Col: 0, Line: 0}, width: 80, height: 24, } // Apply options for _, opt := range opts { opt(&cfg) } buf := core.NewBufferBuilder(). WithLines(cfg.lines). Build() win := core.NewWindowBuilder(). WithBuffer(&buf). WithCursorPos(cfg.pos.Line, cfg.pos.Col). WithDimensions(cfg.width, cfg.height). Build() // Create model with default registers m := NewModelBuilder(). AddBuffer(&buf). AddWindow(&win). WithActiveWindowId(win.Id). WithTermSize(cfg.width, cfg.height). Build() // Set register if provided (must be done AFTER Build because SetRegister // requires the register to already exist in the default register map) if cfg.regContent != nil { err := m.SetRegister(cfg.regName, cfg.regType, cfg.regContent) if err != nil { t.Fatalf("Failed to set register %c: %v", cfg.regName, err) } } return teatest.NewTestModel(t, m, teatest.WithInitialTermSize(cfg.width, cfg.height)) } // Convenience functions for backward compatibility func newTestModelWithLines(t *testing.T, lines []string) *teatest.TestModel { return newTestModel(t, WithLines(lines)) } func newTestModelWithCursorPos(t *testing.T, pos core.Position) *teatest.TestModel { return newTestModel(t, WithCursorPos(pos)) } func newTestModelWithLinesAndCursorPos(t *testing.T, lines []string, pos core.Position) *teatest.TestModel { return newTestModel(t, WithLines(lines), WithCursorPos(pos)) } func newTestModelWithTermSize(t *testing.T, lines []string, pos core.Position, width, height int) *teatest.TestModel { return newTestModel(t, WithLines(lines), WithCursorPos(pos), WithTermSize(width, height)) } // getFinalModel extracts the final model state (sends ctrl+c to quit first) func getFinalModel(t *testing.T, tm *teatest.TestModel) *Model { tm.Send(tea.KeyMsg{Type: tea.KeyCtrlC}) fm := tm.FinalModel(t, teatest.WithFinalTimeout(time.Second)) return fm.(*Model) }