Compare commits
4 Commits
93968e7333
...
a103af0a83
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a103af0a83 | ||
|
|
354fbc4f9b | ||
|
|
15d847e3c8 | ||
|
|
c126242ee1 |
28
.github/actions/test.yml
vendored
Normal file
28
.github/actions/test.yml
vendored
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
name: Run Test Suite
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- "**"
|
||||||
|
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- "**"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: "1.25.5" # Pin version
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: go mod download
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: go test -v ./...
|
||||||
@ -264,7 +264,7 @@
|
|||||||
Buffers are in-memory representations of files. A buffer exists for each open file.
|
Buffers are in-memory representations of files. A buffer exists for each open file.
|
||||||
|
|
||||||
### Buffer Model
|
### Buffer Model
|
||||||
- [ ] Buffer struct (id, filename, lines, modified flag, cursor position)
|
- [x] Buffer struct (id, filename, lines, modified flag, cursor position)
|
||||||
- [ ] Buffer list/manager
|
- [ ] Buffer list/manager
|
||||||
- [ ] Current buffer tracking
|
- [ ] Current buffer tracking
|
||||||
- [ ] Buffer-local settings (tabstop, filetype, etc.)
|
- [ ] Buffer-local settings (tabstop, filetype, etc.)
|
||||||
|
|||||||
@ -137,7 +137,6 @@ func (a CommandExecute) Execute(m Model) tea.Cmd {
|
|||||||
|
|
||||||
cmd, err := a.Registry.Execute(m, cmdLine)
|
cmd, err := a.Registry.Execute(m, cmdLine)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// TODO: Display error message to user
|
|
||||||
m.SetCommandError(err)
|
m.SetCommandError(err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,7 +6,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.gophernest.net/azpect/TextEditor/internal/action"
|
"git.gophernest.net/azpect/TextEditor/internal/action"
|
||||||
"git.gophernest.net/azpect/TextEditor/internal/core"
|
|
||||||
tea "github.com/charmbracelet/bubbletea"
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -107,14 +106,6 @@ func cmdSet(m action.Model, args []string) tea.Cmd {
|
|||||||
|
|
||||||
// Setting: Represents a configurable editor option.
|
// Setting: Represents a configurable editor option.
|
||||||
type Setting struct {
|
type Setting struct {
|
||||||
Name string
|
|
||||||
ShortForm string
|
|
||||||
Type SettingType
|
|
||||||
Get func(s core.EditorSettings) any
|
|
||||||
Set func(m action.Model, val any)
|
|
||||||
}
|
|
||||||
|
|
||||||
type WindowSetting struct {
|
|
||||||
Name string
|
Name string
|
||||||
ShortForm string
|
ShortForm string
|
||||||
Type SettingType
|
Type SettingType
|
||||||
@ -131,23 +122,21 @@ const (
|
|||||||
StringSetting
|
StringSetting
|
||||||
)
|
)
|
||||||
|
|
||||||
// settingsMap defines all available editor settings
|
// settingsMap defines all available settings (both global and window-local)
|
||||||
var settingsMap = []Setting{
|
var settingsMap = []Setting{
|
||||||
|
// Global editor settings
|
||||||
{
|
{
|
||||||
Name: "tabstop",
|
Name: "tabstop",
|
||||||
ShortForm: "ts",
|
ShortForm: "ts",
|
||||||
Type: IntSetting,
|
Type: IntSetting,
|
||||||
Get: func(s core.EditorSettings) any { return s.TabStop },
|
Get: func(m action.Model) any { return m.Settings().TabStop },
|
||||||
Set: func(m action.Model, val any) {
|
Set: func(m action.Model, val any) {
|
||||||
s := m.Settings()
|
s := m.Settings()
|
||||||
s.TabStop = val.(int)
|
s.TabStop = val.(int)
|
||||||
m.SetSettings(s)
|
m.SetSettings(s)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
// Window-local settings
|
||||||
|
|
||||||
// windowSettingsMap defines all available window settings
|
|
||||||
var windowSettingsMap = []WindowSetting{
|
|
||||||
{
|
{
|
||||||
Name: "number",
|
Name: "number",
|
||||||
ShortForm: "nu",
|
ShortForm: "nu",
|
||||||
@ -213,69 +202,27 @@ func lookupSetting(name string) *Setting {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// lookupWindowSetting: Finds a window setting by name, short form, or prefix.
|
|
||||||
func lookupWindowSetting(name string) *WindowSetting {
|
|
||||||
for i := range windowSettingsMap {
|
|
||||||
s := &windowSettingsMap[i]
|
|
||||||
if name == s.Name || name == s.ShortForm {
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
// Prefix matching
|
|
||||||
if len(name) >= len(s.ShortForm) && strings.HasPrefix(s.Name, name) {
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseSetOption: Parses and applies a single :set option.
|
// parseSetOption: Parses and applies a single :set option.
|
||||||
func parseSetOption(m action.Model, opt string) error {
|
func parseSetOption(m action.Model, opt string) error {
|
||||||
// Handle toggle: option!
|
// Handle toggle: option!
|
||||||
if name, ok := strings.CutSuffix(opt, "!"); ok {
|
if name, ok := strings.CutSuffix(opt, "!"); ok {
|
||||||
setting := lookupSetting(name)
|
setting := lookupSetting(name)
|
||||||
if setting != nil {
|
if setting != nil && setting.Type == BoolSetting {
|
||||||
if setting.Type == BoolSetting {
|
currentVal := setting.Get(m).(bool)
|
||||||
// Toggle the boolean
|
|
||||||
currentVal := setting.Get(m.Settings()).(bool)
|
|
||||||
setting.Set(m, !currentVal)
|
setting.Set(m, !currentVal)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
windowSetting := lookupWindowSetting(name)
|
|
||||||
if windowSetting != nil {
|
|
||||||
if windowSetting.Type == BoolSetting {
|
|
||||||
// Toggle the boolean
|
|
||||||
currentVal := windowSetting.Get(m).(bool)
|
|
||||||
windowSetting.Set(m, !currentVal)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle disable: nooption
|
// Handle disable: nooption
|
||||||
if name, ok := strings.CutPrefix(opt, "no"); ok {
|
if name, ok := strings.CutPrefix(opt, "no"); ok {
|
||||||
setting := lookupSetting(name)
|
setting := lookupSetting(name)
|
||||||
if setting != nil {
|
if setting != nil && setting.Type == BoolSetting {
|
||||||
if setting.Type == BoolSetting {
|
|
||||||
setting.Set(m, false)
|
setting.Set(m, false)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
windowSetting := lookupWindowSetting(name)
|
|
||||||
if windowSetting != nil {
|
|
||||||
if windowSetting.Type == BoolSetting {
|
|
||||||
windowSetting.Set(m, false)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle assignment: option=value
|
// Handle assignment: option=value
|
||||||
if strings.Contains(opt, "=") {
|
if strings.Contains(opt, "=") {
|
||||||
parts := strings.SplitN(opt, "=", 2)
|
parts := strings.SplitN(opt, "=", 2)
|
||||||
@ -297,47 +244,15 @@ func parseSetOption(m action.Model, opt string) error {
|
|||||||
boolVal := value == "true" || value == "1" || value == "yes"
|
boolVal := value == "true" || value == "1" || value == "yes"
|
||||||
setting.Set(m, boolVal)
|
setting.Set(m, boolVal)
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
windowSetting := lookupWindowSetting(name)
|
|
||||||
if windowSetting != nil {
|
|
||||||
switch windowSetting.Type {
|
|
||||||
case IntSetting:
|
|
||||||
intVal, err := strconv.Atoi(value)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
windowSetting.Set(m, intVal)
|
|
||||||
case StringSetting:
|
|
||||||
windowSetting.Set(m, value)
|
|
||||||
case BoolSetting:
|
|
||||||
// Handle :set option=true / :set option=false
|
|
||||||
boolVal := value == "true" || value == "1" || value == "yes"
|
|
||||||
windowSetting.Set(m, boolVal)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle enable: option (boolean only)
|
// Handle enable: option (boolean only)
|
||||||
setting := lookupSetting(opt)
|
setting := lookupSetting(opt)
|
||||||
if setting != nil {
|
if setting != nil && setting.Type == BoolSetting {
|
||||||
if setting.Type == BoolSetting {
|
|
||||||
setting.Set(m, true)
|
setting.Set(m, true)
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
windowSetting := lookupWindowSetting(opt)
|
|
||||||
if windowSetting != nil {
|
|
||||||
if windowSetting.Type == BoolSetting {
|
|
||||||
windowSetting.Set(m, true)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
302
internal/core/buffer_test.go
Normal file
302
internal/core/buffer_test.go
Normal file
@ -0,0 +1,302 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
// --------------------------------------------------
|
||||||
|
// Buffer Tests (generated by ClaudeCode)
|
||||||
|
// --------------------------------------------------
|
||||||
|
|
||||||
|
func TestBuffer_InsertLine(t *testing.T) {
|
||||||
|
t.Run("inserts at beginning", func(t *testing.T) {
|
||||||
|
buf := NewBufferBuilder().
|
||||||
|
WithLines([]string{"line 1", "line 2"}).
|
||||||
|
Build()
|
||||||
|
|
||||||
|
buf.InsertLine(0, "new line")
|
||||||
|
|
||||||
|
if buf.LineCount() != 3 {
|
||||||
|
t.Errorf("expected 3 lines, got %d", buf.LineCount())
|
||||||
|
}
|
||||||
|
if buf.Line(0) != "new line" {
|
||||||
|
t.Errorf("expected 'new line', got '%s'", buf.Line(0))
|
||||||
|
}
|
||||||
|
if buf.Line(1) != "line 1" {
|
||||||
|
t.Errorf("expected 'line 1' at index 1, got '%s'", buf.Line(1))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("inserts at end", func(t *testing.T) {
|
||||||
|
buf := NewBufferBuilder().
|
||||||
|
WithLines([]string{"line 1", "line 2"}).
|
||||||
|
Build()
|
||||||
|
|
||||||
|
buf.InsertLine(2, "new line")
|
||||||
|
|
||||||
|
if buf.LineCount() != 3 {
|
||||||
|
t.Errorf("expected 3 lines, got %d", buf.LineCount())
|
||||||
|
}
|
||||||
|
if buf.Line(2) != "new line" {
|
||||||
|
t.Errorf("expected 'new line' at end, got '%s'", buf.Line(2))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("inserts in middle", func(t *testing.T) {
|
||||||
|
buf := NewBufferBuilder().
|
||||||
|
WithLines([]string{"line 1", "line 3"}).
|
||||||
|
Build()
|
||||||
|
|
||||||
|
buf.InsertLine(1, "line 2")
|
||||||
|
|
||||||
|
if buf.LineCount() != 3 {
|
||||||
|
t.Errorf("expected 3 lines, got %d", buf.LineCount())
|
||||||
|
}
|
||||||
|
if buf.Line(1) != "line 2" {
|
||||||
|
t.Errorf("expected 'line 2' at index 1, got '%s'", buf.Line(1))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("handles empty buffer", func(t *testing.T) {
|
||||||
|
buf := NewBufferBuilder().Build()
|
||||||
|
|
||||||
|
buf.InsertLine(0, "first line")
|
||||||
|
|
||||||
|
if buf.LineCount() != 2 { // Original empty line + new line
|
||||||
|
t.Errorf("expected 2 lines, got %d", buf.LineCount())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuffer_DeleteLine(t *testing.T) {
|
||||||
|
t.Run("deletes from beginning", func(t *testing.T) {
|
||||||
|
buf := NewBufferBuilder().
|
||||||
|
WithLines([]string{"DELETE ME", "line 2", "line 3"}).
|
||||||
|
Build()
|
||||||
|
|
||||||
|
buf.DeleteLine(0)
|
||||||
|
|
||||||
|
if buf.LineCount() != 2 {
|
||||||
|
t.Errorf("expected 2 lines, got %d", buf.LineCount())
|
||||||
|
}
|
||||||
|
if buf.Line(0) != "line 2" {
|
||||||
|
t.Errorf("expected 'line 2' at index 0, got '%s'", buf.Line(0))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("deletes from middle", func(t *testing.T) {
|
||||||
|
buf := NewBufferBuilder().
|
||||||
|
WithLines([]string{"line 1", "DELETE ME", "line 3"}).
|
||||||
|
Build()
|
||||||
|
|
||||||
|
buf.DeleteLine(1)
|
||||||
|
|
||||||
|
if buf.LineCount() != 2 {
|
||||||
|
t.Errorf("expected 2 lines, got %d", buf.LineCount())
|
||||||
|
}
|
||||||
|
if buf.Line(1) != "line 3" {
|
||||||
|
t.Errorf("expected 'line 3' at index 1, got '%s'", buf.Line(1))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("deletes from end", func(t *testing.T) {
|
||||||
|
buf := NewBufferBuilder().
|
||||||
|
WithLines([]string{"line 1", "line 2", "DELETE ME"}).
|
||||||
|
Build()
|
||||||
|
|
||||||
|
buf.DeleteLine(2)
|
||||||
|
|
||||||
|
if buf.LineCount() != 2 {
|
||||||
|
t.Errorf("expected 2 lines, got %d", buf.LineCount())
|
||||||
|
}
|
||||||
|
if buf.Line(1) != "line 2" {
|
||||||
|
t.Errorf("expected 'line 2' at index 1, got '%s'", buf.Line(1))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("can delete all lines", func(t *testing.T) {
|
||||||
|
buf := NewBufferBuilder().
|
||||||
|
WithLines([]string{"only line"}).
|
||||||
|
Build()
|
||||||
|
|
||||||
|
buf.DeleteLine(0)
|
||||||
|
|
||||||
|
// Buffer allows being completely empty (0 lines)
|
||||||
|
if buf.LineCount() != 0 {
|
||||||
|
t.Errorf("expected 0 lines after deleting last line, got %d", buf.LineCount())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuffer_SetLine(t *testing.T) {
|
||||||
|
t.Run("updates existing line", func(t *testing.T) {
|
||||||
|
buf := NewBufferBuilder().
|
||||||
|
WithLines([]string{"old content"}).
|
||||||
|
Build()
|
||||||
|
|
||||||
|
buf.SetLine(0, "new content")
|
||||||
|
|
||||||
|
if buf.Line(0) != "new content" {
|
||||||
|
t.Errorf("expected 'new content', got '%s'", buf.Line(0))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("updates middle line", func(t *testing.T) {
|
||||||
|
buf := NewBufferBuilder().
|
||||||
|
WithLines([]string{"line 1", "old", "line 3"}).
|
||||||
|
Build()
|
||||||
|
|
||||||
|
buf.SetLine(1, "new")
|
||||||
|
|
||||||
|
if buf.Line(1) != "new" {
|
||||||
|
t.Errorf("expected 'new', got '%s'", buf.Line(1))
|
||||||
|
}
|
||||||
|
// Verify other lines unchanged
|
||||||
|
if buf.Line(0) != "line 1" {
|
||||||
|
t.Error("line 0 should be unchanged")
|
||||||
|
}
|
||||||
|
if buf.Line(2) != "line 3" {
|
||||||
|
t.Error("line 2 should be unchanged")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("can set to empty string", func(t *testing.T) {
|
||||||
|
buf := NewBufferBuilder().
|
||||||
|
WithLines([]string{"has content"}).
|
||||||
|
Build()
|
||||||
|
|
||||||
|
buf.SetLine(0, "")
|
||||||
|
|
||||||
|
if buf.Line(0) != "" {
|
||||||
|
t.Errorf("expected empty line, got '%s'", buf.Line(0))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuffer_LineCount(t *testing.T) {
|
||||||
|
t.Run("empty buffer has one line", func(t *testing.T) {
|
||||||
|
buf := NewBufferBuilder().Build()
|
||||||
|
|
||||||
|
if buf.LineCount() != 1 {
|
||||||
|
t.Errorf("expected 1 line in empty buffer, got %d", buf.LineCount())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("counts multiple lines", func(t *testing.T) {
|
||||||
|
buf := NewBufferBuilder().
|
||||||
|
WithLines([]string{"a", "b", "c", "d", "e"}).
|
||||||
|
Build()
|
||||||
|
|
||||||
|
if buf.LineCount() != 5 {
|
||||||
|
t.Errorf("expected 5 lines, got %d", buf.LineCount())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("counts after insertions", func(t *testing.T) {
|
||||||
|
buf := NewBufferBuilder().
|
||||||
|
WithLines([]string{"line 1"}).
|
||||||
|
Build()
|
||||||
|
|
||||||
|
buf.InsertLine(1, "line 2")
|
||||||
|
buf.InsertLine(2, "line 3")
|
||||||
|
|
||||||
|
if buf.LineCount() != 3 {
|
||||||
|
t.Errorf("expected 3 lines, got %d", buf.LineCount())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("counts after deletions", func(t *testing.T) {
|
||||||
|
buf := NewBufferBuilder().
|
||||||
|
WithLines([]string{"a", "b", "c", "d", "e"}).
|
||||||
|
Build()
|
||||||
|
|
||||||
|
buf.DeleteLine(2)
|
||||||
|
buf.DeleteLine(2)
|
||||||
|
|
||||||
|
if buf.LineCount() != 3 {
|
||||||
|
t.Errorf("expected 3 lines after 2 deletions, got %d", buf.LineCount())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuffer_Line(t *testing.T) {
|
||||||
|
t.Run("retrieves correct line", func(t *testing.T) {
|
||||||
|
buf := NewBufferBuilder().
|
||||||
|
WithLines([]string{"first", "second", "third"}).
|
||||||
|
Build()
|
||||||
|
|
||||||
|
if buf.Line(0) != "first" {
|
||||||
|
t.Errorf("expected 'first', got '%s'", buf.Line(0))
|
||||||
|
}
|
||||||
|
if buf.Line(1) != "second" {
|
||||||
|
t.Errorf("expected 'second', got '%s'", buf.Line(1))
|
||||||
|
}
|
||||||
|
if buf.Line(2) != "third" {
|
||||||
|
t.Errorf("expected 'third', got '%s'", buf.Line(2))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("handles special characters", func(t *testing.T) {
|
||||||
|
buf := NewBufferBuilder().
|
||||||
|
WithLines([]string{"hello\tworld", "foo\nbar"}).
|
||||||
|
Build()
|
||||||
|
|
||||||
|
if buf.Line(0) != "hello\tworld" {
|
||||||
|
t.Errorf("expected tabs preserved, got '%s'", buf.Line(0))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("handles unicode", func(t *testing.T) {
|
||||||
|
buf := NewBufferBuilder().
|
||||||
|
WithLines([]string{"hello 世界", "emoji 🎉"}).
|
||||||
|
Build()
|
||||||
|
|
||||||
|
if buf.Line(0) != "hello 世界" {
|
||||||
|
t.Errorf("expected unicode preserved, got '%s'", buf.Line(0))
|
||||||
|
}
|
||||||
|
if buf.Line(1) != "emoji 🎉" {
|
||||||
|
t.Errorf("expected emoji preserved, got '%s'", buf.Line(1))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBufferBuilder(t *testing.T) {
|
||||||
|
t.Run("builds with default values", func(t *testing.T) {
|
||||||
|
buf := NewBufferBuilder().Build()
|
||||||
|
|
||||||
|
if buf.LineCount() != 1 {
|
||||||
|
t.Errorf("expected 1 empty line, got %d", buf.LineCount())
|
||||||
|
}
|
||||||
|
if buf.Filename != "" {
|
||||||
|
t.Errorf("expected empty filename, got '%s'", buf.Filename)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("builds with custom lines", func(t *testing.T) {
|
||||||
|
buf := NewBufferBuilder().
|
||||||
|
WithLines([]string{"line 1", "line 2"}).
|
||||||
|
Build()
|
||||||
|
|
||||||
|
if buf.LineCount() != 2 {
|
||||||
|
t.Errorf("expected 2 lines, got %d", buf.LineCount())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("builds with filename", func(t *testing.T) {
|
||||||
|
buf := NewBufferBuilder().
|
||||||
|
WithFilename("test.txt").
|
||||||
|
Build()
|
||||||
|
|
||||||
|
if buf.Filename != "test.txt" {
|
||||||
|
t.Errorf("expected filename 'test.txt', got '%s'", buf.Filename)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("builds with filetype", func(t *testing.T) {
|
||||||
|
buf := NewBufferBuilder().
|
||||||
|
WithFiletype("go").
|
||||||
|
Build()
|
||||||
|
|
||||||
|
if buf.Filetype != "go" {
|
||||||
|
t.Errorf("expected filetype 'go', got '%s'", buf.Filetype)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@ -1,6 +1,5 @@
|
|||||||
package core
|
package core
|
||||||
|
|
||||||
// TODO: No more global settings, window-wide settings
|
|
||||||
type WinOptions struct {
|
type WinOptions struct {
|
||||||
Number bool
|
Number bool
|
||||||
RelativeNumber bool
|
RelativeNumber bool
|
||||||
@ -44,10 +43,7 @@ type Window struct {
|
|||||||
// of the content or attempt to be "above" the content (negative value).
|
// of the content or attempt to be "above" the content (negative value).
|
||||||
func (w *Window) clampCursor() {
|
func (w *Window) clampCursor() {
|
||||||
// Clamp line to valid range [0, lineCount-1]
|
// Clamp line to valid range [0, lineCount-1]
|
||||||
maxLine := w.Buffer.LineCount() - 1
|
maxLine := max(w.Buffer.LineCount()-1, 0)
|
||||||
if maxLine < 0 {
|
|
||||||
maxLine = 0 // Empty buffer edge case
|
|
||||||
}
|
|
||||||
if w.Cursor.Line < 0 {
|
if w.Cursor.Line < 0 {
|
||||||
w.Cursor.Line = 0
|
w.Cursor.Line = 0
|
||||||
} else if w.Cursor.Line > maxLine {
|
} else if w.Cursor.Line > maxLine {
|
||||||
|
|||||||
493
internal/core/window_test.go
Normal file
493
internal/core/window_test.go
Normal file
@ -0,0 +1,493 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
// --------------------------------------------------
|
||||||
|
// Window Tests (generated by ClaudeCode)
|
||||||
|
// --------------------------------------------------
|
||||||
|
|
||||||
|
func TestWindow_SetCursorLine(t *testing.T) {
|
||||||
|
t.Run("clamps cursor below zero", func(t *testing.T) {
|
||||||
|
buf := NewBufferBuilder().
|
||||||
|
WithLines([]string{"line 1", "line 2", "line 3"}).
|
||||||
|
Build()
|
||||||
|
win := NewWindowBuilder().
|
||||||
|
WithBuffer(&buf).
|
||||||
|
Build()
|
||||||
|
|
||||||
|
win.SetCursorLine(-5)
|
||||||
|
|
||||||
|
if win.Cursor.Line != 0 {
|
||||||
|
t.Errorf("expected cursor at line 0, got %d", win.Cursor.Line)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("clamps cursor past end", func(t *testing.T) {
|
||||||
|
buf := NewBufferBuilder().
|
||||||
|
WithLines([]string{"line 1", "line 2", "line 3"}).
|
||||||
|
Build()
|
||||||
|
win := NewWindowBuilder().
|
||||||
|
WithBuffer(&buf).
|
||||||
|
Build()
|
||||||
|
|
||||||
|
win.SetCursorLine(999)
|
||||||
|
|
||||||
|
if win.Cursor.Line != 2 { // 3 lines, max index is 2
|
||||||
|
t.Errorf("expected cursor at line 2, got %d", win.Cursor.Line)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("allows valid position", func(t *testing.T) {
|
||||||
|
buf := NewBufferBuilder().
|
||||||
|
WithLines([]string{"line 1", "line 2", "line 3"}).
|
||||||
|
Build()
|
||||||
|
win := NewWindowBuilder().
|
||||||
|
WithBuffer(&buf).
|
||||||
|
Build()
|
||||||
|
|
||||||
|
win.SetCursorLine(1)
|
||||||
|
|
||||||
|
if win.Cursor.Line != 1 {
|
||||||
|
t.Errorf("expected cursor at line 1, got %d", win.Cursor.Line)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("handles empty buffer", func(t *testing.T) {
|
||||||
|
buf := NewBufferBuilder().Build()
|
||||||
|
win := NewWindowBuilder().
|
||||||
|
WithBuffer(&buf).
|
||||||
|
Build()
|
||||||
|
|
||||||
|
win.SetCursorLine(5)
|
||||||
|
|
||||||
|
if win.Cursor.Line != 0 {
|
||||||
|
t.Errorf("expected cursor at line 0 for empty buffer, got %d", win.Cursor.Line)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWindow_SetCursorCol(t *testing.T) {
|
||||||
|
t.Run("clamps to line length", func(t *testing.T) {
|
||||||
|
buf := NewBufferBuilder().
|
||||||
|
WithLines([]string{"hello"}).
|
||||||
|
Build()
|
||||||
|
win := NewWindowBuilder().
|
||||||
|
WithBuffer(&buf).
|
||||||
|
Build()
|
||||||
|
|
||||||
|
win.SetCursorCol(999)
|
||||||
|
|
||||||
|
// "hello" is 5 chars, max col should be 5 (after last char for insert mode)
|
||||||
|
if win.Cursor.Col > 5 {
|
||||||
|
t.Errorf("expected cursor col <= 5, got %d", win.Cursor.Col)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("clamps below zero", func(t *testing.T) {
|
||||||
|
buf := NewBufferBuilder().
|
||||||
|
WithLines([]string{"hello"}).
|
||||||
|
Build()
|
||||||
|
win := NewWindowBuilder().
|
||||||
|
WithBuffer(&buf).
|
||||||
|
Build()
|
||||||
|
|
||||||
|
win.SetCursorCol(-10)
|
||||||
|
|
||||||
|
if win.Cursor.Col != 0 {
|
||||||
|
t.Errorf("expected cursor col 0, got %d", win.Cursor.Col)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("handles empty line", func(t *testing.T) {
|
||||||
|
buf := NewBufferBuilder().
|
||||||
|
WithLines([]string{""}).
|
||||||
|
Build()
|
||||||
|
win := NewWindowBuilder().
|
||||||
|
WithBuffer(&buf).
|
||||||
|
Build()
|
||||||
|
|
||||||
|
win.SetCursorCol(5)
|
||||||
|
|
||||||
|
if win.Cursor.Col != 0 {
|
||||||
|
t.Errorf("expected cursor at col 0 on empty line, got %d", win.Cursor.Col)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("allows cursor at end of line", func(t *testing.T) {
|
||||||
|
buf := NewBufferBuilder().
|
||||||
|
WithLines([]string{"hello"}).
|
||||||
|
Build()
|
||||||
|
win := NewWindowBuilder().
|
||||||
|
WithBuffer(&buf).
|
||||||
|
Build()
|
||||||
|
|
||||||
|
win.SetCursorCol(5) // After last char
|
||||||
|
|
||||||
|
if win.Cursor.Col != 5 {
|
||||||
|
t.Errorf("expected cursor at col 5, got %d", win.Cursor.Col)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWindow_AdjustScroll(t *testing.T) {
|
||||||
|
t.Run("scrolls down when cursor goes below viewport", func(t *testing.T) {
|
||||||
|
// Create buffer with many lines
|
||||||
|
lines := make([]string, 100)
|
||||||
|
for i := range lines {
|
||||||
|
lines[i] = "line"
|
||||||
|
}
|
||||||
|
buf := NewBufferBuilder().WithLines(lines).Build()
|
||||||
|
|
||||||
|
win := NewWindowBuilder().
|
||||||
|
WithBuffer(&buf).
|
||||||
|
WithHeight(24).
|
||||||
|
Build()
|
||||||
|
|
||||||
|
// Start at top
|
||||||
|
win.SetCursorLine(0)
|
||||||
|
win.AdjustScroll()
|
||||||
|
initialScroll := win.ScrollY
|
||||||
|
|
||||||
|
// Move cursor way down
|
||||||
|
win.SetCursorLine(50)
|
||||||
|
win.AdjustScroll()
|
||||||
|
|
||||||
|
// Scroll should have increased
|
||||||
|
if win.ScrollY <= initialScroll {
|
||||||
|
t.Errorf("expected scroll to increase, was %d, now %d", initialScroll, win.ScrollY)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cursor should be visible
|
||||||
|
viewport := win.ViewportHeight()
|
||||||
|
if win.Cursor.Line < win.ScrollY || win.Cursor.Line >= win.ScrollY+viewport {
|
||||||
|
t.Errorf("cursor at %d not visible in scroll range [%d, %d)",
|
||||||
|
win.Cursor.Line, win.ScrollY, win.ScrollY+viewport)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("scrolls up when cursor goes above viewport", func(t *testing.T) {
|
||||||
|
lines := make([]string, 100)
|
||||||
|
for i := range lines {
|
||||||
|
lines[i] = "line"
|
||||||
|
}
|
||||||
|
buf := NewBufferBuilder().WithLines(lines).Build()
|
||||||
|
|
||||||
|
win := NewWindowBuilder().
|
||||||
|
WithBuffer(&buf).
|
||||||
|
WithHeight(24).
|
||||||
|
Build()
|
||||||
|
|
||||||
|
// Start at bottom
|
||||||
|
win.SetCursorLine(80)
|
||||||
|
win.AdjustScroll()
|
||||||
|
initialScroll := win.ScrollY
|
||||||
|
|
||||||
|
// Move cursor to top
|
||||||
|
win.SetCursorLine(5)
|
||||||
|
win.AdjustScroll()
|
||||||
|
|
||||||
|
// Scroll should have decreased
|
||||||
|
if win.ScrollY >= initialScroll {
|
||||||
|
t.Errorf("expected scroll to decrease, was %d, now %d", initialScroll, win.ScrollY)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cursor should be visible
|
||||||
|
viewport := win.ViewportHeight()
|
||||||
|
if win.Cursor.Line < win.ScrollY || win.Cursor.Line >= win.ScrollY+viewport {
|
||||||
|
t.Errorf("cursor at %d not visible in scroll range [%d, %d)",
|
||||||
|
win.Cursor.Line, win.ScrollY, win.ScrollY+viewport)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("respects scrolloff margin", func(t *testing.T) {
|
||||||
|
lines := make([]string, 100)
|
||||||
|
for i := range lines {
|
||||||
|
lines[i] = "line"
|
||||||
|
}
|
||||||
|
buf := NewBufferBuilder().WithLines(lines).Build()
|
||||||
|
|
||||||
|
win := NewWindowBuilder().
|
||||||
|
WithBuffer(&buf).
|
||||||
|
WithHeight(24).
|
||||||
|
Build()
|
||||||
|
|
||||||
|
// Set scrolloff to 5
|
||||||
|
opts := win.Options
|
||||||
|
opts.ScrollOff = 5
|
||||||
|
win.SetOptions(opts)
|
||||||
|
|
||||||
|
// Move to line 20 and adjust
|
||||||
|
win.SetCursorLine(20)
|
||||||
|
win.AdjustScroll()
|
||||||
|
|
||||||
|
viewport := win.ViewportHeight()
|
||||||
|
distFromTop := win.Cursor.Line - win.ScrollY
|
||||||
|
distFromBottom := (win.ScrollY + viewport - 1) - win.Cursor.Line
|
||||||
|
|
||||||
|
// At least one should respect scrolloff
|
||||||
|
if distFromTop < opts.ScrollOff && distFromBottom < opts.ScrollOff {
|
||||||
|
t.Errorf("scrolloff %d not respected: top=%d, bottom=%d",
|
||||||
|
opts.ScrollOff, distFromTop, distFromBottom)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("handles scrolloff larger than half viewport", func(t *testing.T) {
|
||||||
|
lines := make([]string, 100)
|
||||||
|
for i := range lines {
|
||||||
|
lines[i] = "line"
|
||||||
|
}
|
||||||
|
buf := NewBufferBuilder().WithLines(lines).Build()
|
||||||
|
|
||||||
|
win := NewWindowBuilder().
|
||||||
|
WithBuffer(&buf).
|
||||||
|
WithHeight(24).
|
||||||
|
Build()
|
||||||
|
|
||||||
|
// Set scrolloff larger than half viewport
|
||||||
|
opts := win.Options
|
||||||
|
opts.ScrollOff = 999
|
||||||
|
win.SetOptions(opts)
|
||||||
|
|
||||||
|
win.SetCursorLine(50)
|
||||||
|
win.AdjustScroll()
|
||||||
|
|
||||||
|
// Should not panic or error
|
||||||
|
viewport := win.ViewportHeight()
|
||||||
|
if win.Cursor.Line < win.ScrollY || win.Cursor.Line >= win.ScrollY+viewport {
|
||||||
|
t.Error("cursor should still be visible with large scrolloff")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("handles small viewport", func(t *testing.T) {
|
||||||
|
lines := make([]string, 100)
|
||||||
|
for i := range lines {
|
||||||
|
lines[i] = "line"
|
||||||
|
}
|
||||||
|
buf := NewBufferBuilder().WithLines(lines).Build()
|
||||||
|
|
||||||
|
win := NewWindowBuilder().
|
||||||
|
WithBuffer(&buf).
|
||||||
|
WithHeight(5). // Very small
|
||||||
|
Build()
|
||||||
|
|
||||||
|
win.SetCursorLine(50)
|
||||||
|
win.AdjustScroll()
|
||||||
|
|
||||||
|
// Should not panic
|
||||||
|
viewport := win.ViewportHeight()
|
||||||
|
if viewport > 0 && (win.Cursor.Line < win.ScrollY || win.Cursor.Line >= win.ScrollY+viewport) {
|
||||||
|
t.Error("cursor should be visible in small viewport")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWindow_ViewportHeight(t *testing.T) {
|
||||||
|
t.Run("calculates viewport height correctly", func(t *testing.T) {
|
||||||
|
buf := NewBufferBuilder().Build()
|
||||||
|
win := NewWindowBuilder().
|
||||||
|
WithBuffer(&buf).
|
||||||
|
WithHeight(24).
|
||||||
|
Build()
|
||||||
|
|
||||||
|
// Height - 2 (status bar + command bar)
|
||||||
|
expected := 22
|
||||||
|
if win.ViewportHeight() != expected {
|
||||||
|
t.Errorf("expected viewport height %d, got %d", expected, win.ViewportHeight())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("handles small window", func(t *testing.T) {
|
||||||
|
buf := NewBufferBuilder().Build()
|
||||||
|
win := NewWindowBuilder().
|
||||||
|
WithBuffer(&buf).
|
||||||
|
WithHeight(3).
|
||||||
|
Build()
|
||||||
|
|
||||||
|
// 3 - 2 = 1
|
||||||
|
expected := 1
|
||||||
|
if win.ViewportHeight() != expected {
|
||||||
|
t.Errorf("expected viewport height %d, got %d", expected, win.ViewportHeight())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("handles zero height", func(t *testing.T) {
|
||||||
|
buf := NewBufferBuilder().Build()
|
||||||
|
win := NewWindowBuilder().
|
||||||
|
WithBuffer(&buf).
|
||||||
|
WithHeight(0).
|
||||||
|
Build()
|
||||||
|
|
||||||
|
// With height 0, viewport is 0 - 2 (status + command bars) = -2
|
||||||
|
// This is an edge case that shouldn't occur in practice, but shouldn't panic
|
||||||
|
result := win.ViewportHeight()
|
||||||
|
expected := -2
|
||||||
|
if result != expected {
|
||||||
|
t.Errorf("expected viewport height %d for zero height window, got %d", expected, result)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWindow_SetOptions(t *testing.T) {
|
||||||
|
t.Run("updates options", func(t *testing.T) {
|
||||||
|
buf := NewBufferBuilder().Build()
|
||||||
|
win := NewWindowBuilder().
|
||||||
|
WithBuffer(&buf).
|
||||||
|
Build()
|
||||||
|
|
||||||
|
newOpts := WinOptions{
|
||||||
|
Number: false,
|
||||||
|
RelativeNumber: false,
|
||||||
|
GutterSize: 10,
|
||||||
|
ScrollOff: 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
win.SetOptions(newOpts)
|
||||||
|
|
||||||
|
if win.Options.Number != false {
|
||||||
|
t.Error("expected Number to be false")
|
||||||
|
}
|
||||||
|
if win.Options.RelativeNumber != false {
|
||||||
|
t.Error("expected RelativeNumber to be false")
|
||||||
|
}
|
||||||
|
if win.Options.GutterSize != 10 {
|
||||||
|
t.Errorf("expected GutterSize 10, got %d", win.Options.GutterSize)
|
||||||
|
}
|
||||||
|
if win.Options.ScrollOff != 3 {
|
||||||
|
t.Errorf("expected ScrollOff 3, got %d", win.Options.ScrollOff)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("can toggle individual options", func(t *testing.T) {
|
||||||
|
buf := NewBufferBuilder().Build()
|
||||||
|
win := NewWindowBuilder().
|
||||||
|
WithBuffer(&buf).
|
||||||
|
Build()
|
||||||
|
|
||||||
|
// Get current options
|
||||||
|
opts := win.Options
|
||||||
|
originalNumber := opts.Number
|
||||||
|
|
||||||
|
// Toggle number
|
||||||
|
opts.Number = !opts.Number
|
||||||
|
win.SetOptions(opts)
|
||||||
|
|
||||||
|
if win.Options.Number == originalNumber {
|
||||||
|
t.Error("Number option should have toggled")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWindow_SetAnchor(t *testing.T) {
|
||||||
|
t.Run("sets anchor line", func(t *testing.T) {
|
||||||
|
buf := NewBufferBuilder().
|
||||||
|
WithLines([]string{"a", "b", "c"}).
|
||||||
|
Build()
|
||||||
|
win := NewWindowBuilder().
|
||||||
|
WithBuffer(&buf).
|
||||||
|
Build()
|
||||||
|
|
||||||
|
win.SetAnchorLine(2)
|
||||||
|
|
||||||
|
if win.Anchor.Line != 2 {
|
||||||
|
t.Errorf("expected anchor line 2, got %d", win.Anchor.Line)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("sets anchor col", func(t *testing.T) {
|
||||||
|
buf := NewBufferBuilder().
|
||||||
|
WithLines([]string{"hello world"}).
|
||||||
|
Build()
|
||||||
|
win := NewWindowBuilder().
|
||||||
|
WithBuffer(&buf).
|
||||||
|
Build()
|
||||||
|
|
||||||
|
win.SetAnchorCol(5)
|
||||||
|
|
||||||
|
if win.Anchor.Col != 5 {
|
||||||
|
t.Errorf("expected anchor col 5, got %d", win.Anchor.Col)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWindow_SetDimensions(t *testing.T) {
|
||||||
|
t.Run("updates width and height", func(t *testing.T) {
|
||||||
|
buf := NewBufferBuilder().Build()
|
||||||
|
win := NewWindowBuilder().
|
||||||
|
WithBuffer(&buf).
|
||||||
|
Build()
|
||||||
|
|
||||||
|
win.SetDimensions(100, 50)
|
||||||
|
|
||||||
|
if win.Width != 100 {
|
||||||
|
t.Errorf("expected width 100, got %d", win.Width)
|
||||||
|
}
|
||||||
|
if win.Height != 50 {
|
||||||
|
t.Errorf("expected height 50, got %d", win.Height)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWindowBuilder(t *testing.T) {
|
||||||
|
t.Run("builds with defaults", func(t *testing.T) {
|
||||||
|
buf := NewBufferBuilder().Build()
|
||||||
|
win := NewWindowBuilder().
|
||||||
|
WithBuffer(&buf).
|
||||||
|
Build()
|
||||||
|
|
||||||
|
// Should have default options
|
||||||
|
if win.Options.Number != true {
|
||||||
|
t.Error("expected default Number to be true")
|
||||||
|
}
|
||||||
|
if win.Options.RelativeNumber != true {
|
||||||
|
t.Error("expected default RelativeNumber to be true")
|
||||||
|
}
|
||||||
|
if win.Options.ScrollOff != 8 {
|
||||||
|
t.Errorf("expected default ScrollOff 8, got %d", win.Options.ScrollOff)
|
||||||
|
}
|
||||||
|
if win.Options.GutterSize != 5 {
|
||||||
|
t.Errorf("expected default GutterSize 5, got %d", win.Options.GutterSize)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("builds with custom cursor position", func(t *testing.T) {
|
||||||
|
buf := NewBufferBuilder().
|
||||||
|
WithLines([]string{"a", "b", "c"}).
|
||||||
|
Build()
|
||||||
|
win := NewWindowBuilder().
|
||||||
|
WithBuffer(&buf).
|
||||||
|
WithCursorPos(2, 0).
|
||||||
|
Build()
|
||||||
|
|
||||||
|
if win.Cursor.Line != 2 {
|
||||||
|
t.Errorf("expected cursor line 2, got %d", win.Cursor.Line)
|
||||||
|
}
|
||||||
|
if win.Cursor.Col != 0 {
|
||||||
|
t.Errorf("expected cursor col 0, got %d", win.Cursor.Col)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("builds with custom dimensions", func(t *testing.T) {
|
||||||
|
buf := NewBufferBuilder().Build()
|
||||||
|
win := NewWindowBuilder().
|
||||||
|
WithBuffer(&buf).
|
||||||
|
WithDimensions(120, 40).
|
||||||
|
Build()
|
||||||
|
|
||||||
|
if win.Width != 120 {
|
||||||
|
t.Errorf("expected width 120, got %d", win.Width)
|
||||||
|
}
|
||||||
|
if win.Height != 40 {
|
||||||
|
t.Errorf("expected height 40, got %d", win.Height)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("assigns unique IDs", func(t *testing.T) {
|
||||||
|
buf := NewBufferBuilder().Build()
|
||||||
|
win1 := NewWindowBuilder().WithBuffer(&buf).Build()
|
||||||
|
win2 := NewWindowBuilder().WithBuffer(&buf).Build()
|
||||||
|
|
||||||
|
if win1.Id == win2.Id {
|
||||||
|
t.Errorf("expected unique IDs, both were %d", win1.Id)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@ -10,6 +10,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// NOTE: Lots of this actually sucks ass, but it works for now...
|
// 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
|
// sendKeys sends a sequence of keys to the test model
|
||||||
func sendKeys(tm *teatest.TestModel, keys ...string) {
|
func sendKeys(tm *teatest.TestModel, keys ...string) {
|
||||||
|
|||||||
@ -41,7 +41,7 @@ type Model struct {
|
|||||||
commandError error
|
commandError error
|
||||||
commandOutput string
|
commandOutput string
|
||||||
|
|
||||||
// Global settings (TODO: This needs to be refactored)
|
// Global settings
|
||||||
settings core.EditorSettings
|
settings core.EditorSettings
|
||||||
|
|
||||||
// Registers
|
// Registers
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user