• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

achannarasappa / ticker / 15117210240

19 May 2025 03:35PM UTC coverage: 88.393% (-0.1%) from 88.505%
15117210240

push

github

achannarasappa
fix: truncate watchlist rows on group change

1 of 3 new or added lines in 1 file covered. (33.33%)

2 existing lines in 1 file now uncovered.

2871 of 3248 relevant lines covered (88.39%)

8.39 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

84.21
/internal/ui/component/watchlist/watchlist.go
1
package watchlist
2

3
import (
4
        "fmt"
5
        "strings"
6

7
        c "github.com/achannarasappa/ticker/v4/internal/common"
8
        s "github.com/achannarasappa/ticker/v4/internal/sorter"
9
        row "github.com/achannarasappa/ticker/v4/internal/ui/component/watchlist/row"
10
        u "github.com/achannarasappa/ticker/v4/internal/ui/util"
11

12
        tea "github.com/charmbracelet/bubbletea"
13
)
14

15
// Config represents the configuration for the watchlist component
16
type Config struct {
17
        Separate              bool
18
        ShowHoldings          bool
19
        ExtraInfoExchange     bool
20
        ExtraInfoFundamentals bool
21
        Sort                  string
22
        Styles                c.Styles
23
}
24

25
// Model for watchlist section
26
type Model struct {
27
        width          int
28
        assets         []*c.Asset
29
        assetsBySymbol map[string]*c.Asset
30
        sorter         s.Sorter
31
        config         Config
32
        cellWidths     row.CellWidthsContainer
33
        rows           []*row.Model
34
        rowsBySymbol   map[string]*row.Model
35
}
36

37
// Messages for replacing assets
38
type SetAssetsMsg []c.Asset
39

40
// Messages for updating assets
41
type UpdateAssetsMsg []c.Asset
42

43
// NewModel returns a model with default values
44
func NewModel(config Config) *Model {
17✔
45
        return &Model{
17✔
46
                width:          80,
17✔
47
                config:         config,
17✔
48
                assets:         make([]*c.Asset, 0),
17✔
49
                assetsBySymbol: make(map[string]*c.Asset),
17✔
50
                sorter:         s.NewSorter(config.Sort),
17✔
51
                rowsBySymbol:   make(map[string]*row.Model),
17✔
52
        }
17✔
53
}
17✔
54

55
// Init initializes the watchlist
56
func (m *Model) Init() tea.Cmd {
1✔
57
        return nil
1✔
58
}
1✔
59

60
// Update handles messages for the watchlist
61
func (m *Model) Update(msg tea.Msg) (*Model, tea.Cmd) {
25✔
62
        switch msg := msg.(type) {
25✔
63
        case SetAssetsMsg:
14✔
64

14✔
65
                var cmd tea.Cmd
14✔
66
                cmds := make([]tea.Cmd, 0)
14✔
67

14✔
68
                // Convert []c.Asset to []*c.Asset and update assetsBySymbol map
14✔
69
                assets := make([]*c.Asset, len(msg))
14✔
70
                assetsBySymbol := make(map[string]*c.Asset)
14✔
71

14✔
72
                for i := range msg {
37✔
73
                        assets[i] = &msg[i]
23✔
74
                        assetsBySymbol[msg[i].Symbol] = assets[i]
23✔
75
                }
23✔
76

77
                assets = m.sorter(assets)
14✔
78

14✔
79
                for i, asset := range assets {
37✔
80
                        if i < len(m.rows) {
23✔
81
                                m.rows[i], cmd = m.rows[i].Update(row.UpdateAssetMsg(asset))
×
82
                                cmds = append(cmds, cmd)
×
83
                                m.rowsBySymbol[assets[i].Symbol] = m.rows[i]
×
84
                        } else {
23✔
85
                                m.rows = append(m.rows, row.New(row.Config{
23✔
86
                                        Separate:              m.config.Separate,
23✔
87
                                        ExtraInfoExchange:     m.config.ExtraInfoExchange,
23✔
88
                                        ExtraInfoFundamentals: m.config.ExtraInfoFundamentals,
23✔
89
                                        ShowHoldings:          m.config.ShowHoldings,
23✔
90
                                        Styles:                m.config.Styles,
23✔
91
                                        Asset:                 asset,
23✔
92
                                }))
23✔
93
                                m.rowsBySymbol[assets[i].Symbol] = m.rows[len(m.rows)-1]
23✔
94
                        }
23✔
95
                }
96

97
                if len(assets) < len(m.rows) {
14✔
NEW
98
                        m.rows = m.rows[:len(assets)]
×
NEW
99
                }
×
100

101
                m.assets = assets
14✔
102
                m.assetsBySymbol = assetsBySymbol
14✔
103

14✔
104
                // TODO: only set conditionally if all assets have changed
14✔
105
                m.cellWidths = getCellWidths(m.assets)
14✔
106
                for i, r := range m.rows {
37✔
107
                        m.rows[i], _ = r.Update(row.SetCellWidthsMsg{
23✔
108
                                Width:      m.width,
23✔
109
                                CellWidths: m.cellWidths,
23✔
110
                        })
23✔
111
                }
23✔
112

113
                return m, tea.Batch(cmds...)
14✔
114

115
        case tea.WindowSizeMsg:
10✔
116

10✔
117
                m.width = msg.Width
10✔
118
                m.cellWidths = getCellWidths(m.assets)
10✔
119
                for i, r := range m.rows {
10✔
120
                        m.rows[i], _ = r.Update(row.SetCellWidthsMsg{
×
121
                                Width:      m.width,
×
122
                                CellWidths: m.cellWidths,
×
123
                        })
×
124
                }
×
125

126
                return m, nil
10✔
127

128
        case row.FrameMsg:
×
129

×
130
                var cmd tea.Cmd
×
131
                cmds := make([]tea.Cmd, 0)
×
132

×
133
                // TODO: send message to a specific row rather than all rows
×
134
                for i, r := range m.rows {
×
135
                        m.rows[i], cmd = r.Update(msg)
×
136
                        cmds = append(cmds, cmd)
×
137
                }
×
138

139
                return m, tea.Batch(cmds...)
×
140

141
        }
142

143
        return m, nil
1✔
144
}
145

146
// View rendering hook for bubbletea
147
func (m *Model) View() string {
39✔
148

39✔
149
        if m.width < 80 {
40✔
150
                return fmt.Sprintf("Terminal window too narrow to render content\nResize to fix (%d/80)", m.width)
1✔
151
        }
1✔
152

153
        rows := make([]string, 0)
38✔
154
        for _, row := range m.rows {
88✔
155
                rows = append(rows, row.View())
50✔
156
        }
50✔
157

158
        return strings.Join(rows, "\n")
38✔
159

160
}
161
func getCellWidths(assets []*c.Asset) row.CellWidthsContainer {
24✔
162

24✔
163
        cellMaxWidths := row.CellWidthsContainer{}
24✔
164

24✔
165
        for _, asset := range assets {
47✔
166
                var quoteLength int
23✔
167

23✔
168
                volumeMarketCapLength := len(u.ConvertFloatToString(asset.QuoteExtended.MarketCap, true))
23✔
169

23✔
170
                if asset.QuoteExtended.FiftyTwoWeekHigh == 0.0 {
40✔
171
                        quoteLength = len(u.ConvertFloatToString(asset.QuotePrice.Price, asset.Meta.IsVariablePrecision))
17✔
172
                }
17✔
173

174
                if asset.QuoteExtended.FiftyTwoWeekHigh != 0.0 {
29✔
175
                        quoteLength = len(u.ConvertFloatToString(asset.QuoteExtended.FiftyTwoWeekHigh, asset.Meta.IsVariablePrecision))
6✔
176
                }
6✔
177

178
                if volumeMarketCapLength > cellMaxWidths.WidthVolumeMarketCap {
37✔
179
                        cellMaxWidths.WidthVolumeMarketCap = volumeMarketCapLength
14✔
180
                }
14✔
181

182
                if quoteLength > cellMaxWidths.QuoteLength {
37✔
183
                        cellMaxWidths.QuoteLength = quoteLength
14✔
184
                        cellMaxWidths.WidthQuote = quoteLength + row.WidthChangeStatic
14✔
185
                        cellMaxWidths.WidthQuoteExtended = quoteLength
14✔
186
                        cellMaxWidths.WidthQuoteRange = row.WidthRangeStatic + (quoteLength * 2)
14✔
187
                }
14✔
188

189
                if asset.Holding != (c.Holding{}) {
30✔
190
                        positionLength := len(u.ConvertFloatToString(asset.Holding.Value, asset.Meta.IsVariablePrecision))
7✔
191
                        positionQuantityLength := len(u.ConvertFloatToString(asset.Holding.Quantity, asset.Meta.IsVariablePrecision))
7✔
192

7✔
193
                        if positionLength > cellMaxWidths.PositionLength {
10✔
194
                                cellMaxWidths.PositionLength = positionLength
3✔
195
                                cellMaxWidths.WidthPosition = positionLength + row.WidthChangeStatic + row.WidthPositionGutter
3✔
196
                        }
3✔
197

198
                        if positionLength > cellMaxWidths.WidthPositionExtended {
10✔
199
                                cellMaxWidths.WidthPositionExtended = positionLength
3✔
200
                        }
3✔
201

202
                        if positionQuantityLength > cellMaxWidths.WidthPositionExtended {
8✔
203
                                cellMaxWidths.WidthPositionExtended = positionQuantityLength
1✔
204
                        }
1✔
205

206
                }
207

208
        }
209

210
        return cellMaxWidths
24✔
211

212
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc