Gim/internal/core/gap_buffer_test.go
Hayden Hargreaves e362c9f118
All checks were successful
Run Test Suite / test (push) Successful in 56s
feat: gap buffer is implemented, tested
Not sure if this is perfect, but it seems to be working
2026-04-02 12:39:30 -07:00

456 lines
12 KiB
Go

package core
import "testing"
func TestGapBufferString(t *testing.T) {
t.Run("gapBuffer.String() on empty buffer returns \"\"", func(t *testing.T) {
buf := NewEmptyGapBuffer()
str := buf.String()
if str != "" {
t.Fatalf("buf.String() expected '', got '%s'", str)
}
})
t.Run("gapBuffer.String() on string returns the string", func(t *testing.T) {
buf := NewGapBuffer("Hello world")
str := buf.String()
if str != "Hello world" {
t.Fatalf("buf.String() expected 'Hello world', got '%s'", str)
}
})
t.Run("gapBuffer.String() after moving gap returns the string", func(t *testing.T) {
buf := NewGapBuffer("Hello world")
buf.moveGap(6)
str := buf.String()
if str != "Hello world" {
t.Fatalf("buf.String() expected 'Hello world', got '%s'", str)
}
})
t.Run("gapBuffer.String() after growing gap returns the string", func(t *testing.T) {
buf := NewGapBuffer("Hello world")
buf.grow(16)
str := buf.String()
if str != "Hello world" {
t.Fatalf("buf.String() expected 'Hello world', got '%s'", str)
}
})
t.Run("gapBuffer.String() handles unicode characters", func(t *testing.T) {
buf := NewGapBuffer("Hello 世界 🌍")
str := buf.String()
if str != "Hello 世界 🌍" {
t.Fatalf("buf.String() expected 'Hello 世界 🌍', got '%s'", str)
}
})
}
func TestGapBufferLen(t *testing.T) {
t.Run("empty buffer has length 0", func(t *testing.T) {
buf := NewEmptyGapBuffer()
if buf.Len() != 0 {
t.Fatalf("expected length 0, got %d", buf.Len())
}
})
t.Run("buffer with content returns correct length", func(t *testing.T) {
buf := NewGapBuffer("Hello")
if buf.Len() != 5 {
t.Fatalf("expected length 5, got %d", buf.Len())
}
})
t.Run("length with unicode characters", func(t *testing.T) {
buf := NewGapBuffer("Hi 🌍")
if buf.Len() != 4 {
t.Fatalf("expected length 4, got %d", buf.Len())
}
})
}
func TestGapBufferInsert(t *testing.T) {
t.Run("insert at beginning", func(t *testing.T) {
buf := NewGapBuffer("world")
buf.Insert(0, "Hello ")
if buf.String() != "Hello world" {
t.Fatalf("expected 'Hello world', got '%s'", buf.String())
}
})
t.Run("insert at end", func(t *testing.T) {
buf := NewGapBuffer("Hello")
buf.Insert(5, " world")
if buf.String() != "Hello world" {
t.Fatalf("expected 'Hello world', got '%s'", buf.String())
}
})
t.Run("insert in middle", func(t *testing.T) {
buf := NewGapBuffer("Helloworld")
buf.Insert(5, " ")
if buf.String() != "Hello world" {
t.Fatalf("expected 'Hello world', got '%s'", buf.String())
}
})
t.Run("insert into empty buffer", func(t *testing.T) {
buf := NewEmptyGapBuffer()
buf.Insert(0, "Hello")
if buf.String() != "Hello" {
t.Fatalf("expected 'Hello', got '%s'", buf.String())
}
})
t.Run("insert empty string does nothing", func(t *testing.T) {
buf := NewGapBuffer("Hello")
buf.Insert(2, "")
if buf.String() != "Hello" {
t.Fatalf("expected 'Hello', got '%s'", buf.String())
}
})
t.Run("insert at invalid position does nothing", func(t *testing.T) {
buf := NewGapBuffer("Hello")
buf.Insert(-1, "X")
if buf.String() != "Hello" {
t.Fatalf("expected 'Hello', got '%s'", buf.String())
}
buf.Insert(100, "X")
if buf.String() != "Hello" {
t.Fatalf("expected 'Hello', got '%s'", buf.String())
}
})
t.Run("insert unicode characters", func(t *testing.T) {
buf := NewGapBuffer("Hello")
buf.Insert(5, " 世界")
if buf.String() != "Hello 世界" {
t.Fatalf("expected 'Hello 世界', got '%s'", buf.String())
}
})
t.Run("multiple consecutive insertions", func(t *testing.T) {
buf := NewEmptyGapBuffer()
buf.Insert(0, "a")
buf.Insert(1, "b")
buf.Insert(2, "c")
if buf.String() != "abc" {
t.Fatalf("expected 'abc', got '%s'", buf.String())
}
})
t.Run("insert large text triggers grow", func(t *testing.T) {
buf := NewGapBuffer("Hello")
longText := "Lorem ipsum dolor sit amet, consectetur adipiscing elit"
buf.Insert(5, longText)
expected := "Hello" + longText
if buf.String() != expected {
t.Fatalf("expected '%s', got '%s'", expected, buf.String())
}
})
}
func TestGapBufferDelete(t *testing.T) {
t.Run("delete from beginning", func(t *testing.T) {
buf := NewGapBuffer("Hello world")
buf.Delete(0, 6)
if buf.String() != "world" {
t.Fatalf("expected 'world', got '%s'", buf.String())
}
})
t.Run("delete from end", func(t *testing.T) {
buf := NewGapBuffer("Hello world")
buf.Delete(5, 6)
if buf.String() != "Hello" {
t.Fatalf("expected 'Hello', got '%s'", buf.String())
}
})
t.Run("delete from middle", func(t *testing.T) {
buf := NewGapBuffer("Hello world")
buf.Delete(5, 1)
if buf.String() != "Helloworld" {
t.Fatalf("expected 'Helloworld', got '%s'", buf.String())
}
})
t.Run("delete single character", func(t *testing.T) {
buf := NewGapBuffer("Hello")
buf.Delete(1, 1)
if buf.String() != "Hllo" {
t.Fatalf("expected 'Hllo', got '%s'", buf.String())
}
})
t.Run("delete all content", func(t *testing.T) {
buf := NewGapBuffer("Hello")
buf.Delete(0, 5)
if buf.String() != "" {
t.Fatalf("expected '', got '%s'", buf.String())
}
})
t.Run("delete with count exceeding length", func(t *testing.T) {
buf := NewGapBuffer("Hello")
buf.Delete(2, 100)
if buf.String() != "He" {
t.Fatalf("expected 'He', got '%s'", buf.String())
}
})
t.Run("delete at invalid position does nothing", func(t *testing.T) {
buf := NewGapBuffer("Hello")
buf.Delete(-1, 2)
if buf.String() != "Hello" {
t.Fatalf("expected 'Hello', got '%s'", buf.String())
}
buf.Delete(100, 2)
if buf.String() != "Hello" {
t.Fatalf("expected 'Hello', got '%s'", buf.String())
}
})
t.Run("delete with zero count does nothing", func(t *testing.T) {
buf := NewGapBuffer("Hello")
buf.Delete(2, 0)
if buf.String() != "Hello" {
t.Fatalf("expected 'Hello', got '%s'", buf.String())
}
})
t.Run("delete unicode characters", func(t *testing.T) {
buf := NewGapBuffer("Hello 世界")
buf.Delete(6, 2)
if buf.String() != "Hello " {
t.Fatalf("expected 'Hello ', got '%s'", buf.String())
}
})
}
func TestGapBufferRuneAt(t *testing.T) {
t.Run("get rune at valid position", func(t *testing.T) {
buf := NewGapBuffer("Hello")
if buf.RuneAt(0) != 'H' {
t.Fatalf("expected 'H', got '%c'", buf.RuneAt(0))
}
if buf.RuneAt(4) != 'o' {
t.Fatalf("expected 'o', got '%c'", buf.RuneAt(4))
}
})
t.Run("get rune before gap", func(t *testing.T) {
buf := NewGapBuffer("Hello world")
buf.moveGap(6)
if buf.RuneAt(5) != ' ' {
t.Fatalf("expected ' ', got '%c'", buf.RuneAt(5))
}
})
t.Run("get rune after gap", func(t *testing.T) {
buf := NewGapBuffer("Hello world")
buf.moveGap(6)
if buf.RuneAt(6) != 'w' {
t.Fatalf("expected 'w', got '%c'", buf.RuneAt(6))
}
})
t.Run("get rune at invalid position returns 0", func(t *testing.T) {
buf := NewGapBuffer("Hello")
if buf.RuneAt(-1) != 0 {
t.Fatalf("expected 0, got '%c'", buf.RuneAt(-1))
}
if buf.RuneAt(100) != 0 {
t.Fatalf("expected 0, got '%c'", buf.RuneAt(100))
}
})
t.Run("get unicode rune", func(t *testing.T) {
buf := NewGapBuffer("🌍世界")
if buf.RuneAt(0) != '🌍' {
t.Fatalf("expected '🌍', got '%c'", buf.RuneAt(0))
}
if buf.RuneAt(1) != '世' {
t.Fatalf("expected '世', got '%c'", buf.RuneAt(1))
}
})
}
func TestGapBufferSubstring(t *testing.T) {
t.Run("substring from middle", func(t *testing.T) {
buf := NewGapBuffer("Hello world")
if buf.Substring(0, 5) != "Hello" {
t.Fatalf("expected 'Hello', got '%s'", buf.Substring(0, 5))
}
if buf.Substring(6, 11) != "world" {
t.Fatalf("expected 'world', got '%s'", buf.Substring(6, 11))
}
})
t.Run("substring entire content", func(t *testing.T) {
buf := NewGapBuffer("Hello")
if buf.Substring(0, 5) != "Hello" {
t.Fatalf("expected 'Hello', got '%s'", buf.Substring(0, 5))
}
})
t.Run("substring with start >= end returns empty", func(t *testing.T) {
buf := NewGapBuffer("Hello")
if buf.Substring(3, 3) != "" {
t.Fatalf("expected '', got '%s'", buf.Substring(3, 3))
}
if buf.Substring(3, 2) != "" {
t.Fatalf("expected '', got '%s'", buf.Substring(3, 2))
}
})
t.Run("substring clamps to buffer bounds", func(t *testing.T) {
buf := NewGapBuffer("Hello")
if buf.Substring(-5, 3) != "Hel" {
t.Fatalf("expected 'Hel', got '%s'", buf.Substring(-5, 3))
}
if buf.Substring(2, 100) != "llo" {
t.Fatalf("expected 'llo', got '%s'", buf.Substring(2, 100))
}
})
t.Run("substring with gap in middle", func(t *testing.T) {
buf := NewGapBuffer("Hello world")
buf.moveGap(6)
if buf.Substring(0, 11) != "Hello world" {
t.Fatalf("expected 'Hello world', got '%s'", buf.Substring(0, 11))
}
})
t.Run("substring with unicode", func(t *testing.T) {
buf := NewGapBuffer("Hi 世界")
if buf.Substring(3, 5) != "世界" {
t.Fatalf("expected '世界', got '%s'", buf.Substring(3, 5))
}
})
}
func TestGapBufferSet(t *testing.T) {
t.Run("set replaces content", func(t *testing.T) {
buf := NewGapBuffer("Hello")
buf.Set("Goodbye")
if buf.String() != "Goodbye" {
t.Fatalf("expected 'Goodbye', got '%s'", buf.String())
}
})
t.Run("set to empty string", func(t *testing.T) {
buf := NewGapBuffer("Hello")
buf.Set("")
if buf.String() != "" {
t.Fatalf("expected '', got '%s'", buf.String())
}
})
t.Run("set on empty buffer", func(t *testing.T) {
buf := NewEmptyGapBuffer()
buf.Set("Hello")
if buf.String() != "Hello" {
t.Fatalf("expected 'Hello', got '%s'", buf.String())
}
})
t.Run("set resets gap position", func(t *testing.T) {
buf := NewGapBuffer("Hello")
buf.moveGap(2)
buf.Set("World")
if buf.String() != "World" {
t.Fatalf("expected 'World', got '%s'", buf.String())
}
if buf.gapStart != 5 {
t.Fatalf("expected gap start at 5, got %d", buf.gapStart)
}
})
}
func TestGapBufferClear(t *testing.T) {
t.Run("clear removes all content", func(t *testing.T) {
buf := NewGapBuffer("Hello world")
buf.Clear()
if buf.String() != "" {
t.Fatalf("expected '', got '%s'", buf.String())
}
})
t.Run("clear on empty buffer", func(t *testing.T) {
buf := NewEmptyGapBuffer()
buf.Clear()
if buf.String() != "" {
t.Fatalf("expected '', got '%s'", buf.String())
}
})
t.Run("clear resets gap to start", func(t *testing.T) {
buf := NewGapBuffer("Hello")
buf.Clear()
if buf.gapStart != 0 {
t.Fatalf("expected gap start at 0, got %d", buf.gapStart)
}
if buf.gapEnd != len(buf.buffer) {
t.Fatalf("expected gap end at %d, got %d", len(buf.buffer), buf.gapEnd)
}
})
}
func TestGapBufferGapSize(t *testing.T) {
t.Run("gap size on new buffer", func(t *testing.T) {
buf := NewGapBuffer("Hello")
if buf.GapSize() != 16 {
t.Fatalf("expected gap size 16, got %d", buf.GapSize())
}
})
t.Run("gap size on empty buffer", func(t *testing.T) {
buf := NewEmptyGapBuffer()
if buf.GapSize() != 16 {
t.Fatalf("expected gap size 16, got %d", buf.GapSize())
}
})
t.Run("gap size decreases after insert", func(t *testing.T) {
buf := NewGapBuffer("Hello")
initialGapSize := buf.GapSize()
buf.Insert(5, "XX")
if buf.GapSize() != initialGapSize-2 {
t.Fatalf("expected gap size %d, got %d", initialGapSize-2, buf.GapSize())
}
})
}
func TestGapBufferComplex(t *testing.T) {
t.Run("sequence of operations", func(t *testing.T) {
buf := NewEmptyGapBuffer()
buf.Insert(0, "Hello")
buf.Insert(5, " world")
buf.Delete(5, 1)
buf.Insert(5, ", ")
if buf.String() != "Hello, world" {
t.Fatalf("expected 'Hello, world', got '%s'", buf.String())
}
})
t.Run("insert and delete at different positions", func(t *testing.T) {
buf := NewGapBuffer("abcdefgh")
buf.Delete(2, 4)
buf.Insert(2, "XX")
if buf.String() != "abXXgh" {
t.Fatalf("expected 'abXXgh', got '%s'", buf.String())
}
})
t.Run("editing with gap movement", func(t *testing.T) {
buf := NewGapBuffer("Hello world")
buf.Insert(0, ">> ")
buf.Insert(buf.Len(), " <<")
if buf.String() != ">> Hello world <<" {
t.Fatalf("expected '>> Hello world <<', got '%s'", buf.String())
}
})
}