Files
2026-03-02 22:22:31 +00:00

85 lines
1.8 KiB
Go

package test
import (
"reflect"
"charm.land/bubbles/v2/cursor"
tea "charm.land/bubbletea/v2"
)
func SimulateModel[T interface {
Update(tea.Msg) tea.Cmd
}](model T, first tea.Cmd, observers ...func(tea.Msg)) {
drainCmds(first, func(msg tea.Msg) tea.Cmd {
return model.Update(msg)
}, observers...)
}
func Type(runes string) tea.Cmd {
press := func(r rune) tea.Cmd {
return func() tea.Msg {
return tea.KeyPressMsg{Text: string(r), Code: r}
}
}
var cmds []tea.Cmd
for _, r := range runes {
cmds = append(cmds, press(r))
}
return tea.Sequence(cmds...)
}
func Press(key rune) tea.Cmd {
return func() tea.Msg {
return tea.KeyPressMsg{Code: key}
}
}
func drainCmds(first tea.Cmd, apply func(tea.Msg) tea.Cmd, observers ...func(tea.Msg)) {
queue := []tea.Cmd{first}
for len(queue) > 0 {
var cmd tea.Cmd
cmd, queue = queue[0], queue[1:]
if cmd == nil {
continue
}
msg := cmd()
if msg == nil {
continue
}
switch v := msg.(type) {
case cursor.BlinkMsg:
// Ignore cursor blink messages.
case tea.BatchMsg: // Batch(...)
queue = append(queue, v...)
continue
default:
if slice, ok := asCmdSlice(msg); ok {
queue = append(queue, slice...)
continue
}
for _, observe := range observers {
observe(v)
}
if next := apply(v); next != nil {
queue = append(queue, next)
}
}
}
}
var cmdType = reflect.TypeFor[tea.Cmd]()
// asCmdSlice returns the contents if msg is any named slice whose elements are tea.Cmd.
func asCmdSlice(msg tea.Msg) ([]tea.Cmd, bool) {
val := reflect.ValueOf(msg)
if val.Kind() != reflect.Slice || !val.Type().Elem().AssignableTo(cmdType) {
return nil, false
}
out := make([]tea.Cmd, val.Len())
for i := 0; i < val.Len(); i++ {
out[i] = val.Index(i).Interface().(tea.Cmd)
}
return out, true
}