Skip to content

Commit

Permalink
return tea.cmd from event handlers (#28)
Browse files Browse the repository at this point in the history
Signed-off-by: Alex Goodman <[email protected]>
  • Loading branch information
wagoodman authored Nov 15, 2023
1 parent 1c5dfd1 commit 8f2267d
Show file tree
Hide file tree
Showing 5 changed files with 184 additions and 11 deletions.
2 changes: 1 addition & 1 deletion .github/actions/bootstrap/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ inputs:
go-version:
description: "Go version to install"
required: true
default: "1.20.x"
default: "1.21.x"
use-go-cache:
description: "Restore go cache"
required: true
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ SUCCESS := $(BOLD)$(GREEN)

# Test variables #################################
# the quality gate lower threshold for unit test total % coverage (by function statements)
COVERAGE_THRESHOLD := 80
COVERAGE_THRESHOLD := 70

## Variable assertions

Expand Down
25 changes: 17 additions & 8 deletions event.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,15 @@ var (
} = (*HandlerCollection)(nil)
)

type EventHandlerFn func(partybus.Event) []tea.Model
type EventHandlerFn func(partybus.Event) ([]tea.Model, tea.Cmd)

type EventHandler interface {
partybus.Responder
Handle(partybus.Event) []tea.Model
// Handle optionally generates new models and commands in response to the given event. It might be that the event
// has an effect on the system, but the model is managed by a sub-component, in which case no new model would be
// returned but the Init() call on the managed model would return commands that should be executed in the context
// of the application lifecycle.
Handle(partybus.Event) ([]tea.Model, tea.Cmd)
}

type MessageListener interface {
Expand Down Expand Up @@ -55,11 +59,11 @@ func (d EventDispatcher) RespondsTo() []partybus.EventType {
return d.types
}

func (d EventDispatcher) Handle(e partybus.Event) []tea.Model {
func (d EventDispatcher) Handle(e partybus.Event) ([]tea.Model, tea.Cmd) {
if fn, ok := d.dispatch[e.Type]; ok {
return fn(e)
}
return nil
return nil, nil
}

type HandlerCollection struct {
Expand All @@ -84,12 +88,17 @@ func (h HandlerCollection) RespondsTo() []partybus.EventType {
return ret
}

func (h HandlerCollection) Handle(event partybus.Event) []tea.Model {
var ret []tea.Model
func (h HandlerCollection) Handle(event partybus.Event) ([]tea.Model, tea.Cmd) {
var (
newModels []tea.Model
newCmd tea.Cmd
)
for _, handler := range h.handlers {
ret = append(ret, handler.Handle(event)...)
mods, cmd := handler.Handle(event)
newModels = append(newModels, mods...)
newCmd = tea.Batch(newCmd, cmd)
}
return ret
return newModels, newCmd
}

func (h HandlerCollection) OnMessage(msg tea.Msg) {
Expand Down
162 changes: 162 additions & 0 deletions event_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
package bubbly

import (
"reflect"
"testing"

tea "github.com/charmbracelet/bubbletea"
"github.com/stretchr/testify/assert"
"github.com/wagoodman/go-partybus"
)

var _ tea.Model = (*dummyModel)(nil)

type dummyModel struct {
id string
}

func (d dummyModel) Init() tea.Cmd {
return nil
}

func (d dummyModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if msg, ok := msg.(string); ok {
d.id = msg
}
return d, nil
}

func (d dummyModel) View() string {
return d.id
}

func dummyMsg(s any) tea.Cmd {
return func() tea.Msg {
return s
}
}

func TestEventDispatcher_Handle(t *testing.T) {

tests := []struct {
name string
subject *EventDispatcher
event partybus.Event
wantModels []tea.Model
wantCmd tea.Cmd
}{
{
name: "simple event",
subject: func() *EventDispatcher {
d := NewEventDispatcher()
d.AddHandler("test", func(e partybus.Event) ([]tea.Model, tea.Cmd) {
return []tea.Model{dummyModel{id: "model"}}, dummyMsg("updated")
})
return d
}(),
event: partybus.Event{
Type: "test",
},
wantModels: []tea.Model{dummyModel{id: "model"}},
wantCmd: dummyMsg("updated"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotModels, gotCmd := tt.subject.Handle(tt.event)
if !reflect.DeepEqual(gotModels, tt.wantModels) {
t.Errorf("Handle() got = %v (model), want %v", gotModels, tt.wantModels)
}

if gotCmd != nil && tt.wantCmd == nil {
t.Fatal("got command, but want nil")
} else if gotCmd == nil && tt.wantCmd != nil {
t.Fatal("did not get command, but wanted one")
}

var (
gotMsg tea.Msg
wantMsg tea.Msg
)

if gotCmd != nil {
gotMsg = gotCmd()
}

if tt.wantCmd != nil {
wantMsg = tt.wantCmd()
}

if !assert.Equal(t, wantMsg, gotMsg) {
t.Errorf("Handle() got = %v (msg), want %v", gotMsg, wantMsg)
}

})
}
}

func TestEventDispatcher_RespondsTo(t *testing.T) {

d := NewEventDispatcher()
d.AddHandler("test", func(e partybus.Event) ([]tea.Model, tea.Cmd) {
return []tea.Model{dummyModel{id: "test-model"}}, dummyMsg("test-msg")
})

d.AddHandler("something", func(e partybus.Event) ([]tea.Model, tea.Cmd) {
return []tea.Model{dummyModel{id: "something-model"}}, dummyMsg("something-msg")
})

tests := []struct {
name string
subject *EventDispatcher
want []partybus.EventType
}{
{
name: "responds to registered event",
subject: d,
want: []partybus.EventType{"test", "something"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := tt.subject.RespondsTo()
if !assert.Equal(t, tt.want, got) {
t.Errorf("RespondsTo() = %v, want %v", got, tt.want)
}
})
}
}

func TestHandlerCollection_RespondsTo(t *testing.T) {
d1 := NewEventDispatcher()
d1.AddHandler("test", func(e partybus.Event) ([]tea.Model, tea.Cmd) {
return []tea.Model{dummyModel{id: "test-model"}}, dummyMsg("test-msg")
})

d2 := NewEventDispatcher()
d2.AddHandler("something", func(e partybus.Event) ([]tea.Model, tea.Cmd) {
return []tea.Model{dummyModel{id: "something-model"}}, dummyMsg("something-msg")
})

subject := NewHandlerCollection(d1, d2)

tests := []struct {
name string
subject *HandlerCollection
want []partybus.EventType
}{
{
name: "responds to registered event from all handlers",
subject: subject,
want: []partybus.EventType{"test", "something"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := tt.subject.RespondsTo()
if !assert.Equal(t, tt.want, got) {
t.Errorf("RespondsTo() = %v, want %v", got, tt.want)
}
})
}
}
4 changes: 3 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
module github.com/anchore/bubbly

go 1.18
go 1.21.0

toolchain go1.21.1

require (
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d
Expand Down

0 comments on commit 8f2267d

Please sign in to comment.