Files
MujicaUI/chart/candlestick_chart_test.go
T
ZacharyZhang-NYandClaude Opus 5.5 bcce34cda1 Upgrade MyGo to v0.5.5
MyGo v0.5.5 returns ui.Element as a checked value handle instead of a
pointer. Migrated with `mygo migrate-ui`, then fixed what the codemod
leaves to review:

- Element nil checks become .Valid(); absent elements are ui.Element{}.
- Input handlers and paint callbacks run outside the build pass, where
  elements expire and Context methods are inert: read IsDisabled into a
  local during the build, keep a ui.Handle for Focus, use c.Services()
  for clipboard access, create ui.Local state during the build.
- Add core.AutoFocus: v0.5.5 records an overlay's opener after its
  content builds, so same-frame autofocus lost focus restore on close.
  It focuses one frame later; cascader rows defer their focus likewise.
- core.OverlayLayer identifies build passes by the root element, not
  c.Now(), which repeats across passes and under the Tester clock.
- Context menus survive the opening press counted as PressedOutside;
  split button tooltip no longer reorders its open menu; disabled
  menubar rows pass presses to the panel; Form defers blur-started
  async checks so the growing message no longer moves Submit away
  from the release; Combobox drops its disabled copy now that MyGo
  blocks typing under a disabled wrapper (canary flipped to a guard).
- The Tester clock is virtual: tests use Advance/Now instead of
  time.Sleep/time.Now and read element state inside the view. The web
  mirror advances the clock to wall time before each frame and input.
- AGENTS.md and docs describe the value Element API and the new pin.

Snapshot goldens are darwin-only and were not re-verified here.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-17 01:37:54 -04:00

152 lines
4.5 KiB
Go

package chart
import (
"math"
"strings"
"testing"
"time"
"github.com/egoist/mygo/ui"
"git.zacharyzhang.com/ZacharyZhang-NY/MujicaUI/core"
)
// sampleCandles is a deterministic random walk, hourly from 2026-01-01.
func sampleCandles(n int) []Candle {
out := make([]Candle, n)
price := 100.0
base := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)
for i := range out {
o := price
c := o + math.Sin(float64(i)*0.7)*1.5 + math.Cos(float64(i)*0.13)*0.8
hi, lo := max(o, c)+0.6+math.Abs(math.Sin(float64(i)))*0.5, min(o, c)-0.5-math.Abs(math.Cos(float64(i)))*0.4
out[i] = Candle{Time: base.Add(time.Duration(i) * time.Hour), Open: o, High: hi, Low: lo, Close: c, Volume: 1000 + float64(i%17)*130}
price = c
}
return out
}
func mktState0(r ChartResult) *mktState {
return *ui.Local(r.Element, "mujica.mkt", func() *mktState { return nil })
}
func TestCandlestickChartDrawsOnlyVisible(t *testing.T) {
candles := sampleCandles(100000)
var view MarketView
var st *mktState
tt := ui.NewTester(func(c *ui.Context) {
core.Use(c, core.Settings{})
st = mktState0(CandlestickChart(c, candles, &view, CandlestickOptions{Frame: ChartFrame{Label: "BTC"}}))
}, 900, 400)
tt.Frame()
if st.drawn != 120 {
t.Fatalf("drew %d candles of 100000", st.drawn)
}
tt.Key(0, ui.KeyTab)
for range 40 {
tt.Key(0, ui.KeyMinus)
}
if st.drawn > 900/3 || view.Count > 900/3 {
t.Fatalf("zoomed out: drew %d, view %+v", st.drawn, view)
}
}
func TestCandlestickChartKeyboardPanZoomReadout(t *testing.T) {
candles := sampleCandles(500)
view := MarketView{Count: 50}
var res ChartResult
tt := ui.NewTester(func(c *ui.Context) {
core.Use(c, core.Settings{})
res = CandlestickChart(c, candles, &view, CandlestickOptions{Frame: ChartFrame{Label: "BTC"}})
}, 900, 400)
tt.Key(0, ui.KeyTab)
tt.Key(0, ui.KeyEnd)
if res.Point != 499 || !tt.HasText("2026-01-21 19:00") {
t.Fatalf("End: point %d texts %q", res.Point, tt.Texts())
}
tt.Key(ui.Shift, ui.KeyLeft)
if view.End != 495 || view.Count != 50 {
t.Fatalf("Shift+Left: %+v", view)
}
tt.Key(0, ui.KeyEqual)
if view.Count != 40 {
t.Fatalf("zoom in: %+v", view)
}
tt.Key(0, ui.KeyHome)
tt.Frame()
if f, _ := marketWindow(view, 500, 300); f != 0 {
t.Fatalf("Home did not bring the first candle into view: %+v", view)
}
found := false
for _, s := range tt.Texts() {
found = found || strings.Contains(s, "O 100.00") && strings.Contains(s, "Vol 1,000")
}
if !found {
t.Fatalf("readout %q", tt.Texts())
}
}
func TestCandlestickChartDragPansAndRejects(t *testing.T) {
candles := sampleCandles(300)
view := MarketView{Count: 100}
tt := ui.NewTester(func(c *ui.Context) {
core.Use(c, core.Settings{})
CandlestickChart(c, candles, &view, CandlestickOptions{Frame: ChartFrame{Label: "BTC"}}).Element.Width(860)
}, 900, 400)
tt.Frame()
tt.Press(400, 150)
tt.Move(560, 150) // about 20 candles of 8 DIPs
tt.Release(560, 150)
if view.End < 275 || view.End > 285 {
t.Fatalf("drag right shows older candles: %+v", view)
}
for name, f := range map[string]func(c *ui.Context){
"type": func(c *ui.Context) {
CandlestickChart(c, candles, &view, CandlestickOptions{Frame: ChartFrame{Label: "x"}, Type: 9})
},
"order": func(c *ui.Context) {
bad := []Candle{candles[1], candles[0]}
CandlestickChart(c, bad, &view, CandlestickOptions{Frame: ChartFrame{Label: "x"}})
},
"prices": func(c *ui.Context) {
bad := []Candle{{Time: candles[0].Time, Open: 5, High: 4, Low: 3, Close: 4}}
CandlestickChart(c, bad, &view, CandlestickOptions{Frame: ChartFrame{Label: "x"}})
},
} {
func() {
defer func() {
if recover() == nil {
t.Errorf("%s: no panic", name)
}
}()
ui.NewTester(func(c *ui.Context) { core.Use(c, core.Settings{}); f(c) }, 300, 200)
}()
}
}
func TestCandlestickChartTrendColors(t *testing.T) {
candles := sampleCandles(30)
view := MarketView{Count: 30}
var res ChartResult
tt := ui.NewTester(func(c *ui.Context) {
core.Use(c, core.Settings{Locale: core.ZhCN})
core.UseTrend(c, core.RedUp)
res = CandlestickChart(c, candles, &view, CandlestickOptions{Frame: ChartFrame{Label: "沪深300"}})
}, 600, 300)
tt.Key(0, ui.KeyTab)
tt.Key(0, ui.KeyRight)
if res.Point != 0 || !tt.HasText("开 100.00") {
t.Fatalf("zh readout %q", tt.Texts())
}
}
func TestCandlestickChartSnapshot(t *testing.T) {
candles := sampleCandles(80)
view := MarketView{Count: 60}
snapshot(t, "candlestick_chart", 640, 320, func(c *ui.Context) {
ui.Column(c).Padding(12).Children(func() {
CandlestickChart(c, candles, &view, CandlestickOptions{Frame: ChartFrame{Label: "BTC", Height: 260}})
})
})
}