Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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