test(core): deterministic mid-swipe overview test, shared transition helpers, quitting headless scripts
TestOverviewOpenedMidSwipeDropsIt receives the lift's own scene and then settles on the stepped clock instead of polling real time. settledOf, settleShown, configuresOf and drainConfigures live in one file (settledPlacement was a copy of settledOf). The headless click-focus variants end their script with the quit bind, so the run stops at ~2.6 s instead of the 5 s timeout.
- commit
- be48d93f5eddc32181280474e5e9f2ea9104c3be
- author
- brice · · 2026-10-05 02:08 UTC
- parent
- 1d8bbbe
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
7 files · +91 -86
M internal/app/app_test.go +3 -1
| @@ -268,7 +268,9 @@ func headlessPointerClickFocus(t *testing.T, animated bool) { | |||
| 268 | 268 | var halt sync.Once | |
| 269 | 269 | stopCollector := func() { halt.Do(func() { close(stop); <-collected }) } | |
| 270 | 270 | t.Cleanup(stopCollector) | |
| 271 | - | err := Run(context.Background(), Options{Backend: "headless", NoXwayland: true, Config: cfg, Script: io.NopCloser(strings.NewReader("sleep 1s\nkey Super+Return\nsleep 1s\nmove 600 300\nclick\nsleep 500ms\n")), Timeout: 5 * time.Second, testScenes: scenes}) | |
| 271 | + | // The script ends with the default quit bind: the run stops when it | |
| 272 | + | // is done, the timeout is only a backstop. | |
| 273 | + | err := Run(context.Background(), Options{Backend: "headless", NoXwayland: true, Config: cfg, Script: io.NopCloser(strings.NewReader("sleep 1s\nkey Super+Return\nsleep 1s\nmove 600 300\nclick\nsleep 500ms\nkey Ctrl+Alt+BackSpace\n")), Timeout: 5 * time.Second, testScenes: scenes}) | |
| 272 | 274 | if err != nil { | |
| 273 | 275 | if strings.Contains(strings.ToLower(err.Error()), "vulkan") { | |
| 274 | 276 | t.Skipf("Vulkan unavailable: %v", err) | |
| @@ -268,7 +268,9 @@ func headlessPointerClickFocus(t *testing.T, animated bool) { | |||||
| 268 | var halt sync.Once | 268 | var halt sync.Once | ||
| 269 | stopCollector := func() { halt.Do(func() { close(stop); <-collected }) } | 269 | stopCollector := func() { halt.Do(func() { close(stop); <-collected }) } | ||
| 270 | t.Cleanup(stopCollector) | 270 | t.Cleanup(stopCollector) | ||
| 271 | - | err := Run(context.Background(), Options{Backend: "headless", NoXwayland: true, Config: cfg, Script: io.NopCloser(strings.NewReader("sleep 1s\nkey Super+Return\nsleep 1s\nmove 600 300\nclick\nsleep 500ms\n")), Timeout: 5 * time.Second, testScenes: scenes}) | 271 | + | // The script ends with the default quit bind: the run stops when it |
| 272 | + | // is done, the timeout is only a backstop. | |||
| 273 | + | err := Run(context.Background(), Options{Backend: "headless", NoXwayland: true, Config: cfg, Script: io.NopCloser(strings.NewReader("sleep 1s\nkey Super+Return\nsleep 1s\nmove 600 300\nclick\nsleep 500ms\nkey Ctrl+Alt+BackSpace\n")), Timeout: 5 * time.Second, testScenes: scenes}) | |||
| 272 | if err != nil { | 274 | if err != nil { | ||
| 273 | if strings.Contains(strings.ToLower(err.Error()), "vulkan") { | 275 | if strings.Contains(strings.ToLower(err.Error()), "vulkan") { | ||
| 274 | t.Skipf("Vulkan unavailable: %v", err) | 276 | t.Skipf("Vulkan unavailable: %v", err) | ||
M internal/core/swipe_test.go +23 -22
| @@ -772,38 +772,39 @@ func TestOverviewOpenedMidSwipeDropsIt(t *testing.T) { | |||
| 772 | 772 | r.move(t, -40, 0) | |
| 773 | 773 | r.move(t, -40, 0) | |
| 774 | 774 | r.key(t, "o", ports.ModAlt) | |
| 775 | + | // s is the latest scene. | |
| 776 | + | var s ports.Scene | |
| 775 | 777 | if closeFirst { | |
| 776 | - | r.key(t, "Escape", 0) | |
| 778 | + | s = r.key(t, "Escape", 0)[0] | |
| 777 | 779 | } | |
| 778 | 780 | for range 20 { | |
| 779 | 781 | r.at += 8 * time.Millisecond | |
| 780 | 782 | r.input <- ports.SwipeUpdate{DX: -80, Time: r.at} | |
| 781 | 783 | } | |
| 784 | + | // The updates publish nothing (the overview gesture, or a dropped | |
| 785 | + | // swipe); the lift always publishes, so its scene is received | |
| 786 | + | // before the next event: core is idle when the escape and the | |
| 787 | + | // flips arrive, each waits for its own scene. | |
| 782 | 788 | r.input <- ports.SwipeEnd{Time: r.at} | |
| 789 | + | s = scene(t, r.scenes) | |
| 783 | 790 | if !closeFirst { | |
| 784 | - | r.input <- ports.KeyEvent{Keysym: "Escape", Pressed: true} | |
| 785 | - | } | |
| 786 | - | // Past any spring: the view shows where it settled. The escape's | |
| 787 | - | // own springs (the cards zooming back) start when it is handled, | |
| 788 | - | // which may follow a first flip: flip until the scene is settled. | |
| 789 | - | var s ports.Scene | |
| 790 | - | for deadline := time.Now().Add(5 * time.Second); ; { | |
| 791 | - | r.advance(5 * time.Second) | |
| 792 | - | r.frames <- ports.OutputFrame{Output: wide.Name} | |
| 793 | - | select { | |
| 794 | - | case set := <-r.scenes: | |
| 795 | - | s = set[0] | |
| 796 | - | case <-time.After(20 * time.Millisecond): | |
| 797 | - | } | |
| 798 | - | if len(s.Windows) > 0 && !slices.ContainsFunc(s.Windows, func(w ports.SceneWindow) bool { | |
| 799 | - | // Neither a card nor a window still zooming back. | |
| 800 | - | return w.Preview > 0 || w.Zoom > 0 || w.Fade > 0 | |
| 801 | - | }) { | |
| 791 | + | s = r.key(t, "Escape", 0)[0] | |
| 792 | + | } | |
| 793 | + | // Past any spring: the escape's own springs (the cards zooming | |
| 794 | + | // back) may chain after the swipe's landing, so settle until | |
| 795 | + | // none runs. | |
| 796 | + | for range 4 { | |
| 797 | + | set, ok := r.settleAll(t, wide.Name) | |
| 798 | + | if !ok { | |
| 802 | 799 | break | |
| 803 | 800 | } | |
| 804 | - | if time.Now().After(deadline) { | |
| 805 | - | t.Fatalf("closeFirst=%v: the scene never settled: %+v", closeFirst, s.Windows) | |
| 806 | - | } | |
| 801 | + | s = set[0] | |
| 802 | + | } | |
| 803 | + | if len(s.Windows) == 0 || slices.ContainsFunc(s.Windows, func(w ports.SceneWindow) bool { | |
| 804 | + | // Neither a card nor a window still zooming back. | |
| 805 | + | return w.Preview > 0 || w.Zoom > 0 || w.Fade > 0 | |
| 806 | + | }) { | |
| 807 | + | t.Fatalf("closeFirst=%v: the scene never settled: %+v", closeFirst, s.Windows) | |
| 807 | 808 | } | |
| 808 | 809 | for _, id := range []ports.WindowID{1, 2, 3} { | |
| 809 | 810 | before, _ := rectOf(settled, id) | |
| @@ -772,38 +772,39 @@ func TestOverviewOpenedMidSwipeDropsIt(t *testing.T) { | |||||
| 772 | r.move(t, -40, 0) | 772 | r.move(t, -40, 0) | ||
| 773 | r.move(t, -40, 0) | 773 | r.move(t, -40, 0) | ||
| 774 | r.key(t, "o", ports.ModAlt) | 774 | r.key(t, "o", ports.ModAlt) | ||
| 775 | + | // s is the latest scene. | |||
| 776 | + | var s ports.Scene | |||
| 775 | if closeFirst { | 777 | if closeFirst { | ||
| 776 | - | r.key(t, "Escape", 0) | 778 | + | s = r.key(t, "Escape", 0)[0] |
| 777 | } | 779 | } | ||
| 778 | for range 20 { | 780 | for range 20 { | ||
| 779 | r.at += 8 * time.Millisecond | 781 | r.at += 8 * time.Millisecond | ||
| 780 | r.input <- ports.SwipeUpdate{DX: -80, Time: r.at} | 782 | r.input <- ports.SwipeUpdate{DX: -80, Time: r.at} | ||
| 781 | } | 783 | } | ||
| 784 | + | // The updates publish nothing (the overview gesture, or a dropped | |||
| 785 | + | // swipe); the lift always publishes, so its scene is received | |||
| 786 | + | // before the next event: core is idle when the escape and the | |||
| 787 | + | // flips arrive, each waits for its own scene. | |||
| 782 | r.input <- ports.SwipeEnd{Time: r.at} | 788 | r.input <- ports.SwipeEnd{Time: r.at} | ||
| 789 | + | s = scene(t, r.scenes) | |||
| 783 | if !closeFirst { | 790 | if !closeFirst { | ||
| 784 | - | r.input <- ports.KeyEvent{Keysym: "Escape", Pressed: true} | 791 | + | s = r.key(t, "Escape", 0)[0] |
| 785 | - | } | 792 | + | } |
| 786 | - | // Past any spring: the view shows where it settled. The escape's | 793 | + | // Past any spring: the escape's own springs (the cards zooming |
| 787 | - | // own springs (the cards zooming back) start when it is handled, | 794 | + | // back) may chain after the swipe's landing, so settle until |
| 788 | - | // which may follow a first flip: flip until the scene is settled. | 795 | + | // none runs. |
| 789 | - | var s ports.Scene | 796 | + | for range 4 { |
| 790 | - | for deadline := time.Now().Add(5 * time.Second); ; { | 797 | + | set, ok := r.settleAll(t, wide.Name) |
| 791 | - | r.advance(5 * time.Second) | 798 | + | if !ok { |
| 792 | - | r.frames <- ports.OutputFrame{Output: wide.Name} | |||
| 793 | - | select { | |||
| 794 | - | case set := <-r.scenes: | |||
| 795 | - | s = set[0] | |||
| 796 | - | case <-time.After(20 * time.Millisecond): | |||
| 797 | - | } | |||
| 798 | - | if len(s.Windows) > 0 && !slices.ContainsFunc(s.Windows, func(w ports.SceneWindow) bool { | |||
| 799 | - | // Neither a card nor a window still zooming back. | |||
| 800 | - | return w.Preview > 0 || w.Zoom > 0 || w.Fade > 0 | |||
| 801 | - | }) { | |||
| 802 | break | 799 | break | ||
| 803 | } | 800 | } | ||
| 804 | - | if time.Now().After(deadline) { | 801 | + | s = set[0] |
| 805 | - | t.Fatalf("closeFirst=%v: the scene never settled: %+v", closeFirst, s.Windows) | 802 | + | } |
| 806 | - | } | 803 | + | if len(s.Windows) == 0 || slices.ContainsFunc(s.Windows, func(w ports.SceneWindow) bool { |
| 804 | + | // Neither a card nor a window still zooming back. | |||
| 805 | + | return w.Preview > 0 || w.Zoom > 0 || w.Fade > 0 | |||
| 806 | + | }) { | |||
| 807 | + | t.Fatalf("closeFirst=%v: the scene never settled: %+v", closeFirst, s.Windows) | |||
| 807 | } | 808 | } | ||
| 808 | for _, id := range []ports.WindowID{1, 2, 3} { | 809 | for _, id := range []ports.WindowID{1, 2, 3} { | ||
| 809 | before, _ := rectOf(settled, id) | 810 | before, _ := rectOf(settled, id) | ||
M internal/core/transition_chain_internal_test.go +0 -6
| @@ -10,12 +10,6 @@ import ( | |||
| 10 | 10 | "github.com/bnema/neferwl/internal/ports" | |
| 11 | 11 | ) | |
| 12 | 12 | ||
| 13 | - | // settleShown publishes so the screens' shown layouts are current. | |
| 14 | - | func settleShown(t *testing.T, c *Core) { | |
| 15 | - | t.Helper() | |
| 16 | - | indicatorScene(t, c) | |
| 17 | - | } | |
| 18 | - | ||
| 19 | 13 | // A bind that stops a spring while it acts (Monitor.Focus) must not lose its | |
| 20 | 14 | // speed: the transition that follows starts from what the snapshot recorded. | |
| 21 | 15 | func TestChainedActionsKeepVelocity(t *testing.T) { | |
| @@ -10,12 +10,6 @@ import ( | |||||
| 10 | "github.com/bnema/neferwl/internal/ports" | 10 | "github.com/bnema/neferwl/internal/ports" | ||
| 11 | ) | 11 | ) | ||
| 12 | 12 | ||||
| 13 | - | // settleShown publishes so the screens' shown layouts are current. | |||
| 14 | - | func settleShown(t *testing.T, c *Core) { | |||
| 15 | - | t.Helper() | |||
| 16 | - | indicatorScene(t, c) | |||
| 17 | - | } | |||
| 18 | - | ||||
| 19 | // A bind that stops a spring while it acts (Monitor.Focus) must not lose its | 13 | // A bind that stops a spring while it acts (Monitor.Focus) must not lose its | ||
| 20 | // speed: the transition that follows starts from what the snapshot recorded. | 14 | // speed: the transition that follows starts from what the snapshot recorded. | ||
| 21 | func TestChainedActionsKeepVelocity(t *testing.T) { | 15 | func TestChainedActionsKeepVelocity(t *testing.T) { | ||
M internal/core/transition_fade_internal_test.go +0 -15
| @@ -18,21 +18,6 @@ func fadeCore(t *testing.T) (*Core, *indicatorClock, chan ports.ClientCommand) { | |||
| 18 | 18 | return c, ic, cmds | |
| 19 | 19 | } | |
| 20 | 20 | ||
| 21 | - | // drainConfigures returns the IDs configured since the last drain. | |
| 22 | - | func drainConfigures(cmds chan ports.ClientCommand) []WindowID { | |
| 23 | - | var ids []WindowID | |
| 24 | - | for { | |
| 25 | - | select { | |
| 26 | - | case cmd := <-cmds: | |
| 27 | - | if v, ok := cmd.(ports.ConfigureWindow); ok { | |
| 28 | - | ids = append(ids, v.ID) | |
| 29 | - | } | |
| 30 | - | default: | |
| 31 | - | return ids | |
| 32 | - | } | |
| 33 | - | } | |
| 34 | - | } | |
| 35 | - | ||
| 36 | 21 | func sceneWindow(t *testing.T, s ports.Scene, id WindowID) ports.SceneWindow { | |
| 37 | 22 | t.Helper() | |
| 38 | 23 | i := slices.IndexFunc(s.Windows, func(w ports.SceneWindow) bool { return w.ID == id }) | |
| @@ -18,21 +18,6 @@ func fadeCore(t *testing.T) (*Core, *indicatorClock, chan ports.ClientCommand) { | |||||
| 18 | return c, ic, cmds | 18 | return c, ic, cmds | ||
| 19 | } | 19 | } | ||
| 20 | 20 | ||||
| 21 | - | // drainConfigures returns the IDs configured since the last drain. | |||
| 22 | - | func drainConfigures(cmds chan ports.ClientCommand) []WindowID { | |||
| 23 | - | var ids []WindowID | |||
| 24 | - | for { | |||
| 25 | - | select { | |||
| 26 | - | case cmd := <-cmds: | |||
| 27 | - | if v, ok := cmd.(ports.ConfigureWindow); ok { | |||
| 28 | - | ids = append(ids, v.ID) | |||
| 29 | - | } | |||
| 30 | - | default: | |||
| 31 | - | return ids | |||
| 32 | - | } | |||
| 33 | - | } | |||
| 34 | - | } | |||
| 35 | - | ||||
| 36 | func sceneWindow(t *testing.T, s ports.Scene, id WindowID) ports.SceneWindow { | 21 | func sceneWindow(t *testing.T, s ports.Scene, id WindowID) ports.SceneWindow { | ||
| 37 | t.Helper() | 22 | t.Helper() | ||
| 38 | i := slices.IndexFunc(s.Windows, func(w ports.SceneWindow) bool { return w.ID == id }) | 23 | i := slices.IndexFunc(s.Windows, func(w ports.SceneWindow) bool { return w.ID == id }) | ||
A internal/core/transition_helpers_internal_test.go +56 -0
| @@ -0,0 +1,56 @@ | |||
| 1 | + | package core | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "slices" | |
| 5 | + | "testing" | |
| 6 | + | ||
| 7 | + | "github.com/bnema/neferwl/internal/ports" | |
| 8 | + | ) | |
| 9 | + | ||
| 10 | + | // Helpers shared by the transition tests (transition_*_internal_test.go). | |
| 11 | + | ||
| 12 | + | // settleShown publishes so the screens' shown layouts are current. | |
| 13 | + | func settleShown(t *testing.T, c *Core) { | |
| 14 | + | t.Helper() | |
| 15 | + | indicatorScene(t, c) | |
| 16 | + | } | |
| 17 | + | ||
| 18 | + | // settledOf is the settled placement of window id on sc. | |
| 19 | + | func settledOf(t *testing.T, sc *screen, id WindowID) Placement { | |
| 20 | + | t.Helper() | |
| 21 | + | i := slices.IndexFunc(sc.settledLayout, func(p Placement) bool { return p.ID == id }) | |
| 22 | + | if i < 0 { | |
| 23 | + | t.Fatalf("window %d not in the settled layout %+v", id, sc.settledLayout) | |
| 24 | + | } | |
| 25 | + | return sc.settledLayout[i] | |
| 26 | + | } | |
| 27 | + | ||
| 28 | + | // configuresOf returns the configures sent since the last drain, by window. | |
| 29 | + | func configuresOf(cmds chan ports.ClientCommand) map[WindowID][]ports.ConfigureWindow { | |
| 30 | + | out := map[WindowID][]ports.ConfigureWindow{} | |
| 31 | + | for { | |
| 32 | + | select { | |
| 33 | + | case cmd := <-cmds: | |
| 34 | + | if v, ok := cmd.(ports.ConfigureWindow); ok { | |
| 35 | + | out[v.ID] = append(out[v.ID], v) | |
| 36 | + | } | |
| 37 | + | default: | |
| 38 | + | return out | |
| 39 | + | } | |
| 40 | + | } | |
| 41 | + | } | |
| 42 | + | ||
| 43 | + | // drainConfigures returns the IDs configured since the last drain, in order. | |
| 44 | + | func drainConfigures(cmds chan ports.ClientCommand) []WindowID { | |
| 45 | + | var ids []WindowID | |
| 46 | + | for { | |
| 47 | + | select { | |
| 48 | + | case cmd := <-cmds: | |
| 49 | + | if v, ok := cmd.(ports.ConfigureWindow); ok { | |
| 50 | + | ids = append(ids, v.ID) | |
| 51 | + | } | |
| 52 | + | default: | |
| 53 | + | return ids | |
| 54 | + | } | |
| 55 | + | } | |
| 56 | + | } | |
| @@ -0,0 +1,56 @@ | |||||
| 1 | + | package core | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "slices" | |||
| 5 | + | "testing" | |||
| 6 | + | ||||
| 7 | + | "github.com/bnema/neferwl/internal/ports" | |||
| 8 | + | ) | |||
| 9 | + | ||||
| 10 | + | // Helpers shared by the transition tests (transition_*_internal_test.go). | |||
| 11 | + | ||||
| 12 | + | // settleShown publishes so the screens' shown layouts are current. | |||
| 13 | + | func settleShown(t *testing.T, c *Core) { | |||
| 14 | + | t.Helper() | |||
| 15 | + | indicatorScene(t, c) | |||
| 16 | + | } | |||
| 17 | + | ||||
| 18 | + | // settledOf is the settled placement of window id on sc. | |||
| 19 | + | func settledOf(t *testing.T, sc *screen, id WindowID) Placement { | |||
| 20 | + | t.Helper() | |||
| 21 | + | i := slices.IndexFunc(sc.settledLayout, func(p Placement) bool { return p.ID == id }) | |||
| 22 | + | if i < 0 { | |||
| 23 | + | t.Fatalf("window %d not in the settled layout %+v", id, sc.settledLayout) | |||
| 24 | + | } | |||
| 25 | + | return sc.settledLayout[i] | |||
| 26 | + | } | |||
| 27 | + | ||||
| 28 | + | // configuresOf returns the configures sent since the last drain, by window. | |||
| 29 | + | func configuresOf(cmds chan ports.ClientCommand) map[WindowID][]ports.ConfigureWindow { | |||
| 30 | + | out := map[WindowID][]ports.ConfigureWindow{} | |||
| 31 | + | for { | |||
| 32 | + | select { | |||
| 33 | + | case cmd := <-cmds: | |||
| 34 | + | if v, ok := cmd.(ports.ConfigureWindow); ok { | |||
| 35 | + | out[v.ID] = append(out[v.ID], v) | |||
| 36 | + | } | |||
| 37 | + | default: | |||
| 38 | + | return out | |||
| 39 | + | } | |||
| 40 | + | } | |||
| 41 | + | } | |||
| 42 | + | ||||
| 43 | + | // drainConfigures returns the IDs configured since the last drain, in order. | |||
| 44 | + | func drainConfigures(cmds chan ports.ClientCommand) []WindowID { | |||
| 45 | + | var ids []WindowID | |||
| 46 | + | for { | |||
| 47 | + | select { | |||
| 48 | + | case cmd := <-cmds: | |||
| 49 | + | if v, ok := cmd.(ports.ConfigureWindow); ok { | |||
| 50 | + | ids = append(ids, v.ID) | |||
| 51 | + | } | |||
| 52 | + | default: | |||
| 53 | + | return ids | |||
| 54 | + | } | |||
| 55 | + | } | |||
| 56 | + | } | |||
M internal/core/transition_map_internal_test.go +0 -9
| @@ -9,15 +9,6 @@ import ( | |||
| 9 | 9 | "github.com/bnema/neferwl/internal/ports" | |
| 10 | 10 | ) | |
| 11 | 11 | ||
| 12 | - | func settledOf(t *testing.T, sc *screen, id WindowID) Placement { | |
| 13 | - | t.Helper() | |
| 14 | - | i := slices.IndexFunc(sc.settledLayout, func(p Placement) bool { return p.ID == id }) | |
| 15 | - | if i < 0 { | |
| 16 | - | t.Fatalf("window %d not in the settled layout %+v", id, sc.settledLayout) | |
| 17 | - | } | |
| 18 | - | return sc.settledLayout[i] | |
| 19 | - | } | |
| 20 | - | ||
| 21 | 12 | // A tiled map: the new window fades in from 90 % of its settled rect, its | |
| 22 | 13 | // neighbour slides from where it was, the client is configured once, to | |
| 23 | 14 | // the final size, and everything settles exactly. | |
| @@ -9,15 +9,6 @@ import ( | |||||
| 9 | "github.com/bnema/neferwl/internal/ports" | 9 | "github.com/bnema/neferwl/internal/ports" | ||
| 10 | ) | 10 | ) | ||
| 11 | 11 | ||||
| 12 | - | func settledOf(t *testing.T, sc *screen, id WindowID) Placement { | |||
| 13 | - | t.Helper() | |||
| 14 | - | i := slices.IndexFunc(sc.settledLayout, func(p Placement) bool { return p.ID == id }) | |||
| 15 | - | if i < 0 { | |||
| 16 | - | t.Fatalf("window %d not in the settled layout %+v", id, sc.settledLayout) | |||
| 17 | - | } | |||
| 18 | - | return sc.settledLayout[i] | |||
| 19 | - | } | |||
| 20 | - | ||||
| 21 | // A tiled map: the new window fades in from 90 % of its settled rect, its | 12 | // A tiled map: the new window fades in from 90 % of its settled rect, its | ||
| 22 | // neighbour slides from where it was, the client is configured once, to | 13 | // neighbour slides from where it was, the client is configured once, to | ||
| 23 | // the final size, and everything settles exactly. | 14 | // the final size, and everything settles exactly. | ||
M internal/core/transition_stash_internal_test.go +9 -33
| @@ -30,21 +30,6 @@ func stashRig(t *testing.T, on bool) (*Core, *indicatorClock, *screen, chan port | |||
| 30 | 30 | return c, ic, sc, cmds | |
| 31 | 31 | } | |
| 32 | 32 | ||
| 33 | - | // configuresOf drains the commands into the configures sent per window. | |
| 34 | - | func configuresOf(cmds chan ports.ClientCommand) map[WindowID][]ports.ConfigureWindow { | |
| 35 | - | out := map[WindowID][]ports.ConfigureWindow{} | |
| 36 | - | for { | |
| 37 | - | select { | |
| 38 | - | case cmd := <-cmds: | |
| 39 | - | if v, ok := cmd.(ports.ConfigureWindow); ok { | |
| 40 | - | out[v.ID] = append(out[v.ID], v) | |
| 41 | - | } | |
| 42 | - | default: | |
| 43 | - | return out | |
| 44 | - | } | |
| 45 | - | } | |
| 46 | - | } | |
| 47 | - | ||
| 48 | 33 | // act is a bind: snapshot, action, transition. | |
| 49 | 34 | func act(c *Core, ic *indicatorClock, a Action) { | |
| 50 | 35 | shots := c.snapshot(ic.now) | |
| @@ -52,15 +37,6 @@ func act(c *Core, ic *indicatorClock, a Action) { | |||
| 52 | 37 | c.transition(shots, ic.now) | |
| 53 | 38 | } | |
| 54 | 39 | ||
| 55 | - | func settledPlacement(t *testing.T, sc *screen, id WindowID) Placement { | |
| 56 | - | t.Helper() | |
| 57 | - | i := slices.IndexFunc(sc.settledLayout, func(p Placement) bool { return p.ID == id }) | |
| 58 | - | if i < 0 { | |
| 59 | - | t.Fatalf("window %d not in the settled layout", id) | |
| 60 | - | } | |
| 61 | - | return sc.settledLayout[i] | |
| 62 | - | } | |
| 63 | - | ||
| 64 | 40 | // Showing the stash fades in and grows every window it brings on screen; | |
| 65 | 41 | // the windows it keeps off screen stay out; the settled layout is the | |
| 66 | 42 | // final one at once, with no extra configure. | |
| @@ -79,7 +55,7 @@ func TestStashShowAppears(t *testing.T) { | |||
| 79 | 55 | act(c, ic, ActionToggleStashVisible) | |
| 80 | 56 | s := indicatorScene(t, c) | |
| 81 | 57 | for _, id := range []WindowID{2, 3} { | |
| 82 | - | p := settledPlacement(t, sc, id) | |
| 58 | + | p := settledOf(t, sc, id) | |
| 83 | 59 | w := sceneWindow(t, s, id) | |
| 84 | 60 | if w.Hidden || w.Fade != 1 || w.Rect != scaledRect(p.Rect, appearScale) || w.Zoom < 0.85 || w.Zoom > 0.95 { | |
| 85 | 61 | t.Fatalf("window %d first frame %+v, want invisible at 90 %% of %+v", id, w, p.Rect) | |
| @@ -94,7 +70,7 @@ func TestStashShowAppears(t *testing.T) { | |||
| 94 | 70 | if len(cfgs[id]) != 1 { | |
| 95 | 71 | t.Fatalf("window %d configured %d times, want once: %+v", id, len(cfgs[id]), cfgs[id]) | |
| 96 | 72 | } | |
| 97 | - | want := c.clientRect(settledPlacement(t, sc, id)) | |
| 73 | + | want := c.clientRect(settledOf(t, sc, id)) | |
| 98 | 74 | if v := cfgs[id][0]; !v.Visible || v.Width != want.W || v.Height != want.H { | |
| 99 | 75 | t.Fatalf("window %d configure %+v, want %dx%d (the settled client rect)", id, v, want.W, want.H) | |
| 100 | 76 | } | |
| @@ -109,7 +85,7 @@ func TestStashShowAppears(t *testing.T) { | |||
| 109 | 85 | } | |
| 110 | 86 | s = frame(t, c, ic, 5*time.Second) | |
| 111 | 87 | for _, id := range []WindowID{2, 3} { | |
| 112 | - | p := settledPlacement(t, sc, id) | |
| 88 | + | p := settledOf(t, sc, id) | |
| 113 | 89 | if w := sceneWindow(t, s, id); w.Fade != 0 || w.Zoom != 0 || w.Rect != p.Rect { | |
| 114 | 90 | t.Fatalf("window %d settled %+v, want %+v", id, w, p.Rect) | |
| 115 | 91 | } | |
| @@ -128,7 +104,7 @@ func TestStashHideLeaves(t *testing.T) { | |||
| 128 | 104 | c, ic, sc, cmds := stashRig(t, true) | |
| 129 | 105 | left := map[WindowID]Placement{} | |
| 130 | 106 | for _, id := range []WindowID{2, 3} { | |
| 131 | - | left[id] = settledPlacement(t, sc, id) | |
| 107 | + | left[id] = settledOf(t, sc, id) | |
| 132 | 108 | } | |
| 133 | 109 | act(c, ic, ActionToggleStashVisible) | |
| 134 | 110 | s := indicatorScene(t, c) | |
| @@ -266,7 +242,7 @@ func TestOverviewPileScalesFromStash(t *testing.T) { | |||
| 266 | 242 | s = indicatorScene(t, c) | |
| 267 | 243 | for _, id := range []WindowID{2, 3} { | |
| 268 | 244 | w := sceneWindow(t, s, id) | |
| 269 | - | p := settledPlacement(t, sc, id) | |
| 245 | + | p := settledOf(t, sc, id) | |
| 270 | 246 | if w.Hidden || w.Preview != overviewCardZoom || w.Rect != from[id] || w.Zoom != 1 { | |
| 271 | 247 | t.Fatalf("window %d first frame %+v, want its stash rect %+v at content scale 1 (card %+v)", id, w, from[id], p.Rect) | |
| 272 | 248 | } | |
| @@ -275,7 +251,7 @@ func TestOverviewPileScalesFromStash(t *testing.T) { | |||
| 275 | 251 | s = frame(t, c, ic, 40*time.Millisecond) | |
| 276 | 252 | for _, id := range []WindowID{2, 3} { | |
| 277 | 253 | w := sceneWindow(t, s, id) | |
| 278 | - | p := settledPlacement(t, sc, id) | |
| 254 | + | p := settledOf(t, sc, id) | |
| 279 | 255 | if !(w.Rect.W < from[id].W && w.Rect.W > p.Rect.W) || !(w.Zoom > overviewCardZoom && w.Zoom < 1) { | |
| 280 | 256 | t.Fatalf("window %d mid-way %+v, want between %+v and %+v", id, w, from[id], p.Rect) | |
| 281 | 257 | } | |
| @@ -284,7 +260,7 @@ func TestOverviewPileScalesFromStash(t *testing.T) { | |||
| 284 | 260 | s = frame(t, c, ic, 5*time.Second) | |
| 285 | 261 | for _, id := range []WindowID{2, 3} { | |
| 286 | 262 | w := sceneWindow(t, s, id) | |
| 287 | - | p := settledPlacement(t, sc, id) | |
| 263 | + | p := settledOf(t, sc, id) | |
| 288 | 264 | if w.Rect != p.Rect || w.Zoom != 0 || w.Fade != 0 || w.Preview != overviewCardZoom { | |
| 289 | 265 | t.Fatalf("window %d settled %+v, want the pile card %+v", id, w, p) | |
| 290 | 266 | } | |
| @@ -399,7 +375,7 @@ func TestStashShowDuringHideDoesNotJump(t *testing.T) { | |||
| 399 | 375 | t.Fatalf("fade %v then %v, want it to clear", w.Fade, n.Fade) | |
| 400 | 376 | } | |
| 401 | 377 | s = frame(t, c, ic, 5*time.Second) | |
| 402 | - | p := settledPlacement(t, sc, 2) | |
| 378 | + | p := settledOf(t, sc, 2) | |
| 403 | 379 | if w := sceneWindow(t, s, 2); w.Fade != 0 || w.Zoom != 0 || w.Rect != p.Rect || c.animating() { | |
| 404 | 380 | t.Fatalf("settled %+v, want %+v", w, p.Rect) | |
| 405 | 381 | } | |
| @@ -414,7 +390,7 @@ func TestStashNavigationLeavesFromSlide(t *testing.T) { | |||
| 414 | 390 | indicatorScene(t, c) | |
| 415 | 391 | s := frame(t, c, ic, 30*time.Millisecond) | |
| 416 | 392 | last := sceneWindow(t, s, 2) | |
| 417 | - | settled := settledPlacement(t, sc, 2) | |
| 393 | + | settled := settledOf(t, sc, 2) | |
| 418 | 394 | if last.Rect == settled.Rect { | |
| 419 | 395 | t.Fatal("setup: window 2 already at rest") | |
| 420 | 396 | } | |
| @@ -30,21 +30,6 @@ func stashRig(t *testing.T, on bool) (*Core, *indicatorClock, *screen, chan port | |||||
| 30 | return c, ic, sc, cmds | 30 | return c, ic, sc, cmds | ||
| 31 | } | 31 | } | ||
| 32 | 32 | ||||
| 33 | - | // configuresOf drains the commands into the configures sent per window. | |||
| 34 | - | func configuresOf(cmds chan ports.ClientCommand) map[WindowID][]ports.ConfigureWindow { | |||
| 35 | - | out := map[WindowID][]ports.ConfigureWindow{} | |||
| 36 | - | for { | |||
| 37 | - | select { | |||
| 38 | - | case cmd := <-cmds: | |||
| 39 | - | if v, ok := cmd.(ports.ConfigureWindow); ok { | |||
| 40 | - | out[v.ID] = append(out[v.ID], v) | |||
| 41 | - | } | |||
| 42 | - | default: | |||
| 43 | - | return out | |||
| 44 | - | } | |||
| 45 | - | } | |||
| 46 | - | } | |||
| 47 | - | ||||
| 48 | // act is a bind: snapshot, action, transition. | 33 | // act is a bind: snapshot, action, transition. | ||
| 49 | func act(c *Core, ic *indicatorClock, a Action) { | 34 | func act(c *Core, ic *indicatorClock, a Action) { | ||
| 50 | shots := c.snapshot(ic.now) | 35 | shots := c.snapshot(ic.now) | ||
| @@ -52,15 +37,6 @@ func act(c *Core, ic *indicatorClock, a Action) { | |||||
| 52 | c.transition(shots, ic.now) | 37 | c.transition(shots, ic.now) | ||
| 53 | } | 38 | } | ||
| 54 | 39 | ||||
| 55 | - | func settledPlacement(t *testing.T, sc *screen, id WindowID) Placement { | |||
| 56 | - | t.Helper() | |||
| 57 | - | i := slices.IndexFunc(sc.settledLayout, func(p Placement) bool { return p.ID == id }) | |||
| 58 | - | if i < 0 { | |||
| 59 | - | t.Fatalf("window %d not in the settled layout", id) | |||
| 60 | - | } | |||
| 61 | - | return sc.settledLayout[i] | |||
| 62 | - | } | |||
| 63 | - | ||||
| 64 | // Showing the stash fades in and grows every window it brings on screen; | 40 | // Showing the stash fades in and grows every window it brings on screen; | ||
| 65 | // the windows it keeps off screen stay out; the settled layout is the | 41 | // the windows it keeps off screen stay out; the settled layout is the | ||
| 66 | // final one at once, with no extra configure. | 42 | // final one at once, with no extra configure. | ||
| @@ -79,7 +55,7 @@ func TestStashShowAppears(t *testing.T) { | |||||
| 79 | act(c, ic, ActionToggleStashVisible) | 55 | act(c, ic, ActionToggleStashVisible) | ||
| 80 | s := indicatorScene(t, c) | 56 | s := indicatorScene(t, c) | ||
| 81 | for _, id := range []WindowID{2, 3} { | 57 | for _, id := range []WindowID{2, 3} { | ||
| 82 | - | p := settledPlacement(t, sc, id) | 58 | + | p := settledOf(t, sc, id) |
| 83 | w := sceneWindow(t, s, id) | 59 | w := sceneWindow(t, s, id) | ||
| 84 | if w.Hidden || w.Fade != 1 || w.Rect != scaledRect(p.Rect, appearScale) || w.Zoom < 0.85 || w.Zoom > 0.95 { | 60 | if w.Hidden || w.Fade != 1 || w.Rect != scaledRect(p.Rect, appearScale) || w.Zoom < 0.85 || w.Zoom > 0.95 { | ||
| 85 | t.Fatalf("window %d first frame %+v, want invisible at 90 %% of %+v", id, w, p.Rect) | 61 | t.Fatalf("window %d first frame %+v, want invisible at 90 %% of %+v", id, w, p.Rect) | ||
| @@ -94,7 +70,7 @@ func TestStashShowAppears(t *testing.T) { | |||||
| 94 | if len(cfgs[id]) != 1 { | 70 | if len(cfgs[id]) != 1 { | ||
| 95 | t.Fatalf("window %d configured %d times, want once: %+v", id, len(cfgs[id]), cfgs[id]) | 71 | t.Fatalf("window %d configured %d times, want once: %+v", id, len(cfgs[id]), cfgs[id]) | ||
| 96 | } | 72 | } | ||
| 97 | - | want := c.clientRect(settledPlacement(t, sc, id)) | 73 | + | want := c.clientRect(settledOf(t, sc, id)) |
| 98 | if v := cfgs[id][0]; !v.Visible || v.Width != want.W || v.Height != want.H { | 74 | if v := cfgs[id][0]; !v.Visible || v.Width != want.W || v.Height != want.H { | ||
| 99 | t.Fatalf("window %d configure %+v, want %dx%d (the settled client rect)", id, v, want.W, want.H) | 75 | t.Fatalf("window %d configure %+v, want %dx%d (the settled client rect)", id, v, want.W, want.H) | ||
| 100 | } | 76 | } | ||
| @@ -109,7 +85,7 @@ func TestStashShowAppears(t *testing.T) { | |||||
| 109 | } | 85 | } | ||
| 110 | s = frame(t, c, ic, 5*time.Second) | 86 | s = frame(t, c, ic, 5*time.Second) | ||
| 111 | for _, id := range []WindowID{2, 3} { | 87 | for _, id := range []WindowID{2, 3} { | ||
| 112 | - | p := settledPlacement(t, sc, id) | 88 | + | p := settledOf(t, sc, id) |
| 113 | if w := sceneWindow(t, s, id); w.Fade != 0 || w.Zoom != 0 || w.Rect != p.Rect { | 89 | if w := sceneWindow(t, s, id); w.Fade != 0 || w.Zoom != 0 || w.Rect != p.Rect { | ||
| 114 | t.Fatalf("window %d settled %+v, want %+v", id, w, p.Rect) | 90 | t.Fatalf("window %d settled %+v, want %+v", id, w, p.Rect) | ||
| 115 | } | 91 | } | ||
| @@ -128,7 +104,7 @@ func TestStashHideLeaves(t *testing.T) { | |||||
| 128 | c, ic, sc, cmds := stashRig(t, true) | 104 | c, ic, sc, cmds := stashRig(t, true) | ||
| 129 | left := map[WindowID]Placement{} | 105 | left := map[WindowID]Placement{} | ||
| 130 | for _, id := range []WindowID{2, 3} { | 106 | for _, id := range []WindowID{2, 3} { | ||
| 131 | - | left[id] = settledPlacement(t, sc, id) | 107 | + | left[id] = settledOf(t, sc, id) |
| 132 | } | 108 | } | ||
| 133 | act(c, ic, ActionToggleStashVisible) | 109 | act(c, ic, ActionToggleStashVisible) | ||
| 134 | s := indicatorScene(t, c) | 110 | s := indicatorScene(t, c) | ||
| @@ -266,7 +242,7 @@ func TestOverviewPileScalesFromStash(t *testing.T) { | |||||
| 266 | s = indicatorScene(t, c) | 242 | s = indicatorScene(t, c) | ||
| 267 | for _, id := range []WindowID{2, 3} { | 243 | for _, id := range []WindowID{2, 3} { | ||
| 268 | w := sceneWindow(t, s, id) | 244 | w := sceneWindow(t, s, id) | ||
| 269 | - | p := settledPlacement(t, sc, id) | 245 | + | p := settledOf(t, sc, id) |
| 270 | if w.Hidden || w.Preview != overviewCardZoom || w.Rect != from[id] || w.Zoom != 1 { | 246 | if w.Hidden || w.Preview != overviewCardZoom || w.Rect != from[id] || w.Zoom != 1 { | ||
| 271 | t.Fatalf("window %d first frame %+v, want its stash rect %+v at content scale 1 (card %+v)", id, w, from[id], p.Rect) | 247 | t.Fatalf("window %d first frame %+v, want its stash rect %+v at content scale 1 (card %+v)", id, w, from[id], p.Rect) | ||
| 272 | } | 248 | } | ||
| @@ -275,7 +251,7 @@ func TestOverviewPileScalesFromStash(t *testing.T) { | |||||
| 275 | s = frame(t, c, ic, 40*time.Millisecond) | 251 | s = frame(t, c, ic, 40*time.Millisecond) | ||
| 276 | for _, id := range []WindowID{2, 3} { | 252 | for _, id := range []WindowID{2, 3} { | ||
| 277 | w := sceneWindow(t, s, id) | 253 | w := sceneWindow(t, s, id) | ||
| 278 | - | p := settledPlacement(t, sc, id) | 254 | + | p := settledOf(t, sc, id) |
| 279 | if !(w.Rect.W < from[id].W && w.Rect.W > p.Rect.W) || !(w.Zoom > overviewCardZoom && w.Zoom < 1) { | 255 | if !(w.Rect.W < from[id].W && w.Rect.W > p.Rect.W) || !(w.Zoom > overviewCardZoom && w.Zoom < 1) { | ||
| 280 | t.Fatalf("window %d mid-way %+v, want between %+v and %+v", id, w, from[id], p.Rect) | 256 | t.Fatalf("window %d mid-way %+v, want between %+v and %+v", id, w, from[id], p.Rect) | ||
| 281 | } | 257 | } | ||
| @@ -284,7 +260,7 @@ func TestOverviewPileScalesFromStash(t *testing.T) { | |||||
| 284 | s = frame(t, c, ic, 5*time.Second) | 260 | s = frame(t, c, ic, 5*time.Second) | ||
| 285 | for _, id := range []WindowID{2, 3} { | 261 | for _, id := range []WindowID{2, 3} { | ||
| 286 | w := sceneWindow(t, s, id) | 262 | w := sceneWindow(t, s, id) | ||
| 287 | - | p := settledPlacement(t, sc, id) | 263 | + | p := settledOf(t, sc, id) |
| 288 | if w.Rect != p.Rect || w.Zoom != 0 || w.Fade != 0 || w.Preview != overviewCardZoom { | 264 | if w.Rect != p.Rect || w.Zoom != 0 || w.Fade != 0 || w.Preview != overviewCardZoom { | ||
| 289 | t.Fatalf("window %d settled %+v, want the pile card %+v", id, w, p) | 265 | t.Fatalf("window %d settled %+v, want the pile card %+v", id, w, p) | ||
| 290 | } | 266 | } | ||
| @@ -399,7 +375,7 @@ func TestStashShowDuringHideDoesNotJump(t *testing.T) { | |||||
| 399 | t.Fatalf("fade %v then %v, want it to clear", w.Fade, n.Fade) | 375 | t.Fatalf("fade %v then %v, want it to clear", w.Fade, n.Fade) | ||
| 400 | } | 376 | } | ||
| 401 | s = frame(t, c, ic, 5*time.Second) | 377 | s = frame(t, c, ic, 5*time.Second) | ||
| 402 | - | p := settledPlacement(t, sc, 2) | 378 | + | p := settledOf(t, sc, 2) |
| 403 | if w := sceneWindow(t, s, 2); w.Fade != 0 || w.Zoom != 0 || w.Rect != p.Rect || c.animating() { | 379 | if w := sceneWindow(t, s, 2); w.Fade != 0 || w.Zoom != 0 || w.Rect != p.Rect || c.animating() { | ||
| 404 | t.Fatalf("settled %+v, want %+v", w, p.Rect) | 380 | t.Fatalf("settled %+v, want %+v", w, p.Rect) | ||
| 405 | } | 381 | } | ||
| @@ -414,7 +390,7 @@ func TestStashNavigationLeavesFromSlide(t *testing.T) { | |||||
| 414 | indicatorScene(t, c) | 390 | indicatorScene(t, c) | ||
| 415 | s := frame(t, c, ic, 30*time.Millisecond) | 391 | s := frame(t, c, ic, 30*time.Millisecond) | ||
| 416 | last := sceneWindow(t, s, 2) | 392 | last := sceneWindow(t, s, 2) | ||
| 417 | - | settled := settledPlacement(t, sc, 2) | 393 | + | settled := settledOf(t, sc, 2) |
| 418 | if last.Rect == settled.Rect { | 394 | if last.Rect == settled.Rect { | ||
| 419 | t.Fatal("setup: window 2 already at rest") | 395 | t.Fatal("setup: window 2 already at rest") | ||
| 420 | } | 396 | } | ||