Files
aego/modules/render2d/editor/art_timeline_layout_test.go
sanspie 617c897f59
ci / build (amd64, linux, ubuntu-24.04) (push) Failing after 8m43s
ci / release (push) Skipped
ci-platforms / windows amd64 smoke (push) Skipped
github-native / darwin amd64 native (push) Skipped
github-native / linux amd64 native (push) Skipped
github-native / windows amd64 native (push) Skipped
ci-platforms / darwin amd64 smoke (push) Skipped
ci-platforms / darwin arm64 smoke (push) Skipped
github-native / gitea-pending (push) Skipped
github-native / darwin arm64 native (push) Skipped
github-native / gitea-result (push) Skipped
ci / checks (push) Failing after 6m26s
GitHub / windows amd64 native GitHub-hosted native CI: failure
GitHub / darwin arm64 native GitHub-hosted native CI: failure
GitHub / darwin amd64 native GitHub-hosted native CI: failure
GitHub / linux amd64 native GitHub-hosted native CI: success
test: cover narrow art timeline controls
2026-11-09 14:26:48 +03:00

150 lines
5.3 KiB
Go

package editor
import (
stdctx "context"
"fmt"
"testing"
"aego/art"
"aego/core/mathx"
"aego/platform/input"
"aego/render/text"
"aego/ui/theme"
)
// These exercise the actual immediate-mode controls with synthetic input,
// not an OS/GPU or screenshot acceptance test.
func TestArtTimelineResponsiveControlsAndCelGrid(t *testing.T) {
for _, light := range []bool{false, true} {
for _, width := range []float32{80, 160, 240, 640} {
t.Run(fmt.Sprintf("light=%v/width=%.0f", light, width), func(t *testing.T) {
panel, state, _, area := artDurationFixture(t)
ui, model, session := panel.ctx.UI, panel.model, panel.model.Session
if light {
ui.Theme = theme.Light()
} else {
ui.Theme = theme.Editor()
}
font := text.NewFont(14, 1, 16, 12)
for r := rune(32); r <= 0x04ff; r++ {
font.SetGlyph(r, text.FontGlyph{Advance: 8})
}
ui.SetFont(font)
area.W, area.H = width, 240
first := session.Frame
if _, err := model.Execute(stdctx.Background(), art.Operation{Kind: "add-frame", DurationMS: 100}); err != nil {
t.Fatal(err)
}
frames := session.Document.Info().Frames
if err := session.Select("", first, ""); err != nil {
t.Fatal(err)
}
var overflow []string
ui.Overflow = func(label string, _, _ float32) { overflow = append(overflow, label) }
draw := func() {
ui.Begin(state, mathx.Vec2{X: width, Y: area.H}, 1)
panel.timeline(ui, area, true)
ui.End()
}
click := func(point mathx.Vec2) {
t.Helper()
state.BeginFrame()
state.ApplyMouseMove(point, mathx.Vec2{})
state.ApplyMouseDown(point, input.MouseLeft)
draw()
state.BeginFrame()
state.ApplyMouseUp(point, input.MouseLeft)
draw()
}
state.BeginFrame()
draw()
pad, row, gap := ui.Theme.Padding, ui.Theme.RowHeight, ui.Theme.Spacing
contentW := width - 2*pad
toolbar := mathx.Rect{X: pad, Y: pad, W: min(contentW, 400), H: row}
before := model.History.Cursor()
click(artCell(toolbar, 0, 4).Center())
if !panel.view.playing {
t.Fatal("Play did not activate")
}
click(artCell(toolbar, 0, 4).Center())
click(artCell(toolbar, 1, 4).Center())
if panel.view.playing || !panel.view.onion || model.History.Cursor() != before {
t.Fatal("Pause or Onion changed document history")
}
click(artCell(toolbar, 3, 4).Center())
if session.Frame != frames[1].ID || model.History.Cursor() != before {
t.Fatal("Next did not select the second frame without editing")
}
click(artCell(toolbar, 2, 4).Center())
if session.Frame != first {
t.Fatal("Previous did not return to the first frame")
}
controls := mathx.Rect{X: pad, Y: pad + 2*(row+gap), W: min(contentW, 480), H: row}
click(artCell(controls, 0, 4).Center())
if len(session.Document.Info().Frames) != 3 || model.History.Cursor() != before+1 {
t.Fatal("Add Frame did not create exactly one history entry")
}
if err := model.SessionUndo(); err != nil {
t.Fatal(err)
}
if err := session.Select("", first, ""); err != nil {
t.Fatal(err)
}
state.BeginFrame()
draw()
labelW := min(float32(150), max(float32(86), contentW*.24))
labelW = min(labelW, max(float32(24), contentW-ui.Theme.ScrollbarW-32))
columns := max(1, panel.view.timelineColumns)
cellW := (contentW - labelW - ui.Theme.ScrollbarW) / float32(columns)
gridY := pad + 4*(row+gap)
if contentW < 140 {
gridY += row + gap
}
click(mathx.Vec2{X: pad + labelW + (cellW-2)/2, Y: gridY + row + gap + row/2})
if len(panel.view.timelineCells) != 1 || panel.view.timelineCells[0].Frame != first || model.History.Cursor() != before || panel.err != nil {
t.Fatal("cel grid did not select the canonical frame without editing", panel.err)
}
if len(overflow) != 0 {
t.Fatalf("timeline labels overflow their bounds: %v", overflow)
}
})
}
}
}
func TestArtTimelineShortDockScrollsToCelGrid(t *testing.T) {
panel, state, _, area := artDurationFixture(t)
ui := panel.ctx.UI
area.W, area.H = 240, 96
draw := func() {
ui.Begin(state, mathx.Vec2{X: area.W, Y: area.H}, 1)
panel.timeline(ui, area, true)
ui.End()
}
state.BeginFrame()
draw()
state.BeginFrame()
state.ApplyWheel(mathx.Vec2{X: 30, Y: 30}, mathx.Vec2{Y: -2})
draw()
if panel.view.timelinePanelScroll <= 0 || panel.view.timelineScroll != 0 {
t.Fatalf("short dock did not scroll its controls independently of the layer grid: panel=%v grid=%v", panel.view.timelinePanelScroll, panel.view.timelineScroll)
}
pad, row, gap := ui.Theme.Padding, ui.Theme.RowHeight, ui.Theme.Spacing
contentW := area.W - 2*pad - ui.Theme.ScrollbarW - gap
labelW := min(float32(150), max(float32(86), contentW*.24))
labelW = min(labelW, max(float32(24), contentW-ui.Theme.ScrollbarW-32))
cellW := (contentW - labelW - ui.Theme.ScrollbarW) / float32(max(1, panel.view.timelineColumns))
gridY := pad + 4*(row+gap) - panel.view.timelinePanelScroll
point := mathx.Vec2{X: pad + labelW + (cellW-2)/2, Y: gridY + row + gap + row/2}
state.BeginFrame()
state.ApplyMouseMove(point, mathx.Vec2{})
state.ApplyMouseDown(point, input.MouseLeft)
draw()
state.BeginFrame()
state.ApplyMouseUp(point, input.MouseLeft)
draw()
if len(panel.view.timelineCells) != 1 || panel.err != nil {
t.Fatal("scrolling did not make the cel grid interactive", panel.err)
}
}