▍ humdrum codex / glint v1.0.2
license AGPL-3.0
4.7 KB raw
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
package app

import (
	"strings"
	"testing"

	"glint/internal/editor"

	tea "github.com/charmbracelet/bubbletea"
)

// altSemicolon is the Alt+; key event that opens the spell popup.
var altSemicolon = tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune(";"), Alt: true}

func editorPos(row, col int) editor.Position { return editor.Position{Row: row, Col: col} }

func TestAltSemicolonOpensPopupOnMisspelling(t *testing.T) {
	a := newApp()
	a.setSize(100, 24)
	a.editor.SetContent([]byte("a recieve here"))
	a.editor.SetCursor(editorPos(0, 4)) // inside "recieve"

	a.handleKey(altSemicolon)
	if a.mode != ModeSpell {
		t.Fatalf("mode = %d, want ModeSpell", a.mode)
	}
	if a.spell.word != "recieve" {
		t.Errorf("popup word = %q, want recieve", a.spell.word)
	}
	if len(a.spell.options) < 3 {
		t.Errorf("want suggestions + add + ignore, got %d options", len(a.spell.options))
	}
}

func TestAltSemicolonNoMisspellingOpensToggleOnly(t *testing.T) {
	a := newApp()
	a.setSize(100, 24)
	a.editor.SetContent([]byte("all correct words"))
	a.editor.SetCursor(editorPos(0, 1))
	a.handleKey(altSemicolon)
	// With no flagged word, Alt+; opens a minimal toggle-only popup (no word, a
	// single Toggle option) so spellcheck can always be turned off/on.
	if a.mode != ModeSpell {
		t.Fatal("Alt+; did not open the toggle-only popup")
	}
	if a.spell.word != "" || len(a.spell.options) != 1 || a.spell.options[0].kind != spellToggle {
		t.Errorf("want toggle-only popup, got word=%q opts=%+v", a.spell.word, a.spell.options)
	}
}

func TestSpellPopupNumberKeyReplaces(t *testing.T) {
	a := newApp()
	a.setSize(100, 24)
	a.editor.SetContent([]byte("a recieve here"))
	a.editor.SetCursor(editorPos(0, 4))
	a.handleKey(altSemicolon)

	// The first suggestion for "recieve" is "receive"; press "1" to apply it.
	if a.spell.options[0].value != "receive" {
		t.Fatalf("first suggestion = %q, want receive", a.spell.options[0].value)
	}
	a.handleKey(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("1")})
	if a.mode != ModeEditor {
		t.Errorf("popup did not close after applying; mode = %d", a.mode)
	}
	if got := a.editor.Lines[0]; got != "a receive here" {
		t.Errorf("line after replace = %q, want \"a receive here\"", got)
	}
}

func TestSpellPopupAddKey(t *testing.T) {
	a := newApp()
	a.setSize(100, 24)
	a.editor.SetContent([]byte("my zzplonk word"))
	a.editor.SetCursor(editorPos(0, 4))
	a.handleKey(altSemicolon)
	if a.mode != ModeSpell {
		t.Fatalf("popup did not open on zzplonk")
	}
	a.handleKey(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("i")}) // ignore for session
	if a.mode != ModeEditor {
		t.Errorf("popup did not close after ignore")
	}
	if strings.Contains(a.status, "failed") {
		t.Errorf("ignore reported failure: %q", a.status)
	}
}

func TestSpellPopupToggleOff(t *testing.T) {
	a := newApp()
	a.setSize(100, 24)
	a.editor.SetContent([]byte("a recieve here"))
	a.editor.SetCursor(editorPos(0, 4))
	a.handleKey(altSemicolon)
	a.handleKey(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("t")}) // toggle off
	if a.editor.SpellEnabled() {
		t.Error("toggle did not disable spellcheck")
	}
	// With spellcheck off there are no underlines, but Alt+; still opens a
	// toggle-only popup so it can be turned back on.
	a.handleKey(altSemicolon)
	if a.mode != ModeSpell {
		t.Fatal("Alt+; with spellcheck off did not open the toggle popup")
	}
	a.handleKey(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("t")}) // toggle on
	if !a.editor.SpellEnabled() {
		t.Error("toggle did not re-enable spellcheck")
	}
}

// A plain click (press then release) on a misspelled word opens its popup
// (TASK-027: the popup must wait for release so a drag can win instead).
func TestMouseClickOnMisspellingOpensPopup(t *testing.T) {
	a := newApp()
	a.setSize(100, 24)
	a.editor.SetContent([]byte("a recieve here"))
	lm := a.leftMargin()
	y := a.topPad() - 1
	x := lm + 4 // inside "recieve"
	a.Update(tea.MouseMsg{Button: tea.MouseButtonLeft, Action: tea.MouseActionPress, X: x, Y: y})
	a.Update(tea.MouseMsg{Button: tea.MouseButtonLeft, Action: tea.MouseActionRelease, X: x, Y: y})
	if a.mode != ModeSpell {
		t.Fatalf("plain click on a misspelling: mode = %d, want ModeSpell", a.mode)
	}
}

// Dragging from a misspelled word selects text and must NOT pop the spell popup.
func TestMouseDragFromMisspellingSelectsNotPopup(t *testing.T) {
	a := newApp()
	a.setSize(100, 24)
	a.editor.SetContent([]byte("a recieve here"))
	lm := a.leftMargin()
	y := a.topPad() - 1
	a.Update(tea.MouseMsg{Button: tea.MouseButtonLeft, Action: tea.MouseActionPress, X: lm + 4, Y: y})
	a.Update(tea.MouseMsg{Button: tea.MouseButtonLeft, Action: tea.MouseActionMotion, X: lm + 9, Y: y})
	if a.mode == ModeSpell {
		t.Fatal("drag from a misspelling opened the spell popup")
	}
	if !a.editor.HasSelection() {
		t.Fatal("drag from a misspelling did not select")
	}
}