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>
124 lines
3.4 KiB
Go
124 lines
3.4 KiB
Go
package chart
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/egoist/mygo/ui"
|
|
|
|
"git.zacharyzhang.com/ZacharyZhang-NY/MujicaUI/core"
|
|
"git.zacharyzhang.com/ZacharyZhang-NY/MujicaUI/data"
|
|
"git.zacharyzhang.com/ZacharyZhang-NY/MujicaUI/theme"
|
|
)
|
|
|
|
func TestMarketChartsStatesDisabledAndBoundaries(t *testing.T) {
|
|
for _, name := range []string{"Candlestick", "OHLC", "HeikinAshi", "Quote", "Volume", "Depth", "Profile", "Overlay", "Compare", "Alerts", "Drawings"} {
|
|
t.Run(name, func(t *testing.T) {
|
|
cs := sampleCandles(30)
|
|
v := MarketView{Count: 30}
|
|
raw := false
|
|
bids, asks := sampleBook()
|
|
bins := VolumeProfileOf(cs, 12)
|
|
quotes, _, _ := samplePoints()
|
|
symbols := []CompareSymbol{{Name: "Other", Closes: closesOf(cs)}}
|
|
alerts := []PriceAlertLine{{ID: "a", Price: 100}}
|
|
drawings := DrawingTools{}
|
|
status := data.DataReady
|
|
disabled, empty := false, false
|
|
var r ChartResult
|
|
var box ui.Rect
|
|
tt := ui.NewTester(func(c *ui.Context) {
|
|
core.Use(c, core.Settings{Density: theme.Compact})
|
|
ui.Column(c).Disabled(disabled).Width(600).Children(func() {
|
|
candles := cs
|
|
if empty {
|
|
candles = nil
|
|
}
|
|
frame := ChartFrame{Label: "Market", Height: 180, Status: status, Error: "Archive unavailable"}
|
|
opts := CandlestickOptions{Frame: frame}
|
|
switch name {
|
|
case "OHLC":
|
|
r = OHLCChart(c, candles, &v, opts)
|
|
case "HeikinAshi":
|
|
r = HeikinAshiChart(c, candles, &v, &raw, opts)
|
|
case "Quote":
|
|
q := quotes
|
|
if empty {
|
|
q = nil
|
|
}
|
|
r = LineQuoteChart(c, q, LineQuoteChartOptions{Frame: frame, PrevClose: 100})
|
|
case "Volume":
|
|
r = VolumeChart(c, candles, &v, VolumeChartOptions{Frame: frame})
|
|
case "Depth":
|
|
b, a := bids, asks
|
|
if empty {
|
|
b = nil
|
|
a = nil
|
|
}
|
|
r = DepthChart(c, b, a, DepthChartOptions{Frame: frame})
|
|
case "Profile":
|
|
b := bins
|
|
if empty {
|
|
b = nil
|
|
}
|
|
r = VolumeProfile(c, b, VolumeProfileOptions{Frame: frame})
|
|
default:
|
|
if name == "Overlay" {
|
|
opts.Overlays = []IndicatorOverlay{{Kind: IndicatorMA}}
|
|
}
|
|
if name == "Compare" {
|
|
opts.Compare = &symbols
|
|
}
|
|
if name == "Alerts" {
|
|
opts.Alerts = &alerts
|
|
}
|
|
if name == "Drawings" {
|
|
opts.Drawings = &drawings
|
|
}
|
|
r = CandlestickChart(c, candles, &v, opts)
|
|
}
|
|
box = r.Element.Bounds()
|
|
})
|
|
}, 620, 350)
|
|
for range 10 {
|
|
if tt.Focused("Market") {
|
|
break
|
|
}
|
|
tt.Key(0, ui.KeyTab)
|
|
}
|
|
tt.Key(0, ui.KeyHome)
|
|
tt.Key(0, ui.KeyRight)
|
|
if r.Point < 0 || len(tt.Announcements()) == 0 {
|
|
t.Fatalf("keyboard path point %d", r.Point)
|
|
}
|
|
tt.Key(0, ui.KeyHome)
|
|
first := r.Point
|
|
tt.Key(0, ui.KeyLeft)
|
|
if name != "Depth" && name != "Profile" && r.Point != first {
|
|
t.Fatalf("left boundary %d", r.Point)
|
|
}
|
|
disabled = true
|
|
before := v
|
|
tt.Frame()
|
|
tt.Key(0, ui.KeyEqual)
|
|
tt.Scroll(200, 120, 0, 20)
|
|
tt.ClickAt(200, 120)
|
|
if v != before {
|
|
t.Fatalf("disabled window changed: %+v", v)
|
|
}
|
|
disabled = false
|
|
tt.Frame()
|
|
tt.Frame() // Bounds reads the previous frame
|
|
height := box.H
|
|
for _, st := range []data.DataStatus{data.DataLoading, data.DataFailed, data.DataReady} {
|
|
status = st
|
|
empty = st == data.DataReady
|
|
tt.Frame()
|
|
tt.Frame()
|
|
if r.Point != -1 || box.H != height {
|
|
t.Fatalf("state %v: point %d height %v want %v", st, r.Point, box.H, height)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|