1package core_test
2
3import (
4 "testing"
5 "time"
6
7 "github.com/bnema/neferwl/internal/ports"
8)
9
10func animationsOff(c *ports.Config) { c.Animations.On = false }
11
12// stillMoves sends n swipe updates of (dx, dy) and expects no scene: with
13// animations off the view does not follow the fingers.
14func (r *swipeRig) stillMoves(t *testing.T, n int, dx, dy float64) {
15 t.Helper()
16 for range n {
17 r.at += 8 * time.Millisecond
18 r.input <- ports.SwipeUpdate{DX: dx, DY: dy, Time: r.at}
19 }
20 if s, ok := r.published(); ok {
21 t.Fatalf("a swipe update published a scene (%d windows): the view follows the fingers", len(s))
22 }
23}
24
25// published reports the scene set core published for the events sent so
26// far, if any. A flip that names no output is a barrier: core handles its
27// events in order and publishes before taking the next one, so once the
28// send returns every earlier event is handled.
29func (r *swipeRig) published() ([]ports.Scene, bool) {
30 send(r.frames, ports.OutputFrame{Output: "sync"})
31 select {
32 case s := <-r.scenes:
33 return s, true
34 default:
35 return nil, false
36 }
37}
38
39func focusedID(s ports.Scene) ports.WindowID {
40 for _, w := range s.Windows {
41 if w.Focused {
42 return w.ID
43 }
44 }
45 return 0
46}
47
48func TestAnimationsOffColumnSwipeFocusesAtOnce(t *testing.T) {
49 r := startSwipe(t, animationsOff)
50 before := threeColumns(t, r)
51 r.begin()
52 r.stillMoves(t, 10, -40, 0)
53 s := r.end(t, false)
54 if got := focusedID(s); got != 2 {
55 t.Fatalf("focused %d right after the lift, want 2", got)
56 }
57 if got, _ := rectOf(before, 3); got.X == 0 {
58 t.Fatalf("bad setup: column 3 at %+v", got)
59 }
60 r.noFrameScene(t)
61}
62
63func TestAnimationsOffWorkspaceSwipeLandsAtOnce(t *testing.T) {
64 r := startSwipe(t, animationsOff)
65 r.workspaces(t)
66 r.begin()
67 r.stillMoves(t, 9, 0, 30)
68 s := r.end(t, false)
69 if _, ok := rectOf(s, 1); ok {
70 t.Fatal("old workspace still shown right after the lift")
71 }
72 if got, ok := rectOf(s, 2); !ok || got.Y != 0 {
73 t.Fatalf("workspace 2 at %+v (shown %t), want the settled rect", got, ok)
74 }
75}
76
77func TestAnimationsOffSlowSwipeStays(t *testing.T) {
78 r := startSwipe(t, animationsOff)
79 before := threeColumns(t, r)
80 r.begin()
81 r.stillMoves(t, 2, -10, 0)
82 r.input <- ports.SwipeEnd{Time: r.at + time.Second}
83 if s := scene(t, r.scenes); !s.SameAs(before) {
84 t.Fatalf("a short slow swipe changed the scene: %+v, was %+v", s.Windows, before.Windows)
85 }
86 r.noFrameScene(t)
87}
88
89func TestReloadToOffSettlesALanding(t *testing.T) {
90 r := startSwipe(t, nil)
91 threeColumns(t, r)
92 s := r.flick(t, 10, -40, 0)
93 if got, _ := rectOf(s, 1); got.X == 0 {
94 t.Fatal("no landing slide to interrupt")
95 }
96 cfg := r.cfg
97 cfg.Animations.On = false
98 r.reload <- ports.ConfigChanged{Config: cfg}
99 s = scene(t, r.scenes)
100 if got, _ := rectOf(s, 1); got.X != 0 {
101 t.Fatalf("column 1 at %d after the reload, want the settled rect", got.X)
102 }
103 r.noFrameScene(t)
104}
105
106// noFrameScene sends page flips and expects no scene: no spring runs, so
107// the flips have nothing to move and publish nothing.
108func (r *swipeRig) noFrameScene(t *testing.T, outputs ...string) {
109 t.Helper()
110 if len(outputs) == 0 {
111 outputs = []string{wide.Name}
112 }
113 for range 2 {
114 r.advance(16 * time.Millisecond)
115 for _, o := range outputs {
116 r.frames <- ports.OutputFrame{Output: o}
117 }
118 }
119 if s, ok := r.published(); ok {
120 t.Fatalf("a flip published a scene (%d windows): a spring still runs", len(s))
121 }
122}
123
124// landing flicks left and returns the rect of column 1 after each given
125// time since the lift.
126func landing(t *testing.T, slowdown float64, after ...time.Duration) []int {
127 t.Helper()
128 r := startSwipe(t, func(c *ports.Config) { c.Animations.Slowdown = slowdown })
129 threeColumns(t, r)
130 r.flick(t, 10, -40, 0)
131 var xs []int
132 var at time.Duration
133 for _, d := range after {
134 got, _ := rectOf(r.frame(t, d-at), 1)
135 xs = append(xs, got.X)
136 at = d
137 }
138 return xs
139}
140
141func TestSlowdownStretchesALanding(t *testing.T) {
142 // Critically damped at stiffness 800: settled after about 330 ms, 660 ms
143 // with slowdown 2 (the exact doubling is checked on the motion).
144 one := landing(t, 1, 100*time.Millisecond, 400*time.Millisecond)
145 two := landing(t, 2, 100*time.Millisecond, 400*time.Millisecond, 700*time.Millisecond)
146 if one[0] == 0 || two[0] == 0 {
147 t.Fatalf("landing does not move after 100 ms: %v %v", one, two)
148 }
149 if one[1] != 0 || two[1] == 0 {
150 t.Fatalf("at 400 ms: slowdown 1 at %d, want settled; slowdown 2 at %d, want moving", one[1], two[1])
151 }
152 if two[2] != 0 {
153 t.Fatalf("slowdown 2 does not settle by 700 ms: %v", two)
154 }
155}