test(core): drive the hidden grab popup test through Run and cover fullscreen focus and moves
- commit
- 80d95c9c58ae63f1d5699503f546befeb39bbd1b
- author
- brice · · 2026-09-28 14:31 UTC
- committer
- brice · · 2026-09-28 14:44 UTC
- parent
- 10fab51
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
4 files · +69 -28
M internal/core/floating_test.go +18 -0
| @@ -122,6 +122,24 @@ func TestFullscreenPinsFocus(t *testing.T) { | |||
| 122 | 122 | if e := w.Apply(ActionCloseWindow); e.Close != 3 { | |
| 123 | 123 | t.Fatalf("close %d, want the fullscreen window", e.Close) | |
| 124 | 124 | } | |
| 125 | + | // A fixed-overflow fullscreen tile pins the focus too. | |
| 126 | + | f := workspace() | |
| 127 | + | f.Overflow = OverflowFixed | |
| 128 | + | f.AddWindow(1) | |
| 129 | + | f.AddWindow(2) | |
| 130 | + | f.SetFullscreen(2, true) | |
| 131 | + | if f.FocusColumn(-1); f.Focus != 1 { | |
| 132 | + | t.Fatalf("fixed: focus moved to column %d", f.Focus) | |
| 133 | + | } | |
| 134 | + | // Scroll overflow: moving off a fullscreen column still works; it no | |
| 135 | + | // longer covers. | |
| 136 | + | s := workspace() | |
| 137 | + | s.AddWindow(1) | |
| 138 | + | s.AddWindow(2) | |
| 139 | + | s.SetFullscreen(2, true) | |
| 140 | + | if s.FocusColumn(-1); s.Focus != 0 || s.cover() != 0 { | |
| 141 | + | t.Fatalf("scroll: focus %d cover %d", s.Focus, s.cover()) | |
| 142 | + | } | |
| 125 | 143 | } | |
| 126 | 144 | ||
| 127 | 145 | // A fullscreen window does not float until it leaves fullscreen. | |
| @@ -122,6 +122,24 @@ func TestFullscreenPinsFocus(t *testing.T) { | |||||
| 122 | if e := w.Apply(ActionCloseWindow); e.Close != 3 { | 122 | if e := w.Apply(ActionCloseWindow); e.Close != 3 { | ||
| 123 | t.Fatalf("close %d, want the fullscreen window", e.Close) | 123 | t.Fatalf("close %d, want the fullscreen window", e.Close) | ||
| 124 | } | 124 | } | ||
| 125 | + | // A fixed-overflow fullscreen tile pins the focus too. | |||
| 126 | + | f := workspace() | |||
| 127 | + | f.Overflow = OverflowFixed | |||
| 128 | + | f.AddWindow(1) | |||
| 129 | + | f.AddWindow(2) | |||
| 130 | + | f.SetFullscreen(2, true) | |||
| 131 | + | if f.FocusColumn(-1); f.Focus != 1 { | |||
| 132 | + | t.Fatalf("fixed: focus moved to column %d", f.Focus) | |||
| 133 | + | } | |||
| 134 | + | // Scroll overflow: moving off a fullscreen column still works; it no | |||
| 135 | + | // longer covers. | |||
| 136 | + | s := workspace() | |||
| 137 | + | s.AddWindow(1) | |||
| 138 | + | s.AddWindow(2) | |||
| 139 | + | s.SetFullscreen(2, true) | |||
| 140 | + | if s.FocusColumn(-1); s.Focus != 0 || s.cover() != 0 { | |||
| 141 | + | t.Fatalf("scroll: focus %d cover %d", s.Focus, s.cover()) | |||
| 142 | + | } | |||
| 125 | } | 143 | } | ||
| 126 | 144 | ||||
| 127 | // A fullscreen window does not float until it leaves fullscreen. | 145 | // A fullscreen window does not float until it leaves fullscreen. | ||
M internal/core/layer_input_test.go +36 -0
| @@ -394,3 +394,39 @@ func TestPopupInputRegionPassesThroughToWindow(t *testing.T) { | |||
| 394 | 394 | t.Fatalf("popup interior motion: %+v", v) | |
| 395 | 395 | } | |
| 396 | 396 | } | |
| 397 | + | ||
| 398 | + | // A grabbing popup hidden with its parent under a fullscreen window is | |
| 399 | + | // closed, and the keyboard goes to the fullscreen window. | |
| 400 | + | func TestHiddenGrabPopupLosesKeyboard(t *testing.T) { | |
| 401 | + | cfg := config.Defaults() | |
| 402 | + | cfg.Border.Width = 0 | |
| 403 | + | client := make(chan ports.ClientEvent, 8) | |
| 404 | + | output := make(chan ports.OutputEvent, 8) | |
| 405 | + | commands := make(chan ports.ClientCommand, 64) | |
| 406 | + | scenes := make(chan []ports.Scene, 1) | |
| 407 | + | c, err := core.New(cfg, core.Channels{Client: client, Output: output, Commands: commands, Scenes: scenes}) | |
| 408 | + | if err != nil { | |
| 409 | + | t.Fatal(err) | |
| 410 | + | } | |
| 411 | + | ctx, cancel := context.WithCancel(context.Background()) | |
| 412 | + | defer cancel() | |
| 413 | + | go c.Run(ctx) | |
| 414 | + | output <- ports.OutputAdded{Info: ports.OutputInfo{Name: "OUT-1", Width: 100, Height: 80}} | |
| 415 | + | <-scenes | |
| 416 | + | client <- ports.WindowMapped{ID: 1} | |
| 417 | + | <-scenes | |
| 418 | + | pos := ports.Positioner{Width: 10, Height: 10, AnchorRect: ports.Rect{W: 1, H: 1}, Anchor: ports.EdgeBottomRight, Gravity: ports.EdgeBottomRight} | |
| 419 | + | client <- ports.PopupRequest{ID: 3, Parent: 1, Positioner: pos, Grab: true} | |
| 420 | + | next(t, commands, func(v ports.ConfigurePopup) bool { return v.ID == 3 }) | |
| 421 | + | client <- ports.PopupMapped{ID: 3} | |
| 422 | + | if f := next(t, commands, anyOf[ports.FocusWindow]); f.ID != 3 { | |
| 423 | + | t.Fatalf("focus %d, want the grabbing popup", f.ID) | |
| 424 | + | } | |
| 425 | + | // A new fullscreen window covers its parent: the popup is hidden. | |
| 426 | + | client <- ports.WindowMapped{ID: 2} | |
| 427 | + | client <- ports.WindowFullscreenRequest{ID: 2, Fullscreen: true} | |
| 428 | + | next(t, commands, func(v ports.ClosePopup) bool { return v.ID == 3 }) | |
| 429 | + | if f := next(t, commands, func(v ports.FocusWindow) bool { return v.ID != 3 }); f.ID != 2 { | |
| 430 | + | t.Fatalf("focus %d, want the fullscreen window", f.ID) | |
| 431 | + | } | |
| 432 | + | } | |
| @@ -394,3 +394,39 @@ func TestPopupInputRegionPassesThroughToWindow(t *testing.T) { | |||||
| 394 | t.Fatalf("popup interior motion: %+v", v) | 394 | t.Fatalf("popup interior motion: %+v", v) | ||
| 395 | } | 395 | } | ||
| 396 | } | 396 | } | ||
| 397 | + | ||||
| 398 | + | // A grabbing popup hidden with its parent under a fullscreen window is | |||
| 399 | + | // closed, and the keyboard goes to the fullscreen window. | |||
| 400 | + | func TestHiddenGrabPopupLosesKeyboard(t *testing.T) { | |||
| 401 | + | cfg := config.Defaults() | |||
| 402 | + | cfg.Border.Width = 0 | |||
| 403 | + | client := make(chan ports.ClientEvent, 8) | |||
| 404 | + | output := make(chan ports.OutputEvent, 8) | |||
| 405 | + | commands := make(chan ports.ClientCommand, 64) | |||
| 406 | + | scenes := make(chan []ports.Scene, 1) | |||
| 407 | + | c, err := core.New(cfg, core.Channels{Client: client, Output: output, Commands: commands, Scenes: scenes}) | |||
| 408 | + | if err != nil { | |||
| 409 | + | t.Fatal(err) | |||
| 410 | + | } | |||
| 411 | + | ctx, cancel := context.WithCancel(context.Background()) | |||
| 412 | + | defer cancel() | |||
| 413 | + | go c.Run(ctx) | |||
| 414 | + | output <- ports.OutputAdded{Info: ports.OutputInfo{Name: "OUT-1", Width: 100, Height: 80}} | |||
| 415 | + | <-scenes | |||
| 416 | + | client <- ports.WindowMapped{ID: 1} | |||
| 417 | + | <-scenes | |||
| 418 | + | pos := ports.Positioner{Width: 10, Height: 10, AnchorRect: ports.Rect{W: 1, H: 1}, Anchor: ports.EdgeBottomRight, Gravity: ports.EdgeBottomRight} | |||
| 419 | + | client <- ports.PopupRequest{ID: 3, Parent: 1, Positioner: pos, Grab: true} | |||
| 420 | + | next(t, commands, func(v ports.ConfigurePopup) bool { return v.ID == 3 }) | |||
| 421 | + | client <- ports.PopupMapped{ID: 3} | |||
| 422 | + | if f := next(t, commands, anyOf[ports.FocusWindow]); f.ID != 3 { | |||
| 423 | + | t.Fatalf("focus %d, want the grabbing popup", f.ID) | |||
| 424 | + | } | |||
| 425 | + | // A new fullscreen window covers its parent: the popup is hidden. | |||
| 426 | + | client <- ports.WindowMapped{ID: 2} | |||
| 427 | + | client <- ports.WindowFullscreenRequest{ID: 2, Fullscreen: true} | |||
| 428 | + | next(t, commands, func(v ports.ClosePopup) bool { return v.ID == 3 }) | |||
| 429 | + | if f := next(t, commands, func(v ports.FocusWindow) bool { return v.ID != 3 }); f.ID != 2 { | |||
| 430 | + | t.Fatalf("focus %d, want the fullscreen window", f.ID) | |||
| 431 | + | } | |||
| 432 | + | } | |||
M internal/core/monitor_test.go +15 -0
| @@ -770,4 +770,19 @@ func TestFollowMoveIntoFullscreenLands(t *testing.T) { | |||
| 770 | 770 | } | |
| 771 | 771 | } | |
| 772 | 772 | } | |
| 773 | + | // Without follow, the fullscreen workspace keeps its window: the moved | |
| 774 | + | // one waits there, hidden, and the view stays. | |
| 775 | + | m := newMonitor("", "") | |
| 776 | + | m.SetOutput(100, 80) | |
| 777 | + | m.SetOverflow(OverflowFixed) | |
| 778 | + | m.AddWindow(1) | |
| 779 | + | m.AddWindow(2) | |
| 780 | + | m.ToggleFullscreen() | |
| 781 | + | m.Focus(len(m.Workspaces) - 1) | |
| 782 | + | m.AddWindow(5) | |
| 783 | + | from := m.Current() | |
| 784 | + | m.MoveToWorkspace(1, true) | |
| 785 | + | if fs := m.Workspaces[1]; m.Current() != from || fs.cover() != 2 || fs.floatIndex(5) < 0 { | |
| 786 | + | t.Fatalf("no follow: workspaces %v floats %v", windows(m), fs.Floats) | |
| 787 | + | } | |
| 773 | 788 | } | |
| @@ -770,4 +770,19 @@ func TestFollowMoveIntoFullscreenLands(t *testing.T) { | |||||
| 770 | } | 770 | } | ||
| 771 | } | 771 | } | ||
| 772 | } | 772 | } | ||
| 773 | + | // Without follow, the fullscreen workspace keeps its window: the moved | |||
| 774 | + | // one waits there, hidden, and the view stays. | |||
| 775 | + | m := newMonitor("", "") | |||
| 776 | + | m.SetOutput(100, 80) | |||
| 777 | + | m.SetOverflow(OverflowFixed) | |||
| 778 | + | m.AddWindow(1) | |||
| 779 | + | m.AddWindow(2) | |||
| 780 | + | m.ToggleFullscreen() | |||
| 781 | + | m.Focus(len(m.Workspaces) - 1) | |||
| 782 | + | m.AddWindow(5) | |||
| 783 | + | from := m.Current() | |||
| 784 | + | m.MoveToWorkspace(1, true) | |||
| 785 | + | if fs := m.Workspaces[1]; m.Current() != from || fs.cover() != 2 || fs.floatIndex(5) < 0 { | |||
| 786 | + | t.Fatalf("no follow: workspaces %v floats %v", windows(m), fs.Floats) | |||
| 787 | + | } | |||
| 773 | } | 788 | } | ||
M internal/core/popup_test.go +0 -28
| @@ -73,31 +73,3 @@ func TestFloatingWindows(t *testing.T) { | |||
| 73 | 73 | } | |
| 74 | 74 | } | |
| 75 | 75 | ||
| 76 | - | // A grabbing popup hidden with its parent under a fullscreen window has no | |
| 77 | - | // keyboard; the fullscreen window has it. | |
| 78 | - | func TestHiddenGrabPopupLosesKeyboard(t *testing.T) { | |
| 79 | - | cfg := ports.Config{} | |
| 80 | - | cfg.Keyboard.CmdKey = "super" | |
| 81 | - | cfg.Layout.MaxColumns = 2 | |
| 82 | - | c, err := New(cfg, Channels{Scenes: make(chan []ports.Scene, 1)}) | |
| 83 | - | if err != nil { | |
| 84 | - | t.Fatal(err) | |
| 85 | - | } | |
| 86 | - | c.addScreen(ports.OutputInfo{Name: "OUT-1", Width: 100, Height: 80}) | |
| 87 | - | m := c.cur().mon | |
| 88 | - | m.AddWindow(1) | |
| 89 | - | m.AddWindow(2) | |
| 90 | - | c.popups[3] = &popupState{id: 3, parent: 1, grab: true, mapped: true, rect: Rect{W: 5, H: 5}} | |
| 91 | - | c.popupOrder = append(c.popupOrder, 3) | |
| 92 | - | m.Current().FocusID(1) | |
| 93 | - | if f := c.keyboardFocus(); f != 3 { | |
| 94 | - | t.Fatalf("focus %d, want the grabbing popup", f) | |
| 95 | - | } | |
| 96 | - | m.SetFullscreen(2, true) | |
| 97 | - | // Scroll overflow: the fullscreen window covers once its column is on | |
| 98 | - | // screen. | |
| 99 | - | m.Current().FocusID(2) | |
| 100 | - | if f := c.keyboardFocus(); f != 2 { | |
| 101 | - | t.Fatalf("focus %d, want the fullscreen window", f) | |
| 102 | - | } | |
| 103 | - | } | |
| @@ -73,31 +73,3 @@ func TestFloatingWindows(t *testing.T) { | |||||
| 73 | } | 73 | } | ||
| 74 | } | 74 | } | ||
| 75 | 75 | ||||
| 76 | - | // A grabbing popup hidden with its parent under a fullscreen window has no | |||
| 77 | - | // keyboard; the fullscreen window has it. | |||
| 78 | - | func TestHiddenGrabPopupLosesKeyboard(t *testing.T) { | |||
| 79 | - | cfg := ports.Config{} | |||
| 80 | - | cfg.Keyboard.CmdKey = "super" | |||
| 81 | - | cfg.Layout.MaxColumns = 2 | |||
| 82 | - | c, err := New(cfg, Channels{Scenes: make(chan []ports.Scene, 1)}) | |||
| 83 | - | if err != nil { | |||
| 84 | - | t.Fatal(err) | |||
| 85 | - | } | |||
| 86 | - | c.addScreen(ports.OutputInfo{Name: "OUT-1", Width: 100, Height: 80}) | |||
| 87 | - | m := c.cur().mon | |||
| 88 | - | m.AddWindow(1) | |||
| 89 | - | m.AddWindow(2) | |||
| 90 | - | c.popups[3] = &popupState{id: 3, parent: 1, grab: true, mapped: true, rect: Rect{W: 5, H: 5}} | |||
| 91 | - | c.popupOrder = append(c.popupOrder, 3) | |||
| 92 | - | m.Current().FocusID(1) | |||
| 93 | - | if f := c.keyboardFocus(); f != 3 { | |||
| 94 | - | t.Fatalf("focus %d, want the grabbing popup", f) | |||
| 95 | - | } | |||
| 96 | - | m.SetFullscreen(2, true) | |||
| 97 | - | // Scroll overflow: the fullscreen window covers once its column is on | |||
| 98 | - | // screen. | |||
| 99 | - | m.Current().FocusID(2) | |||
| 100 | - | if f := c.keyboardFocus(); f != 2 { | |||
| 101 | - | t.Fatalf("focus %d, want the fullscreen window", f) | |||
| 102 | - | } | |||
| 103 | - | } | |||