diff --git a/internal/accounts.go b/internal/accounts.go index c5c2ab50..6926ab5b 100644 --- a/internal/accounts.go +++ b/internal/accounts.go @@ -113,7 +113,7 @@ func GetAccount(client *api.ClientWithResponses, address string) (api.Account, e return *r.JSON200, nil } -func getExpiresTime(t Time, key api.ParticipationKey, state *StateModel) time.Time { +func GetExpiresTime(t Time, key api.ParticipationKey, state *StateModel) time.Time { now := t.Now() var expires = now.Add(-(time.Hour * 24 * 365 * 100)) if key.LastBlockProposal != nil && state.Status.LastRound != 0 && state.Metrics.RoundTime != 0 { @@ -153,14 +153,14 @@ func AccountsFromState(state *StateModel, t Time, client *api.ClientWithResponse Address: key.Address, Status: account.Status, Balance: account.Amount / 1000000, - Expires: getExpiresTime(t, key, state), + Expires: GetExpiresTime(t, key, state), Keys: 1, } } else { val.Keys++ if val.Expires.Before(t.Now()) { now := t.Now() - var expires = getExpiresTime(t, key, state) + var expires = GetExpiresTime(t, key, state) if !expires.Before(now) { val.Expires = expires } diff --git a/internal/accounts_test.go b/internal/accounts_test.go index 73c75d12..5ae63095 100644 --- a/internal/accounts_test.go +++ b/internal/accounts_test.go @@ -2,16 +2,13 @@ package internal import ( "github.com/algorandfoundation/hack-tui/api" + "github.com/algorandfoundation/hack-tui/ui/test/mock" "github.com/oapi-codegen/oapi-codegen/v2/pkg/securityprovider" "github.com/stretchr/testify/assert" "testing" "time" ) -type TestClock struct{} - -func (TestClock) Now() time.Time { return time.Time{} } - func Test_AccountsFromState(t *testing.T) { // Setup elevated client @@ -145,7 +142,7 @@ func Test_AccountsFromState(t *testing.T) { } // Calculate expiration - clock := new(TestClock) + clock := new(mock.Clock) now := clock.Now() roundDiff := max(0, effectiveLastValid-int(state.Status.LastRound)) distance := int(state.Metrics.RoundTime) * roundDiff diff --git a/ui/pages/accounts/accounts_test.go b/ui/pages/accounts/accounts_test.go index a3589d9a..a5a58cc6 100644 --- a/ui/pages/accounts/accounts_test.go +++ b/ui/pages/accounts/accounts_test.go @@ -2,8 +2,8 @@ package accounts import ( "bytes" - "github.com/algorandfoundation/hack-tui/api" "github.com/algorandfoundation/hack-tui/internal" + "github.com/algorandfoundation/hack-tui/ui/test" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/x/ansi" "github.com/charmbracelet/x/exp/golden" @@ -12,16 +12,47 @@ import ( "time" ) +func Test_New(t *testing.T) { + m := New(&internal.StateModel{}) + acc := m.SelectedAccount() + + if acc != nil { + t.Errorf("Expected no accounts to exist, got %s", acc.Address) + } + m, cmd := m.HandleMessage(tea.KeyMsg{ + Type: tea.KeyRunes, + Runes: []rune("enter"), + }) + + if cmd != nil { + t.Errorf("Expected no comand") + } + + m = New(test.GetState()) + m, _ = m.HandleMessage(tea.WindowSizeMsg{Width: 80, Height: 40}) + + if m.Data.Admin { + t.Errorf("Admin flag should be false, got true") + } + + // Fetch state after message handling + acc = m.SelectedAccount() + if acc == nil { + t.Errorf("expected true, got false") + } + + // Update syncing state + m.Data.Status.State = internal.SyncingState + m.makeRows() + if m.Data.Status.State != internal.SyncingState { + + } +} + func Test_Snapshot(t *testing.T) { t.Run("Visible", func(t *testing.T) { - model := New(&internal.StateModel{ - Status: internal.StatusModel{}, - Metrics: internal.MetricsModel{}, - Accounts: nil, - ParticipationKeys: nil, - Admin: false, - Watching: false, - }) + model := New(test.GetState()) + model, _ = model.HandleMessage(tea.WindowSizeMsg{Width: 80, Height: 40}) got := ansi.Strip(model.View()) golden.RequireEqual(t, []byte(got)) @@ -29,52 +60,8 @@ func Test_Snapshot(t *testing.T) { } func Test_Messages(t *testing.T) { - var testKeys = []api.ParticipationKey{ - { - Address: "ABC", - EffectiveFirstValid: nil, - EffectiveLastValid: nil, - Id: "", - Key: api.AccountParticipation{ - SelectionParticipationKey: nil, - StateProofKey: nil, - VoteFirstValid: 0, - VoteKeyDilution: 0, - VoteLastValid: 0, - VoteParticipationKey: nil, - }, - LastBlockProposal: nil, - LastStateProof: nil, - LastVote: nil, - }, - } - sm := &internal.StateModel{ - Status: internal.StatusModel{}, - Metrics: internal.MetricsModel{}, - Accounts: nil, - ParticipationKeys: &testKeys, - Admin: false, - Watching: false, - } - values := make(map[string]internal.Account) - for _, key := range *sm.ParticipationKeys { - val, ok := values[key.Address] - if !ok { - values[key.Address] = internal.Account{ - Address: key.Address, - Status: "Offline", - Balance: 0, - Expires: time.Unix(0, 0), - Keys: 1, - } - } else { - val.Keys++ - values[key.Address] = val - } - } - sm.Accounts = values // Create the Model - m := New(sm) + m := New(test.GetState()) tm := teatest.NewTestModel( t, m, @@ -91,7 +78,7 @@ func Test_Messages(t *testing.T) { teatest.WithDuration(time.Second*3), ) - tm.Send(*sm) + tm.Send(test.GetState()) tm.Send(tea.KeyMsg{ Type: tea.KeyRunes, diff --git a/ui/pages/accounts/controller.go b/ui/pages/accounts/controller.go index 408ce060..8bbc39cb 100644 --- a/ui/pages/accounts/controller.go +++ b/ui/pages/accounts/controller.go @@ -17,9 +17,6 @@ func (m ViewModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } func (m ViewModel) HandleMessage(msg tea.Msg) (ViewModel, tea.Cmd) { - var cmd tea.Cmd - var cmds []tea.Cmd - switch msg := msg.(type) { case internal.StateModel: m.Data = &msg @@ -28,8 +25,9 @@ func (m ViewModel) HandleMessage(msg tea.Msg) (ViewModel, tea.Cmd) { switch msg.String() { case "enter": selAcc := m.SelectedAccount() - if selAcc != (internal.Account{}) { - cmds = append(cmds, app.EmitAccountSelected(selAcc)) + if selAcc != nil { + var cmds []tea.Cmd + cmds = append(cmds, app.EmitAccountSelected(*selAcc)) cmds = append(cmds, app.EmitShowPage(app.KeysPage)) return m, tea.Batch(cmds...) } @@ -48,10 +46,8 @@ func (m ViewModel) HandleMessage(msg tea.Msg) (ViewModel, tea.Cmd) { m.table.SetColumns(m.makeColumns(m.Width)) } - m.table, cmd = m.table.Update(msg) - if cmd != nil { - cmds = append(cmds, cmd) - } - cmds = append(cmds, cmd) - return m, tea.Batch(cmds...) + // Handle Table Update + m.table, _ = m.table.Update(msg) + + return m, nil } diff --git a/ui/pages/accounts/model.go b/ui/pages/accounts/model.go index f85d3cbf..4b71aab7 100644 --- a/ui/pages/accounts/model.go +++ b/ui/pages/accounts/model.go @@ -54,11 +54,12 @@ func New(state *internal.StateModel) ViewModel { return m } -func (m ViewModel) SelectedAccount() internal.Account { - var account internal.Account +func (m ViewModel) SelectedAccount() *internal.Account { + var account *internal.Account var selectedRow = m.table.SelectedRow() if selectedRow != nil { - account = m.Data.Accounts[selectedRow[0]] + selectedAccount := m.Data.Accounts[selectedRow[0]] + account = &selectedAccount } return account } diff --git a/ui/pages/accounts/testdata/Test_Snapshot/Visible.golden b/ui/pages/accounts/testdata/Test_Snapshot/Visible.golden index 84982831..98a06efd 100644 --- a/ui/pages/accounts/testdata/Test_Snapshot/Visible.golden +++ b/ui/pages/accounts/testdata/Test_Snapshot/Visible.golden @@ -1,7 +1,7 @@ ╭──Accounts────────────────────────────────────────────────────────────────────╮ │ Account Keys Status Expires Balance │ │─────────────────────────────────────────────────────────────────────────── │ -│ │ +│ ABC 2 Offline NA 0 │ │ │ │ │ │ │ diff --git a/ui/pages/keys/keys_test.go b/ui/pages/keys/keys_test.go index 0f10f733..2665d803 100644 --- a/ui/pages/keys/keys_test.go +++ b/ui/pages/keys/keys_test.go @@ -3,8 +3,8 @@ package keys import ( "bytes" "github.com/algorandfoundation/hack-tui/api" - "github.com/algorandfoundation/hack-tui/internal" "github.com/algorandfoundation/hack-tui/ui/app" + "github.com/algorandfoundation/hack-tui/ui/test" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/x/ansi" "github.com/charmbracelet/x/exp/golden" @@ -13,44 +13,6 @@ import ( "time" ) -var testVoteKey = []byte("TESTKEY") -var testKeys = []api.ParticipationKey{ - { - Address: "ABC", - EffectiveFirstValid: nil, - EffectiveLastValid: nil, - Id: "123", - Key: api.AccountParticipation{ - SelectionParticipationKey: nil, - StateProofKey: nil, - VoteFirstValid: 0, - VoteKeyDilution: 0, - VoteLastValid: 0, - VoteParticipationKey: testVoteKey, - }, - LastBlockProposal: nil, - LastStateProof: nil, - LastVote: nil, - }, - { - Address: "ABC", - EffectiveFirstValid: nil, - EffectiveLastValid: nil, - Id: "1234", - Key: api.AccountParticipation{ - SelectionParticipationKey: nil, - StateProofKey: nil, - VoteFirstValid: 0, - VoteKeyDilution: 0, - VoteLastValid: 0, - VoteParticipationKey: nil, - }, - LastBlockProposal: nil, - LastStateProof: nil, - LastVote: nil, - }, -} - func Test_New(t *testing.T) { m := New("ABC", nil) if m.Address != "ABC" { @@ -60,22 +22,21 @@ func Test_New(t *testing.T) { if active { t.Errorf("Expected to not find a selected key") } - m, err := m.HandleMessage(tea.KeyMsg{ + m, cmd := m.HandleMessage(tea.KeyMsg{ Type: tea.KeyRunes, Runes: []rune("enter"), }) - - if err != nil { - t.Errorf("Expected no error") + if cmd != nil { + t.Errorf("Expected no commands") } - m.Data = &testKeys + m.Data = &test.Keys m, _ = m.HandleMessage(app.AccountSelected{Address: "ABC", Participation: &api.AccountParticipation{ SelectionParticipationKey: nil, StateProofKey: nil, VoteFirstValid: 0, VoteKeyDilution: 0, VoteLastValid: 0, - VoteParticipationKey: testVoteKey, + VoteParticipationKey: test.VoteKey, }}) d, active = m.SelectedKey() if !active { @@ -92,7 +53,7 @@ func Test_New(t *testing.T) { func Test_Snapshot(t *testing.T) { t.Run("Visible", func(t *testing.T) { - model := New("ABC", &testKeys) + model := New("ABC", &test.Keys) model, _ = model.HandleMessage(tea.WindowSizeMsg{Width: 80, Height: 40}) got := ansi.Strip(model.View()) golden.RequireEqual(t, []byte(got)) @@ -100,33 +61,9 @@ func Test_Snapshot(t *testing.T) { } func Test_Messages(t *testing.T) { - sm := &internal.StateModel{ - Status: internal.StatusModel{}, - Metrics: internal.MetricsModel{}, - Accounts: nil, - ParticipationKeys: &testKeys, - Admin: false, - Watching: false, - } - values := make(map[string]internal.Account) - for _, key := range *sm.ParticipationKeys { - val, ok := values[key.Address] - if !ok { - values[key.Address] = internal.Account{ - Address: key.Address, - Status: "Offline", - Balance: 0, - Expires: time.Unix(0, 0), - Keys: 1, - } - } else { - val.Keys++ - values[key.Address] = val - } - } - sm.Accounts = values + // Create the Model - m := New("ABC", &testKeys) + m := New("ABC", &test.Keys) //m, _ = m.Address = "ABC" tm := teatest.NewTestModel( t, m, @@ -144,7 +81,7 @@ func Test_Messages(t *testing.T) { ) // Emit a state message - tm.Send(*sm) + tm.Send(*test.GetState()) // Send delete finished tm.Send(app.DeleteFinished{ diff --git a/ui/test/fixtures.go b/ui/test/fixtures.go new file mode 100644 index 00000000..688eb09b --- /dev/null +++ b/ui/test/fixtures.go @@ -0,0 +1,94 @@ +package test + +import ( + "github.com/algorandfoundation/hack-tui/api" + "github.com/algorandfoundation/hack-tui/internal" + "github.com/algorandfoundation/hack-tui/ui/test/mock" + "time" +) + +var VoteKey = []byte("TESTKEY") +var Keys = []api.ParticipationKey{ + { + Address: "ABC", + EffectiveFirstValid: nil, + EffectiveLastValid: nil, + Id: "123", + Key: api.AccountParticipation{ + SelectionParticipationKey: nil, + StateProofKey: nil, + VoteFirstValid: 0, + VoteKeyDilution: 100, + VoteLastValid: 30000, + VoteParticipationKey: VoteKey, + }, + LastBlockProposal: nil, + LastStateProof: nil, + LastVote: nil, + }, + { + Address: "ABC", + EffectiveFirstValid: nil, + EffectiveLastValid: nil, + Id: "1234", + Key: api.AccountParticipation{ + SelectionParticipationKey: nil, + StateProofKey: nil, + VoteFirstValid: 0, + VoteKeyDilution: 100, + VoteLastValid: 30000, + VoteParticipationKey: nil, + }, + LastBlockProposal: nil, + LastStateProof: nil, + LastVote: nil, + }, +} + +func GetState() *internal.StateModel { + sm := &internal.StateModel{ + Status: internal.StatusModel{ + State: internal.StableState, + Version: "v-test", + Network: "v-test-network", + Voting: false, + NeedsUpdate: false, + LastRound: 0, + }, + Metrics: internal.MetricsModel{ + Enabled: true, + Window: 100, + RoundTime: time.Second * 2, + TPS: 2.5, + RX: 0, + TX: 0, + LastTS: time.Time{}, + LastRX: 0, + LastTX: 0, + }, + Accounts: nil, + ParticipationKeys: &Keys, + Admin: false, + Watching: false, + } + values := make(map[string]internal.Account) + clock := new(mock.Clock) + for _, key := range *sm.ParticipationKeys { + val, ok := values[key.Address] + if !ok { + values[key.Address] = internal.Account{ + Address: key.Address, + Status: "Offline", + Balance: 0, + Expires: internal.GetExpiresTime(clock, key, sm), + Keys: 1, + } + } else { + val.Keys++ + values[key.Address] = val + } + } + sm.Accounts = values + + return sm +} diff --git a/ui/test/mock/clock.go b/ui/test/mock/clock.go new file mode 100644 index 00000000..66039202 --- /dev/null +++ b/ui/test/mock/clock.go @@ -0,0 +1,7 @@ +package mock + +import "time" + +type Clock struct{} + +func (Clock) Now() time.Time { return time.Time{} } diff --git a/ui/viewport.go b/ui/viewport.go index 92029844..909f20d5 100644 --- a/ui/viewport.go +++ b/ui/viewport.go @@ -96,9 +96,9 @@ func (m ViewportViewModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } if m.page == app.AccountsPage { selAcc := m.accountsPage.SelectedAccount() - if selAcc != (internal.Account{}) { + if selAcc != nil { m.page = app.KeysPage - return m, app.EmitAccountSelected(selAcc) + return m, app.EmitAccountSelected(*selAcc) } return m, nil }