• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.text_formatting"""
15
16import unittest
17from parameterized import parameterized  # type: ignore
18
19from prompt_toolkit.formatted_text import ANSI
20
21from pw_console.text_formatting import (
22    get_line_height,
23    insert_linebreaks,
24    split_lines,
25)
26
27
28class TestTextFormatting(unittest.TestCase):
29    """Tests for manipulating prompt_toolkit formatted text tuples."""
30    def setUp(self):
31        self.maxDiff = None  # pylint: disable=invalid-name
32
33    @parameterized.expand([
34        (
35            'with short prefix height 2',
36            len('LINE that should be wrapped'),  # text_width
37            len('|                |'),  # screen_width
38            len('--->'),  # prefix_width
39            (   'LINE that should b\n'
40                '--->e wrapped     \n').count('\n'),  # expected_height
41            len(             '_____'),  # expected_trailing_characters
42        ),
43        (
44            'with short prefix height 3',
45            len('LINE that should be wrapped three times.'),  # text_width
46            len('|                |'),  # screen_width
47            len('--->'),  # prefix_width
48            (   'LINE that should b\n'
49                '--->e wrapped thre\n'
50                '--->e times.      \n').count('\n'),  # expected_height
51            len(            '______'),  # expected_trailing_characters
52        ),
53        (
54            'with short prefix height 4',
55            len('LINE that should be wrapped even more times, say four.'),
56            len('|                |'),  # screen_width
57            len('--->'),  # prefix_width
58            (   'LINE that should b\n'
59                '--->e wrapped even\n'
60                '---> more times, s\n'
61                '--->ay four.      \n').count('\n'),  # expected_height
62            len(            '______'),  # expected_trailing_characters
63        ),
64        (
65            'no wrapping needed',
66            len('LINE wrapped'),  # text_width
67            len('|                |'),  # screen_width
68            len('--->'),  # prefix_width
69            (   'LINE wrapped      \n').count('\n'),  # expected_height
70            len(            '______'),  # expected_trailing_characters
71        ),
72        (
73            'prefix is > screen width',
74            len('LINE that should be wrapped'),  # text_width
75            len('|                |'),  # screen_width
76            len('------------------>'),  # prefix_width
77            (   'LINE that should b\n'
78                'e wrapped         \n').count('\n'),  # expected_height
79            len(         '_________'),  # expected_trailing_characters
80        ),
81        (
82            'prefix is == screen width',
83            len('LINE that should be wrapped'),  # text_width
84            len('|                |'),  # screen_width
85            len('----------------->'),  # prefix_width
86            (   'LINE that should b\n'
87                'e wrapped         \n').count('\n'),  # expected_height
88            len(         '_________'),  # expected_trailing_characters
89        ),
90    ]) # yapf: disable
91
92    def test_get_line_height(self, _name, text_width, screen_width,
93                             prefix_width, expected_height,
94                             expected_trailing_characters) -> None:
95        """Test line height calculations."""
96        height, remaining_width = get_line_height(text_width, screen_width,
97                                                  prefix_width)
98        self.assertEqual(height, expected_height)
99        self.assertEqual(remaining_width, expected_trailing_characters)
100
101    # pylint: disable=line-too-long
102    @parameterized.expand([
103        (
104            'One line with ANSI escapes and no included breaks',
105            12,  # screen_width
106            False,  # truncate_long_lines
107            'Lorem ipsum \x1b[34m\x1b[1mdolor sit amet\x1b[0m, consectetur adipiscing elit.',  # message
108            ANSI(
109                # Line 1
110                'Lorem ipsum \n'
111                # Line 2
112                '\x1b[34m\x1b[1m'  # zero width
113                'dolor sit am\n'
114                # Line 3
115                'et'
116                '\x1b[0m'  # zero width
117                ', consecte\n'
118                # Line 4
119                'tur adipisci\n'
120                # Line 5
121                'ng elit.\n').__pt_formatted_text__(),
122            5,  # expected_height
123        ),
124        (
125            'One line with ANSI escapes and included breaks',
126            12,  # screen_width
127            False,  # truncate_long_lines
128            'Lorem\n ipsum \x1b[34m\x1b[1mdolor sit amet\x1b[0m, consectetur adipiscing elit.',  # message
129            ANSI(
130                # Line 1
131                'Lorem\n'
132                # Line 2
133                ' ipsum \x1b[34m\x1b[1mdolor\n'
134                # Line 3
135                ' sit amet\x1b[0m, c\n'
136                # Line 4
137                'onsectetur a\n'
138                # Line 5
139                'dipiscing el\n'
140                # Line 6
141                'it.\n'
142                ).__pt_formatted_text__(),
143            6,  # expected_height
144        ),
145        (
146            'One line with ANSI escapes and included breaks; truncate lines enabled',
147            12,  # screen_width
148            True,  # truncate_long_lines
149            'Lorem\n ipsum dolor sit amet, consectetur adipiscing \nelit.\n',  # message
150            ANSI(
151                # Line 1
152                'Lorem\n'
153                # Line 2
154                ' ipsum dolor\n'
155                # Line 3
156                'elit.\n'
157                ).__pt_formatted_text__(),
158            3,  # expected_height
159        ),
160        (
161            'wrapping enabled with a line break just after screen_width',
162            10,  # screen_width
163            False,  # truncate_long_lines
164            '01234567890\nTest Log\n',  # message
165            ANSI(
166                '0123456789\n'
167                '0\n'
168                'Test Log\n'
169                ).__pt_formatted_text__(),
170            3,  # expected_height
171        ),
172        (
173            'log message with a line break at screen_width',
174            10,  # screen_width
175            True,  # truncate_long_lines
176            '0123456789\nTest Log\n',  # message
177            ANSI(
178                '0123456789\n'
179                'Test Log\n'
180                ).__pt_formatted_text__(),
181            2,  # expected_height
182        ),
183    ]) # yapf: disable
184    # pylint: enable=line-too-long
185    def test_insert_linebreaks(
186        self,
187        _name,
188        screen_width,
189        truncate_long_lines,
190        raw_text,
191        expected_fragments,
192        expected_height,
193    ) -> None:
194        """Test inserting linebreaks to wrap lines."""
195
196        formatted_text = ANSI(raw_text).__pt_formatted_text__()
197
198        fragments, line_height = insert_linebreaks(
199            formatted_text,
200            max_line_width=screen_width,
201            truncate_long_lines=truncate_long_lines)
202
203        self.assertEqual(fragments, expected_fragments)
204        self.assertEqual(line_height, expected_height)
205
206    @parameterized.expand([
207        (
208            'flattened split',
209            ANSI(
210                'Lorem\n'
211                ' ipsum dolor\n'
212                'elit.\n'
213            ).__pt_formatted_text__(),
214            [
215                ANSI('Lorem').__pt_formatted_text__(),
216                ANSI(' ipsum dolor').__pt_formatted_text__(),
217                ANSI('elit.').__pt_formatted_text__(),
218            ],  # expected_lines
219        ),
220        (
221            'split fragments from insert_linebreaks',
222            insert_linebreaks(
223                ANSI(
224                    'Lorem\n ipsum dolor sit amet, consectetur adipiscing elit.'
225                ).__pt_formatted_text__(),
226                max_line_width=15,
227                # [0] for the fragments, [1] is line_height
228                truncate_long_lines=False)[0],
229            [
230                ANSI('Lorem').__pt_formatted_text__(),
231                ANSI(' ipsum dolor si').__pt_formatted_text__(),
232                ANSI('t amet, consect').__pt_formatted_text__(),
233                ANSI('etur adipiscing').__pt_formatted_text__(),
234                ANSI(' elit.').__pt_formatted_text__(),
235            ],
236        ),
237        (
238            'empty lines',
239            # Each line should have at least one StyleAndTextTuple but without
240            # an ending line break.
241            [
242                ('', '\n'),
243                ('', '\n'),
244            ],
245            [
246                [('', '')],
247                [('', '')],
248            ],
249        )
250    ]) # yapf: disable
251    def test_split_lines(
252        self,
253        _name,
254        input_fragments,
255        expected_lines,
256    ) -> None:
257        """Test splitting flattened StyleAndTextTuples into a list of lines."""
258
259        result_lines = split_lines(input_fragments)
260
261        self.assertEqual(result_lines, expected_lines)
262
263
264if __name__ == '__main__':
265    unittest.main()
266