test(core): review fixes for the animations-on variants
Restore the default animations setting of the sized-workspace tests, make the on variants fail when no spring ever ran, extend the last-output unplug test with a workspace switch spring and a replug, and share one mock clock between the swipe and landing rigs.
- commit
- 27a2aaf96fc0cbe23e19e44cfb51f31aade45c84
- author
- brice · · 2026-10-04 20:40 UTC
- parent
- 431292a
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
6 files · +149 -89
M internal/core/animations_interrupt_internal_test.go +69 -6
| @@ -124,19 +124,82 @@ func TestOutputUnplugMidRectMotion(t *testing.T) { | |||
| 124 | 124 | } | |
| 125 | 125 | } | |
| 126 | 126 | ||
| 127 | - | // The last output going away keeps its screen as a placeholder: its rect | |
| 128 | - | // motions must not outlive it (no frame would ever settle them but the | |
| 129 | - | // fallback timer). | |
| 127 | + | // The last output going away keeps its screen as a placeholder: none of its | |
| 128 | + | // springs (rect motions, a workspace switch) may outlive the output, or the | |
| 129 | + | // fallback timer would wake core for a screen nothing draws. When an output | |
| 130 | + | // comes back, a new move animates and lands from a fresh Seq. | |
| 130 | 131 | func TestLastOutputUnplugMidRectMotion(t *testing.T) { | |
| 131 | 132 | c, ic := indicatorCore(t) | |
| 132 | 133 | sc := c.cur() | |
| 133 | 134 | movingRects(t, c, ic, sc) | |
| 135 | + | // A workspace switch spring runs too. | |
| 136 | + | m := sc.mon | |
| 137 | + | m.switchOff, m.switchMotion = 0.5, c.spring(workspaceSpring(0.5, 0), ic.now) | |
| 138 | + | if !m.switchMotion.on || !sc.springing() { | |
| 139 | + | t.Fatal("no workspace switch to interrupt") | |
| 140 | + | } | |
| 134 | 141 | c.removeScreen("A") | |
| 142 | + | if sc.mon.springing() || len(sc.rects) != 0 || sc.rectsWS != nil || sc.mon.switchMotion.on || sc.mon.switchOff != 0 { | |
| 143 | + | t.Fatalf("the placeholder keeps springs: monitor %t, rects %d, switch %t (%v)", sc.mon.springing(), len(sc.rects), sc.mon.switchMotion.on, sc.mon.switchOff) | |
| 144 | + | } | |
| 135 | 145 | if err := c.publish(context.Background()); err != nil { | |
| 136 | 146 | t.Fatal(err) | |
| 137 | 147 | } | |
| 138 | - | <-c.ch.Scenes | |
| 139 | - | if len(sc.rects) != 0 || c.animating() { | |
| 140 | - | t.Fatalf("the placeholder keeps motions: rects %d, animating %t", len(sc.rects), c.animating()) | |
| 148 | + | gone := (<-c.ch.Scenes)[0] | |
| 149 | + | if c.animating() || c.frameC != nil { | |
| 150 | + | t.Fatalf("the placeholder keeps asking for frames: animating %t, timer %t", c.animating(), c.frameC != nil) | |
| 151 | + | } | |
| 152 | + | ||
| 153 | + | // The output comes back: its windows are where the move left them. | |
| 154 | + | c.addScreen(ports.OutputInfo{Name: "A", Width: 300, Height: 200}) | |
| 155 | + | settled := indicatorScene(t, c) | |
| 156 | + | if settled.Seq <= gone.Seq { | |
| 157 | + | t.Fatalf("scene after the replug reuses Seq %d (placeholder %d)", settled.Seq, gone.Seq) | |
| 158 | + | } | |
| 159 | + | x := func(s ports.Scene, id WindowID) int { | |
| 160 | + | for _, w := range s.Windows { | |
| 161 | + | if w.ID == id { | |
| 162 | + | return w.Rect.X | |
| 163 | + | } | |
| 164 | + | } | |
| 165 | + | t.Fatalf("window %d not in the scene", id) | |
| 166 | + | return 0 | |
| 167 | + | } | |
| 168 | + | if x(settled, 2) != 0 || x(settled, 1) != 150 { | |
| 169 | + | t.Fatalf("after the replug 1 at %d and 2 at %d, want the settled 150 and 0", x(settled, 1), x(settled, 2)) | |
| 170 | + | } | |
| 171 | + | if c.animating() || len(c.cur().rects) != 0 { | |
| 172 | + | t.Fatal("a motion runs after the replug") | |
| 173 | + | } | |
| 174 | + | ||
| 175 | + | // A new move animates from the settled rects and lands. | |
| 176 | + | before := c.snapshot() | |
| 177 | + | c.applyAction(ActionMoveColumnRight) | |
| 178 | + | c.transition(before, ic.now) | |
| 179 | + | start := indicatorScene(t, c) | |
| 180 | + | if x(start, 1) != 150 || x(start, 2) != 0 { | |
| 181 | + | t.Fatalf("right after the move 1 at %d and 2 at %d, want the old 150 and 0", x(start, 1), x(start, 2)) | |
| 182 | + | } | |
| 183 | + | ic.now = ic.now.Add(16 * time.Millisecond) | |
| 184 | + | if err := c.step(context.Background(), nil); err != nil { | |
| 185 | + | t.Fatal(err) | |
| 186 | + | } | |
| 187 | + | mid := (<-c.ch.Scenes)[0] | |
| 188 | + | if got := x(mid, 1); got >= 150 || got <= 0 { | |
| 189 | + | t.Fatalf("window 1 at %d on the first frame, want between 0 and 150", got) | |
| 190 | + | } | |
| 191 | + | if mid.Seq == start.Seq { | |
| 192 | + | t.Fatalf("an animating frame reuses Seq %d", mid.Seq) | |
| 193 | + | } | |
| 194 | + | ic.now = ic.now.Add(5 * time.Second) | |
| 195 | + | if err := c.step(context.Background(), nil); err != nil { | |
| 196 | + | t.Fatal(err) | |
| 197 | + | } | |
| 198 | + | end := (<-c.ch.Scenes)[0] | |
| 199 | + | if x(end, 1) != 0 || x(end, 2) != 150 || end.Seq == mid.Seq { | |
| 200 | + | t.Fatalf("landed 1 at %d and 2 at %d (Seq %d after %d), want 0 and 150 and a fresh Seq", x(end, 1), x(end, 2), end.Seq, mid.Seq) | |
| 201 | + | } | |
| 202 | + | if c.animating() || c.frameC != nil { | |
| 203 | + | t.Fatal("still animating after the landing") | |
| 141 | 204 | } | |
| 142 | 205 | } | |
| @@ -124,19 +124,82 @@ func TestOutputUnplugMidRectMotion(t *testing.T) { | |||||
| 124 | } | 124 | } | ||
| 125 | } | 125 | } | ||
| 126 | 126 | ||||
| 127 | - | // The last output going away keeps its screen as a placeholder: its rect | 127 | + | // The last output going away keeps its screen as a placeholder: none of its |
| 128 | - | // motions must not outlive it (no frame would ever settle them but the | 128 | + | // springs (rect motions, a workspace switch) may outlive the output, or the |
| 129 | - | // fallback timer). | 129 | + | // fallback timer would wake core for a screen nothing draws. When an output |
| 130 | + | // comes back, a new move animates and lands from a fresh Seq. | |||
| 130 | func TestLastOutputUnplugMidRectMotion(t *testing.T) { | 131 | func TestLastOutputUnplugMidRectMotion(t *testing.T) { | ||
| 131 | c, ic := indicatorCore(t) | 132 | c, ic := indicatorCore(t) | ||
| 132 | sc := c.cur() | 133 | sc := c.cur() | ||
| 133 | movingRects(t, c, ic, sc) | 134 | movingRects(t, c, ic, sc) | ||
| 135 | + | // A workspace switch spring runs too. | |||
| 136 | + | m := sc.mon | |||
| 137 | + | m.switchOff, m.switchMotion = 0.5, c.spring(workspaceSpring(0.5, 0), ic.now) | |||
| 138 | + | if !m.switchMotion.on || !sc.springing() { | |||
| 139 | + | t.Fatal("no workspace switch to interrupt") | |||
| 140 | + | } | |||
| 134 | c.removeScreen("A") | 141 | c.removeScreen("A") | ||
| 142 | + | if sc.mon.springing() || len(sc.rects) != 0 || sc.rectsWS != nil || sc.mon.switchMotion.on || sc.mon.switchOff != 0 { | |||
| 143 | + | t.Fatalf("the placeholder keeps springs: monitor %t, rects %d, switch %t (%v)", sc.mon.springing(), len(sc.rects), sc.mon.switchMotion.on, sc.mon.switchOff) | |||
| 144 | + | } | |||
| 135 | if err := c.publish(context.Background()); err != nil { | 145 | if err := c.publish(context.Background()); err != nil { | ||
| 136 | t.Fatal(err) | 146 | t.Fatal(err) | ||
| 137 | } | 147 | } | ||
| 138 | - | <-c.ch.Scenes | 148 | + | gone := (<-c.ch.Scenes)[0] |
| 139 | - | if len(sc.rects) != 0 || c.animating() { | 149 | + | if c.animating() || c.frameC != nil { |
| 140 | - | t.Fatalf("the placeholder keeps motions: rects %d, animating %t", len(sc.rects), c.animating()) | 150 | + | t.Fatalf("the placeholder keeps asking for frames: animating %t, timer %t", c.animating(), c.frameC != nil) |
| 151 | + | } | |||
| 152 | + | ||||
| 153 | + | // The output comes back: its windows are where the move left them. | |||
| 154 | + | c.addScreen(ports.OutputInfo{Name: "A", Width: 300, Height: 200}) | |||
| 155 | + | settled := indicatorScene(t, c) | |||
| 156 | + | if settled.Seq <= gone.Seq { | |||
| 157 | + | t.Fatalf("scene after the replug reuses Seq %d (placeholder %d)", settled.Seq, gone.Seq) | |||
| 158 | + | } | |||
| 159 | + | x := func(s ports.Scene, id WindowID) int { | |||
| 160 | + | for _, w := range s.Windows { | |||
| 161 | + | if w.ID == id { | |||
| 162 | + | return w.Rect.X | |||
| 163 | + | } | |||
| 164 | + | } | |||
| 165 | + | t.Fatalf("window %d not in the scene", id) | |||
| 166 | + | return 0 | |||
| 167 | + | } | |||
| 168 | + | if x(settled, 2) != 0 || x(settled, 1) != 150 { | |||
| 169 | + | t.Fatalf("after the replug 1 at %d and 2 at %d, want the settled 150 and 0", x(settled, 1), x(settled, 2)) | |||
| 170 | + | } | |||
| 171 | + | if c.animating() || len(c.cur().rects) != 0 { | |||
| 172 | + | t.Fatal("a motion runs after the replug") | |||
| 173 | + | } | |||
| 174 | + | ||||
| 175 | + | // A new move animates from the settled rects and lands. | |||
| 176 | + | before := c.snapshot() | |||
| 177 | + | c.applyAction(ActionMoveColumnRight) | |||
| 178 | + | c.transition(before, ic.now) | |||
| 179 | + | start := indicatorScene(t, c) | |||
| 180 | + | if x(start, 1) != 150 || x(start, 2) != 0 { | |||
| 181 | + | t.Fatalf("right after the move 1 at %d and 2 at %d, want the old 150 and 0", x(start, 1), x(start, 2)) | |||
| 182 | + | } | |||
| 183 | + | ic.now = ic.now.Add(16 * time.Millisecond) | |||
| 184 | + | if err := c.step(context.Background(), nil); err != nil { | |||
| 185 | + | t.Fatal(err) | |||
| 186 | + | } | |||
| 187 | + | mid := (<-c.ch.Scenes)[0] | |||
| 188 | + | if got := x(mid, 1); got >= 150 || got <= 0 { | |||
| 189 | + | t.Fatalf("window 1 at %d on the first frame, want between 0 and 150", got) | |||
| 190 | + | } | |||
| 191 | + | if mid.Seq == start.Seq { | |||
| 192 | + | t.Fatalf("an animating frame reuses Seq %d", mid.Seq) | |||
| 193 | + | } | |||
| 194 | + | ic.now = ic.now.Add(5 * time.Second) | |||
| 195 | + | if err := c.step(context.Background(), nil); err != nil { | |||
| 196 | + | t.Fatal(err) | |||
| 197 | + | } | |||
| 198 | + | end := (<-c.ch.Scenes)[0] | |||
| 199 | + | if x(end, 1) != 0 || x(end, 2) != 150 || end.Seq == mid.Seq { | |||
| 200 | + | t.Fatalf("landed 1 at %d and 2 at %d (Seq %d after %d), want 0 and 150 and a fresh Seq", x(end, 1), x(end, 2), end.Seq, mid.Seq) | |||
| 201 | + | } | |||
| 202 | + | if c.animating() || c.frameC != nil { | |||
| 203 | + | t.Fatal("still animating after the landing") | |||
| 141 | } | 204 | } | ||
| 142 | } | 205 | } | ||
M internal/core/border_invariants_test.go +1 -1
| @@ -54,7 +54,7 @@ func TestBorderSceneInvariantsAnimated(t *testing.T) { | |||
| 54 | 54 | moved++ | |
| 55 | 55 | } | |
| 56 | 56 | // Settled means no spring runs: a further flip has nothing to do. | |
| 57 | - | if _, again := r.flipAll(t, 16*time.Millisecond, left.Name, right.Name); again { | |
| 57 | + | if _, again := r.clk.flip(t, r.frames, r.scenes, 16*time.Millisecond, left.Name, right.Name); again { | |
| 58 | 58 | t.Fatal("a flip after the settle still published: a spring runs") | |
| 59 | 59 | } | |
| 60 | 60 | return settled | |
| @@ -54,7 +54,7 @@ func TestBorderSceneInvariantsAnimated(t *testing.T) { | |||||
| 54 | moved++ | 54 | moved++ | ||
| 55 | } | 55 | } | ||
| 56 | // Settled means no spring runs: a further flip has nothing to do. | 56 | // Settled means no spring runs: a further flip has nothing to do. | ||
| 57 | - | if _, again := r.flipAll(t, 16*time.Millisecond, left.Name, right.Name); again { | 57 | + | if _, again := r.clk.flip(t, r.frames, r.scenes, 16*time.Millisecond, left.Name, right.Name); again { |
| 58 | t.Fatal("a flip after the settle still published: a spring runs") | 58 | t.Fatal("a flip after the settle still published: a spring runs") | ||
| 59 | } | 59 | } | ||
| 60 | return settled | 60 | return settled | ||
M internal/core/drag_test.go +10 -3
| @@ -27,6 +27,8 @@ type dragRig struct { | |||
| 27 | 27 | frames chan ports.OutputFrame | |
| 28 | 28 | clock *stepClock | |
| 29 | 29 | outputs []string | |
| 30 | + | // lands counts the settles that found a spring running. | |
| 31 | + | lands int | |
| 30 | 32 | } | |
| 31 | 33 | ||
| 32 | 34 | // startDrag runs core with Super as Cmd, no border, 10 px gaps, and | |
| @@ -73,6 +75,12 @@ func newDragRig(t *testing.T, animated bool, edit func(*ports.Config), outs ...p | |||
| 73 | 75 | if animated { | |
| 74 | 76 | r.frames, r.clock = make(chan ports.OutputFrame), newStepClock(t) | |
| 75 | 77 | ch.Frames, ch.Clock = r.frames, r.clock.clock | |
| 78 | + | // An on variant that never saw a spring would be the off variant again. | |
| 79 | + | t.Cleanup(func() { | |
| 80 | + | if r.lands == 0 { | |
| 81 | + | t.Error("no spring ever ran: the animations-on variant checked nothing") | |
| 82 | + | } | |
| 83 | + | }) | |
| 76 | 84 | } | |
| 77 | 85 | c, err := core.New(cfg, ch) | |
| 78 | 86 | if err != nil { | |
| @@ -115,9 +123,8 @@ const barrier ports.WindowID = 1 << 30 | |||
| 115 | 123 | func (r *dragRig) settle(t *testing.T) []ports.Scene { | |
| 116 | 124 | t.Helper() | |
| 117 | 125 | if r.frames != nil { | |
| 118 | - | r.clock.advance(5 * time.Second) | |
| 119 | - | for _, o := range r.outputs { | |
| 120 | - | send(r.frames, ports.OutputFrame{Output: o}) | |
| 126 | + | if _, ok := r.clock.settle(t, r.frames, r.scenes, r.outputs...); ok { | |
| 127 | + | r.lands++ | |
| 121 | 128 | } | |
| 122 | 129 | } | |
| 123 | 130 | send(r.client, ports.ClientEvent(ports.WindowResized{ID: barrier})) | |
| @@ -27,6 +27,8 @@ type dragRig struct { | |||||
| 27 | frames chan ports.OutputFrame | 27 | frames chan ports.OutputFrame | ||
| 28 | clock *stepClock | 28 | clock *stepClock | ||
| 29 | outputs []string | 29 | outputs []string | ||
| 30 | + | // lands counts the settles that found a spring running. | |||
| 31 | + | lands int | |||
| 30 | } | 32 | } | ||
| 31 | 33 | ||||
| 32 | // startDrag runs core with Super as Cmd, no border, 10 px gaps, and | 34 | // startDrag runs core with Super as Cmd, no border, 10 px gaps, and | ||
| @@ -73,6 +75,12 @@ func newDragRig(t *testing.T, animated bool, edit func(*ports.Config), outs ...p | |||||
| 73 | if animated { | 75 | if animated { | ||
| 74 | r.frames, r.clock = make(chan ports.OutputFrame), newStepClock(t) | 76 | r.frames, r.clock = make(chan ports.OutputFrame), newStepClock(t) | ||
| 75 | ch.Frames, ch.Clock = r.frames, r.clock.clock | 77 | ch.Frames, ch.Clock = r.frames, r.clock.clock | ||
| 78 | + | // An on variant that never saw a spring would be the off variant again. | |||
| 79 | + | t.Cleanup(func() { | |||
| 80 | + | if r.lands == 0 { | |||
| 81 | + | t.Error("no spring ever ran: the animations-on variant checked nothing") | |||
| 82 | + | } | |||
| 83 | + | }) | |||
| 76 | } | 84 | } | ||
| 77 | c, err := core.New(cfg, ch) | 85 | c, err := core.New(cfg, ch) | ||
| 78 | if err != nil { | 86 | if err != nil { | ||
| @@ -115,9 +123,8 @@ const barrier ports.WindowID = 1 << 30 | |||||
| 115 | func (r *dragRig) settle(t *testing.T) []ports.Scene { | 123 | func (r *dragRig) settle(t *testing.T) []ports.Scene { | ||
| 116 | t.Helper() | 124 | t.Helper() | ||
| 117 | if r.frames != nil { | 125 | if r.frames != nil { | ||
| 118 | - | r.clock.advance(5 * time.Second) | 126 | + | if _, ok := r.clock.settle(t, r.frames, r.scenes, r.outputs...); ok { |
| 119 | - | for _, o := range r.outputs { | 127 | + | r.lands++ |
| 120 | - | send(r.frames, ports.OutputFrame{Output: o}) | |||
| 121 | } | 128 | } | ||
| 122 | } | 129 | } | ||
| 123 | send(r.client, ports.ClientEvent(ports.WindowResized{ID: barrier})) | 130 | send(r.client, ports.ClientEvent(ports.WindowResized{ID: barrier})) | ||
M internal/core/step_clock_test.go +41 -9
| @@ -44,13 +44,21 @@ func (s *stepClock) advance(d time.Duration) { | |||
| 44 | 44 | s.mu.Unlock() | |
| 45 | 45 | } | |
| 46 | 46 | ||
| 47 | - | // settle moves the clock past every spring, flips each output and returns | |
| 48 | - | // the set core published for them (ok false: no spring ran, so nothing | |
| 49 | - | // moved). The last flip names no output: core takes it only after it | |
| 50 | - | // handled the previous ones, so the scene channel then holds their result. | |
| 51 | - | func (s *stepClock) settle(t *testing.T, frames chan ports.OutputFrame, scenes chan []ports.Scene, outputs ...string) ([]ports.Scene, bool) { | |
| 47 | + | // flip moves the clock by d and sends a page flip for each output, then | |
| 48 | + | // returns the scene set core published for them (ok false: none, so no | |
| 49 | + | // spring ran). Core publishes before it takes its next event, so a flip that | |
| 50 | + | // names no output is a barrier: once its send returns, every earlier event | |
| 51 | + | // is handled and the scene channel holds the result. One is sent first too, | |
| 52 | + | // and a scene left over from earlier events is dropped, so a scene found | |
| 53 | + | // afterwards is the flips' own. | |
| 54 | + | func (s *stepClock) flip(t *testing.T, frames chan ports.OutputFrame, scenes chan []ports.Scene, d time.Duration, outputs ...string) ([]ports.Scene, bool) { | |
| 52 | 55 | t.Helper() | |
| 53 | - | s.advance(5 * time.Second) | |
| 56 | + | send(frames, ports.OutputFrame{Output: "sync"}) | |
| 57 | + | select { | |
| 58 | + | case <-scenes: | |
| 59 | + | default: | |
| 60 | + | } | |
| 61 | + | s.advance(d) | |
| 54 | 62 | for _, o := range outputs { | |
| 55 | 63 | send(frames, ports.OutputFrame{Output: o}) | |
| 56 | 64 | } | |
| @@ -63,6 +71,12 @@ func (s *stepClock) settle(t *testing.T, frames chan ports.OutputFrame, scenes c | |||
| 63 | 71 | } | |
| 64 | 72 | } | |
| 65 | 73 | ||
| 74 | + | // settle is flip past every spring. | |
| 75 | + | func (s *stepClock) settle(t *testing.T, frames chan ports.OutputFrame, scenes chan []ports.Scene, outputs ...string) ([]ports.Scene, bool) { | |
| 76 | + | t.Helper() | |
| 77 | + | return s.flip(t, frames, scenes, 5*time.Second, outputs...) | |
| 78 | + | } | |
| 79 | + | ||
| 66 | 80 | // landRig is a multiRig whose settle lands every running spring: with | |
| 67 | 81 | // animations on it runs on a clock the test moves (swipeRig) and sends the | |
| 68 | 82 | // page flips; with animations off it is the plain rig and land is the | |
| @@ -71,6 +85,8 @@ func (s *stepClock) settle(t *testing.T, frames chan ports.OutputFrame, scenes c | |||
| 71 | 85 | type landRig struct { | |
| 72 | 86 | *multiRig | |
| 73 | 87 | land func([]ports.Scene) []ports.Scene | |
| 88 | + | // lands counts the settles that found a spring running. | |
| 89 | + | lands *int | |
| 74 | 90 | } | |
| 75 | 91 | ||
| 76 | 92 | // startLanding runs core on the given outputs (gaps and terminal policy as | |
| @@ -84,7 +100,7 @@ func startLanding(t *testing.T, animated bool, edit func(*ports.Config), outs .. | |||
| 84 | 100 | } | |
| 85 | 101 | c.Animations.On = false | |
| 86 | 102 | }, outs...) | |
| 87 | - | return &landRig{multiRig: r, land: func(set []ports.Scene) []ports.Scene { return set }} | |
| 103 | + | return &landRig{multiRig: r, land: func(set []ports.Scene) []ports.Scene { return set }, lands: new(int)} | |
| 88 | 104 | } | |
| 89 | 105 | defaults := altCmdDefaults() | |
| 90 | 106 | r := startSwipeOn(t, func(c *ports.Config) { | |
| @@ -94,10 +110,26 @@ func startLanding(t *testing.T, animated bool, edit func(*ports.Config), outs .. | |||
| 94 | 110 | } | |
| 95 | 111 | c.Animations.On = true | |
| 96 | 112 | }, outs...) | |
| 97 | - | return &landRig{multiRig: r.multiRig, land: func(set []ports.Scene) []ports.Scene { return r.landed(t, set) }} | |
| 113 | + | lands := new(int) | |
| 114 | + | // An on variant that never saw a spring would be the off variant again. | |
| 115 | + | t.Cleanup(func() { | |
| 116 | + | if *lands == 0 { | |
| 117 | + | t.Error("no spring ever ran: the animations-on variant checked nothing") | |
| 118 | + | } | |
| 119 | + | }) | |
| 120 | + | return &landRig{multiRig: r.multiRig, lands: lands, land: func(set []ports.Scene) []ports.Scene { | |
| 121 | + | landed, ok := r.settleAll(t, r.outs...) | |
| 122 | + | if !ok { | |
| 123 | + | return set | |
| 124 | + | } | |
| 125 | + | *lands++ | |
| 126 | + | return landed | |
| 127 | + | }} | |
| 98 | 128 | } | |
| 99 | 129 | ||
| 100 | - | // both runs body with animations off, then on. | |
| 130 | + | // both runs body with animations off, then on. A rig of the on run fails the | |
| 131 | + | // test when it never settled a running spring (startLanding, | |
| 132 | + | // startAnimatedDragRig). | |
| 101 | 133 | func both(t *testing.T, body func(t *testing.T, animated bool)) { | |
| 102 | 134 | t.Helper() | |
| 103 | 135 | t.Run("off", func(t *testing.T) { body(t, false) }) | |
| @@ -44,13 +44,21 @@ func (s *stepClock) advance(d time.Duration) { | |||||
| 44 | s.mu.Unlock() | 44 | s.mu.Unlock() | ||
| 45 | } | 45 | } | ||
| 46 | 46 | ||||
| 47 | - | // settle moves the clock past every spring, flips each output and returns | 47 | + | // flip moves the clock by d and sends a page flip for each output, then |
| 48 | - | // the set core published for them (ok false: no spring ran, so nothing | 48 | + | // returns the scene set core published for them (ok false: none, so no |
| 49 | - | // moved). The last flip names no output: core takes it only after it | 49 | + | // spring ran). Core publishes before it takes its next event, so a flip that |
| 50 | - | // handled the previous ones, so the scene channel then holds their result. | 50 | + | // names no output is a barrier: once its send returns, every earlier event |
| 51 | - | func (s *stepClock) settle(t *testing.T, frames chan ports.OutputFrame, scenes chan []ports.Scene, outputs ...string) ([]ports.Scene, bool) { | 51 | + | // is handled and the scene channel holds the result. One is sent first too, |
| 52 | + | // and a scene left over from earlier events is dropped, so a scene found | |||
| 53 | + | // afterwards is the flips' own. | |||
| 54 | + | func (s *stepClock) flip(t *testing.T, frames chan ports.OutputFrame, scenes chan []ports.Scene, d time.Duration, outputs ...string) ([]ports.Scene, bool) { | |||
| 52 | t.Helper() | 55 | t.Helper() | ||
| 53 | - | s.advance(5 * time.Second) | 56 | + | send(frames, ports.OutputFrame{Output: "sync"}) |
| 57 | + | select { | |||
| 58 | + | case <-scenes: | |||
| 59 | + | default: | |||
| 60 | + | } | |||
| 61 | + | s.advance(d) | |||
| 54 | for _, o := range outputs { | 62 | for _, o := range outputs { | ||
| 55 | send(frames, ports.OutputFrame{Output: o}) | 63 | send(frames, ports.OutputFrame{Output: o}) | ||
| 56 | } | 64 | } | ||
| @@ -63,6 +71,12 @@ func (s *stepClock) settle(t *testing.T, frames chan ports.OutputFrame, scenes c | |||||
| 63 | } | 71 | } | ||
| 64 | } | 72 | } | ||
| 65 | 73 | ||||
| 74 | + | // settle is flip past every spring. | |||
| 75 | + | func (s *stepClock) settle(t *testing.T, frames chan ports.OutputFrame, scenes chan []ports.Scene, outputs ...string) ([]ports.Scene, bool) { | |||
| 76 | + | t.Helper() | |||
| 77 | + | return s.flip(t, frames, scenes, 5*time.Second, outputs...) | |||
| 78 | + | } | |||
| 79 | + | ||||
| 66 | // landRig is a multiRig whose settle lands every running spring: with | 80 | // landRig is a multiRig whose settle lands every running spring: with | ||
| 67 | // animations on it runs on a clock the test moves (swipeRig) and sends the | 81 | // animations on it runs on a clock the test moves (swipeRig) and sends the | ||
| 68 | // page flips; with animations off it is the plain rig and land is the | 82 | // page flips; with animations off it is the plain rig and land is the | ||
| @@ -71,6 +85,8 @@ func (s *stepClock) settle(t *testing.T, frames chan ports.OutputFrame, scenes c | |||||
| 71 | type landRig struct { | 85 | type landRig struct { | ||
| 72 | *multiRig | 86 | *multiRig | ||
| 73 | land func([]ports.Scene) []ports.Scene | 87 | land func([]ports.Scene) []ports.Scene | ||
| 88 | + | // lands counts the settles that found a spring running. | |||
| 89 | + | lands *int | |||
| 74 | } | 90 | } | ||
| 75 | 91 | ||||
| 76 | // startLanding runs core on the given outputs (gaps and terminal policy as | 92 | // startLanding runs core on the given outputs (gaps and terminal policy as | ||
| @@ -84,7 +100,7 @@ func startLanding(t *testing.T, animated bool, edit func(*ports.Config), outs .. | |||||
| 84 | } | 100 | } | ||
| 85 | c.Animations.On = false | 101 | c.Animations.On = false | ||
| 86 | }, outs...) | 102 | }, outs...) | ||
| 87 | - | return &landRig{multiRig: r, land: func(set []ports.Scene) []ports.Scene { return set }} | 103 | + | return &landRig{multiRig: r, land: func(set []ports.Scene) []ports.Scene { return set }, lands: new(int)} |
| 88 | } | 104 | } | ||
| 89 | defaults := altCmdDefaults() | 105 | defaults := altCmdDefaults() | ||
| 90 | r := startSwipeOn(t, func(c *ports.Config) { | 106 | r := startSwipeOn(t, func(c *ports.Config) { | ||
| @@ -94,10 +110,26 @@ func startLanding(t *testing.T, animated bool, edit func(*ports.Config), outs .. | |||||
| 94 | } | 110 | } | ||
| 95 | c.Animations.On = true | 111 | c.Animations.On = true | ||
| 96 | }, outs...) | 112 | }, outs...) | ||
| 97 | - | return &landRig{multiRig: r.multiRig, land: func(set []ports.Scene) []ports.Scene { return r.landed(t, set) }} | 113 | + | lands := new(int) |
| 114 | + | // An on variant that never saw a spring would be the off variant again. | |||
| 115 | + | t.Cleanup(func() { | |||
| 116 | + | if *lands == 0 { | |||
| 117 | + | t.Error("no spring ever ran: the animations-on variant checked nothing") | |||
| 118 | + | } | |||
| 119 | + | }) | |||
| 120 | + | return &landRig{multiRig: r.multiRig, lands: lands, land: func(set []ports.Scene) []ports.Scene { | |||
| 121 | + | landed, ok := r.settleAll(t, r.outs...) | |||
| 122 | + | if !ok { | |||
| 123 | + | return set | |||
| 124 | + | } | |||
| 125 | + | *lands++ | |||
| 126 | + | return landed | |||
| 127 | + | }} | |||
| 98 | } | 128 | } | ||
| 99 | 129 | ||||
| 100 | - | // both runs body with animations off, then on. | 130 | + | // both runs body with animations off, then on. A rig of the on run fails the |
| 131 | + | // test when it never settled a running spring (startLanding, | |||
| 132 | + | // startAnimatedDragRig). | |||
| 101 | func both(t *testing.T, body func(t *testing.T, animated bool)) { | 133 | func both(t *testing.T, body func(t *testing.T, animated bool)) { | ||
| 102 | t.Helper() | 134 | t.Helper() | ||
| 103 | t.Run("off", func(t *testing.T) { body(t, false) }) | 135 | t.Run("off", func(t *testing.T) { body(t, false) }) | ||
M internal/core/swipe_test.go +7 -56
| @@ -2,14 +2,11 @@ package core_test | |||
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | 4 | "context" | |
| 5 | - | "sync" | |
| 6 | 5 | "testing" | |
| 7 | 6 | "time" | |
| 8 | 7 | ||
| 9 | 8 | "github.com/bnema/neferwl/internal/core" | |
| 10 | - | portsmocks "github.com/bnema/neferwl/internal/mocks/ports" | |
| 11 | 9 | "github.com/bnema/neferwl/internal/ports" | |
| 12 | - | "github.com/stretchr/testify/mock" | |
| 13 | 10 | ) | |
| 14 | 11 | ||
| 15 | 12 | // swipeRig runs core on one 800x600 output with no gaps or border, a | |
| @@ -17,8 +14,7 @@ import ( | |||
| 17 | 14 | type swipeRig struct { | |
| 18 | 15 | *multiRig | |
| 19 | 16 | frames chan ports.OutputFrame | |
| 20 | - | mu sync.Mutex | |
| 21 | - | now time.Time | |
| 17 | + | clk *stepClock | |
| 22 | 18 | // at is the device time of the next swipe event. | |
| 23 | 19 | at time.Duration | |
| 24 | 20 | // outs are the plugged outputs, for landed. | |
| @@ -42,26 +38,13 @@ func startSwipeOn(t *testing.T, edit func(*ports.Config), outs ...ports.OutputIn | |||
| 42 | 38 | if edit != nil { | |
| 43 | 39 | edit(&cfg) | |
| 44 | 40 | } | |
| 45 | - | r := &swipeRig{frames: make(chan ports.OutputFrame), now: time.Unix(100, 0)} | |
| 41 | + | r := &swipeRig{frames: make(chan ports.OutputFrame), clk: newStepClock(t)} | |
| 46 | 42 | r.multiRig = &multiRig{ | |
| 47 | 43 | client: make(chan ports.ClientEvent, 16), input: make(chan ports.InputEvent, 64), | |
| 48 | 44 | output: make(chan ports.OutputEvent, 4), reload: make(chan ports.ConfigChanged, 4), | |
| 49 | 45 | commands: make(chan ports.ClientCommand, 1024), scenes: make(chan []ports.Scene, 1), spawn: make(chan ports.SpawnRequest, 16), state: make(chan ports.State, 1), cfg: cfg, | |
| 50 | 46 | } | |
| 51 | - | clock := portsmocks.NewMockClock(t) | |
| 52 | - | clock.EXPECT().Now().RunAndReturn(func() time.Time { | |
| 53 | - | r.mu.Lock() | |
| 54 | - | defer r.mu.Unlock() | |
| 55 | - | return r.now | |
| 56 | - | }).Maybe() | |
| 57 | - | // The fallback timer never fires: frames come from the test. One timer | |
| 58 | - | // built before core runs: a mock created by core while the test ends | |
| 59 | - | // would race its own cleanup check. | |
| 60 | - | timer := portsmocks.NewMockTimer(t) | |
| 61 | - | timer.EXPECT().C().Return(make(chan time.Time)).Maybe() | |
| 62 | - | timer.EXPECT().Stop().Return(true).Maybe() | |
| 63 | - | clock.EXPECT().NewTimer(mock.Anything).Return(timer).Maybe() | |
| 64 | - | c, err := core.New(cfg, core.Channels{Client: r.client, Input: r.input, Output: r.output, Config: r.reload, Commands: r.commands, Scenes: r.scenes, Spawn: r.spawn, State: r.state, Clock: clock, Frames: r.frames}) | |
| 47 | + | c, err := core.New(cfg, core.Channels{Client: r.client, Input: r.input, Output: r.output, Config: r.reload, Commands: r.commands, Scenes: r.scenes, Spawn: r.spawn, State: r.state, Clock: r.clk.clock, Frames: r.frames}) | |
| 65 | 48 | if err != nil { | |
| 66 | 49 | t.Fatal(err) | |
| 67 | 50 | } | |
| @@ -76,21 +59,8 @@ func startSwipeOn(t *testing.T, edit func(*ports.Config), outs ...ports.OutputIn | |||
| 76 | 59 | return r | |
| 77 | 60 | } | |
| 78 | 61 | ||
| 79 | - | // landed is the scene set once every spring an action started on any output | |
| 80 | - | // landed: the set itself when nothing animates (animations off, or an | |
| 81 | - | // action that moves nothing). | |
| 82 | - | func (r *swipeRig) landed(t *testing.T, set []ports.Scene) []ports.Scene { | |
| 83 | - | t.Helper() | |
| 84 | - | if landed, ok := r.settleAll(t, r.outs...); ok { | |
| 85 | - | return landed | |
| 86 | - | } | |
| 87 | - | return set | |
| 88 | - | } | |
| 89 | - | ||
| 90 | 62 | func (r *swipeRig) advance(d time.Duration) { | |
| 91 | - | r.mu.Lock() | |
| 92 | - | r.now = r.now.Add(d) | |
| 93 | - | r.mu.Unlock() | |
| 63 | + | r.clk.advance(d) | |
| 94 | 64 | } | |
| 95 | 65 | ||
| 96 | 66 | // begin starts a swipe; nothing moves until it picks an axis. | |
| @@ -127,30 +97,11 @@ func (r *swipeRig) settle(t *testing.T) ports.Scene { | |||
| 127 | 97 | return r.frame(t, 5*time.Second) | |
| 128 | 98 | } | |
| 129 | 99 | ||
| 130 | - | // flipAll moves the clock by d, sends a page flip for each output and | |
| 131 | - | // returns the latest scene set they published (ok false: none, nothing | |
| 132 | - | // animated). The last flip names no output: core takes it only after it | |
| 133 | - | // handled the previous ones, so the scene channel then holds their result. | |
| 134 | - | func (r *swipeRig) flipAll(t *testing.T, d time.Duration, outputs ...string) (set []ports.Scene, ok bool) { | |
| 135 | - | t.Helper() | |
| 136 | - | r.advance(d) | |
| 137 | - | for _, o := range outputs { | |
| 138 | - | send(r.frames, ports.OutputFrame{Output: o}) | |
| 139 | - | } | |
| 140 | - | send(r.frames, ports.OutputFrame{Output: "sync"}) | |
| 141 | - | select { | |
| 142 | - | case set = <-r.scenes: | |
| 143 | - | return set, true | |
| 144 | - | default: | |
| 145 | - | return nil, false | |
| 146 | - | } | |
| 147 | - | } | |
| 148 | - | ||
| 149 | - | // settleAll moves the clock past every spring on the outputs and returns | |
| 150 | - | // the scene set that results (ok false: nothing was animating). | |
| 100 | + | // settleAll moves the clock past every spring, flips the outputs and | |
| 101 | + | // returns the scene set that results (ok false: nothing was animating). | |
| 151 | 102 | func (r *swipeRig) settleAll(t *testing.T, outputs ...string) ([]ports.Scene, bool) { | |
| 152 | 103 | t.Helper() | |
| 153 | - | return r.flipAll(t, 5*time.Second, outputs...) | |
| 104 | + | return r.clk.settle(t, r.frames, r.scenes, outputs...) | |
| 154 | 105 | } | |
| 155 | 106 | ||
| 156 | 107 | // flick sends a quick swipe of n updates of (dx, dy) and lifts the | |
| @@ -2,14 +2,11 @@ package core_test | |||||
| 2 | 2 | ||||
| 3 | import ( | 3 | import ( | ||
| 4 | "context" | 4 | "context" | ||
| 5 | - | "sync" | |||
| 6 | "testing" | 5 | "testing" | ||
| 7 | "time" | 6 | "time" | ||
| 8 | 7 | ||||
| 9 | "github.com/bnema/neferwl/internal/core" | 8 | "github.com/bnema/neferwl/internal/core" | ||
| 10 | - | portsmocks "github.com/bnema/neferwl/internal/mocks/ports" | |||
| 11 | "github.com/bnema/neferwl/internal/ports" | 9 | "github.com/bnema/neferwl/internal/ports" | ||
| 12 | - | "github.com/stretchr/testify/mock" | |||
| 13 | ) | 10 | ) | ||
| 14 | 11 | ||||
| 15 | // swipeRig runs core on one 800x600 output with no gaps or border, a | 12 | // swipeRig runs core on one 800x600 output with no gaps or border, a | ||
| @@ -17,8 +14,7 @@ import ( | |||||
| 17 | type swipeRig struct { | 14 | type swipeRig struct { | ||
| 18 | *multiRig | 15 | *multiRig | ||
| 19 | frames chan ports.OutputFrame | 16 | frames chan ports.OutputFrame | ||
| 20 | - | mu sync.Mutex | 17 | + | clk *stepClock |
| 21 | - | now time.Time | |||
| 22 | // at is the device time of the next swipe event. | 18 | // at is the device time of the next swipe event. | ||
| 23 | at time.Duration | 19 | at time.Duration | ||
| 24 | // outs are the plugged outputs, for landed. | 20 | // outs are the plugged outputs, for landed. | ||
| @@ -42,26 +38,13 @@ func startSwipeOn(t *testing.T, edit func(*ports.Config), outs ...ports.OutputIn | |||||
| 42 | if edit != nil { | 38 | if edit != nil { | ||
| 43 | edit(&cfg) | 39 | edit(&cfg) | ||
| 44 | } | 40 | } | ||
| 45 | - | r := &swipeRig{frames: make(chan ports.OutputFrame), now: time.Unix(100, 0)} | 41 | + | r := &swipeRig{frames: make(chan ports.OutputFrame), clk: newStepClock(t)} |
| 46 | r.multiRig = &multiRig{ | 42 | r.multiRig = &multiRig{ | ||
| 47 | client: make(chan ports.ClientEvent, 16), input: make(chan ports.InputEvent, 64), | 43 | client: make(chan ports.ClientEvent, 16), input: make(chan ports.InputEvent, 64), | ||
| 48 | output: make(chan ports.OutputEvent, 4), reload: make(chan ports.ConfigChanged, 4), | 44 | output: make(chan ports.OutputEvent, 4), reload: make(chan ports.ConfigChanged, 4), | ||
| 49 | commands: make(chan ports.ClientCommand, 1024), scenes: make(chan []ports.Scene, 1), spawn: make(chan ports.SpawnRequest, 16), state: make(chan ports.State, 1), cfg: cfg, | 45 | commands: make(chan ports.ClientCommand, 1024), scenes: make(chan []ports.Scene, 1), spawn: make(chan ports.SpawnRequest, 16), state: make(chan ports.State, 1), cfg: cfg, | ||
| 50 | } | 46 | } | ||
| 51 | - | clock := portsmocks.NewMockClock(t) | 47 | + | c, err := core.New(cfg, core.Channels{Client: r.client, Input: r.input, Output: r.output, Config: r.reload, Commands: r.commands, Scenes: r.scenes, Spawn: r.spawn, State: r.state, Clock: r.clk.clock, Frames: r.frames}) |
| 52 | - | clock.EXPECT().Now().RunAndReturn(func() time.Time { | |||
| 53 | - | r.mu.Lock() | |||
| 54 | - | defer r.mu.Unlock() | |||
| 55 | - | return r.now | |||
| 56 | - | }).Maybe() | |||
| 57 | - | // The fallback timer never fires: frames come from the test. One timer | |||
| 58 | - | // built before core runs: a mock created by core while the test ends | |||
| 59 | - | // would race its own cleanup check. | |||
| 60 | - | timer := portsmocks.NewMockTimer(t) | |||
| 61 | - | timer.EXPECT().C().Return(make(chan time.Time)).Maybe() | |||
| 62 | - | timer.EXPECT().Stop().Return(true).Maybe() | |||
| 63 | - | clock.EXPECT().NewTimer(mock.Anything).Return(timer).Maybe() | |||
| 64 | - | c, err := core.New(cfg, core.Channels{Client: r.client, Input: r.input, Output: r.output, Config: r.reload, Commands: r.commands, Scenes: r.scenes, Spawn: r.spawn, State: r.state, Clock: clock, Frames: r.frames}) | |||
| 65 | if err != nil { | 48 | if err != nil { | ||
| 66 | t.Fatal(err) | 49 | t.Fatal(err) | ||
| 67 | } | 50 | } | ||
| @@ -76,21 +59,8 @@ func startSwipeOn(t *testing.T, edit func(*ports.Config), outs ...ports.OutputIn | |||||
| 76 | return r | 59 | return r | ||
| 77 | } | 60 | } | ||
| 78 | 61 | ||||
| 79 | - | // landed is the scene set once every spring an action started on any output | |||
| 80 | - | // landed: the set itself when nothing animates (animations off, or an | |||
| 81 | - | // action that moves nothing). | |||
| 82 | - | func (r *swipeRig) landed(t *testing.T, set []ports.Scene) []ports.Scene { | |||
| 83 | - | t.Helper() | |||
| 84 | - | if landed, ok := r.settleAll(t, r.outs...); ok { | |||
| 85 | - | return landed | |||
| 86 | - | } | |||
| 87 | - | return set | |||
| 88 | - | } | |||
| 89 | - | ||||
| 90 | func (r *swipeRig) advance(d time.Duration) { | 62 | func (r *swipeRig) advance(d time.Duration) { | ||
| 91 | - | r.mu.Lock() | 63 | + | r.clk.advance(d) |
| 92 | - | r.now = r.now.Add(d) | |||
| 93 | - | r.mu.Unlock() | |||
| 94 | } | 64 | } | ||
| 95 | 65 | ||||
| 96 | // begin starts a swipe; nothing moves until it picks an axis. | 66 | // begin starts a swipe; nothing moves until it picks an axis. | ||
| @@ -127,30 +97,11 @@ func (r *swipeRig) settle(t *testing.T) ports.Scene { | |||||
| 127 | return r.frame(t, 5*time.Second) | 97 | return r.frame(t, 5*time.Second) | ||
| 128 | } | 98 | } | ||
| 129 | 99 | ||||
| 130 | - | // flipAll moves the clock by d, sends a page flip for each output and | 100 | + | // settleAll moves the clock past every spring, flips the outputs and |
| 131 | - | // returns the latest scene set they published (ok false: none, nothing | 101 | + | // returns the scene set that results (ok false: nothing was animating). |
| 132 | - | // animated). The last flip names no output: core takes it only after it | |||
| 133 | - | // handled the previous ones, so the scene channel then holds their result. | |||
| 134 | - | func (r *swipeRig) flipAll(t *testing.T, d time.Duration, outputs ...string) (set []ports.Scene, ok bool) { | |||
| 135 | - | t.Helper() | |||
| 136 | - | r.advance(d) | |||
| 137 | - | for _, o := range outputs { | |||
| 138 | - | send(r.frames, ports.OutputFrame{Output: o}) | |||
| 139 | - | } | |||
| 140 | - | send(r.frames, ports.OutputFrame{Output: "sync"}) | |||
| 141 | - | select { | |||
| 142 | - | case set = <-r.scenes: | |||
| 143 | - | return set, true | |||
| 144 | - | default: | |||
| 145 | - | return nil, false | |||
| 146 | - | } | |||
| 147 | - | } | |||
| 148 | - | ||||
| 149 | - | // settleAll moves the clock past every spring on the outputs and returns | |||
| 150 | - | // the scene set that results (ok false: nothing was animating). | |||
| 151 | func (r *swipeRig) settleAll(t *testing.T, outputs ...string) ([]ports.Scene, bool) { | 102 | func (r *swipeRig) settleAll(t *testing.T, outputs ...string) ([]ports.Scene, bool) { | ||
| 152 | t.Helper() | 103 | t.Helper() | ||
| 153 | - | return r.flipAll(t, 5*time.Second, outputs...) | 104 | + | return r.clk.settle(t, r.frames, r.scenes, outputs...) |
| 154 | } | 105 | } | ||
| 155 | 106 | ||||
| 156 | // flick sends a quick swipe of n updates of (dx, dy) and lifts the | 107 | // flick sends a quick swipe of n updates of (dx, dy) and lifts the | ||
M internal/core/workspace_size_test.go +21 -14
| @@ -8,19 +8,10 @@ import ( | |||
| 8 | 8 | ||
| 9 | 9 | var ultrawide = ports.OutputInfo{Name: "DP-3", Make: "Acme", Model: "W", Serial: "3", Width: 3440, Height: 1440} | |
| 10 | 10 | ||
| 11 | - | // sizedRig runs an ultrawide with the named workspace "focus" (1920x1080) | |
| 12 | - | // bound to Alt+w, and shows it. | |
| 13 | - | func sizedRig(t *testing.T, overflow string, edit func(*ports.Config)) (*multiRig, ports.Scene) { | |
| 14 | - | t.Helper() | |
| 15 | - | r, s := sizedLanding(t, false, overflow, edit) | |
| 16 | - | return r.multiRig, s | |
| 17 | - | } | |
| 18 | - | ||
| 19 | - | // sizedLanding is sizedRig with animations on or off; the scene it returns | |
| 20 | - | // and every one of its keyLanded and mapLanded are settled. | |
| 21 | - | func sizedLanding(t *testing.T, animated bool, overflow string, edit func(*ports.Config)) (*landRig, ports.Scene) { | |
| 22 | - | t.Helper() | |
| 23 | - | r := startLanding(t, animated, func(c *ports.Config) { | |
| 11 | + | // sizedConfig is the config of the sized-workspace rigs: gaps off, two | |
| 12 | + | // columns, the named workspace "focus" (1920x1080) bound to Alt+w. | |
| 13 | + | func sizedConfig(overflow string, edit func(*ports.Config)) func(*ports.Config) { | |
| 14 | + | return func(c *ports.Config) { | |
| 24 | 15 | c.Layout.Gaps = 0 | |
| 25 | 16 | c.Layout.MaxColumns = 2 | |
| 26 | 17 | c.Workspaces = []ports.WorkspaceConfig{{Name: "focus", Size: [2]int{1920, 1080}, LayoutRules: ports.LayoutRules{Overflow: overflow}}} | |
| @@ -28,7 +19,23 @@ func sizedLanding(t *testing.T, animated bool, overflow string, edit func(*ports | |||
| 28 | 19 | if edit != nil { | |
| 29 | 20 | edit(c) | |
| 30 | 21 | } | |
| 31 | - | }, ultrawide) | |
| 22 | + | } | |
| 23 | + | } | |
| 24 | + | ||
| 25 | + | // sizedRig runs an ultrawide with the named workspace "focus" (1920x1080) | |
| 26 | + | // bound to Alt+w, and shows it. The animations setting is the default's | |
| 27 | + | // unless edit changes it. | |
| 28 | + | func sizedRig(t *testing.T, overflow string, edit func(*ports.Config)) (*multiRig, ports.Scene) { | |
| 29 | + | t.Helper() | |
| 30 | + | r := startMulti(t, sizedConfig(overflow, edit), ultrawide) | |
| 31 | + | return r, r.key(t, "w", ports.ModAlt)[0] | |
| 32 | + | } | |
| 33 | + | ||
| 34 | + | // sizedLanding is sizedRig with animations forced on or off; the scene it | |
| 35 | + | // returns and every keyLanded and mapLanded of the rig are settled. | |
| 36 | + | func sizedLanding(t *testing.T, animated bool, overflow string, edit func(*ports.Config)) (*landRig, ports.Scene) { | |
| 37 | + | t.Helper() | |
| 38 | + | r := startLanding(t, animated, sizedConfig(overflow, edit), ultrawide) | |
| 32 | 39 | return r, r.keyLanded(t, "w", ports.ModAlt)[0] | |
| 33 | 40 | } | |
| 34 | 41 | ||
| @@ -8,19 +8,10 @@ import ( | |||||
| 8 | 8 | ||||
| 9 | var ultrawide = ports.OutputInfo{Name: "DP-3", Make: "Acme", Model: "W", Serial: "3", Width: 3440, Height: 1440} | 9 | var ultrawide = ports.OutputInfo{Name: "DP-3", Make: "Acme", Model: "W", Serial: "3", Width: 3440, Height: 1440} | ||
| 10 | 10 | ||||
| 11 | - | // sizedRig runs an ultrawide with the named workspace "focus" (1920x1080) | 11 | + | // sizedConfig is the config of the sized-workspace rigs: gaps off, two |
| 12 | - | // bound to Alt+w, and shows it. | 12 | + | // columns, the named workspace "focus" (1920x1080) bound to Alt+w. |
| 13 | - | func sizedRig(t *testing.T, overflow string, edit func(*ports.Config)) (*multiRig, ports.Scene) { | 13 | + | func sizedConfig(overflow string, edit func(*ports.Config)) func(*ports.Config) { |
| 14 | - | t.Helper() | 14 | + | return func(c *ports.Config) { |
| 15 | - | r, s := sizedLanding(t, false, overflow, edit) | |||
| 16 | - | return r.multiRig, s | |||
| 17 | - | } | |||
| 18 | - | ||||
| 19 | - | // sizedLanding is sizedRig with animations on or off; the scene it returns | |||
| 20 | - | // and every one of its keyLanded and mapLanded are settled. | |||
| 21 | - | func sizedLanding(t *testing.T, animated bool, overflow string, edit func(*ports.Config)) (*landRig, ports.Scene) { | |||
| 22 | - | t.Helper() | |||
| 23 | - | r := startLanding(t, animated, func(c *ports.Config) { | |||
| 24 | c.Layout.Gaps = 0 | 15 | c.Layout.Gaps = 0 | ||
| 25 | c.Layout.MaxColumns = 2 | 16 | c.Layout.MaxColumns = 2 | ||
| 26 | c.Workspaces = []ports.WorkspaceConfig{{Name: "focus", Size: [2]int{1920, 1080}, LayoutRules: ports.LayoutRules{Overflow: overflow}}} | 17 | c.Workspaces = []ports.WorkspaceConfig{{Name: "focus", Size: [2]int{1920, 1080}, LayoutRules: ports.LayoutRules{Overflow: overflow}}} | ||
| @@ -28,7 +19,23 @@ func sizedLanding(t *testing.T, animated bool, overflow string, edit func(*ports | |||||
| 28 | if edit != nil { | 19 | if edit != nil { | ||
| 29 | edit(c) | 20 | edit(c) | ||
| 30 | } | 21 | } | ||
| 31 | - | }, ultrawide) | 22 | + | } |
| 23 | + | } | |||
| 24 | + | ||||
| 25 | + | // sizedRig runs an ultrawide with the named workspace "focus" (1920x1080) | |||
| 26 | + | // bound to Alt+w, and shows it. The animations setting is the default's | |||
| 27 | + | // unless edit changes it. | |||
| 28 | + | func sizedRig(t *testing.T, overflow string, edit func(*ports.Config)) (*multiRig, ports.Scene) { | |||
| 29 | + | t.Helper() | |||
| 30 | + | r := startMulti(t, sizedConfig(overflow, edit), ultrawide) | |||
| 31 | + | return r, r.key(t, "w", ports.ModAlt)[0] | |||
| 32 | + | } | |||
| 33 | + | ||||
| 34 | + | // sizedLanding is sizedRig with animations forced on or off; the scene it | |||
| 35 | + | // returns and every keyLanded and mapLanded of the rig are settled. | |||
| 36 | + | func sizedLanding(t *testing.T, animated bool, overflow string, edit func(*ports.Config)) (*landRig, ports.Scene) { | |||
| 37 | + | t.Helper() | |||
| 38 | + | r := startLanding(t, animated, sizedConfig(overflow, edit), ultrawide) | |||
| 32 | return r, r.keyLanded(t, "w", ports.ModAlt)[0] | 39 | return r, r.keyLanded(t, "w", ports.ModAlt)[0] | ||
| 33 | } | 40 | } | ||
| 34 | 41 | ||||