Skip to content

Commit

Permalink
feat: HintView (#56)
Browse files Browse the repository at this point in the history
* chore: deck separate file, rename Hint.Hint to Hint.Value
  • Loading branch information
igor-sirotin authored Jun 30, 2024
1 parent 7f48508 commit c30d3a0
Show file tree
Hide file tree
Showing 12 changed files with 261 additions and 36 deletions.
20 changes: 10 additions & 10 deletions internal/view/DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,18 @@ package view

```shell

Room: 238mvM91TbC9UJdUqKwtWFb
● Waku: 8 peer(s)
Room: W1Rka5Dz8GVGzgy1iq6gS5puS1x3JroeW4ZDTfBZkCfm (dealer)

Issue: https://github.com/golang/go/issues/19412
proposal: spec: add sum types / discriminated unions
[LanguageChange] [v2] [Proposal] [NeedsInvestigation]

Issue: https://github.com/golang/go/issues/19412
Title: Implement room encryption

╭───────┬─────┬─────────┬────────┬─────────╮
│ Alice │ Bob │ Charlie │ Didukh │ Sirotin │
├───────┼─────┼─────────┼────────┼─────────┤
│ 1 │ 3 │ 8 │ 13 │ 3 │
╰───────┴─────┴─────────┴────────┴─────────╯

Your vote:
│ Alice │ Bob │ Charlie │ Didukh │ Sirotin │ Recommended: 3
├───────┼─────┼─────────┼────────┼─────────┤ Acceptable: x - too big votes variety
│ 1 │ 3 │ 8 │ 13 │ 3 │ What to do: Listen to Alice and Didukh arguments
╰───────┴─────┴─────────┴────────┴─────────╯
╭───╮
╭───╮ ╭───╮ │ 3 │ ╭───╮ ╭───╮ ╭────╮ ╭────╮ ╭────╮
│ 1 │ │ 2 │ ╰───╯ │ 5 │ │ 8 │ │ 13 │ │ 21 │ │ 34 │
Expand Down
78 changes: 78 additions & 0 deletions internal/view/components/hintview/model.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package hintview

import (
"fmt"

tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"

"github.com/six78/2-story-points-cli/internal/config"
"github.com/six78/2-story-points-cli/internal/view/components/voteview"
"github.com/six78/2-story-points-cli/internal/view/messages"
"github.com/six78/2-story-points-cli/pkg/protocol"
)

var (
headerStyle = lipgloss.NewStyle() // .Foreground(lipgloss.Color("#FAFAFA"))
acceptableStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#00FF00"))
unacceptableStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#FF0000"))
textStyle = lipgloss.NewStyle() // .Foreground(lipgloss.Color("#FAFAFA"))
MentionStyle = textStyle.Copy().Italic(true).Foreground(config.UserColor)
)

type Model struct {
hint *protocol.Hint
}

func New() Model {
return Model{
hint: nil,
}
}

func (m Model) Init() tea.Cmd {
return nil
}

func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
switch msg := msg.(type) {
case messages.GameStateMessage:
if msg.State == nil || !msg.State.VotesRevealed {
m.hint = nil
break
}

issue := msg.State.Issues.Get(msg.State.ActiveIssue)
if issue != nil {
m.hint = issue.Hint
}
}

return m, nil
}

func (m Model) View() string {
if m.hint == nil {
return ""
}

verdictStyle := unacceptableStyle
verdictText := "x"
if m.hint.Acceptable {
verdictStyle = acceptableStyle
verdictText = "✓"
}

rejectionReason := ""
if !m.hint.Acceptable {
rejectionReason = fmt.Sprintf(" (%s)", textStyle.Render(m.hint.RejectReason))
}

return lipgloss.JoinVertical(lipgloss.Top,
"",
headerStyle.Render("Recommended:")+""+voteview.Render(m.hint.Value),
headerStyle.Render("Acceptable:")+" "+verdictStyle.Render(verdictText)+rejectionReason,
headerStyle.Render("What to do:")+" "+textStyle.Render(m.hint.Advice),
"",
)
}
111 changes: 111 additions & 0 deletions internal/view/components/hintview/model_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package hintview

import (
"fmt"
"strings"
"testing"

"github.com/brianvoe/gofakeit/v6"
"github.com/stretchr/testify/require"

"github.com/six78/2-story-points-cli/internal/view/messages"
"github.com/six78/2-story-points-cli/pkg/protocol"
)

func TestInit(t *testing.T) {
model := New()
cmd := model.Init()
require.Nil(t, cmd)
require.Nil(t, model.hint)
}

func TestUpdateNilState(t *testing.T) {
model := New()
_ = model.Init()
model, cmd := model.Update(messages.GameStateMessage{State: nil})
require.Nil(t, cmd)
require.Nil(t, model.hint)
require.Empty(t, model.View())
}

func TestUpdateVotesNotRevealed(t *testing.T) {
model := New()
_ = model.Init()
model, cmd := model.Update(messages.GameStateMessage{
State: &protocol.State{
VotesRevealed: false,
},
})
require.Nil(t, cmd)
require.Nil(t, model.hint)
require.Empty(t, model.View())
}

func TestUpdateAcceptableVote(t *testing.T) {
model := New()

cmd := model.Init()
require.Nil(t, cmd)
require.Nil(t, model.hint)

// Test acceptable vote

testCases := []struct {
name string
acceptable bool
}{
{
name: "acceptable vote",
acceptable: true,
},
{
name: "non-acceptable vote",
acceptable: false,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {

issue := protocol.Issue{
ID: protocol.IssueID(gofakeit.UUID()),
Hint: &protocol.Hint{
Acceptable: tc.acceptable,
Value: protocol.VoteValue(gofakeit.LetterN(5)),
RejectReason: gofakeit.LetterN(10),
},
}
model, cmd = model.Update(messages.GameStateMessage{
State: &protocol.State{
Issues: protocol.IssuesList{&issue},
ActiveIssue: issue.ID,
VotesRevealed: true,
},
})
require.Nil(t, cmd)
require.NotNil(t, model.hint)
require.Equal(t, *issue.Hint, *model.hint)

expectedVerdict := "✓"
if !tc.acceptable {
expectedVerdict = "x" + fmt.Sprintf(" (%s)", issue.Hint.RejectReason)
}

expectedLines := []string{
"",
"Recommended: " + string(issue.Hint.Value),
"Acceptable: " + expectedVerdict,
"What to do:",
"",
}

lines := strings.Split(model.View(), "\n")
require.Len(t, lines, len(expectedLines))

for i, line := range lines {
trimmedLine := strings.Trim(line, " ")
require.Equal(t, expectedLines[i], trimmedLine)
}
})
}
}
8 changes: 7 additions & 1 deletion internal/view/components/voteview/voteview.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package voteview

import (
"strconv"

"github.com/charmbracelet/bubbles/spinner"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"

"github.com/six78/2-story-points-cli/pkg/protocol"
"strconv"
)

var (
Expand Down Expand Up @@ -141,3 +143,7 @@ func VoteStyle(vote protocol.VoteValue) *lipgloss.Style {
}
return &LightVoteStyle
}

func Render(vote protocol.VoteValue) string {
return VoteStyle(vote).Render(string(vote))
}
15 changes: 11 additions & 4 deletions internal/view/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,24 @@ package view

import (
"fmt"
"net/url"
"strings"
"time"

"github.com/charmbracelet/bubbles/key"
"github.com/charmbracelet/bubbles/spinner"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/pkg/errors"
"go.uber.org/zap"

"github.com/six78/2-story-points-cli/internal/config"
"github.com/six78/2-story-points-cli/internal/transport"
"github.com/six78/2-story-points-cli/internal/view/commands"
"github.com/six78/2-story-points-cli/internal/view/components/deckview"
"github.com/six78/2-story-points-cli/internal/view/components/errorview"
"github.com/six78/2-story-points-cli/internal/view/components/eventhandler"
"github.com/six78/2-story-points-cli/internal/view/components/hintview"
"github.com/six78/2-story-points-cli/internal/view/components/issuesview"
"github.com/six78/2-story-points-cli/internal/view/components/issueview"
"github.com/six78/2-story-points-cli/internal/view/components/playersview"
Expand All @@ -24,10 +31,6 @@ import (
"github.com/six78/2-story-points-cli/internal/view/update"
"github.com/six78/2-story-points-cli/pkg/game"
"github.com/six78/2-story-points-cli/pkg/protocol"
"go.uber.org/zap"
"net/url"
"strings"
"time"
)

type model struct {
Expand All @@ -47,6 +50,7 @@ type model struct {
roomViewState states.RoomView
errorView errorview.Model
playersView playersview.Model
hintView hintview.Model
shortcutsView shortcutsview.Model
wakuStatusView wakustatusview.Model
deckView deckview.Model
Expand Down Expand Up @@ -85,6 +89,7 @@ func initialModel(game *game.Game, transport transport.Service) model {
spinner: createSpinner(),
errorView: errorview.New(),
playersView: playersview.New(),
hintView: hintview.New(),
shortcutsView: shortcutsview.New(),
wakuStatusView: wakustatusview.New(),
deckView: deckView,
Expand All @@ -109,6 +114,7 @@ func (m model) Init() tea.Cmd {
m.spinner.Tick,
m.errorView.Init(),
m.playersView.Init(),
m.hintView.Init(),
m.shortcutsView.Init(),
m.wakuStatusView.Init(),
m.deckView.Init(),
Expand Down Expand Up @@ -290,6 +296,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.spinner, cmds.SpinnerCommand = m.spinner.Update(msg)
m.errorView = m.errorView.Update(msg)
m.playersView, cmds.PlayersCommand = m.playersView.Update(msg)
m.hintView, _ = m.hintView.Update(msg)
m.shortcutsView = m.shortcutsView.Update(msg, m.roomViewState)
m.wakuStatusView = m.wakuStatusView.Update(msg)
m.deckView = m.deckView.Update(msg)
Expand Down
6 changes: 4 additions & 2 deletions internal/view/render.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ package view

import (
"fmt"
"strings"

"github.com/charmbracelet/lipgloss"

"github.com/six78/2-story-points-cli/internal/config"
"github.com/six78/2-story-points-cli/internal/view/states"
"strings"
)

/*
Expand Down Expand Up @@ -93,7 +95,7 @@ func (m model) renderRoomCurrentIssueView() string {
return lipgloss.JoinVertical(lipgloss.Top,
m.issueView.View(),
"",
m.playersView.View(),
lipgloss.JoinHorizontal(lipgloss.Left, m.playersView.View(), " ", m.hintView.View()),
m.deckView.View(),
)
}
Expand Down
5 changes: 2 additions & 3 deletions pkg/game/hints.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"sort"

"github.com/pkg/errors"
"golang.org/x/exp/slices"

"github.com/six78/2-story-points-cli/pkg/protocol"
)
Expand Down Expand Up @@ -50,7 +49,7 @@ func GetResultHint(deck protocol.Deck, issueVotes protocol.IssueVotes) (*protoco

// Build the hint based on the measures
hint := &protocol.Hint{
Hint: medianValue,
Value: medianValue,
Advice: "",
Acceptable: true,
}
Expand All @@ -71,7 +70,7 @@ func GetResultHint(deck protocol.Deck, issueVotes protocol.IssueVotes) (*protoco
func getVotesAsDeckIndexes(issueVotes protocol.IssueVotes, deck protocol.Deck) ([]int, error) {
indexes := make([]int, 0, len(issueVotes))
for _, vote := range issueVotes {
index := slices.Index(deck, vote.Value)
index := deck.Index(vote.Value)
if index < 0 {
return nil, ErrVoteNotFoundInDeck
}
Expand Down
Loading

0 comments on commit c30d3a0

Please sign in to comment.