xref: /aosp_15_r20/external/pigweed/pw_console/py/window_manager_test.py (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1# Copyright 2021 The Pigweed Authors
2#
3# Licensed under the Apache License, Version 2.0 (the "License"); you may not
4# use this file except in compliance with the License. You may obtain a copy of
5# the License at
6#
7#     https://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations under
13# the License.
14"""Tests for pw_console.console_app"""
15
16import logging
17import unittest
18from unittest.mock import MagicMock
19
20from prompt_toolkit.application import create_app_session
21from prompt_toolkit.output import ColorDepth
22
23# inclusive-language: ignore
24from prompt_toolkit.output import DummyOutput as FakeOutput
25
26from pw_console.console_app import ConsoleApp
27from pw_console.console_prefs import ConsolePrefs
28from pw_console.window_manager import _WINDOW_SPLIT_ADJUST
29from pw_console.window_list import _WINDOW_HEIGHT_ADJUST, DisplayMode
30
31
32def _create_console_app(logger_count=2):
33    prefs = ConsolePrefs(
34        project_file=False, project_user_file=False, user_file=False
35    )
36    prefs.set_code_theme('default')
37    console_app = ConsoleApp(color_depth=ColorDepth.DEPTH_8_BIT, prefs=prefs)
38    console_app.focus_on_container = MagicMock()
39
40    loggers = {}
41    for i in range(logger_count):
42        loggers['Log{}'.format(i)] = [logging.getLogger('test_log{}'.format(i))]
43    for window_title, logger_instances in loggers.items():
44        console_app.add_log_handler(window_title, logger_instances)
45    return console_app
46
47
48_WINDOW_MANAGER_WIDTH = 80
49_WINDOW_MANAGER_HEIGHT = 30
50_DEFAULT_WINDOW_WIDTH = 10
51_DEFAULT_WINDOW_HEIGHT = 10
52
53
54def _window_list_widths(window_manager):
55    window_manager.update_window_manager_size(
56        _WINDOW_MANAGER_WIDTH, _WINDOW_MANAGER_HEIGHT
57    )
58
59    return [
60        window_list.width.preferred
61        for window_list in window_manager.window_lists
62    ]
63
64
65def _window_list_heights(window_manager):
66    window_manager.update_window_manager_size(
67        _WINDOW_MANAGER_WIDTH, _WINDOW_MANAGER_HEIGHT
68    )
69
70    return [
71        window_list.height.preferred
72        for window_list in window_manager.window_lists
73    ]
74
75
76def _window_pane_widths(window_manager, window_list_index=0):
77    window_manager.update_window_manager_size(
78        _WINDOW_MANAGER_WIDTH, _WINDOW_MANAGER_HEIGHT
79    )
80
81    return [
82        pane.width.preferred
83        for pane in window_manager.window_lists[window_list_index].active_panes
84    ]
85
86
87def _window_pane_heights(window_manager, window_list_index=0):
88    window_manager.update_window_manager_size(
89        _WINDOW_MANAGER_WIDTH, _WINDOW_MANAGER_HEIGHT
90    )
91
92    return [
93        pane.height.preferred
94        for pane in window_manager.window_lists[window_list_index].active_panes
95    ]
96
97
98def _window_pane_counts(window_manager):
99    return [
100        len(window_list.active_panes)
101        for window_list in window_manager.window_lists
102    ]
103
104
105def window_pane_titles(window_manager):
106    return [
107        [
108            pane.pane_title() + ' - ' + pane.pane_subtitle()
109            for pane in window_list.active_panes
110        ]
111        for window_list in window_manager.window_lists
112    ]
113
114
115def target_list_and_pane(window_manager, list_index, pane_index):
116    # pylint: disable=protected-access
117    # Bypass prompt_toolkit has_focus()
118    pane = window_manager.window_lists[list_index].active_panes[pane_index]
119    # If the pane is in focus it will be visible.
120    pane.show_pane = True
121    window_manager._get_active_window_list_and_pane = MagicMock(  # type: ignore
122        return_value=(
123            window_manager.window_lists[list_index],
124            window_manager.window_lists[list_index].active_panes[pane_index],
125        )
126    )
127
128
129class TestWindowManager(unittest.TestCase):
130    # pylint: disable=protected-access
131    """Tests for window management functions."""
132
133    maxDiff = None
134
135    def test_find_window_list_and_pane(self) -> None:
136        """Test getting the window list for a given pane."""
137        with create_app_session(output=FakeOutput()):
138            console_app = _create_console_app(logger_count=3)
139
140            window_manager = console_app.window_manager
141            self.assertEqual([4], _window_pane_counts(window_manager))
142
143            # Move 2 windows to the right into their own splits
144            target_list_and_pane(window_manager, 0, 0)
145            window_manager.move_pane_right()
146            target_list_and_pane(window_manager, 0, 0)
147            window_manager.move_pane_right()
148            target_list_and_pane(window_manager, 1, 0)
149            window_manager.move_pane_right()
150            # 3 splits, first split has 2 windows
151            self.assertEqual([2, 1, 1], _window_pane_counts(window_manager))
152
153            # Move the first window in the first split left
154            target_list_and_pane(window_manager, 0, 0)
155            window_manager.move_pane_left()
156            # 4 splits, each with their own window
157            self.assertEqual([1, 1, 1, 1], _window_pane_counts(window_manager))
158
159            # Move the first window to the right
160            target_list_and_pane(window_manager, 0, 0)
161            window_manager.move_pane_right()
162            # 3 splits, first split has 2 windows
163            self.assertEqual([2, 1, 1], _window_pane_counts(window_manager))
164
165            target_pane = window_manager.window_lists[2].active_panes[0]
166
167            (
168                result_window_list,
169                result_pane_index,
170            ) = window_manager.find_window_list_and_pane_index(target_pane)
171            self.assertEqual(
172                (result_window_list, result_pane_index),
173                (window_manager.window_lists[2], 0),
174            )
175            window_manager.remove_pane(target_pane)
176            self.assertEqual([2, 1], _window_pane_counts(window_manager))
177
178    def test_window_list_moving_and_resizing(self) -> None:
179        """Test window split movement resizing."""
180        with create_app_session(output=FakeOutput()):
181            console_app = _create_console_app(logger_count=3)
182
183            window_manager = console_app.window_manager
184
185            target_list_and_pane(window_manager, 0, 0)
186            # Should have one window list split of size 50.
187            self.assertEqual(
188                _window_list_widths(window_manager),
189                [_WINDOW_MANAGER_WIDTH],
190            )
191
192            # Move one pane to the right, creating a new window_list split.
193            window_manager.move_pane_right()
194
195            self.assertEqual(
196                _window_list_widths(window_manager),
197                [
198                    int(_WINDOW_MANAGER_WIDTH / 2),
199                    int(_WINDOW_MANAGER_WIDTH / 2),
200                ],
201            )
202
203            # Move another pane to the right twice, creating a third
204            # window_list split.
205            target_list_and_pane(window_manager, 0, 0)
206            window_manager.move_pane_right()
207
208            # Above window pane is at a new location
209            target_list_and_pane(window_manager, 1, 0)
210            window_manager.move_pane_right()
211
212            # Should have 3 splits now
213            self.assertEqual(
214                _window_list_widths(window_manager),
215                [
216                    int(_WINDOW_MANAGER_WIDTH / 3),
217                    int(_WINDOW_MANAGER_WIDTH / 3),
218                    int(_WINDOW_MANAGER_WIDTH / 3),
219                ],
220            )
221
222            # Total of 4 active panes
223            self.assertEqual(len(list(window_manager.active_panes())), 4)
224
225            # Target the middle split
226            target_list_and_pane(window_manager, 1, 0)
227            # Shrink the middle split twice
228            window_manager.shrink_split()
229            window_manager.shrink_split()
230            self.assertEqual(
231                _window_list_widths(window_manager),
232                [
233                    int(_WINDOW_MANAGER_WIDTH / 3),
234                    int(_WINDOW_MANAGER_WIDTH / 3) - (2 * _WINDOW_SPLIT_ADJUST),
235                    int(_WINDOW_MANAGER_WIDTH / 3) + (2 * _WINDOW_SPLIT_ADJUST),
236                ],
237            )
238
239            # Target the first split
240            target_list_and_pane(window_manager, 0, 0)
241            window_manager.reset_split_sizes()
242            # Shrink the first split twice
243            window_manager.shrink_split()
244            self.assertEqual(
245                _window_list_widths(window_manager),
246                [
247                    int(_WINDOW_MANAGER_WIDTH / 3) - (1 * _WINDOW_SPLIT_ADJUST),
248                    int(_WINDOW_MANAGER_WIDTH / 3) + (1 * _WINDOW_SPLIT_ADJUST),
249                    int(_WINDOW_MANAGER_WIDTH / 3),
250                ],
251            )
252
253            # Target the third (last) split
254            target_list_and_pane(window_manager, 2, 0)
255            window_manager.reset_split_sizes()
256            # Shrink the third split once
257            window_manager.shrink_split()
258            self.assertEqual(
259                _window_list_widths(window_manager),
260                [
261                    int(_WINDOW_MANAGER_WIDTH / 3),
262                    int(_WINDOW_MANAGER_WIDTH / 3) + (1 * _WINDOW_SPLIT_ADJUST),
263                    int(_WINDOW_MANAGER_WIDTH / 3) - (1 * _WINDOW_SPLIT_ADJUST),
264                ],
265            )
266
267            window_manager.reset_split_sizes()
268            # Enlarge the third split a few times.
269            window_manager.enlarge_split()
270            window_manager.enlarge_split()
271            window_manager.enlarge_split()
272            self.assertEqual(
273                _window_list_widths(window_manager),
274                [
275                    int(_WINDOW_MANAGER_WIDTH / 3),
276                    int(_WINDOW_MANAGER_WIDTH / 3) - (3 * _WINDOW_SPLIT_ADJUST),
277                    int(_WINDOW_MANAGER_WIDTH / 3) + (3 * _WINDOW_SPLIT_ADJUST),
278                ],
279            )
280
281            # Target the middle split
282            target_list_and_pane(window_manager, 1, 0)
283            # Move the middle window pane left
284            window_manager.move_pane_left()
285            # This is called on the next render pass
286            window_manager.rebalance_window_list_sizes()
287            # Middle split should be removed
288            self.assertEqual(
289                _window_list_widths(window_manager),
290                [
291                    int(_WINDOW_MANAGER_WIDTH / 2) - (3 * _WINDOW_SPLIT_ADJUST),
292                    # This split is removed
293                    int(_WINDOW_MANAGER_WIDTH / 2) + (2 * _WINDOW_SPLIT_ADJUST),
294                ],
295            )
296
297            # Revert sizes to default
298            window_manager.reset_split_sizes()
299            self.assertEqual(
300                _window_list_widths(window_manager),
301                [
302                    int(_WINDOW_MANAGER_WIDTH / 2),
303                    int(_WINDOW_MANAGER_WIDTH / 2),
304                ],
305            )
306
307    def test_get_pane_titles(self) -> None:
308        """Test window resizing."""
309        with create_app_session(output=FakeOutput()):
310            console_app = _create_console_app(logger_count=3)
311
312            window_manager = console_app.window_manager
313            list_pane_titles = [
314                # Remove mouse click handler partials in tup[2]
315                [(tup[0], tup[1]) for tup in window_list.get_pane_titles()]
316                for window_list in window_manager.window_lists
317            ]
318            self.assertEqual(
319                list_pane_titles[0],
320                [
321                    ('', ' '),
322                    ('class:window-tab-inactive', ' Log2 test_log2 '),
323                    ('', ' '),
324                    ('class:window-tab-inactive', ' Log1 test_log1 '),
325                    ('', ' '),
326                    ('class:window-tab-inactive', ' Log0 test_log0 '),
327                    ('', ' '),
328                    ('class:window-tab-inactive', ' Python Repl  '),
329                    ('', ' '),
330                ],
331            )
332
333    def test_window_pane_movement_resizing(self) -> None:
334        """Test window resizing."""
335        with create_app_session(output=FakeOutput()):
336            console_app = _create_console_app(logger_count=3)
337
338            window_manager = console_app.window_manager
339
340            # 4 panes, 3 for the loggers and 1 for the repl.
341            self.assertEqual(
342                len(window_manager.first_window_list().active_panes), 4
343            )
344
345            def target_window_pane(index: int):
346                # Bypass prompt_toolkit has_focus()
347                window_manager._get_active_window_list_and_pane = (
348                    MagicMock(  # type: ignore
349                        return_value=(
350                            window_manager.window_lists[0],
351                            window_manager.window_lists[0].active_panes[index],
352                        )
353                    )
354                )
355                window_list = console_app.window_manager.first_window_list()
356                window_list.get_current_active_pane = MagicMock(  # type: ignore
357                    return_value=window_list.active_panes[index]
358                )
359
360            # Target the first window pane
361            target_window_pane(0)
362
363            # Shrink the first pane
364            window_manager.shrink_pane()
365            self.assertEqual(
366                _window_pane_heights(window_manager),
367                [
368                    _DEFAULT_WINDOW_HEIGHT - (1 * _WINDOW_HEIGHT_ADJUST),
369                    _DEFAULT_WINDOW_HEIGHT + (1 * _WINDOW_HEIGHT_ADJUST),
370                    _DEFAULT_WINDOW_HEIGHT,
371                    _DEFAULT_WINDOW_HEIGHT,
372                ],
373            )
374
375            # Reset pane sizes
376            window_manager.window_lists[0].current_window_list_height = (
377                4 * _DEFAULT_WINDOW_HEIGHT
378            )
379            window_manager.reset_pane_sizes()
380            self.assertEqual(
381                _window_pane_heights(window_manager),
382                [
383                    _DEFAULT_WINDOW_HEIGHT,
384                    _DEFAULT_WINDOW_HEIGHT,
385                    _DEFAULT_WINDOW_HEIGHT,
386                    _DEFAULT_WINDOW_HEIGHT,
387                ],
388            )
389
390            # Shrink last pane
391            target_window_pane(3)
392
393            window_manager.shrink_pane()
394            self.assertEqual(
395                _window_pane_heights(window_manager),
396                [
397                    _DEFAULT_WINDOW_HEIGHT,
398                    _DEFAULT_WINDOW_HEIGHT,
399                    _DEFAULT_WINDOW_HEIGHT + (1 * _WINDOW_HEIGHT_ADJUST),
400                    _DEFAULT_WINDOW_HEIGHT - (1 * _WINDOW_HEIGHT_ADJUST),
401                ],
402            )
403
404            # Enlarge second pane
405            target_window_pane(1)
406            window_manager.reset_pane_sizes()
407
408            window_manager.enlarge_pane()
409            window_manager.enlarge_pane()
410            self.assertEqual(
411                _window_pane_heights(window_manager),
412                [
413                    _DEFAULT_WINDOW_HEIGHT,
414                    _DEFAULT_WINDOW_HEIGHT + (2 * _WINDOW_HEIGHT_ADJUST),
415                    _DEFAULT_WINDOW_HEIGHT - (2 * _WINDOW_HEIGHT_ADJUST),
416                    _DEFAULT_WINDOW_HEIGHT,
417                ],
418            )
419
420            # Check window pane ordering
421            self.assertEqual(
422                window_pane_titles(window_manager),
423                [
424                    [
425                        'Log2 - test_log2',
426                        'Log1 - test_log1',
427                        'Log0 - test_log0',
428                        'Python Repl - ',
429                    ],
430                ],
431            )
432
433            target_window_pane(0)
434            window_manager.move_pane_down()
435            self.assertEqual(
436                window_pane_titles(window_manager),
437                [
438                    [
439                        'Log1 - test_log1',
440                        'Log2 - test_log2',
441                        'Log0 - test_log0',
442                        'Python Repl - ',
443                    ],
444                ],
445            )
446            target_window_pane(2)
447            window_manager.move_pane_up()
448            target_window_pane(1)
449            window_manager.move_pane_up()
450            self.assertEqual(
451                window_pane_titles(window_manager),
452                [
453                    [
454                        'Log0 - test_log0',
455                        'Log1 - test_log1',
456                        'Log2 - test_log2',
457                        'Python Repl - ',
458                    ],
459                ],
460            )
461            target_window_pane(0)
462            window_manager.move_pane_up()
463            self.assertEqual(
464                window_pane_titles(window_manager),
465                [
466                    [
467                        'Log0 - test_log0',
468                        'Log1 - test_log1',
469                        'Log2 - test_log2',
470                        'Python Repl - ',
471                    ],
472                ],
473            )
474
475    def test_focus_next_and_previous_pane(self) -> None:
476        """Test switching focus to next and previous window panes."""
477        with create_app_session(output=FakeOutput()):
478            console_app = _create_console_app(logger_count=4)
479
480            window_manager = console_app.window_manager
481            window_manager.window_lists[0].set_display_mode(DisplayMode.STACK)
482            self.assertEqual(
483                window_pane_titles(window_manager),
484                [
485                    [
486                        'Log3 - test_log3',
487                        'Log2 - test_log2',
488                        'Log1 - test_log1',
489                        'Log0 - test_log0',
490                        'Python Repl - ',
491                    ],
492                ],
493            )
494
495            # Scenario: Move between panes with a single stacked window list.
496
497            # Set the first pane in focus.
498            target_list_and_pane(window_manager, 0, 0)
499            # Switch focus to the next pane
500            window_manager.focus_next_pane()
501            # Pane index 1 should now be focused.
502            console_app.focus_on_container.assert_called_once_with(
503                window_manager.window_lists[0].active_panes[1]
504            )
505            console_app.focus_on_container.reset_mock()
506
507            # Set the first pane in focus.
508            target_list_and_pane(window_manager, 0, 0)
509            # Switch focus to the previous pane
510            window_manager.focus_previous_pane()
511            # Previous pane should wrap around to the last pane in the first
512            # window_list.
513            console_app.focus_on_container.assert_called_once_with(
514                window_manager.window_lists[0].active_panes[-1]
515            )
516            console_app.focus_on_container.reset_mock()
517
518            # Set the last pane in focus.
519            target_list_and_pane(window_manager, 0, 4)
520            # Switch focus to the next pane
521            window_manager.focus_next_pane()
522            # Next pane should wrap around to the first pane in the first
523            # window_list.
524            console_app.focus_on_container.assert_called_once_with(
525                window_manager.window_lists[0].active_panes[0]
526            )
527            console_app.focus_on_container.reset_mock()
528
529            # Scenario: Move between panes with a single tabbed window list.
530
531            # Switch to Tabbed view mode
532            window_manager.window_lists[0].set_display_mode(DisplayMode.TABBED)
533            # The set_display_mode call above will call focus_on_container once.
534            console_app.focus_on_container.reset_mock()
535
536            # Setup the switch_to_tab mock
537            window_manager.window_lists[0].switch_to_tab = MagicMock(
538                wraps=window_manager.window_lists[0].switch_to_tab
539            )
540
541            # Set the first pane/tab in focus.
542            target_list_and_pane(window_manager, 0, 0)
543            # Switch focus to the next pane/tab
544            window_manager.focus_next_pane()
545            # Check switch_to_tab is called
546            window_manager.window_lists[
547                0
548            ].switch_to_tab.assert_called_once_with(1)
549            # And that focus_on_container is called only once
550            console_app.focus_on_container.assert_called_once_with(
551                window_manager.window_lists[0].active_panes[1]
552            )
553            console_app.focus_on_container.reset_mock()
554            window_manager.window_lists[0].switch_to_tab.reset_mock()
555
556            # Set the last pane/tab in focus.
557            target_list_and_pane(window_manager, 0, 4)
558            # Switch focus to the next pane/tab
559            window_manager.focus_next_pane()
560            # Check switch_to_tab is called
561            window_manager.window_lists[
562                0
563            ].switch_to_tab.assert_called_once_with(0)
564            # And that focus_on_container is called only once
565            console_app.focus_on_container.assert_called_once_with(
566                window_manager.window_lists[0].active_panes[0]
567            )
568            console_app.focus_on_container.reset_mock()
569            window_manager.window_lists[0].switch_to_tab.reset_mock()
570
571            # Set the first pane/tab in focus.
572            target_list_and_pane(window_manager, 0, 0)
573            # Switch focus to the prev pane/tab
574            window_manager.focus_previous_pane()
575            # Check switch_to_tab is called
576            window_manager.window_lists[
577                0
578            ].switch_to_tab.assert_called_once_with(4)
579            # And that focus_on_container is called only once
580            console_app.focus_on_container.assert_called_once_with(
581                window_manager.window_lists[0].active_panes[4]
582            )
583            console_app.focus_on_container.reset_mock()
584            window_manager.window_lists[0].switch_to_tab.reset_mock()
585
586            # Scenario: Move between multiple window lists with mixed stacked
587            # and tabbed view modes.
588
589            # Setup: Move two panes to the right into their own stacked
590            # window_list.
591            target_list_and_pane(window_manager, 0, 4)
592            window_manager.move_pane_right()
593            target_list_and_pane(window_manager, 0, 3)
594            window_manager.move_pane_right()
595            self.assertEqual(
596                window_pane_titles(window_manager),
597                [
598                    [
599                        'Log3 - test_log3',
600                        'Log2 - test_log2',
601                        'Log1 - test_log1',
602                    ],
603                    [
604                        'Log0 - test_log0',
605                        'Python Repl - ',
606                    ],
607                ],
608            )
609
610            # Setup the switch_to_tab mock on the second window_list
611            window_manager.window_lists[1].switch_to_tab = MagicMock(
612                wraps=window_manager.window_lists[1].switch_to_tab
613            )
614
615            # Set Log1 in focus
616            target_list_and_pane(window_manager, 0, 2)
617            window_manager.focus_next_pane()
618            # Log0 should now have focus
619            console_app.focus_on_container.assert_called_once_with(
620                window_manager.window_lists[1].active_panes[0]
621            )
622            console_app.focus_on_container.reset_mock()
623
624            # Set Log0 in focus
625            target_list_and_pane(window_manager, 1, 0)
626            window_manager.focus_previous_pane()
627            # Log1 should now have focus
628            console_app.focus_on_container.assert_called_once_with(
629                window_manager.window_lists[0].active_panes[2]
630            )
631            # The first window list is in tabbed mode so switch_to_tab should be
632            # called once.
633            window_manager.window_lists[
634                0
635            ].switch_to_tab.assert_called_once_with(2)
636            # Reset
637            window_manager.window_lists[0].switch_to_tab.reset_mock()
638            console_app.focus_on_container.reset_mock()
639
640            # Set Python Repl in focus
641            target_list_and_pane(window_manager, 1, 1)
642            window_manager.focus_next_pane()
643            # Log3 should now have focus
644            console_app.focus_on_container.assert_called_once_with(
645                window_manager.window_lists[0].active_panes[0]
646            )
647            window_manager.window_lists[
648                0
649            ].switch_to_tab.assert_called_once_with(0)
650            # Reset
651            window_manager.window_lists[0].switch_to_tab.reset_mock()
652            console_app.focus_on_container.reset_mock()
653
654            # Set Log3 in focus
655            target_list_and_pane(window_manager, 0, 0)
656            window_manager.focus_next_pane()
657            # Log2 should now have focus
658            console_app.focus_on_container.assert_called_once_with(
659                window_manager.window_lists[0].active_panes[1]
660            )
661            window_manager.window_lists[
662                0
663            ].switch_to_tab.assert_called_once_with(1)
664            # Reset
665            window_manager.window_lists[0].switch_to_tab.reset_mock()
666            console_app.focus_on_container.reset_mock()
667
668            # Set Python Repl in focus
669            target_list_and_pane(window_manager, 1, 1)
670            window_manager.focus_previous_pane()
671            # Log0 should now have focus
672            console_app.focus_on_container.assert_called_once_with(
673                window_manager.window_lists[1].active_panes[0]
674            )
675            # The second window list is in stacked mode so switch_to_tab should
676            # not be called.
677            window_manager.window_lists[1].switch_to_tab.assert_not_called()
678            # Reset
679            window_manager.window_lists[1].switch_to_tab.reset_mock()
680            console_app.focus_on_container.reset_mock()
681
682    def test_resize_vertical_splits(self) -> None:
683        """Test resizing window splits."""
684        with create_app_session(output=FakeOutput()):
685            console_app = _create_console_app(logger_count=4)
686            window_manager = console_app.window_manager
687
688            # Required before moving windows
689            window_manager.update_window_manager_size(
690                _WINDOW_MANAGER_WIDTH, _WINDOW_MANAGER_HEIGHT
691            )
692            window_manager.create_root_container()
693
694            # Vertical split by default
695            self.assertTrue(window_manager.vertical_window_list_spliting())
696
697            # Move windows to create 3 splits
698            target_list_and_pane(window_manager, 0, 0)
699            window_manager.move_pane_right()
700            target_list_and_pane(window_manager, 0, 0)
701            window_manager.move_pane_right()
702            target_list_and_pane(window_manager, 1, 1)
703            window_manager.move_pane_right()
704
705            # Check windows are where expected
706            self.assertEqual(
707                window_pane_titles(window_manager),
708                [
709                    [
710                        'Log1 - test_log1',
711                        'Log0 - test_log0',
712                        'Python Repl - ',
713                    ],
714                    [
715                        'Log2 - test_log2',
716                    ],
717                    [
718                        'Log3 - test_log3',
719                    ],
720                ],
721            )
722
723            # Check initial split widths
724            widths = [
725                int(_WINDOW_MANAGER_WIDTH / 3),
726                int(_WINDOW_MANAGER_WIDTH / 3),
727                int(_WINDOW_MANAGER_WIDTH / 3),
728            ]
729            self.assertEqual(_window_list_widths(window_manager), widths)
730
731            # Decrease size of first split
732            window_manager.adjust_split_size(window_manager.window_lists[0], -4)
733            widths = [
734                widths[0] - (4 * _WINDOW_SPLIT_ADJUST),
735                widths[1] + (4 * _WINDOW_SPLIT_ADJUST),
736                widths[2],
737            ]
738            self.assertEqual(_window_list_widths(window_manager), widths)
739
740            # Increase size of last split
741            widths = [
742                widths[0],
743                widths[1] - (4 * _WINDOW_SPLIT_ADJUST),
744                widths[2] + (4 * _WINDOW_SPLIT_ADJUST),
745            ]
746            window_manager.adjust_split_size(window_manager.window_lists[2], 4)
747            self.assertEqual(_window_list_widths(window_manager), widths)
748
749            # Check heights are all the same
750            window_manager.rebalance_window_list_sizes()
751            heights = [
752                int(_WINDOW_MANAGER_HEIGHT),
753                int(_WINDOW_MANAGER_HEIGHT),
754                int(_WINDOW_MANAGER_HEIGHT),
755            ]
756            self.assertEqual(_window_list_heights(window_manager), heights)
757
758    def test_resize_horizontal_splits(self) -> None:
759        """Test resizing window splits."""
760        with create_app_session(output=FakeOutput()):
761            console_app = _create_console_app(logger_count=4)
762            window_manager = console_app.window_manager
763
764            # We want horizontal window splits
765            window_manager.vertical_window_list_spliting = MagicMock(
766                return_value=False
767            )
768            self.assertFalse(window_manager.vertical_window_list_spliting())
769
770            # Required before moving windows
771            window_manager.update_window_manager_size(
772                _WINDOW_MANAGER_WIDTH, _WINDOW_MANAGER_HEIGHT
773            )
774            window_manager.create_root_container()
775
776            # Move windows to create 3 splits
777            target_list_and_pane(window_manager, 0, 0)
778            window_manager.move_pane_right()
779            target_list_and_pane(window_manager, 0, 0)
780            window_manager.move_pane_right()
781            target_list_and_pane(window_manager, 1, 1)
782            window_manager.move_pane_right()
783
784            # Check windows are where expected
785            self.assertEqual(
786                window_pane_titles(window_manager),
787                [
788                    [
789                        'Log1 - test_log1',
790                        'Log0 - test_log0',
791                        'Python Repl - ',
792                    ],
793                    [
794                        'Log2 - test_log2',
795                    ],
796                    [
797                        'Log3 - test_log3',
798                    ],
799                ],
800            )
801
802            # Check initial split widths
803            heights = [
804                int(_WINDOW_MANAGER_HEIGHT / 3),
805                int(_WINDOW_MANAGER_HEIGHT / 3),
806                int(_WINDOW_MANAGER_HEIGHT / 3),
807            ]
808            self.assertEqual(_window_list_heights(window_manager), heights)
809
810            # Decrease size of first split
811            window_manager.adjust_split_size(window_manager.window_lists[0], -4)
812            heights = [
813                heights[0] - (4 * _WINDOW_SPLIT_ADJUST),
814                heights[1] + (4 * _WINDOW_SPLIT_ADJUST),
815                heights[2],
816            ]
817            self.assertEqual(_window_list_heights(window_manager), heights)
818
819            # Increase size of last split
820            heights = [
821                heights[0],
822                heights[1] - (4 * _WINDOW_SPLIT_ADJUST),
823                heights[2] + (4 * _WINDOW_SPLIT_ADJUST),
824            ]
825            window_manager.adjust_split_size(window_manager.window_lists[2], 4)
826            self.assertEqual(_window_list_heights(window_manager), heights)
827
828            # Check widths are all the same
829            window_manager.rebalance_window_list_sizes()
830            widths = [
831                int(_WINDOW_MANAGER_WIDTH),
832                int(_WINDOW_MANAGER_WIDTH),
833                int(_WINDOW_MANAGER_WIDTH),
834            ]
835            self.assertEqual(_window_list_widths(window_manager), widths)
836
837
838if __name__ == '__main__':
839    unittest.main()
840