diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2022-09-16 09:09:35 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2022-09-16 09:09:35 +0000 |
commit | 0dfe1c9e2780469e3a4696e8fb3e6f717a7ebeb7 (patch) | |
tree | a0b651b55ea02e3b00bbc5eedba566fdd6bd7c08 /tests | |
parent | Initial commit. (diff) | |
download | terminaltables-0dfe1c9e2780469e3a4696e8fb3e6f717a7ebeb7.tar.xz terminaltables-0dfe1c9e2780469e3a4696e8fb3e6f717a7ebeb7.zip |
Adding upstream version 3.1.0.upstream/3.1.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'tests')
42 files changed, 3239 insertions, 0 deletions
diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..b91337b --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1,5 @@ +"""Allows importing from screenshot.""" + +import py + +PROJECT_ROOT = py.path.local(__file__).dirpath().join('..') diff --git a/tests/screenshot.py b/tests/screenshot.py new file mode 100644 index 0000000..6ccb593 --- /dev/null +++ b/tests/screenshot.py @@ -0,0 +1,292 @@ +"""Take screenshots and search for subimages in images.""" + +import ctypes +import os +import random +import struct +import subprocess +import time + +try: + from itertools import izip +except ImportError: + izip = zip # Py3 + +from tests import PROJECT_ROOT + +STARTF_USESHOWWINDOW = getattr(subprocess, 'STARTF_USESHOWWINDOW', 1) +STILL_ACTIVE = 259 +SW_MAXIMIZE = 3 + + +class StartupInfo(ctypes.Structure): + """STARTUPINFO structure.""" + + _fields_ = [ + ('cb', ctypes.c_ulong), + ('lpReserved', ctypes.c_char_p), + ('lpDesktop', ctypes.c_char_p), + ('lpTitle', ctypes.c_char_p), + ('dwX', ctypes.c_ulong), + ('dwY', ctypes.c_ulong), + ('dwXSize', ctypes.c_ulong), + ('dwYSize', ctypes.c_ulong), + ('dwXCountChars', ctypes.c_ulong), + ('dwYCountChars', ctypes.c_ulong), + ('dwFillAttribute', ctypes.c_ulong), + ('dwFlags', ctypes.c_ulong), + ('wShowWindow', ctypes.c_ushort), + ('cbReserved2', ctypes.c_ushort), + ('lpReserved2', ctypes.c_char_p), + ('hStdInput', ctypes.c_ulong), + ('hStdOutput', ctypes.c_ulong), + ('hStdError', ctypes.c_ulong), + ] + + def __init__(self, maximize=False, title=None): + """Constructor. + + :param bool maximize: Start process in new console window, maximized. + :param bytes title: Set new window title to this instead of exe path. + """ + super(StartupInfo, self).__init__() + self.cb = ctypes.sizeof(self) + if maximize: + self.dwFlags |= STARTF_USESHOWWINDOW + self.wShowWindow = SW_MAXIMIZE + if title: + self.lpTitle = ctypes.c_char_p(title) + + +class ProcessInfo(ctypes.Structure): + """PROCESS_INFORMATION structure.""" + + _fields_ = [ + ('hProcess', ctypes.c_void_p), + ('hThread', ctypes.c_void_p), + ('dwProcessId', ctypes.c_ulong), + ('dwThreadId', ctypes.c_ulong), + ] + + +class RunNewConsole(object): + """Run the command in a new console window. Windows only. Use in a with statement. + + subprocess sucks and really limits your access to the win32 API. Its implementation is half-assed. Using this so + that STARTUPINFO.lpTitle actually works and STARTUPINFO.dwFillAttribute produce the expected result. + """ + + def __init__(self, command, maximized=False, title=None): + """Constructor. + + :param iter command: Command to run. + :param bool maximized: Start process in new console window, maximized. + :param bytes title: Set new window title to this. Needed by user32.FindWindow. + """ + if title is None: + title = 'pytest-{0}-{1}'.format(os.getpid(), random.randint(1000, 9999)).encode('ascii') + self.startup_info = StartupInfo(maximize=maximized, title=title) + self.process_info = ProcessInfo() + self.command_str = subprocess.list2cmdline(command).encode('ascii') + self._handles = list() + self._kernel32 = ctypes.LibraryLoader(ctypes.WinDLL).kernel32 + self._kernel32.GetExitCodeProcess.argtypes = [ctypes.c_void_p, ctypes.POINTER(ctypes.c_ulong)] + self._kernel32.GetExitCodeProcess.restype = ctypes.c_long + + def __del__(self): + """Close win32 handles.""" + while self._handles: + try: + self._kernel32.CloseHandle(self._handles.pop(0)) # .pop() is thread safe. + except IndexError: + break + + def __enter__(self): + """Entering the `with` block. Runs the process.""" + if not self._kernel32.CreateProcessA( + None, # lpApplicationName + self.command_str, # lpCommandLine + None, # lpProcessAttributes + None, # lpThreadAttributes + False, # bInheritHandles + subprocess.CREATE_NEW_CONSOLE, # dwCreationFlags + None, # lpEnvironment + str(PROJECT_ROOT).encode('ascii'), # lpCurrentDirectory + ctypes.byref(self.startup_info), # lpStartupInfo + ctypes.byref(self.process_info) # lpProcessInformation + ): + raise ctypes.WinError() + + # Add handles added by the OS. + self._handles.append(self.process_info.hProcess) + self._handles.append(self.process_info.hThread) + + # Get hWnd. + self.hwnd = 0 + for _ in range(int(5 / 0.1)): + # Takes time for console window to initialize. + self.hwnd = ctypes.windll.user32.FindWindowA(None, self.startup_info.lpTitle) + if self.hwnd: + break + time.sleep(0.1) + assert self.hwnd + + # Return generator that yields window size/position. + return self._iter_pos() + + def __exit__(self, *_): + """Cleanup.""" + try: + # Verify process exited 0. + status = ctypes.c_ulong(STILL_ACTIVE) + while status.value == STILL_ACTIVE: + time.sleep(0.1) + if not self._kernel32.GetExitCodeProcess(self.process_info.hProcess, ctypes.byref(status)): + raise ctypes.WinError() + assert status.value == 0 + finally: + # Close handles. + self.__del__() + + def _iter_pos(self): + """Yield new console window's current position and dimensions. + + :return: Yields region the new window is in (left, upper, right, lower). + :rtype: tuple + """ + rect = ctypes.create_string_buffer(16) # To be written to by GetWindowRect. RECT structure. + while ctypes.windll.user32.GetWindowRect(self.hwnd, rect): + left, top, right, bottom = struct.unpack('llll', rect.raw) + width, height = right - left, bottom - top + assert width > 1 + assert height > 1 + yield left, top, right, bottom + raise StopIteration + + +def iter_rows(pil_image): + """Yield tuple of pixels for each row in the image. + + itertools.izip in Python 2.x and zip in Python 3.x are writen in C. Much faster than anything else I've found + written in pure Python. + + From: + http://stackoverflow.com/questions/1624883/alternative-way-to-split-a-list-into-groups-of-n/1625023#1625023 + + :param PIL.Image.Image pil_image: Image to read from. + + :return: Yields rows. + :rtype: tuple + """ + iterator = izip(*(iter(pil_image.getdata()),) * pil_image.width) + for row in iterator: + yield row + + +def get_most_interesting_row(pil_image): + """Look for a row in the image that has the most unique pixels. + + :param PIL.Image.Image pil_image: Image to read from. + + :return: Row (tuple of pixel tuples), row as a set, first pixel tuple, y offset from top. + :rtype: tuple + """ + final = (None, set(), None, None) # row, row_set, first_pixel, y_pos + for y_pos, row in enumerate(iter_rows(pil_image)): + row_set = set(row) + if len(row_set) > len(final[1]): + final = row, row_set, row[0], y_pos + if len(row_set) == pil_image.width: + break # Can't get bigger. + return final + + +def count_subimages(screenshot, subimg): + """Check how often subimg appears in the screenshot image. + + :param PIL.Image.Image screenshot: Screen shot to search through. + :param PIL.Image.Image subimg: Subimage to search for. + + :return: Number of times subimg appears in the screenshot. + :rtype: int + """ + # Get row to search for. + si_pixels = list(subimg.getdata()) # Load entire subimg into memory. + si_width = subimg.width + si_height = subimg.height + si_row, si_row_set, si_pixel, si_y = get_most_interesting_row(subimg) + occurrences = 0 + + # Look for subimg row in screenshot, then crop and compare pixel arrays. + for y_pos, row in enumerate(iter_rows(screenshot)): + if si_row_set - set(row): + continue # Some pixels not found. + for x_pos in range(screenshot.width - si_width + 1): + if row[x_pos] != si_pixel: + continue # First pixel does not match. + if row[x_pos:x_pos + si_width] != si_row: + continue # Row does not match. + # Found match for interesting row of subimg in screenshot. + y_corrected = y_pos - si_y + with screenshot.crop((x_pos, y_corrected, x_pos + si_width, y_corrected + si_height)) as cropped: + if list(cropped.getdata()) == si_pixels: + occurrences += 1 + + return occurrences + + +def try_candidates(screenshot, subimg_candidates, expected_count): + """Call count_subimages() for each subimage candidate until. + + If you get ImportError run "pip install pillow". Only OSX and Windows is supported. + + :param PIL.Image.Image screenshot: Screen shot to search through. + :param iter subimg_candidates: Subimage paths to look for. List of strings. + :param int expected_count: Try until any a subimage candidate is found this many times. + + :return: Number of times subimg appears in the screenshot. + :rtype: int + """ + from PIL import Image + count_found = 0 + + for subimg_path in subimg_candidates: + with Image.open(subimg_path) as rgba_s: + with rgba_s.convert(mode='RGB') as subimg: + # Make sure subimage isn't too large. + assert subimg.width < 256 + assert subimg.height < 256 + + # Count. + count_found = count_subimages(screenshot, subimg) + if count_found == expected_count: + break # No need to try other candidates. + + return count_found + + +def screenshot_until_match(save_to, timeout, subimg_candidates, expected_count, gen): + """Take screenshots until one of the 'done' subimages is found. Image is saved when subimage found or at timeout. + + If you get ImportError run "pip install pillow". Only OSX and Windows is supported. + + :param str save_to: Save screenshot to this PNG file path when expected count found or timeout. + :param int timeout: Give up after these many seconds. + :param iter subimg_candidates: Subimage paths to look for. List of strings. + :param int expected_count: Keep trying until any of subimg_candidates is found this many times. + :param iter gen: Generator yielding window position and size to crop screenshot to. + """ + from PIL import ImageGrab + assert save_to.endswith('.png') + stop_after = time.time() + timeout + + # Take screenshots until subimage is found. + while True: + with ImageGrab.grab(next(gen)) as rgba: + with rgba.convert(mode='RGB') as screenshot: + count_found = try_candidates(screenshot, subimg_candidates, expected_count) + if count_found == expected_count or time.time() > stop_after: + screenshot.save(save_to) + assert count_found == expected_count + return + time.sleep(0.5) diff --git a/tests/test_all_tables_e2e/__init__.py b/tests/test_all_tables_e2e/__init__.py new file mode 100644 index 0000000..785cc5a --- /dev/null +++ b/tests/test_all_tables_e2e/__init__.py @@ -0,0 +1 @@ +"""Allows importing from screenshot.""" diff --git a/tests/test_all_tables_e2e/sub_ascii_win10.bmp b/tests/test_all_tables_e2e/sub_ascii_win10.bmp Binary files differnew file mode 100644 index 0000000..fe21fa7 --- /dev/null +++ b/tests/test_all_tables_e2e/sub_ascii_win10.bmp diff --git a/tests/test_all_tables_e2e/sub_ascii_winxp.bmp b/tests/test_all_tables_e2e/sub_ascii_winxp.bmp Binary files differnew file mode 100644 index 0000000..4105d11 --- /dev/null +++ b/tests/test_all_tables_e2e/sub_ascii_winxp.bmp diff --git a/tests/test_all_tables_e2e/sub_double_win10.bmp b/tests/test_all_tables_e2e/sub_double_win10.bmp Binary files differnew file mode 100644 index 0000000..e6b00ae --- /dev/null +++ b/tests/test_all_tables_e2e/sub_double_win10.bmp diff --git a/tests/test_all_tables_e2e/sub_double_win10b.bmp b/tests/test_all_tables_e2e/sub_double_win10b.bmp Binary files differnew file mode 100644 index 0000000..a527959 --- /dev/null +++ b/tests/test_all_tables_e2e/sub_double_win10b.bmp diff --git a/tests/test_all_tables_e2e/sub_double_winxp.bmp b/tests/test_all_tables_e2e/sub_double_winxp.bmp Binary files differnew file mode 100644 index 0000000..aae7b24 --- /dev/null +++ b/tests/test_all_tables_e2e/sub_double_winxp.bmp diff --git a/tests/test_all_tables_e2e/sub_single_win10.bmp b/tests/test_all_tables_e2e/sub_single_win10.bmp Binary files differnew file mode 100644 index 0000000..ff6f272 --- /dev/null +++ b/tests/test_all_tables_e2e/sub_single_win10.bmp diff --git a/tests/test_all_tables_e2e/sub_single_win10b.bmp b/tests/test_all_tables_e2e/sub_single_win10b.bmp Binary files differnew file mode 100644 index 0000000..c8d1e36 --- /dev/null +++ b/tests/test_all_tables_e2e/sub_single_win10b.bmp diff --git a/tests/test_all_tables_e2e/sub_single_winxp.bmp b/tests/test_all_tables_e2e/sub_single_winxp.bmp Binary files differnew file mode 100644 index 0000000..c4f5873 --- /dev/null +++ b/tests/test_all_tables_e2e/sub_single_winxp.bmp diff --git a/tests/test_all_tables_e2e/test_ascii_table.py b/tests/test_all_tables_e2e/test_ascii_table.py new file mode 100644 index 0000000..51ebc2a --- /dev/null +++ b/tests/test_all_tables_e2e/test_ascii_table.py @@ -0,0 +1,145 @@ +"""AsciiTable end to end testing.""" + +import sys +from textwrap import dedent + +import py +import pytest + +from terminaltables import AsciiTable +from terminaltables.terminal_io import IS_WINDOWS +from tests import PROJECT_ROOT +from tests.screenshot import RunNewConsole, screenshot_until_match + +HERE = py.path.local(__file__).dirpath() + + +def test_single_line(): + """Test single-lined cells.""" + table_data = [ + ['Name', 'Color', 'Type'], + ['Avocado', 'green', 'nut'], + ['Tomato', 'red', 'fruit'], + ['Lettuce', 'green', 'vegetable'], + ['Watermelon', 'green'], + [], + ] + table = AsciiTable(table_data, 'Example') + table.inner_footing_row_border = True + table.justify_columns[0] = 'left' + table.justify_columns[1] = 'center' + table.justify_columns[2] = 'right' + actual = table.table + + expected = ( + '+Example-----+-------+-----------+\n' + '| Name | Color | Type |\n' + '+------------+-------+-----------+\n' + '| Avocado | green | nut |\n' + '| Tomato | red | fruit |\n' + '| Lettuce | green | vegetable |\n' + '| Watermelon | green | |\n' + '+------------+-------+-----------+\n' + '| | | |\n' + '+------------+-------+-----------+' + ) + assert actual == expected + + +def test_multi_line(): + """Test multi-lined cells.""" + table_data = [ + ['Show', 'Characters'], + ['Rugrats', 'Tommy Pickles, Chuckie Finster, Phillip DeVille, Lillian DeVille, Angelica Pickles,\nDil Pickles'], + ['South Park', 'Stan Marsh, Kyle Broflovski, Eric Cartman, Kenny McCormick'] + ] + table = AsciiTable(table_data) + + # Test defaults. + actual = table.table + expected = ( + '+------------+-------------------------------------------------------------------------------------+\n' + '| Show | Characters |\n' + '+------------+-------------------------------------------------------------------------------------+\n' + '| Rugrats | Tommy Pickles, Chuckie Finster, Phillip DeVille, Lillian DeVille, Angelica Pickles, |\n' + '| | Dil Pickles |\n' + '| South Park | Stan Marsh, Kyle Broflovski, Eric Cartman, Kenny McCormick |\n' + '+------------+-------------------------------------------------------------------------------------+' + ) + assert actual == expected + + # Test inner row border. + table.inner_row_border = True + actual = table.table + expected = ( + '+------------+-------------------------------------------------------------------------------------+\n' + '| Show | Characters |\n' + '+------------+-------------------------------------------------------------------------------------+\n' + '| Rugrats | Tommy Pickles, Chuckie Finster, Phillip DeVille, Lillian DeVille, Angelica Pickles, |\n' + '| | Dil Pickles |\n' + '+------------+-------------------------------------------------------------------------------------+\n' + '| South Park | Stan Marsh, Kyle Broflovski, Eric Cartman, Kenny McCormick |\n' + '+------------+-------------------------------------------------------------------------------------+' + ) + assert actual == expected + + # Justify right. + table.justify_columns = {1: 'right'} + actual = table.table + expected = ( + '+------------+-------------------------------------------------------------------------------------+\n' + '| Show | Characters |\n' + '+------------+-------------------------------------------------------------------------------------+\n' + '| Rugrats | Tommy Pickles, Chuckie Finster, Phillip DeVille, Lillian DeVille, Angelica Pickles, |\n' + '| | Dil Pickles |\n' + '+------------+-------------------------------------------------------------------------------------+\n' + '| South Park | Stan Marsh, Kyle Broflovski, Eric Cartman, Kenny McCormick |\n' + '+------------+-------------------------------------------------------------------------------------+' + ) + assert actual == expected + + +@pytest.mark.skipif(str(not IS_WINDOWS)) +@pytest.mark.skipif('True') # https://github.com/Robpol86/terminaltables/issues/30 +def test_windows_screenshot(tmpdir): + """Test on Windows in a new console window. Take a screenshot to verify it works. + + :param tmpdir: pytest fixture. + """ + script = tmpdir.join('script.py') + command = [sys.executable, str(script)] + screenshot = PROJECT_ROOT.join('test_ascii_table.png') + if screenshot.check(): + screenshot.remove() + + # Generate script. + script_template = dedent(u"""\ + from __future__ import print_function + import os, time + from colorclass import Color, Windows + from terminaltables import AsciiTable + Windows.enable(auto_colors=True) + stop_after = time.time() + 20 + + table_data = [ + [Color('{b}Name{/b}'), Color('{b}Color{/b}'), Color('{b}Misc{/b}')], + ['Avocado', Color('{autogreen}green{/fg}'), 100], + ['Tomato', Color('{autored}red{/fg}'), 0.5], + ['Lettuce', Color('{autogreen}green{/fg}'), None], + ] + print(AsciiTable(table_data).table) + + print('Waiting for screenshot_until_match()...') + while not os.path.exists(r'%s') and time.time() < stop_after: + time.sleep(0.5) + """) + script_contents = script_template % str(screenshot) + script.write(script_contents.encode('utf-8'), mode='wb') + + # Setup expected. + sub_images = [str(p) for p in HERE.listdir('sub_ascii_*.bmp')] + assert sub_images + + # Run. + with RunNewConsole(command) as gen: + screenshot_until_match(str(screenshot), 15, sub_images, 1, gen) diff --git a/tests/test_all_tables_e2e/test_double_table.py b/tests/test_all_tables_e2e/test_double_table.py new file mode 100644 index 0000000..892357a --- /dev/null +++ b/tests/test_all_tables_e2e/test_double_table.py @@ -0,0 +1,245 @@ +"""DoubleTable end to end testing.""" + +import sys +from textwrap import dedent + +import py +import pytest + +from terminaltables import DoubleTable +from terminaltables.terminal_io import IS_WINDOWS +from tests import PROJECT_ROOT +from tests.screenshot import RunNewConsole, screenshot_until_match + +HERE = py.path.local(__file__).dirpath() + + +def test_single_line(): + """Test single-lined cells.""" + table_data = [ + ['Name', 'Color', 'Type'], + ['Avocado', 'green', 'nut'], + ['Tomato', 'red', 'fruit'], + ['Lettuce', 'green', 'vegetable'], + ['Watermelon', 'green'], + [], + ] + table = DoubleTable(table_data, 'Example') + table.inner_footing_row_border = True + table.justify_columns[0] = 'left' + table.justify_columns[1] = 'center' + table.justify_columns[2] = 'right' + actual = table.table + + expected = ( + u'\u2554Example\u2550\u2550\u2550\u2550\u2550\u2566\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2566\u2550\u2550' + u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557\n' + + u'\u2551 Name \u2551 Color \u2551 Type \u2551\n' + + u'\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u256c\u2550\u2550\u2550\u2550' + u'\u2550\u2550\u2550\u256c\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563\n' + + u'\u2551 Avocado \u2551 green \u2551 nut \u2551\n' + + u'\u2551 Tomato \u2551 red \u2551 fruit \u2551\n' + + u'\u2551 Lettuce \u2551 green \u2551 vegetable \u2551\n' + + u'\u2551 Watermelon \u2551 green \u2551 \u2551\n' + + u'\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u256c\u2550\u2550\u2550\u2550' + u'\u2550\u2550\u2550\u256c\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563\n' + + u'\u2551 \u2551 \u2551 \u2551\n' + + u'\u255a\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2569\u2550\u2550\u2550\u2550' + u'\u2550\u2550\u2550\u2569\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255d' + ) + assert actual == expected + + +def test_multi_line(): + """Test multi-lined cells.""" + table_data = [ + ['Show', 'Characters'], + ['Rugrats', 'Tommy Pickles, Chuckie Finster, Phillip DeVille, Lillian DeVille, Angelica Pickles,\nDil Pickles'], + ['South Park', 'Stan Marsh, Kyle Broflovski, Eric Cartman, Kenny McCormick'] + ] + table = DoubleTable(table_data) + + # Test defaults. + actual = table.table + expected = ( + u'\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2566\u2550\u2550\u2550\u2550' + u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550' + u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550' + u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550' + u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550' + u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557\n' + + u'\u2551 Show \u2551 Characters ' + u'\u2551\n' + + u'\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u256c\u2550\u2550\u2550\u2550' + u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550' + u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550' + u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550' + u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550' + u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563\n' + + u'\u2551 Rugrats \u2551 Tommy Pickles, Chuckie Finster, Phillip DeVille, Lillian DeVille, Angelica Pickles, ' + u'\u2551\n' + + u'\u2551 \u2551 Dil Pickles ' + u'\u2551\n' + + u'\u2551 South Park \u2551 Stan Marsh, Kyle Broflovski, Eric Cartman, Kenny McCormick ' + u'\u2551\n' + + u'\u255a\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2569\u2550\u2550\u2550\u2550' + u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550' + u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550' + u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550' + u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550' + u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255d' + ) + assert actual == expected + + # Test inner row border. + table.inner_row_border = True + actual = table.table + expected = ( + u'\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2566\u2550\u2550\u2550\u2550' + u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550' + u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550' + u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550' + u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550' + u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557\n' + + u'\u2551 Show \u2551 Characters ' + u'\u2551\n' + + u'\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u256c\u2550\u2550\u2550\u2550' + u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550' + u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550' + u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550' + u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550' + u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563\n' + + u'\u2551 Rugrats \u2551 Tommy Pickles, Chuckie Finster, Phillip DeVille, Lillian DeVille, Angelica Pickles, ' + u'\u2551\n' + + u'\u2551 \u2551 Dil Pickles ' + u'\u2551\n' + + u'\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u256c\u2550\u2550\u2550\u2550' + u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550' + u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550' + u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550' + u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550' + u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563\n' + + u'\u2551 South Park \u2551 Stan Marsh, Kyle Broflovski, Eric Cartman, Kenny McCormick ' + u'\u2551\n' + + u'\u255a\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2569\u2550\u2550\u2550\u2550' + u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550' + u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550' + u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550' + u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550' + u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255d' + ) + assert actual == expected + + # Justify right. + table.justify_columns = {1: 'right'} + actual = table.table + expected = ( + u'\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2566\u2550\u2550\u2550\u2550' + u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550' + u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550' + u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550' + u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550' + u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557\n' + + u'\u2551 Show \u2551 Characters ' + u'\u2551\n' + + u'\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u256c\u2550\u2550\u2550\u2550' + u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550' + u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550' + u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550' + u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550' + u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563\n' + + u'\u2551 Rugrats \u2551 Tommy Pickles, Chuckie Finster, Phillip DeVille, Lillian DeVille, Angelica Pickles, ' + u'\u2551\n' + + u'\u2551 \u2551 Dil Pickles ' + u'\u2551\n' + + u'\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u256c\u2550\u2550\u2550\u2550' + u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550' + u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550' + u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550' + u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550' + u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563\n' + + u'\u2551 South Park \u2551 Stan Marsh, Kyle Broflovski, Eric Cartman, Kenny McCormick ' + u'\u2551\n' + + u'\u255a\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2569\u2550\u2550\u2550\u2550' + u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550' + u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550' + u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550' + u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550' + u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255d' + ) + assert actual == expected + + +@pytest.mark.skipif(str(not IS_WINDOWS)) +@pytest.mark.skipif('True') # https://github.com/Robpol86/terminaltables/issues/30 +def test_windows_screenshot(tmpdir): + """Test on Windows in a new console window. Take a screenshot to verify it works. + + :param tmpdir: pytest fixture. + """ + script = tmpdir.join('script.py') + command = [sys.executable, str(script)] + screenshot = PROJECT_ROOT.join('test_double_table.png') + if screenshot.check(): + screenshot.remove() + + # Generate script. + script_template = dedent(u"""\ + from __future__ import print_function + import os, time + from colorclass import Color, Windows + from terminaltables import DoubleTable + Windows.enable(auto_colors=True) + stop_after = time.time() + 20 + + table_data = [ + [Color('{b}Name{/b}'), Color('{b}Color{/b}'), Color('{b}Misc{/b}')], + ['Avocado', Color('{autogreen}green{/fg}'), 100], + ['Tomato', Color('{autored}red{/fg}'), 0.5], + ['Lettuce', Color('{autogreen}green{/fg}'), None], + ] + print(DoubleTable(table_data).table) + + print('Waiting for screenshot_until_match()...') + while not os.path.exists(r'%s') and time.time() < stop_after: + time.sleep(0.5) + """) + script_contents = script_template % str(screenshot) + script.write(script_contents.encode('utf-8'), mode='wb') + + # Setup expected. + sub_images = [str(p) for p in HERE.listdir('sub_double_*.bmp')] + assert sub_images + + # Run. + with RunNewConsole(command) as gen: + screenshot_until_match(str(screenshot), 15, sub_images, 1, gen) diff --git a/tests/test_all_tables_e2e/test_github_table.py b/tests/test_all_tables_e2e/test_github_table.py new file mode 100644 index 0000000..6176215 --- /dev/null +++ b/tests/test_all_tables_e2e/test_github_table.py @@ -0,0 +1,77 @@ +"""GithubFlavoredMarkdownTable end to end testing.""" + +from terminaltables import GithubFlavoredMarkdownTable + + +def test_single_line(): + """Test single-lined cells.""" + table_data = [ + ['Name', 'Color', 'Type'], + ['Avocado', 'green', 'nut'], + ['Tomato', 'red', 'fruit'], + ['Lettuce', 'green', 'vegetable'], + ['Watermelon', 'green'], + [], + ] + table = GithubFlavoredMarkdownTable(table_data) + table.inner_footing_row_border = True + table.justify_columns[0] = 'left' + table.justify_columns[1] = 'center' + table.justify_columns[2] = 'right' + actual = table.table + + expected = ( + '| Name | Color | Type |\n' + '|:-----------|:-----:|----------:|\n' + '| Avocado | green | nut |\n' + '| Tomato | red | fruit |\n' + '| Lettuce | green | vegetable |\n' + '| Watermelon | green | |\n' + '| | | |' + ) + assert actual == expected + + +def test_multi_line(): + """Test multi-lined cells.""" + table_data = [ + ['Show', 'Characters'], + ['Rugrats', 'Tommy Pickles, Chuckie Finster, Phillip DeVille, Lillian DeVille, Angelica Pickles,\nDil Pickles'], + ['South Park', 'Stan Marsh, Kyle Broflovski, Eric Cartman, Kenny McCormick'] + ] + table = GithubFlavoredMarkdownTable(table_data) + + # Test defaults. + actual = table.table + expected = ( + '| Show | Characters |\n' + '|------------|-------------------------------------------------------------------------------------|\n' + '| Rugrats | Tommy Pickles, Chuckie Finster, Phillip DeVille, Lillian DeVille, Angelica Pickles, |\n' + '| | Dil Pickles |\n' + '| South Park | Stan Marsh, Kyle Broflovski, Eric Cartman, Kenny McCormick |' + ) + assert actual == expected + + # Test inner row border. + table.inner_row_border = True + actual = table.table + expected = ( + '| Show | Characters |\n' + '|------------|-------------------------------------------------------------------------------------|\n' + '| Rugrats | Tommy Pickles, Chuckie Finster, Phillip DeVille, Lillian DeVille, Angelica Pickles, |\n' + '| | Dil Pickles |\n' + '| South Park | Stan Marsh, Kyle Broflovski, Eric Cartman, Kenny McCormick |' + ) + assert actual == expected + + # Justify right. + table.justify_columns = {1: 'right'} + actual = table.table + expected = ( + '| Show | Characters |\n' + '|------------|------------------------------------------------------------------------------------:|\n' + '| Rugrats | Tommy Pickles, Chuckie Finster, Phillip DeVille, Lillian DeVille, Angelica Pickles, |\n' + '| | Dil Pickles |\n' + '| South Park | Stan Marsh, Kyle Broflovski, Eric Cartman, Kenny McCormick |' + ) + assert actual == expected diff --git a/tests/test_all_tables_e2e/test_porcelain_table.py b/tests/test_all_tables_e2e/test_porcelain_table.py new file mode 100644 index 0000000..7677188 --- /dev/null +++ b/tests/test_all_tables_e2e/test_porcelain_table.py @@ -0,0 +1,59 @@ +"""PorcelainTable end to end testing.""" + +from terminaltables import PorcelainTable + + +def test_single_line(): + """Test single-lined cells.""" + table_data = [ + ['Name', 'Color', 'Type'], + ['Avocado', 'green', 'nut'], + ['Tomato', 'red', 'fruit'], + ['Lettuce', 'green', 'vegetable'], + ['Watermelon', 'green'] + ] + table = PorcelainTable(table_data) + table.justify_columns[0] = 'left' + table.justify_columns[1] = 'center' + table.justify_columns[2] = 'right' + actual = table.table + + expected = ( + ' Name | Color | Type \n' + ' Avocado | green | nut \n' + ' Tomato | red | fruit \n' + ' Lettuce | green | vegetable \n' + ' Watermelon | green | ' + ) + assert actual == expected + + +def test_multi_line(): + """Test multi-lined cells.""" + table_data = [ + ['Show', 'Characters'], + ['Rugrats', 'Tommy Pickles, Chuckie Finster, Phillip DeVille, Lillian DeVille, Angelica Pickles,\nDil Pickles'], + ['South Park', 'Stan Marsh, Kyle Broflovski, Eric Cartman, Kenny McCormick'] + ] + table = PorcelainTable(table_data) + + # Test defaults. + actual = table.table + expected = ( + ' Show | Characters \n' + ' Rugrats | Tommy Pickles, Chuckie Finster, Phillip DeVille, Lillian DeVille, Angelica Pickles, \n' + ' | Dil Pickles \n' + ' South Park | Stan Marsh, Kyle Broflovski, Eric Cartman, Kenny McCormick ' + ) + assert actual == expected + + # Justify right. + table.justify_columns = {1: 'right'} + actual = table.table + expected = ( + ' Show | Characters \n' + ' Rugrats | Tommy Pickles, Chuckie Finster, Phillip DeVille, Lillian DeVille, Angelica Pickles, \n' + ' | Dil Pickles \n' + ' South Park | Stan Marsh, Kyle Broflovski, Eric Cartman, Kenny McCormick ' + ) + assert actual == expected diff --git a/tests/test_all_tables_e2e/test_single_table.py b/tests/test_all_tables_e2e/test_single_table.py new file mode 100644 index 0000000..f4fa6b9 --- /dev/null +++ b/tests/test_all_tables_e2e/test_single_table.py @@ -0,0 +1,171 @@ +"""SingleTable end to end testing on Linux/OSX.""" + +import pytest + +from terminaltables import SingleTable +from terminaltables.terminal_io import IS_WINDOWS + +pytestmark = pytest.mark.skipif(str(IS_WINDOWS)) + + +def test_single_line(): + """Test single-lined cells.""" + table_data = [ + ['Name', 'Color', 'Type'], + ['Avocado', 'green', 'nut'], + ['Tomato', 'red', 'fruit'], + ['Lettuce', 'green', 'vegetable'], + ['Watermelon', 'green'], + [], + ] + table = SingleTable(table_data, 'Example') + table.inner_footing_row_border = True + table.justify_columns[0] = 'left' + table.justify_columns[1] = 'center' + table.justify_columns[2] = 'right' + actual = table.table + + expected = ( + '\033(0\x6c\033(BExample\033(0\x71\x71\x71\x71\x71\x77\x71\x71\x71\x71\x71\x71\x71\x77\x71\x71\x71\x71\x71\x71' + '\x71\x71\x71\x71\x71\x6b\033(B\n' + + '\033(0\x78\033(B Name \033(0\x78\033(B Color \033(0\x78\033(B Type \033(0\x78\033(B\n' + + '\033(0\x74\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x6e\x71\x71\x71\x71\x71\x71\x71\x6e\x71\x71\x71\x71' + '\x71\x71\x71\x71\x71\x71\x71\x75\033(B\n' + + '\033(0\x78\033(B Avocado \033(0\x78\033(B green \033(0\x78\033(B nut \033(0\x78\033(B\n' + + '\033(0\x78\033(B Tomato \033(0\x78\033(B red \033(0\x78\033(B fruit \033(0\x78\033(B\n' + + '\033(0\x78\033(B Lettuce \033(0\x78\033(B green \033(0\x78\033(B vegetable \033(0\x78\033(B\n' + + '\033(0\x78\033(B Watermelon \033(0\x78\033(B green \033(0\x78\033(B \033(0\x78\033(B\n' + + '\033(0\x74\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x6e\x71\x71\x71\x71\x71\x71\x71\x6e\x71\x71\x71\x71' + '\x71\x71\x71\x71\x71\x71\x71\x75\033(B\n' + + '\033(0\x78\033(B \033(0\x78\033(B \033(0\x78\033(B \033(0\x78\033(B\n' + + '\033(0\x6d\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x76\x71\x71\x71\x71\x71\x71\x71\x76\x71\x71\x71\x71' + '\x71\x71\x71\x71\x71\x71\x71\x6a\033(B' + ) + assert actual == expected + + +def test_multi_line(): + """Test multi-lined cells.""" + table_data = [ + ['Show', 'Characters'], + ['Rugrats', 'Tommy Pickles, Chuckie Finster, Phillip DeVille, Lillian DeVille, Angelica Pickles,\nDil Pickles'], + ['South Park', 'Stan Marsh, Kyle Broflovski, Eric Cartman, Kenny McCormick'] + ] + table = SingleTable(table_data) + + # Test defaults. + actual = table.table + expected = ( + '\033(0\x6c\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x77\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71' + '\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71' + '\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71' + '\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x6b\033(B\n' + + '\033(0\x78\033(B Show \033(0\x78\033(B Characters ' + ' \033(0\x78\033(B\n' + + '\033(0\x74\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x6e\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71' + '\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71' + '\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71' + '\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x75\033(B\n' + + '\033(0\x78\033(B Rugrats \033(0\x78\033(B Tommy Pickles, Chuckie Finster, Phillip DeVille, Lillian DeVille,' + ' Angelica Pickles, \033(0\x78\033(B\n' + + '\033(0\x78\033(B \033(0\x78\033(B Dil Pickles ' + ' \033(0\x78\033(B\n' + + '\033(0\x78\033(B South Park \033(0\x78\033(B Stan Marsh, Kyle Broflovski, Eric Cartman, Kenny McCormick ' + ' \033(0\x78\033(B\n' + + '\033(0\x6d\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x76\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71' + '\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71' + '\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71' + '\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x6a\033(B' + ) + assert actual == expected + + # Test inner row border. + table.inner_row_border = True + actual = table.table + expected = ( + '\033(0\x6c\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x77\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71' + '\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71' + '\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71' + '\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x6b\033(B\n' + + '\033(0\x78\033(B Show \033(0\x78\033(B Characters ' + ' \033(0\x78\033(B\n' + + '\033(0\x74\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x6e\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71' + '\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71' + '\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71' + '\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x75\033(B\n' + + '\033(0\x78\033(B Rugrats \033(0\x78\033(B Tommy Pickles, Chuckie Finster, Phillip DeVille, Lillian DeVille,' + ' Angelica Pickles, \033(0\x78\033(B\n' + + '\033(0\x78\033(B \033(0\x78\033(B Dil Pickles ' + ' \033(0\x78\033(B\n' + + '\033(0\x74\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x6e\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71' + '\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71' + '\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71' + '\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x75\033(B\n' + + '\033(0\x78\033(B South Park \033(0\x78\033(B Stan Marsh, Kyle Broflovski, Eric Cartman, Kenny McCormick ' + ' \033(0\x78\033(B\n' + + '\033(0\x6d\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x76\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71' + '\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71' + '\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71' + '\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x6a\033(B' + ) + assert actual == expected + + # Justify right. + table.justify_columns = {1: 'right'} + actual = table.table + expected = ( + '\033(0\x6c\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x77\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71' + '\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71' + '\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71' + '\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x6b\033(B\n' + + '\033(0\x78\033(B Show \033(0\x78\033(B ' + ' Characters \033(0\x78\033(B\n' + + '\033(0\x74\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x6e\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71' + '\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71' + '\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71' + '\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x75\033(B\n' + + '\033(0\x78\033(B Rugrats \033(0\x78\033(B Tommy Pickles, Chuckie Finster, Phillip DeVille, Lillian DeVille,' + ' Angelica Pickles, \033(0\x78\033(B\n' + + '\033(0\x78\033(B \033(0\x78\033(B ' + ' Dil Pickles \033(0\x78\033(B\n' + + '\033(0\x74\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x6e\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71' + '\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71' + '\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71' + '\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x75\033(B\n' + + '\033(0\x78\033(B South Park \033(0\x78\033(B Stan Marsh, Kyle Broflovski, ' + 'Eric Cartman, Kenny McCormick \033(0\x78\033(B\n' + + '\033(0\x6d\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x76\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71' + '\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71' + '\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71' + '\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x6a\033(B' + ) + assert actual == expected diff --git a/tests/test_all_tables_e2e/test_single_table_windows.py b/tests/test_all_tables_e2e/test_single_table_windows.py new file mode 100644 index 0000000..a15fa3a --- /dev/null +++ b/tests/test_all_tables_e2e/test_single_table_windows.py @@ -0,0 +1,246 @@ +"""SingleTable end to end testing on Windows.""" + +import sys +from textwrap import dedent + +import py +import pytest + +from terminaltables import SingleTable +from terminaltables.terminal_io import IS_WINDOWS +from tests import PROJECT_ROOT +from tests.screenshot import RunNewConsole, screenshot_until_match + +HERE = py.path.local(__file__).dirpath() +pytestmark = pytest.mark.skipif(str(not IS_WINDOWS)) + + +def test_single_line(): + """Test single-lined cells.""" + table_data = [ + ['Name', 'Color', 'Type'], + ['Avocado', 'green', 'nut'], + ['Tomato', 'red', 'fruit'], + ['Lettuce', 'green', 'vegetable'], + ['Watermelon', 'green'], + [], + ] + table = SingleTable(table_data, 'Example') + table.inner_footing_row_border = True + table.justify_columns[0] = 'left' + table.justify_columns[1] = 'center' + table.justify_columns[2] = 'right' + actual = table.table + + expected = ( + u'\u250cExample\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500' + u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n' + + u'\u2502 Name \u2502 Color \u2502 Type \u2502\n' + + u'\u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500' + u'\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n' + + u'\u2502 Avocado \u2502 green \u2502 nut \u2502\n' + + u'\u2502 Tomato \u2502 red \u2502 fruit \u2502\n' + + u'\u2502 Lettuce \u2502 green \u2502 vegetable \u2502\n' + + u'\u2502 Watermelon \u2502 green \u2502 \u2502\n' + + u'\u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500' + u'\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n' + + u'\u2502 \u2502 \u2502 \u2502\n' + + u'\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500' + u'\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518' + ) + assert actual == expected + + +def test_multi_line(): + """Test multi-lined cells.""" + table_data = [ + ['Show', 'Characters'], + ['Rugrats', 'Tommy Pickles, Chuckie Finster, Phillip DeVille, Lillian DeVille, Angelica Pickles,\nDil Pickles'], + ['South Park', 'Stan Marsh, Kyle Broflovski, Eric Cartman, Kenny McCormick'] + ] + table = SingleTable(table_data) + + # Test defaults. + actual = table.table + expected = ( + u'\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500' + u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500' + u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500' + u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500' + u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500' + u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n' + + u'\u2502 Show \u2502 Characters ' + u'\u2502\n' + + u'\u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500' + u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500' + u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500' + u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500' + u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500' + u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n' + + u'\u2502 Rugrats \u2502 Tommy Pickles, Chuckie Finster, Phillip DeVille, Lillian DeVille, Angelica Pickles, ' + u'\u2502\n' + + u'\u2502 \u2502 Dil Pickles ' + u'\u2502\n' + + u'\u2502 South Park \u2502 Stan Marsh, Kyle Broflovski, Eric Cartman, Kenny McCormick ' + u'\u2502\n' + + u'\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500' + u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500' + u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500' + u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500' + u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500' + u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518' + ) + assert actual == expected + + # Test inner row border. + table.inner_row_border = True + actual = table.table + expected = ( + u'\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500' + u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500' + u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500' + u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500' + u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500' + u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n' + + u'\u2502 Show \u2502 Characters ' + u'\u2502\n' + + u'\u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500' + u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500' + u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500' + u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500' + u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500' + u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n' + + u'\u2502 Rugrats \u2502 Tommy Pickles, Chuckie Finster, Phillip DeVille, Lillian DeVille, Angelica Pickles, ' + u'\u2502\n' + + u'\u2502 \u2502 Dil Pickles ' + u'\u2502\n' + + u'\u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500' + u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500' + u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500' + u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500' + u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500' + u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n' + + u'\u2502 South Park \u2502 Stan Marsh, Kyle Broflovski, Eric Cartman, Kenny McCormick ' + u'\u2502\n' + + u'\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500' + u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500' + u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500' + u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500' + u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500' + u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518' + ) + assert actual == expected + + # Justify right. + table.justify_columns = {1: 'right'} + actual = table.table + expected = ( + u'\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500' + u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500' + u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500' + u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500' + u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500' + u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n' + + u'\u2502 Show \u2502 Characters ' + u'\u2502\n' + + u'\u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500' + u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500' + u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500' + u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500' + u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500' + u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n' + + u'\u2502 Rugrats \u2502 Tommy Pickles, Chuckie Finster, Phillip DeVille, Lillian DeVille, Angelica Pickles, ' + u'\u2502\n' + + u'\u2502 \u2502 Dil Pickles ' + u'\u2502\n' + + u'\u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500' + u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500' + u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500' + u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500' + u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500' + u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n' + + u'\u2502 South Park \u2502 Stan Marsh, Kyle Broflovski, Eric Cartman, Kenny McCormick ' + u'\u2502\n' + + u'\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500' + u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500' + u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500' + u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500' + u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500' + u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518' + ) + assert actual == expected + + +@pytest.mark.skipif(str(not IS_WINDOWS)) +@pytest.mark.skipif('True') # https://github.com/Robpol86/terminaltables/issues/30 +def test_windows_screenshot(tmpdir): + """Test on Windows in a new console window. Take a screenshot to verify it works. + + :param tmpdir: pytest fixture. + """ + script = tmpdir.join('script.py') + command = [sys.executable, str(script)] + screenshot = PROJECT_ROOT.join('test_single_table.png') + if screenshot.check(): + screenshot.remove() + + # Generate script. + script_template = dedent(u"""\ + from __future__ import print_function + import os, time + from colorclass import Color, Windows + from terminaltables import SingleTable + Windows.enable(auto_colors=True) + stop_after = time.time() + 20 + + table_data = [ + [Color('{b}Name{/b}'), Color('{b}Color{/b}'), Color('{b}Misc{/b}')], + ['Avocado', Color('{autogreen}green{/fg}'), 100], + ['Tomato', Color('{autored}red{/fg}'), 0.5], + ['Lettuce', Color('{autogreen}green{/fg}'), None], + ] + print(SingleTable(table_data).table) + + print('Waiting for screenshot_until_match()...') + while not os.path.exists(r'%s') and time.time() < stop_after: + time.sleep(0.5) + """) + script_contents = script_template % str(screenshot) + script.write(script_contents.encode('utf-8'), mode='wb') + + # Setup expected. + sub_images = [str(p) for p in HERE.listdir('sub_single_*.bmp')] + assert sub_images + + # Run. + with RunNewConsole(command) as gen: + screenshot_until_match(str(screenshot), 15, sub_images, 1, gen) diff --git a/tests/test_ascii_table.py b/tests/test_ascii_table.py new file mode 100644 index 0000000..020a443 --- /dev/null +++ b/tests/test_ascii_table.py @@ -0,0 +1,108 @@ +"""Test AsciiTable class.""" + +import pytest + +from terminaltables.other_tables import AsciiTable + +SINGLE_LINE = ( + ('Name', 'Color', 'Type'), + ('Avocado', 'green', 'nut'), + ('Tomato', 'red', 'fruit'), + ('Lettuce', 'green', 'vegetable'), +) + +MULTI_LINE = ( + ('Show', 'Characters'), + ('Rugrats', 'Tommy Pickles, Chuckie Finster, Phillip DeVille, Lillian DeVille, Angelica Pickles,\nDil Pickles'), + ('South Park', 'Stan Marsh, Kyle Broflovski, Eric Cartman, Kenny McCormick'), +) + + +@pytest.fixture(autouse=True) +def patch(monkeypatch): + """Monkeypatch before every test function in this module. + + :param monkeypatch: pytest fixture. + """ + monkeypatch.setattr('terminaltables.ascii_table.terminal_size', lambda: (79, 24)) + monkeypatch.setattr('terminaltables.width_and_alignment.terminal_size', lambda: (79, 24)) + + +@pytest.mark.parametrize('table_data,column_number,expected', [ + ([], 0, IndexError), + ([[]], 0, IndexError), + ([['']], 1, IndexError), + (SINGLE_LINE, 0, 55), + (SINGLE_LINE, 1, 53), + (SINGLE_LINE, 2, 57), + (MULTI_LINE, 0, -11), + (MULTI_LINE, 1, 62), +]) +def test_column_max_width(table_data, column_number, expected): + """Test method in class. + + :param iter table_data: Passed to AsciiTable.__init__(). + :param int column_number: Passed to AsciiTable.column_max_width(). + :param int expected: Expected return value of AsciiTable.column_max_width(). + """ + table = AsciiTable(table_data) + + if expected == IndexError: + with pytest.raises(IndexError): + table.column_max_width(column_number) + return + + actual = table.column_max_width(column_number) + assert actual == expected + + +def test_column_widths(): + """Test method in class.""" + assert AsciiTable([]).column_widths == list() + + table = AsciiTable(SINGLE_LINE) + actual = table.column_widths + assert actual == [7, 5, 9] + + +@pytest.mark.parametrize('table_data,terminal_width,expected', [ + ([], None, True), + ([[]], None, True), + ([['']], None, True), + (SINGLE_LINE, None, True), + (SINGLE_LINE, 30, False), + (MULTI_LINE, None, False), + (MULTI_LINE, 100, True), +]) +def test_ok(monkeypatch, table_data, terminal_width, expected): + """Test method in class. + + :param monkeypatch: pytest fixture. + :param iter table_data: Passed to AsciiTable.__init__(). + :param int terminal_width: Monkeypatch width of terminal_size() if not None. + :param bool expected: Expected return value. + """ + if terminal_width is not None: + monkeypatch.setattr('terminaltables.ascii_table.terminal_size', lambda: (terminal_width, 24)) + table = AsciiTable(table_data) + actual = table.ok + assert actual is expected + + +@pytest.mark.parametrize('table_data,expected', [ + ([], 2), + ([[]], 2), + ([['']], 4), + ([[' ']], 5), + (SINGLE_LINE, 31), + (MULTI_LINE, 100), +]) +def test_table_width(table_data, expected): + """Test method in class. + + :param iter table_data: Passed to AsciiTable.__init__(). + :param int expected: Expected return value. + """ + table = AsciiTable(table_data) + actual = table.table_width + assert actual == expected diff --git a/tests/test_base_table/test_gen_row_lines.py b/tests/test_base_table/test_gen_row_lines.py new file mode 100644 index 0000000..0d0f43c --- /dev/null +++ b/tests/test_base_table/test_gen_row_lines.py @@ -0,0 +1,86 @@ +"""Test method in BaseTable class.""" + +import pytest + +from terminaltables.base_table import BaseTable + + +@pytest.mark.parametrize('style', ['heading', 'footing', 'row']) +def test_single_line(style): + """Test with single-line row. + + :param str style: Passed to method. + """ + row = ['Row One Column One', 'Two', 'Three'] + table = BaseTable([row]) + actual = [tuple(i) for i in table.gen_row_lines(row, style, [18, 3, 5], 1)] + expected = [ + ('|', ' Row One Column One ', '|', ' Two ', '|', ' Three ', '|'), + ] + assert actual == expected + + +@pytest.mark.parametrize('style', ['heading', 'footing', 'row']) +def test_multi_line(style): + """Test with multi-line row. + + :param str style: Passed to method. + """ + row = ['Row One\nColumn One', 'Two', 'Three'] + table = BaseTable([row]) + actual = [tuple(i) for i in table.gen_row_lines(row, style, [10, 3, 5], 2)] + expected = [ + ('|', ' Row One ', '|', ' Two ', '|', ' Three ', '|'), + ('|', ' Column One ', '|', ' ', '|', ' ', '|'), + ] + assert actual == expected + + +@pytest.mark.parametrize('style', ['heading', 'footing', 'row']) +def test_no_padding_no_borders(style): + """Test without padding or borders. + + :param str style: Passed to method. + """ + row = ['Row One\nColumn One', 'Two', 'Three'] + table = BaseTable([row]) + table.inner_column_border = False + table.outer_border = False + table.padding_left = 0 + table.padding_right = 0 + actual = [tuple(i) for i in table.gen_row_lines(row, style, [10, 3, 5], 2)] + expected = [ + ('Row One ', 'Two', 'Three'), + ('Column One', ' ', ' '), + ] + assert actual == expected + + +@pytest.mark.parametrize('style', ['heading', 'footing', 'row']) +def test_uneven(style): + """Test with row missing cells. + + :param str style: Passed to method. + """ + row = ['Row One Column One'] + table = BaseTable([row]) + actual = [tuple(i) for i in table.gen_row_lines(row, style, [18, 3, 5], 1)] + expected = [ + ('|', ' Row One Column One ', '|', ' ', '|', ' ', '|'), + ] + assert actual == expected + + +@pytest.mark.parametrize('style', ['heading', 'footing', 'row']) +def test_empty_table(style): + """Test empty table. + + :param str style: Passed to method. + """ + row = [] + table = BaseTable([row]) + actual = [tuple(i) for i in table.gen_row_lines(row, style, [], 0)] + expected = [ + ('|', '|'), + ] + assert actual == expected diff --git a/tests/test_base_table/test_gen_table.py b/tests/test_base_table/test_gen_table.py new file mode 100644 index 0000000..54d5fe1 --- /dev/null +++ b/tests/test_base_table/test_gen_table.py @@ -0,0 +1,225 @@ +"""Test method in BaseTable class.""" + +import pytest + +from terminaltables.base_table import BaseTable +from terminaltables.build import flatten +from terminaltables.width_and_alignment import max_dimensions + + +@pytest.mark.parametrize('inner_heading_row_border', [True, False]) +@pytest.mark.parametrize('inner_footing_row_border', [True, False]) +@pytest.mark.parametrize('inner_row_border', [True, False]) +def test_inner_row_borders(inner_heading_row_border, inner_footing_row_border, inner_row_border): + """Test heading/footing/row borders. + + :param bool inner_heading_row_border: Passed to table. + :param bool inner_footing_row_border: Passed to table. + :param bool inner_row_border: Passed to table. + """ + table_data = [ + ['Name', 'Color', 'Type'], + ['Avocado', 'green', 'nut'], + ['Tomato', 'red', 'fruit'], + ['Lettuce', 'green', 'vegetable'], + ] + table = BaseTable(table_data) + table.inner_heading_row_border = inner_heading_row_border + table.inner_footing_row_border = inner_footing_row_border + table.inner_row_border = inner_row_border + inner_widths, inner_heights, outer_widths = max_dimensions(table_data, table.padding_left, table.padding_right)[:3] + actual = flatten(table.gen_table(inner_widths, inner_heights, outer_widths)) + + # Determine expected. + if inner_row_border: + expected = ( + '+---------+-------+-----------+\n' + '| Name | Color | Type |\n' + '+---------+-------+-----------+\n' + '| Avocado | green | nut |\n' + '+---------+-------+-----------+\n' + '| Tomato | red | fruit |\n' + '+---------+-------+-----------+\n' + '| Lettuce | green | vegetable |\n' + '+---------+-------+-----------+' + ) + elif inner_heading_row_border and inner_footing_row_border: + expected = ( + '+---------+-------+-----------+\n' + '| Name | Color | Type |\n' + '+---------+-------+-----------+\n' + '| Avocado | green | nut |\n' + '| Tomato | red | fruit |\n' + '+---------+-------+-----------+\n' + '| Lettuce | green | vegetable |\n' + '+---------+-------+-----------+' + ) + elif inner_heading_row_border: + expected = ( + '+---------+-------+-----------+\n' + '| Name | Color | Type |\n' + '+---------+-------+-----------+\n' + '| Avocado | green | nut |\n' + '| Tomato | red | fruit |\n' + '| Lettuce | green | vegetable |\n' + '+---------+-------+-----------+' + ) + elif inner_footing_row_border: + expected = ( + '+---------+-------+-----------+\n' + '| Name | Color | Type |\n' + '| Avocado | green | nut |\n' + '| Tomato | red | fruit |\n' + '+---------+-------+-----------+\n' + '| Lettuce | green | vegetable |\n' + '+---------+-------+-----------+' + ) + else: + expected = ( + '+---------+-------+-----------+\n' + '| Name | Color | Type |\n' + '| Avocado | green | nut |\n' + '| Tomato | red | fruit |\n' + '| Lettuce | green | vegetable |\n' + '+---------+-------+-----------+' + ) + + assert actual == expected + + +@pytest.mark.parametrize('outer_border', [True, False]) +def test_outer_borders(outer_border): + """Test left/right/top/bottom table borders. + + :param bool outer_border: Passed to table. + """ + table_data = [ + ['Name', 'Color', 'Type'], + ['Avocado', 'green', 'nut'], + ['Tomato', 'red', 'fruit'], + ['Lettuce', 'green', 'vegetable'], + ] + table = BaseTable(table_data, 'Example Table') + table.outer_border = outer_border + inner_widths, inner_heights, outer_widths = max_dimensions(table_data, table.padding_left, table.padding_right)[:3] + actual = flatten(table.gen_table(inner_widths, inner_heights, outer_widths)) + + # Determine expected. + if outer_border: + expected = ( + '+Example Table----+-----------+\n' + '| Name | Color | Type |\n' + '+---------+-------+-----------+\n' + '| Avocado | green | nut |\n' + '| Tomato | red | fruit |\n' + '| Lettuce | green | vegetable |\n' + '+---------+-------+-----------+' + ) + else: + expected = ( + ' Name | Color | Type \n' + '---------+-------+-----------\n' + ' Avocado | green | nut \n' + ' Tomato | red | fruit \n' + ' Lettuce | green | vegetable ' + ) + + assert actual == expected + + +@pytest.mark.parametrize('mode', ['row', 'one', 'blank', 'empty', 'none']) +@pytest.mark.parametrize('bare', [False, True]) +def test_one_no_rows(mode, bare): + """Test with one or no rows. + + :param str mode: Type of table contents to test. + :param bool bare: Disable padding/borders. + """ + if mode == 'row': + table_data = [ + ['Avocado', 'green', 'nut'], + ] + elif mode == 'one': + table_data = [ + ['Avocado'], + ] + elif mode == 'blank': + table_data = [ + [''], + ] + elif mode == 'empty': + table_data = [ + [], + ] + else: + table_data = [ + ] + table = BaseTable(table_data) + if bare: + table.inner_column_border = False + table.inner_footing_row_border = False + table.inner_heading_row_border = False + table.inner_row_border = False + table.outer_border = False + table.padding_left = 0 + table.padding_right = 0 + inner_widths, inner_heights, outer_widths = max_dimensions(table_data, table.padding_left, table.padding_right)[:3] + actual = flatten(table.gen_table(inner_widths, inner_heights, outer_widths)) + + # Determine expected. + if mode == 'row': + if bare: + expected = ( + 'Avocadogreennut' + ) + else: + expected = ( + '+---------+-------+-----+\n' + '| Avocado | green | nut |\n' + '+---------+-------+-----+' + ) + elif mode == 'one': + if bare: + expected = ( + 'Avocado' + ) + else: + expected = ( + '+---------+\n' + '| Avocado |\n' + '+---------+' + ) + elif mode == 'blank': # Remember there's still padding. + if bare: + expected = ( + '' + ) + else: + expected = ( + '+--+\n' + '| |\n' + '+--+' + ) + elif mode == 'empty': + if bare: + expected = ( + '' + ) + else: + expected = ( + '++\n' + '||\n' + '++' + ) + else: + if bare: + expected = ( + '' + ) + else: + expected = ( + '++\n' + '++' + ) + + assert actual == expected diff --git a/tests/test_base_table/test_horizontal_border.py b/tests/test_base_table/test_horizontal_border.py new file mode 100644 index 0000000..e162261 --- /dev/null +++ b/tests/test_base_table/test_horizontal_border.py @@ -0,0 +1,98 @@ +"""Test method in BaseTable class.""" + +import pytest + +from terminaltables.base_table import BaseTable +from terminaltables.width_and_alignment import max_dimensions + +SINGLE_LINE = ( + ('Name', 'Color', 'Type'), + ('Avocado', 'green', 'nut'), + ('Tomato', 'red', 'fruit'), + ('Lettuce', 'green', 'vegetable'), +) + + +@pytest.mark.parametrize('inner_column_border', [True, False]) +@pytest.mark.parametrize('style', ['top', 'bottom']) +def test_top_bottom(inner_column_border, style): + """Test top and bottom borders. + + :param bool inner_column_border: Passed to table class. + :param str style: Passed to method. + """ + table = BaseTable(SINGLE_LINE, 'Example') + table.inner_column_border = inner_column_border + outer_widths = max_dimensions(table.table_data, table.padding_left, table.padding_right)[2] + + # Determine expected. + if style == 'top' and inner_column_border: + expected = '+Example--+-------+-----------+' + elif style == 'top': + expected = '+Example--------------------+' + elif style == 'bottom' and inner_column_border: + expected = '+---------+-------+-----------+' + else: + expected = '+---------------------------+' + + # Test. + actual = ''.join(table.horizontal_border(style, outer_widths)) + assert actual == expected + + +@pytest.mark.parametrize('inner_column_border', [True, False]) +@pytest.mark.parametrize('outer_border', [True, False]) +@pytest.mark.parametrize('style', ['heading', 'footing']) +def test_heading_footing(inner_column_border, outer_border, style): + """Test heading and footing borders. + + :param bool inner_column_border: Passed to table class. + :param bool outer_border: Passed to table class. + :param str style: Passed to method. + """ + table = BaseTable(SINGLE_LINE) + table.inner_column_border = inner_column_border + table.outer_border = outer_border + outer_widths = max_dimensions(table.table_data, table.padding_left, table.padding_right)[2] + + # Determine expected. + if style == 'heading' and outer_border: + expected = '+---------+-------+-----------+' if inner_column_border else '+---------------------------+' + elif style == 'heading': + expected = '---------+-------+-----------' if inner_column_border else '---------------------------' + elif style == 'footing' and outer_border: + expected = '+---------+-------+-----------+' if inner_column_border else '+---------------------------+' + else: + expected = '---------+-------+-----------' if inner_column_border else '---------------------------' + + # Test. + actual = ''.join(table.horizontal_border(style, outer_widths)) + assert actual == expected + + +@pytest.mark.parametrize('inner_column_border', [True, False]) +@pytest.mark.parametrize('outer_border', [True, False]) +def test_row(inner_column_border, outer_border): + """Test inner borders. + + :param bool inner_column_border: Passed to table class. + :param bool outer_border: Passed to table class. + """ + table = BaseTable(SINGLE_LINE) + table.inner_column_border = inner_column_border + table.outer_border = outer_border + outer_widths = max_dimensions(table.table_data, table.padding_left, table.padding_right)[2] + + # Determine expected. + if inner_column_border and outer_border: + expected = '+---------+-------+-----------+' + elif inner_column_border: + expected = '---------+-------+-----------' + elif outer_border: + expected = '+---------------------------+' + else: + expected = '---------------------------' + + # Test. + actual = ''.join(table.horizontal_border('row', outer_widths)) + assert actual == expected diff --git a/tests/test_base_table/test_table.py b/tests/test_base_table/test_table.py new file mode 100644 index 0000000..c5b5a89 --- /dev/null +++ b/tests/test_base_table/test_table.py @@ -0,0 +1,196 @@ +# coding: utf-8 +"""Test property in BaseTable class.""" + +from colorama import Fore +from colorclass import Color +from termcolor import colored + +from terminaltables.base_table import BaseTable + + +def test_ascii(): + """Test with ASCII characters.""" + table_data = [ + ['Name', 'Color', 'Type'], + ['Avocado', 'green', 'nut'], + ['Tomato', 'red', 'fruit'], + ['Lettuce', 'green', 'vegetable'], + ] + table = BaseTable(table_data) + actual = table.table + + expected = ( + '+---------+-------+-----------+\n' + '| Name | Color | Type |\n' + '+---------+-------+-----------+\n' + '| Avocado | green | nut |\n' + '| Tomato | red | fruit |\n' + '| Lettuce | green | vegetable |\n' + '+---------+-------+-----------+' + ) + + assert actual == expected + + +def test_int(): + """Test with integers instead of strings.""" + table_data = [ + [100, 10, 1], + [0, 3, 6], + [1, 4, 7], + [2, 5, 8], + ] + table = BaseTable(table_data, 1234567890) + actual = table.table + + expected = ( + '+1234567890+---+\n' + '| 100 | 10 | 1 |\n' + '+-----+----+---+\n' + '| 0 | 3 | 6 |\n' + '| 1 | 4 | 7 |\n' + '| 2 | 5 | 8 |\n' + '+-----+----+---+' + ) + + assert actual == expected + + +def test_float(): + """Test with floats instead of strings.""" + table_data = [ + [1.0, 22.0, 333.0], + [0.1, 3.1, 6.1], + [1.1, 4.1, 7.1], + [2.1, 5.1, 8.1], + ] + table = BaseTable(table_data, 0.12345678) + actual = table.table + + expected = ( + '+0.12345678--+-------+\n' + '| 1.0 | 22.0 | 333.0 |\n' + '+-----+------+-------+\n' + '| 0.1 | 3.1 | 6.1 |\n' + '| 1.1 | 4.1 | 7.1 |\n' + '| 2.1 | 5.1 | 8.1 |\n' + '+-----+------+-------+' + ) + + assert actual == expected + + +def test_bool_none(): + """Test with NoneType/boolean instead of strings.""" + table_data = [ + [True, False, None], + [True, False, None], + [False, None, True], + [None, True, False], + ] + table = BaseTable(table_data, True) + actual = table.table + + expected = ( + '+True---+-------+-------+\n' + '| True | False | None |\n' + '+-------+-------+-------+\n' + '| True | False | None |\n' + '| False | None | True |\n' + '| None | True | False |\n' + '+-------+-------+-------+' + ) + + assert actual == expected + + +def test_cjk(): + """Test with CJK characters.""" + table_data = [ + ['CJK'], + ['蓝色'], + ['世界你好'], + ] + table = BaseTable(table_data) + actual = table.table + + expected = ( + '+----------+\n' + '| CJK |\n' + '+----------+\n' + '| 蓝色 |\n' + '| 世界你好 |\n' + '+----------+' + ) + + assert actual == expected + + +def test_rtl(): + """Test with RTL characters.""" + table_data = [ + ['RTL'], + ['שלום'], + ['معرب'], + ] + table = BaseTable(table_data) + actual = table.table + + expected = ( + '+------+\n' + '| RTL |\n' + '+------+\n' + '| שלום |\n' + '| معرب |\n' + '+------+' + ) + + assert actual == expected + + +def test_rtl_large(): + """Test large table of RTL characters.""" + table_data = [ + ['اكتب', 'اللون', 'اسم'], + ['البندق', 'أخضر', 'أفوكادو'], + ['ثمرة', 'أحمر', 'بندورة'], + ['الخضروات', 'أخضر', 'الخس'], + ] + table = BaseTable(table_data, 'جوجل المترجم') + actual = table.table + + expected = ( + '+جوجل المترجم------+---------+\n' + '| اكتب | اللون | اسم |\n' + '+----------+-------+---------+\n' + '| البندق | أخضر | أفوكادو |\n' + '| ثمرة | أحمر | بندورة |\n' + '| الخضروات | أخضر | الخس |\n' + '+----------+-------+---------+' + ) + + assert actual == expected + + +def test_color(): + """Test with color characters.""" + table_data = [ + ['ansi', '\033[31mRed\033[39m', '\033[32mGreen\033[39m', '\033[34mBlue\033[39m'], + ['colorclass', Color('{red}Red{/red}'), Color('{green}Green{/green}'), Color('{blue}Blue{/blue}')], + ['colorama', Fore.RED + 'Red' + Fore.RESET, Fore.GREEN + 'Green' + Fore.RESET, Fore.BLUE + 'Blue' + Fore.RESET], + ['termcolor', colored('Red', 'red'), colored('Green', 'green'), colored('Blue', 'blue')], + ] + table = BaseTable(table_data) + table.inner_heading_row_border = False + actual = table.table + + expected = ( + u'+------------+-----+-------+------+\n' + u'| ansi | \033[31mRed\033[39m | \033[32mGreen\033[39m | \033[34mBlue\033[39m |\n' + u'| colorclass | \033[31mRed\033[39m | \033[32mGreen\033[39m | \033[34mBlue\033[39m |\n' + u'| colorama | \033[31mRed\033[39m | \033[32mGreen\033[39m | \033[34mBlue\033[39m |\n' + u'| termcolor | \033[31mRed\033[0m | \033[32mGreen\033[0m | \033[34mBlue\033[0m |\n' + u'+------------+-----+-------+------+' + ) + + assert actual == expected diff --git a/tests/test_build/test_build_border.py b/tests/test_build/test_build_border.py new file mode 100644 index 0000000..9c410fd --- /dev/null +++ b/tests/test_build/test_build_border.py @@ -0,0 +1,312 @@ +# coding: utf-8 +"""Test function in module.""" + +import pytest +from colorama import Fore, Style +from colorclass import Color +from termcolor import colored + +from terminaltables.build import build_border + + +@pytest.mark.parametrize('outer_widths,horizontal,left,intersect,right,expected', [ + ([5, 6, 7], '-', '<', '+', '>', '<-----+------+------->'), + ([1, 1, 1], '-', '', '', '', '---'), + ([1, 1, 1], '', '', '', '', ''), + ([1], '-', '<', '+', '>', '<->'), + ([], '-', '<', '+', '>', '<>'), +]) +def test_no_title(outer_widths, horizontal, left, intersect, right, expected): + """Test without title. + + :param iter outer_widths: List of integers representing column widths with padding. + :param str horizontal: Character to stretch across each column. + :param str left: Left border. + :param str intersect: Column separator. + :param str right: Right border. + :param str expected: Expected output. + """ + actual = build_border(outer_widths, horizontal, left, intersect, right) + assert ''.join(actual) == expected + + +@pytest.mark.parametrize('outer_widths,intersect,expected', [ + ([20], '+', 'Applications--------'), + ([20], '', 'Applications--------'), + + ([15, 5], '+', 'Applications---+-----'), + ([15, 5], '', 'Applications--------'), + + ([12], '+', 'Applications'), + ([12], '', 'Applications'), + + ([12, 1], '+', 'Applications+-'), + ([12, 1], '', 'Applications-'), + + ([12, 0], '+', 'Applications+'), + ([12, 0], '', 'Applications'), +]) +@pytest.mark.parametrize('left,right', [('', ''), ('<', '>')]) +def test_first_column_fit(outer_widths, left, intersect, right, expected): + """Test with title that fits in the first column. + + :param iter outer_widths: List of integers representing column widths with padding. + :param str left: Left border. + :param str intersect: Column separator. + :param str right: Right border. + :param str expected: Expected output. + """ + if left and right: + expected = left + expected + right + actual = build_border(outer_widths, '-', left, intersect, right, title='Applications') + assert ''.join(actual) == expected + + +@pytest.mark.parametrize('outer_widths,expected', [ + ([20], 'Applications--------'), + ([10, 10], 'Applications--------'), + ([5, 5, 5, 5], 'Applications--------'), + ([3, 2, 3, 2, 3, 2, 3, 2], 'Applications--------'), + ([1] * 20, 'Applications--------'), + ([10, 5], 'Applications---'), + ([9, 5], 'Applications--'), + ([8, 5], 'Applications-'), + ([7, 5], 'Applications'), + ([6, 5], '-----------'), +]) +@pytest.mark.parametrize('left,right', [('', ''), ('<', '>')]) +def test_no_intersect(outer_widths, left, right, expected): + """Test with no column dividers. + + :param iter outer_widths: List of integers representing column widths. + :param str left: Left border. + :param str right: Right border. + :param str expected: Expected output. + """ + if left and right: + expected = left + expected + right + actual = build_border(outer_widths, '-', left, '', right, title='Applications') + assert ''.join(actual) == expected + + +@pytest.mark.parametrize('outer_widths,expected', [ + ([20], 'Applications--------'), + ([0, 20], 'Applications---------'), + ([20, 0], 'Applications--------+'), + ([0, 0, 20], 'Applications----------'), + ([20, 0, 0], 'Applications--------++'), + + ([10, 10], 'Applications---------'), + ([11, 9], 'Applications---------'), + ([12, 8], 'Applications+--------'), + ([13, 7], 'Applications-+-------'), + + ([5, 5, 5, 5], 'Applications-----+-----'), + ([4, 4, 6, 6], 'Applications----+------'), + ([3, 3, 7, 7], 'Applications---+-------'), + ([2, 2, 7, 9], 'Applications-+---------'), + ([1, 1, 9, 9], 'Applications-+---------'), + + ([2, 2, 2, 2, 2, 2, 2], 'Applications--+--+--'), + ([1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 'Applications-+-+-+-'), + ([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 'Applications++++++++'), + + ([2, 2, 2, 2], '--+--+--+--'), + ([1, 1, 1, 1, 1], '-+-+-+-+-'), + ([0, 0, 0, 0, 0, 0, 0, 0, 0, 0], '+++++++++'), +]) +@pytest.mark.parametrize('left,right', [('', ''), ('<', '>')]) +def test_intersect(outer_widths, left, right, expected): + """Test with column dividers. + + :param iter outer_widths: List of integers representing column widths. + :param str left: Left border. + :param str right: Right border. + :param str expected: Expected output. + """ + if left and right: + expected = left + expected + right + actual = build_border(outer_widths, '-', left, '+', right, title='Applications') + assert ''.join(actual) == expected + + +@pytest.mark.parametrize('outer_widths,intersect,expected', [ + ([12], '+', u'蓝色--------'), + ([12], '', u'蓝色--------'), + ([7, 5], '+', u'蓝色---+-----'), + ([7, 5], '', u'蓝色--------'), + ([4], '+', u'蓝色'), + ([4], '', u'蓝色'), + ([4, 1], '+', u'蓝色+-'), + ([4, 1], '', u'蓝色-'), + ([4, 0], '+', u'蓝色+'), + ([4, 0], '', u'蓝色'), + ([12], '', u'蓝色--------'), + ([6, 6], '', u'蓝色--------'), + ([3, 3, 3, 3], '', u'蓝色--------'), + ([2, 1, 2, 1, 2, 1, 2, 1], '', u'蓝色--------'), + ([1] * 12, '', u'蓝色--------'), + ([2, 4], '', u'蓝色--'), + ([1, 4], '', u'蓝色-'), + ([1, 3], '', u'蓝色'), + ([1, 2], '', u'---'), + ([2], '', u'--'), + ([12], '+', u'蓝色--------'), + ([0, 12], '+', u'蓝色---------'), + ([12, 0], '+', u'蓝色--------+'), + ([0, 0, 12], '+', u'蓝色----------'), + ([12, 0, 0], '+', u'蓝色--------++'), + ([3, 3], '+', u'蓝色---'), + ([4, 2], '+', u'蓝色+--'), + ([5, 1], '+', u'蓝色-+-'), + ([3, 3, 3, 3], '+', u'蓝色---+---+---'), + ([2, 2, 4, 4], '+', u'蓝色-+----+----'), + ([1, 1, 5, 5], '+', u'蓝色-----+-----'), + ([2, 2, 2, 2], '+', u'蓝色-+--+--'), + ([1, 1, 1, 1, 1], '+', u'蓝色-+-+-'), + ([0, 0, 0, 0, 0, 0, 0], '+', u'蓝色++'), + ([1, 1], '+', u'-+-'), +]) +@pytest.mark.parametrize('left,right', [('', ''), ('<', '>')]) +def test_cjk(outer_widths, left, intersect, right, expected): + """Test with CJK characters in title. + + :param iter outer_widths: List of integers representing column widths. + :param str left: Left border. + :param str intersect: Column separator. + :param str right: Right border. + :param str expected: Expected output. + """ + if left and right: + expected = left + expected + right + actual = build_border(outer_widths, '-', left, intersect, right, title=u'蓝色') + assert ''.join(actual) == expected + + +@pytest.mark.parametrize('outer_widths,intersect,expected', [ + ([12], '+', u'معرب--------'), + ([12], '', u'معرب--------'), + ([7, 5], '+', u'معرب---+-----'), + ([7, 5], '', u'معرب--------'), + ([4], '+', u'معرب'), + ([4], '', u'معرب'), + ([4, 1], '+', u'معرب+-'), + ([4, 1], '', u'معرب-'), + ([4, 0], '+', u'معرب+'), + ([4, 0], '', u'معرب'), + ([12], '', u'معرب--------'), + ([6, 6], '', u'معرب--------'), + ([3, 3, 3, 3], '', u'معرب--------'), + ([2, 1, 2, 1, 2, 1, 2, 1], '', u'معرب--------'), + ([1] * 12, '', u'معرب--------'), + ([2, 4], '', u'معرب--'), + ([1, 4], '', u'معرب-'), + ([1, 3], '', u'معرب'), + ([1, 2], '', u'---'), + ([2], '', u'--'), + ([12], '+', u'معرب--------'), + ([0, 12], '+', u'معرب---------'), + ([12, 0], '+', u'معرب--------+'), + ([0, 0, 12], '+', u'معرب----------'), + ([12, 0, 0], '+', u'معرب--------++'), + ([3, 3], '+', u'معرب---'), + ([4, 2], '+', u'معرب+--'), + ([5, 1], '+', u'معرب-+-'), + ([3, 3, 3, 3], '+', u'معرب---+---+---'), + ([2, 2, 4, 4], '+', u'معرب-+----+----'), + ([1, 1, 5, 5], '+', u'معرب-----+-----'), + ([2, 2, 2, 2], '+', u'معرب-+--+--'), + ([1, 1, 1, 1, 1], '+', u'معرب-+-+-'), + ([0, 0, 0, 0, 0, 0, 0], '+', u'معرب++'), + ([1, 1], '+', u'-+-'), +]) +@pytest.mark.parametrize('left,right', [('', ''), ('<', '>')]) +def test_rtl(outer_widths, left, intersect, right, expected): + """Test with RTL characters in title. + + :param iter outer_widths: List of integers representing column widths. + :param str left: Left border. + :param str intersect: Column separator. + :param str right: Right border. + :param str expected: Expected output. + """ + if left and right: + expected = left + expected + right + actual = build_border(outer_widths, '-', left, intersect, right, title=u'معرب') + assert ''.join(actual) == expected + + +@pytest.mark.parametrize('outer_widths,intersect,expected', [ + ([12], '+', '\x1b[34mTEST\x1b[0m--------'), + ([12], '', '\x1b[34mTEST\x1b[0m--------'), + ([7, 5], '+', '\x1b[34mTEST\x1b[0m---+-----'), + ([7, 5], '', '\x1b[34mTEST\x1b[0m--------'), + ([4], '+', '\x1b[34mTEST\x1b[0m'), + ([4], '', '\x1b[34mTEST\x1b[0m'), + ([4, 1], '+', '\x1b[34mTEST\x1b[0m+-'), + ([4, 1], '', '\x1b[34mTEST\x1b[0m-'), + ([4, 0], '+', '\x1b[34mTEST\x1b[0m+'), + ([4, 0], '', '\x1b[34mTEST\x1b[0m'), + ([12], '', '\x1b[34mTEST\x1b[0m--------'), + ([6, 6], '', '\x1b[34mTEST\x1b[0m--------'), + ([3, 3, 3, 3], '', '\x1b[34mTEST\x1b[0m--------'), + ([2, 1, 2, 1, 2, 1, 2, 1], '', '\x1b[34mTEST\x1b[0m--------'), + ([1] * 12, '', '\x1b[34mTEST\x1b[0m--------'), + ([2, 4], '', '\x1b[34mTEST\x1b[0m--'), + ([1, 4], '', '\x1b[34mTEST\x1b[0m-'), + ([1, 3], '', '\x1b[34mTEST\x1b[0m'), + ([1, 2], '', '---'), + ([12], '+', '\x1b[34mTEST\x1b[0m--------'), + ([0, 12], '+', '\x1b[34mTEST\x1b[0m---------'), + ([12, 0], '+', '\x1b[34mTEST\x1b[0m--------+'), + ([0, 0, 12], '+', '\x1b[34mTEST\x1b[0m----------'), + ([12, 0, 0], '+', '\x1b[34mTEST\x1b[0m--------++'), + ([3, 3], '+', '\x1b[34mTEST\x1b[0m---'), + ([4, 2], '+', '\x1b[34mTEST\x1b[0m+--'), + ([5, 1], '+', '\x1b[34mTEST\x1b[0m-+-'), + ([3, 3, 3, 3], '+', '\x1b[34mTEST\x1b[0m---+---+---'), + ([2, 2, 4, 4], '+', '\x1b[34mTEST\x1b[0m-+----+----'), + ([1, 1, 5, 5], '+', '\x1b[34mTEST\x1b[0m-----+-----'), + ([2, 2, 2, 2], '+', '\x1b[34mTEST\x1b[0m-+--+--'), + ([1, 1, 1, 1, 1], '+', '\x1b[34mTEST\x1b[0m-+-+-'), + ([0, 0, 0, 0, 0, 0, 0], '+', '\x1b[34mTEST\x1b[0m++'), + ([1, 1], '+', '-+-'), +]) +@pytest.mark.parametrize('left,right', [('', ''), ('<', '>')]) +@pytest.mark.parametrize('title', [ + '\x1b[34mTEST\x1b[0m', + Color('{blue}TEST{/all}'), + Fore.BLUE + 'TEST' + Style.RESET_ALL, + colored('TEST', 'blue'), +]) +def test_colors(outer_widths, left, intersect, right, title, expected): + """Test with color title characters. + + :param iter outer_widths: List of integers representing column widths with padding. + :param str left: Left border. + :param str intersect: Column separator. + :param str right: Right border. + :param title: Title in border with color codes. + :param str expected: Expected output. + """ + if left and right: + expected = left + expected + right + actual = build_border(outer_widths, '-', left, intersect, right, title=title) + assert ''.join(actual) == expected + + +@pytest.mark.parametrize('outer_widths,title,expected', [ + ([3, 3, 3], 123, '<123+---+--->'), + ([3, 3, 3], 0.9, '<0.9+---+--->'), + ([3, 3, 3], True, '<True---+--->'), + ([3, 3, 3], False, '<False--+--->'), +]) +def test_non_string(outer_widths, title, expected): + """Test with non-string values. + + :param iter outer_widths: List of integers representing column widths with padding. + :param title: Title in border. + :param str expected: Expected output. + """ + actual = build_border(outer_widths, '-', '<', '+', '>', title=title) + assert ''.join(actual) == expected diff --git a/tests/test_build/test_build_row.py b/tests/test_build/test_build_row.py new file mode 100644 index 0000000..ce55944 --- /dev/null +++ b/tests/test_build/test_build_row.py @@ -0,0 +1,104 @@ +"""Test function in module.""" + +from terminaltables.build import build_row + + +def test_one_line(): + """Test with one line cells.""" + row = [ + ['Left Cell'], ['Center Cell'], ['Right Cell'], + ] + actual = [tuple(i) for i in build_row(row, '>', '|', '<')] + expected = [ + ('>', 'Left Cell', '|', 'Center Cell', '|', 'Right Cell', '<'), + ] + assert actual == expected + + +def test_two_line(): + """Test with two line cells.""" + row = [ + [ + 'Left ', + 'Cell1', + ], + + [ + 'Center', + 'Cell2 ', + ], + + [ + 'Right', + 'Cell3', + ], + ] + actual = [tuple(i) for i in build_row(row, '>', '|', '<')] + expected = [ + ('>', 'Left ', '|', 'Center', '|', 'Right', '<'), + ('>', 'Cell1', '|', 'Cell2 ', '|', 'Cell3', '<'), + ] + assert actual == expected + + +def test_three_line(): + """Test with three line cells.""" + row = [ + [ + 'Left ', + 'Cell1', + ' ', + ], + + [ + 'Center', + 'Cell2 ', + ' ', + ], + + [ + 'Right', + 'Cell3', + ' ', + ], + ] + actual = [tuple(i) for i in build_row(row, '>', '|', '<')] + expected = [ + ('>', 'Left ', '|', 'Center', '|', 'Right', '<'), + ('>', 'Cell1', '|', 'Cell2 ', '|', 'Cell3', '<'), + ('>', ' ', '|', ' ', '|', ' ', '<'), + ] + assert actual == expected + + +def test_single(): + """Test with single cell.""" + actual = [tuple(i) for i in build_row([['Cell']], '>', '|', '<')] + expected = [ + ('>', 'Cell', '<'), + ] + assert actual == expected + + +def test_empty(): + """Test with empty cell.""" + actual = [tuple(i) for i in build_row([['']], '>', '|', '<')] + expected = [ + ('>', '', '<'), + ] + assert actual == expected + + +def test_no_cells(): + """Test with no cells.""" + actual = [tuple(i) for i in build_row([[]], '>', '|', '<')] + expected = [ + ('>', '<'), + ] + assert actual == expected + + actual = [tuple(i) for i in build_row([], '>', '|', '<')] + expected = [ + ('>', '<'), + ] + assert actual == expected diff --git a/tests/test_build/test_combine.py b/tests/test_build/test_combine.py new file mode 100644 index 0000000..b296ffd --- /dev/null +++ b/tests/test_build/test_combine.py @@ -0,0 +1,37 @@ +"""Test function in module.""" + +import pytest + +from terminaltables.build import combine + + +@pytest.mark.parametrize('generator', [False, True]) +def test_borders(generator): + """Test with borders. + + :param bool generator: Test with generator instead of list. + """ + line = ['One', 'Two', 'Three'] + actual = list(combine(iter(line) if generator else line, '>', '|', '<')) + assert actual == ['>', 'One', '|', 'Two', '|', 'Three', '<'] + + +@pytest.mark.parametrize('generator', [False, True]) +def test_no_border(generator): + """Test without borders. + + :param bool generator: Test with generator instead of list. + """ + line = ['One', 'Two', 'Three'] + actual = list(combine(iter(line) if generator else line, '', '', '')) + assert actual == ['One', 'Two', 'Three'] + + +@pytest.mark.parametrize('generator', [False, True]) +def test_no_items(generator): + """Test with empty list. + + :param bool generator: Test with generator instead of list. + """ + actual = list(combine(iter([]) if generator else [], '>', '|', '<')) + assert actual == ['>', '<'] diff --git a/tests/test_build/test_flatten.py b/tests/test_build/test_flatten.py new file mode 100644 index 0000000..aacfdbd --- /dev/null +++ b/tests/test_build/test_flatten.py @@ -0,0 +1,25 @@ +"""Test function in module.""" + +from terminaltables.build import flatten + + +def test_one_line(): + """Test with one line cells.""" + table = [ + ['>', 'Left Cell', '|', 'Center Cell', '|', 'Right Cell', '<'], + ] + actual = flatten(table) + expected = '>Left Cell|Center Cell|Right Cell<' + assert actual == expected + + +def test_two_line(): + """Test with two line cells.""" + table = [ + ['>', 'Left ', '|', 'Center', '|', 'Right', '<'], + ['>', 'Cell1', '|', 'Cell2 ', '|', 'Cell3', '<'], + ] + actual = flatten(table) + expected = ('>Left |Center|Right<\n' + '>Cell1|Cell2 |Cell3<') + assert actual == expected diff --git a/tests/test_examples.py b/tests/test_examples.py new file mode 100644 index 0000000..f0799f9 --- /dev/null +++ b/tests/test_examples.py @@ -0,0 +1,32 @@ +"""Test example scripts.""" + +from __future__ import print_function + +import os +import subprocess +import sys + +import pytest + +from tests import PROJECT_ROOT + + +@pytest.mark.parametrize('filename', map('example{0}.py'.format, (1, 2, 3))) +def test(filename): + """Test with subprocess. + + :param str filename: Example script filename to run. + """ + command = [sys.executable, str(PROJECT_ROOT.join(filename))] + env = dict(os.environ, PYTHONIOENCODING='utf-8') + + # Run. + proc = subprocess.Popen(command, env=env, stderr=subprocess.STDOUT, stdout=subprocess.PIPE) + output = proc.communicate()[0] + + # Verify. + try: + assert proc.poll() == 0 + except AssertionError: + print(output) + raise diff --git a/tests/test_terminal_io/__init__.py b/tests/test_terminal_io/__init__.py new file mode 100644 index 0000000..93738fc --- /dev/null +++ b/tests/test_terminal_io/__init__.py @@ -0,0 +1,45 @@ +"""Common objects used by tests in directory.""" + +from terminaltables import terminal_io + + +class MockKernel32(object): + """Mock kernel32.""" + + def __init__(self, stderr=terminal_io.INVALID_HANDLE_VALUE, stdout=terminal_io.INVALID_HANDLE_VALUE): + """Constructor.""" + self.stderr = stderr + self.stdout = stdout + self.csbi_err = b'x\x00)#\x00\x00\x87\x05\x07\x00\x00\x00j\x05w\x00\x87\x05x\x00J\x00' # 119 x 29 + self.csbi_out = b'L\x00,\x01\x00\x00*\x01\x07\x00\x00\x00\x0e\x01K\x00*\x01L\x00L\x00' # 75 x 28 + self.setConsoleTitleA_called = False + self.setConsoleTitleW_called = False + + def GetConsoleScreenBufferInfo(self, handle, lpcsbi): # noqa + """Mock GetConsoleScreenBufferInfo. + + :param handle: Unused handle. + :param lpcsbi: ctypes.create_string_buffer() return value. + """ + if handle == self.stderr: + lpcsbi.raw = self.csbi_err + else: + lpcsbi.raw = self.csbi_out + return 1 + + def GetStdHandle(self, handle): # noqa + """Mock GetStdHandle. + + :param int handle: STD_ERROR_HANDLE or STD_OUTPUT_HANDLE. + """ + return self.stderr if handle == terminal_io.STD_ERROR_HANDLE else self.stdout + + def SetConsoleTitleA(self, _): # noqa + """Mock SetConsoleTitleA.""" + self.setConsoleTitleA_called = True + return 1 + + def SetConsoleTitleW(self, _): # noqa + """Mock SetConsoleTitleW.""" + self.setConsoleTitleW_called = True + return 1 diff --git a/tests/test_terminal_io/sub_title_ascii_win10.bmp b/tests/test_terminal_io/sub_title_ascii_win10.bmp Binary files differnew file mode 100644 index 0000000..638d0a3 --- /dev/null +++ b/tests/test_terminal_io/sub_title_ascii_win10.bmp diff --git a/tests/test_terminal_io/sub_title_ascii_win2012.bmp b/tests/test_terminal_io/sub_title_ascii_win2012.bmp Binary files differnew file mode 100644 index 0000000..04f0f2a --- /dev/null +++ b/tests/test_terminal_io/sub_title_ascii_win2012.bmp diff --git a/tests/test_terminal_io/sub_title_ascii_winxp.bmp b/tests/test_terminal_io/sub_title_ascii_winxp.bmp Binary files differnew file mode 100644 index 0000000..c40a2d2 --- /dev/null +++ b/tests/test_terminal_io/sub_title_ascii_winxp.bmp diff --git a/tests/test_terminal_io/sub_title_cjk_win10.bmp b/tests/test_terminal_io/sub_title_cjk_win10.bmp Binary files differnew file mode 100644 index 0000000..052e6b5 --- /dev/null +++ b/tests/test_terminal_io/sub_title_cjk_win10.bmp diff --git a/tests/test_terminal_io/sub_title_cjk_win2012.bmp b/tests/test_terminal_io/sub_title_cjk_win2012.bmp Binary files differnew file mode 100644 index 0000000..ec48a85 --- /dev/null +++ b/tests/test_terminal_io/sub_title_cjk_win2012.bmp diff --git a/tests/test_terminal_io/sub_title_cjk_winxp.bmp b/tests/test_terminal_io/sub_title_cjk_winxp.bmp Binary files differnew file mode 100644 index 0000000..349f685 --- /dev/null +++ b/tests/test_terminal_io/sub_title_cjk_winxp.bmp diff --git a/tests/test_terminal_io/test_get_console_info.py b/tests/test_terminal_io/test_get_console_info.py new file mode 100644 index 0000000..1a9b98f --- /dev/null +++ b/tests/test_terminal_io/test_get_console_info.py @@ -0,0 +1,28 @@ +# coding: utf-8 +"""Test function in module.""" + +import ctypes + +import pytest + +from terminaltables.terminal_io import get_console_info, INVALID_HANDLE_VALUE, IS_WINDOWS + +from tests.test_terminal_io import MockKernel32 + + +def test(): + """Test function.""" + # Test live WinError. + if IS_WINDOWS: + with pytest.raises(OSError): + get_console_info(ctypes.windll.kernel32, 0) + + # Test INVALID_HANDLE_VALUE. + kernel32 = MockKernel32(stderr=1) + with pytest.raises(OSError): + get_console_info(kernel32, INVALID_HANDLE_VALUE) + + # Test no error with mock methods. + width, height = get_console_info(kernel32, 1) + assert width == 119 + assert height == 29 diff --git a/tests/test_terminal_io/test_set_terminal_title.py b/tests/test_terminal_io/test_set_terminal_title.py new file mode 100644 index 0000000..6d58301 --- /dev/null +++ b/tests/test_terminal_io/test_set_terminal_title.py @@ -0,0 +1,110 @@ +# coding: utf-8 +"""Test function in module.""" + +import sys +from textwrap import dedent + +import py +import pytest + +from terminaltables.terminal_io import IS_WINDOWS, set_terminal_title + +from tests import PROJECT_ROOT +from tests.screenshot import RunNewConsole, screenshot_until_match +from tests.test_terminal_io import MockKernel32 + +HERE = py.path.local(__file__).dirpath() + + +@pytest.mark.parametrize('is_windows', [False, True]) +@pytest.mark.parametrize('mode', ['ascii', 'unicode', 'bytes']) +def test(monkeypatch, is_windows, mode): + """Test function. + + :param monkeypatch: pytest fixture. + :param bool is_windows: Monkeypatch terminal_io.IS_WINDOWS + :param str mode: Scenario to test for. + """ + monkeypatch.setattr('terminaltables.terminal_io.IS_WINDOWS', is_windows) + kernel32 = MockKernel32() + + # Title. + if mode == 'ascii': + title = 'Testing terminaltables.' + elif mode == 'unicode': + title = u'Testing terminaltables with unicode: 世界你好蓝色' + else: + title = b'Testing terminaltables with bytes.' + + # Run. + assert set_terminal_title(title, kernel32) + if not is_windows: + return + + # Verify. + if mode == 'ascii': + assert kernel32.setConsoleTitleA_called + assert not kernel32.setConsoleTitleW_called + elif mode == 'unicode': + assert not kernel32.setConsoleTitleA_called + assert kernel32.setConsoleTitleW_called + else: + assert kernel32.setConsoleTitleA_called + assert not kernel32.setConsoleTitleW_called + + +@pytest.mark.skipif(str(not IS_WINDOWS)) +@pytest.mark.skipif('True') # https://github.com/Robpol86/terminaltables/issues/30 +@pytest.mark.parametrize('mode', ['ascii', 'unicode', 'bytes']) +def test_windows_screenshot(tmpdir, mode): + """Test function on Windows in a new console window. Take a screenshot to verify it works. + + :param tmpdir: pytest fixture. + :param str mode: Scenario to test for. + """ + script = tmpdir.join('script.py') + command = [sys.executable, str(script)] + change_title = tmpdir.join('change_title') + screenshot = PROJECT_ROOT.join('test_terminal_io.png') + if screenshot.check(): + screenshot.remove() + + # Determine title. + if mode == 'ascii': + title = "'test ASCII test'" + elif mode == 'unicode': + title = u"u'test 世界你好蓝色 test'" + else: + title = "b'test ASCII test'" + + # Generate script. + script_template = dedent(u"""\ + # coding: utf-8 + from __future__ import print_function + import os, time + from terminaltables.terminal_io import set_terminal_title + stop_after = time.time() + 20 + + print('Waiting for FindWindowA() in RunNewConsole.__enter__()...') + while not os.path.exists(r'{change_title}') and time.time() < stop_after: + time.sleep(0.5) + assert set_terminal_title({title}) is True + + print('Waiting for screenshot_until_match()...') + while not os.path.exists(r'{screenshot}') and time.time() < stop_after: + time.sleep(0.5) + """) + script_contents = script_template.format(change_title=str(change_title), title=title, screenshot=str(screenshot)) + script.write(script_contents.encode('utf-8'), mode='wb') + + # Setup expected. + if mode == 'unicode': + sub_images = [str(p) for p in HERE.listdir('sub_title_cjk_*.bmp')] + else: + sub_images = [str(p) for p in HERE.listdir('sub_title_ascii_*.bmp')] + assert sub_images + + # Run. + with RunNewConsole(command) as gen: + change_title.ensure(file=True) # Touch file. + screenshot_until_match(str(screenshot), 15, sub_images, 1, gen) diff --git a/tests/test_terminal_io/test_terminal_size.py b/tests/test_terminal_io/test_terminal_size.py new file mode 100644 index 0000000..ba14d18 --- /dev/null +++ b/tests/test_terminal_io/test_terminal_size.py @@ -0,0 +1,54 @@ +# coding: utf-8 +"""Test function in module.""" + +import pytest + +from terminaltables.terminal_io import DEFAULT_HEIGHT, DEFAULT_WIDTH, INVALID_HANDLE_VALUE, IS_WINDOWS, terminal_size + +from tests.test_terminal_io import MockKernel32 + + +@pytest.mark.parametrize('stderr', [1, INVALID_HANDLE_VALUE]) +@pytest.mark.parametrize('stdout', [2, INVALID_HANDLE_VALUE]) +def test_windows(monkeypatch, stderr, stdout): + """Test function with IS_WINDOWS=True. + + :param monkeypatch: pytest fixture. + :param int stderr: Mock handle value. + :param int stdout: Mock handle value. + """ + monkeypatch.setattr('terminaltables.terminal_io.IS_WINDOWS', True) + + kernel32 = MockKernel32(stderr=stderr, stdout=stdout) + width, height = terminal_size(kernel32) + + if stderr == INVALID_HANDLE_VALUE and stdout == INVALID_HANDLE_VALUE: + assert width == DEFAULT_WIDTH + assert height == DEFAULT_HEIGHT + elif stdout == INVALID_HANDLE_VALUE: + assert width == 119 + assert height == 29 + elif stderr == INVALID_HANDLE_VALUE: + assert width == 75 + assert height == 28 + else: + assert width == 119 + assert height == 29 + + +@pytest.mark.skipif(str(IS_WINDOWS)) +def test_nix(monkeypatch): + """Test function with IS_WINDOWS=False. + + :param monkeypatch: pytest fixture. + """ + # Test exception (no terminal within pytest). + width, height = terminal_size() + assert width == DEFAULT_WIDTH + assert height == DEFAULT_HEIGHT + + # Test mocked. + monkeypatch.setattr('fcntl.ioctl', lambda *_: b'\x1d\x00w\x00\xca\x02\x96\x01') + width, height = terminal_size() + assert width == 119 + assert height == 29 diff --git a/tests/test_width_and_alignment/test_align_and_pad_cell.py b/tests/test_width_and_alignment/test_align_and_pad_cell.py new file mode 100644 index 0000000..e0a928e --- /dev/null +++ b/tests/test_width_and_alignment/test_align_and_pad_cell.py @@ -0,0 +1,202 @@ +# coding: utf-8 +"""Test function in module.""" + +import pytest +from colorama import Fore +from colorclass import Color +from termcolor import colored + +from terminaltables.width_and_alignment import align_and_pad_cell + + +@pytest.mark.parametrize('string,align,width,expected', [ + ('test', '', 4, ['test']), + (123, '', 3, ['123']), + (0.9, '', 3, ['0.9']), + (None, '', 4, ['None']), + (True, '', 4, ['True']), + (False, '', 5, ['False']), + (Color('{blue}Test{/blue}'), '', 4, ['\x1b[34mTest\x1b[39m']), + (Fore.BLUE + 'Test' + Fore.RESET, '', 4, ['\x1b[34mTest\x1b[39m']), + (colored('Test', 'blue'), '', 4, ['\x1b[34mTest\x1b[0m']), + ('蓝色', '', 4, ['蓝色']), + (u'שלום', '', 4, [u'\u05e9\u05dc\u05d5\u05dd']), + (u'معرب', '', 4, [u'\u0645\u0639\u0631\u0628']), + + ('test', '', 5, ['test ']), + (123, '', 4, ['123 ']), + (0.9, '', 4, ['0.9 ']), + (None, '', 5, ['None ']), + (True, '', 5, ['True ']), + (False, '', 6, ['False ']), + (Color('{blue}Test{/blue}'), '', 5, ['\x1b[34mTest\x1b[39m ']), + (Fore.BLUE + 'Test' + Fore.RESET, '', 5, ['\x1b[34mTest\x1b[39m ']), + (colored('Test', 'blue'), '', 5, ['\x1b[34mTest\x1b[0m ']), + ('蓝色', '', 5, ['蓝色 ']), + (u'שלום', '', 5, [u'\u05e9\u05dc\u05d5\u05dd ']), + (u'معرب', '', 5, [u'\u0645\u0639\u0631\u0628 ']), + + ('test', 'left', 5, ['test ']), + (123, 'left', 4, ['123 ']), + (0.9, 'left', 4, ['0.9 ']), + (None, 'left', 5, ['None ']), + (True, 'left', 5, ['True ']), + (False, 'left', 6, ['False ']), + (Color('{blue}Test{/blue}'), 'left', 5, ['\x1b[34mTest\x1b[39m ']), + (Fore.BLUE + 'Test' + Fore.RESET, 'left', 5, ['\x1b[34mTest\x1b[39m ']), + (colored('Test', 'blue'), 'left', 5, ['\x1b[34mTest\x1b[0m ']), + ('蓝色', 'left', 5, ['蓝色 ']), + (u'שלום', 'left', 5, [u'\u05e9\u05dc\u05d5\u05dd ']), + (u'معرب', 'left', 5, [u'\u0645\u0639\u0631\u0628 ']), + + ('test', 'right', 5, [' test']), + (123, 'right', 4, [' 123']), + (0.9, 'right', 4, [' 0.9']), + (None, 'right', 5, [' None']), + (True, 'right', 5, [' True']), + (False, 'right', 6, [' False']), + (Color('{blue}Test{/blue}'), 'right', 5, [' \x1b[34mTest\x1b[39m']), + (Fore.BLUE + 'Test' + Fore.RESET, 'right', 5, [' \x1b[34mTest\x1b[39m']), + (colored('Test', 'blue'), 'right', 5, [' \x1b[34mTest\x1b[0m']), + ('蓝色', 'right', 5, [' 蓝色']), + (u'שלום', 'right', 5, [u' \u05e9\u05dc\u05d5\u05dd']), + (u'معرب', 'right', 5, [u' \u0645\u0639\u0631\u0628']), + + ('test', 'center', 6, [' test ']), + (123, 'center', 5, [' 123 ']), + (0.9, 'center', 5, [' 0.9 ']), + (None, 'center', 6, [' None ']), + (True, 'center', 6, [' True ']), + (False, 'center', 7, [' False ']), + (Color('{blue}Test{/blue}'), 'center', 6, [' \x1b[34mTest\x1b[39m ']), + (Fore.BLUE + 'Test' + Fore.RESET, 'center', 6, [' \x1b[34mTest\x1b[39m ']), + (colored('Test', 'blue'), 'center', 6, [' \x1b[34mTest\x1b[0m ']), + ('蓝色', 'center', 6, [' 蓝色 ']), + (u'שלום', 'center', 6, [u' \u05e9\u05dc\u05d5\u05dd ']), + (u'معرب', 'center', 6, [u' \u0645\u0639\u0631\u0628 ']), +]) +def test_width(string, align, width, expected): + """Test width and horizontal alignment. + + :param str string: String to test. + :param str align: Horizontal alignment. + :param int width: Expand string to this width without padding. + :param list expected: Expected output string. + """ + actual = align_and_pad_cell(string, (align,), (width, 1), (0, 0, 0, 0)) + assert actual == expected + + +@pytest.mark.parametrize('string,align,height,expected', [ + ('test', '', 1, ['test']), + (Color('{blue}Test{/blue}'), '', 1, ['\x1b[34mTest\x1b[39m']), + (Fore.BLUE + 'Test' + Fore.RESET, '', 1, ['\x1b[34mTest\x1b[39m']), + (colored('Test', 'blue'), '', 1, ['\x1b[34mTest\x1b[0m']), + ('蓝色', '', 1, ['蓝色']), + (u'שלום', '', 1, [u'\u05e9\u05dc\u05d5\u05dd']), + (u'معرب', '', 1, [u'\u0645\u0639\u0631\u0628']), + + ('test', '', 2, ['test', ' ']), + (Color('{blue}Test{/blue}'), '', 2, ['\x1b[34mTest\x1b[39m', ' ']), + (Fore.BLUE + 'Test' + Fore.RESET, '', 2, ['\x1b[34mTest\x1b[39m', ' ']), + (colored('Test', 'blue'), '', 2, ['\x1b[34mTest\x1b[0m', ' ']), + ('蓝色', '', 2, ['蓝色', ' ']), + (u'שלום', '', 2, [u'\u05e9\u05dc\u05d5\u05dd', ' ']), + (u'معرب', '', 2, [u'\u0645\u0639\u0631\u0628', ' ']), + + ('test', 'top', 2, ['test', ' ']), + (Color('{blue}Test{/blue}'), 'top', 2, ['\x1b[34mTest\x1b[39m', ' ']), + (Fore.BLUE + 'Test' + Fore.RESET, 'top', 2, ['\x1b[34mTest\x1b[39m', ' ']), + (colored('Test', 'blue'), 'top', 2, ['\x1b[34mTest\x1b[0m', ' ']), + ('蓝色', 'top', 2, ['蓝色', ' ']), + (u'שלום', 'top', 2, [u'\u05e9\u05dc\u05d5\u05dd', ' ']), + (u'معرب', 'top', 2, [u'\u0645\u0639\u0631\u0628', ' ']), + + ('test', 'bottom', 2, [' ', 'test']), + (Color('{blue}Test{/blue}'), 'bottom', 2, [' ', '\x1b[34mTest\x1b[39m']), + (Fore.BLUE + 'Test' + Fore.RESET, 'bottom', 2, [' ', '\x1b[34mTest\x1b[39m']), + (colored('Test', 'blue'), 'bottom', 2, [' ', '\x1b[34mTest\x1b[0m']), + ('蓝色', 'bottom', 2, [' ', '蓝色']), + (u'שלום', 'bottom', 2, [' ', u'\u05e9\u05dc\u05d5\u05dd']), + (u'معرب', 'bottom', 2, [' ', u'\u0645\u0639\u0631\u0628']), + + ('test', 'middle', 3, [' ', 'test', ' ']), + (Color('{blue}Test{/blue}'), 'middle', 3, [' ', '\x1b[34mTest\x1b[39m', ' ']), + (Fore.BLUE + 'Test' + Fore.RESET, 'middle', 3, [' ', '\x1b[34mTest\x1b[39m', ' ']), + (colored('Test', 'blue'), 'middle', 3, [' ', '\x1b[34mTest\x1b[0m', ' ']), + ('蓝色', 'middle', 3, [' ', '蓝色', ' ']), + (u'שלום', 'middle', 3, [' ', u'\u05e9\u05dc\u05d5\u05dd', ' ']), + (u'معرب', 'middle', 3, [' ', u'\u0645\u0639\u0631\u0628', ' ']), +]) +def test_height(string, align, height, expected): + """Test height and vertical alignment. + + :param str string: String to test. + :param str align: Horizontal alignment. + :param int height: Expand string to this height without padding. + :param list expected: Expected output string. + """ + actual = align_and_pad_cell(string, (align,), (4, height), (0, 0, 0, 0)) + assert actual == expected + + +@pytest.mark.parametrize('string,align,expected', [ + ('', '', ['.......', '.......', '.......', '.......', '.......']), + ('\n', '', ['.......', '.......', '.......', '.......', '.......']), + ('a\nb\nc', '', ['.......', '.a.....', '.b.....', '.c.....', '.......']), + ('test', '', ['.......', '.test..', '.......', '.......', '.......']), + + ('', 'left', ['.......', '.......', '.......', '.......', '.......']), + ('\n', 'left', ['.......', '.......', '.......', '.......', '.......']), + ('a\nb\nc', 'left', ['.......', '.a.....', '.b.....', '.c.....', '.......']), + ('test', 'left', ['.......', '.test..', '.......', '.......', '.......']), + + ('', 'right', ['.......', '.......', '.......', '.......', '.......']), + ('\n', 'right', ['.......', '.......', '.......', '.......', '.......']), + ('a\nb\nc', 'right', ['.......', '.....a.', '.....b.', '.....c.', '.......']), + ('test', 'right', ['.......', '..test.', '.......', '.......', '.......']), + + ('', 'center', ['.......', '.......', '.......', '.......', '.......']), + ('\n', 'center', ['.......', '.......', '.......', '.......', '.......']), + ('a\nb\nc', 'center', ['.......', '...a...', '...b...', '...c...', '.......']), + ('test', 'center', ['.......', '..test.', '.......', '.......', '.......']), + + ('', 'top', ['.......', '.......', '.......', '.......', '.......']), + ('\n', 'top', ['.......', '.......', '.......', '.......', '.......']), + ('a\nb\nc', 'top', ['.......', '.a.....', '.b.....', '.c.....', '.......']), + ('test', 'top', ['.......', '.test..', '.......', '.......', '.......']), + + ('', 'bottom', ['.......', '.......', '.......', '.......', '.......']), + ('\n', 'bottom', ['.......', '.......', '.......', '.......', '.......']), + ('a\nb\nc', 'bottom', ['.......', '.a.....', '.b.....', '.c.....', '.......']), + ('test', 'bottom', ['.......', '.......', '.......', '.test..', '.......']), + + ('', 'middle', ['.......', '.......', '.......', '.......', '.......']), + ('\n', 'middle', ['.......', '.......', '.......', '.......', '.......']), + ('a\nb\nc', 'middle', ['.......', '.a.....', '.b.....', '.c.....', '.......']), + ('test', 'middle', ['.......', '.......', '.test..', '.......', '.......']), + + ( + u'蓝色\nשלום\nمعرب', + '', + ['.......', u'.蓝色..', u'.\u05e9\u05dc\u05d5\u05dd..', u'.\u0645\u0639\u0631\u0628..', '.......'] + ), + + ( + '\n'.join((Color('{blue}Test{/blue}'), Fore.BLUE + 'Test' + Fore.RESET, colored('Test', 'blue'))), + '', + ['.......', '.\x1b[34mTest\x1b[39m..', '.\x1b[34mTest\x1b[39m..', '.\x1b[34mTest\x1b[0m..', '.......'] + ), + + # (Color('{blue}A\nB{/blue}'), '', '.......\n.\x1b[34mA\x1b[39m.....\n.\x1b[34mB\x1b[39m.....\n.......\n.......'), + +]) +def test_odd_width_height_pad_space(string, align, expected): + """Test odd number width, height, padding, and dots for whitespaces. + + :param str string: String to test. + :param str align: Alignment in any dimension but one at a time. + :param list expected: Expected output string. + """ + actual = align_and_pad_cell(string, (align,), (5, 3), (1, 1, 1, 1), '.') + assert actual == expected diff --git a/tests/test_width_and_alignment/test_column_max_width.py b/tests/test_width_and_alignment/test_column_max_width.py new file mode 100644 index 0000000..696c9bf --- /dev/null +++ b/tests/test_width_and_alignment/test_column_max_width.py @@ -0,0 +1,107 @@ +"""Test function in module.""" + +import pytest + +from terminaltables.width_and_alignment import column_max_width, max_dimensions + + +@pytest.fixture(autouse=True) +def patch(monkeypatch): + """Monkeypatch before every test function in this module. + + :param monkeypatch: pytest fixture. + """ + monkeypatch.setattr('terminaltables.width_and_alignment.terminal_size', lambda: (79, 24)) + + +def test_empty(): + """Test with zero-length cells.""" + assert column_max_width(max_dimensions([['']])[0], 0, 0, 0, 0) == 79 + assert column_max_width(max_dimensions([['', '', '']])[0], 0, 0, 0, 0) == 79 + assert column_max_width(max_dimensions([['', '', ''], ['', '', '']])[0], 0, 0, 0, 0) == 79 + + assert column_max_width(max_dimensions([['']])[0], 0, 2, 1, 2) == 75 + assert column_max_width(max_dimensions([['', '', '']])[0], 0, 2, 1, 2) == 69 + assert column_max_width(max_dimensions([['', '', ''], ['', '', '']])[0], 0, 2, 1, 2) == 69 + + +def test_single_line(): + """Test with single-line cells.""" + table_data = [ + ['Name', 'Color', 'Type'], + ['Avocado', 'green', 'nut'], + ['Tomato', 'red', 'fruit'], + ['Lettuce', 'green', 'vegetable'], + ] + inner_widths = max_dimensions(table_data)[0] + + # '| Lettuce | green | vegetable |' + outer, inner, padding = 2, 1, 2 + assert column_max_width(inner_widths, 0, outer, inner, padding) == 55 + assert column_max_width(inner_widths, 1, outer, inner, padding) == 53 + assert column_max_width(inner_widths, 2, outer, inner, padding) == 57 + + # ' Lettuce | green | vegetable ' + outer = 0 + assert column_max_width(inner_widths, 0, outer, inner, padding) == 57 + assert column_max_width(inner_widths, 1, outer, inner, padding) == 55 + assert column_max_width(inner_widths, 2, outer, inner, padding) == 59 + + # '| Lettuce green vegetable |' + outer, inner = 2, 0 + assert column_max_width(inner_widths, 0, outer, inner, padding) == 57 + assert column_max_width(inner_widths, 1, outer, inner, padding) == 55 + assert column_max_width(inner_widths, 2, outer, inner, padding) == 59 + + # ' Lettuce green vegetable ' + outer = 0 + assert column_max_width(inner_widths, 0, outer, inner, padding) == 59 + assert column_max_width(inner_widths, 1, outer, inner, padding) == 57 + assert column_max_width(inner_widths, 2, outer, inner, padding) == 61 + + # '|Lettuce |green |vegetable |' + outer, inner, padding = 2, 1, 1 + assert column_max_width(inner_widths, 0, outer, inner, padding) == 58 + assert column_max_width(inner_widths, 1, outer, inner, padding) == 56 + assert column_max_width(inner_widths, 2, outer, inner, padding) == 60 + + # '|Lettuce |green |vegetable |' + padding = 5 + assert column_max_width(inner_widths, 0, outer, inner, padding) == 46 + assert column_max_width(inner_widths, 1, outer, inner, padding) == 44 + assert column_max_width(inner_widths, 2, outer, inner, padding) == 48 + + table_data = [ + ['Name', 'Color', 'Type'], + ['Avocado', 'green', 'nut'], + ['Tomato', 'red', 'fruit'], + ['Lettuce', 'green', 'vegetable'], + ['Watermelon', 'green', 'fruit'], + ] + inner_widths = max_dimensions(table_data)[0] + outer, inner, padding = 2, 1, 2 + assert column_max_width(inner_widths, 0, outer, inner, padding) == 55 + assert column_max_width(inner_widths, 1, outer, inner, padding) == 50 + assert column_max_width(inner_widths, 2, outer, inner, padding) == 54 + + +def test_multi_line(monkeypatch): + """Test with multi-line cells. + + :param monkeypatch: pytest fixture. + """ + table_data = [ + ['Show', 'Characters'], + ['Rugrats', ('Tommy Pickles, Chuckie Finster, Phillip DeVille, Lillian DeVille, Angelica Pickles,\n' + 'Susie Carmichael, Dil Pickles, Kimi Finster, Spike')], + ['South Park', 'Stan Marsh, Kyle Broflovski, Eric Cartman, Kenny McCormick'] + ] + inner_widths = max_dimensions(table_data)[0] + outer, inner, padding = 2, 1, 2 + + assert column_max_width(inner_widths, 0, outer, inner, padding) == -11 + assert column_max_width(inner_widths, 1, outer, inner, padding) == 62 + + monkeypatch.setattr('terminaltables.width_and_alignment.terminal_size', lambda: (100, 24)) + assert column_max_width(inner_widths, 0, outer, inner, padding) == 10 + assert column_max_width(inner_widths, 1, outer, inner, padding) == 83 diff --git a/tests/test_width_and_alignment/test_max_dimensions.py b/tests/test_width_and_alignment/test_max_dimensions.py new file mode 100644 index 0000000..fc97883 --- /dev/null +++ b/tests/test_width_and_alignment/test_max_dimensions.py @@ -0,0 +1,100 @@ +# coding: utf-8 +"""Test function in module.""" + +import pytest +from colorama import Fore +from colorclass import Color +from termcolor import colored + +from terminaltables.width_and_alignment import max_dimensions + + +@pytest.mark.parametrize('table_data,expected_w,expected_h', [ + ([[]], [], [0]), + ([['']], [0], [0]), + ([['', '']], [0, 0], [0]), + + ([[], []], [], [0, 0]), + ([[''], ['']], [0], [0, 0]), + ([['', ''], ['', '']], [0, 0], [0, 0]), +]) +def test_zero_length(table_data, expected_w, expected_h): + """Test zero-length or empty tables. + + :param list table_data: Input table data to test. + :param list expected_w: Expected widths. + :param list expected_h: Expected heights. + """ + actual = max_dimensions(table_data) + assert actual == (expected_w, expected_h, expected_w, expected_h) + + +def test_single_line(): + """Test widths.""" + table_data = [ + ['Name', 'Color', 'Type'], + ['Avocado', 'green', 'nut'], + ['Tomato', 'red', 'fruit'], + ['Lettuce', 'green', 'vegetable'], + ] + assert max_dimensions(table_data, 1, 1) == ([7, 5, 9], [1, 1, 1, 1], [9, 7, 11], [1, 1, 1, 1]) + + table_data.append(['Watermelon', 'green', 'fruit']) + assert max_dimensions(table_data, 2, 2) == ([10, 5, 9], [1, 1, 1, 1, 1], [14, 9, 13], [1, 1, 1, 1, 1]) + + +def test_multi_line(): + """Test heights.""" + table_data = [ + ['One\nTwo', 'Buckle\nMy\nShoe'], + ] + assert max_dimensions(table_data, 0, 0, 1, 1) == ([3, 6], [3], [3, 6], [5]) + + table_data = [ + ['Show', 'Characters'], + ['Rugrats', ('Tommy Pickles, Chuckie Finster, Phillip DeVille, Lillian DeVille, Angelica Pickles,\n' + 'Susie Carmichael, Dil Pickles, Kimi Finster, Spike')], + ['South Park', 'Stan Marsh, Kyle Broflovski, Eric Cartman, Kenny McCormick'] + ] + assert max_dimensions(table_data, 0, 0, 2, 2) == ([10, 83], [1, 2, 1], [10, 83], [5, 6, 5]) + + +def test_trailing_newline(): + r"""Test with trailing \n.""" + table_data = [ + ['Row One\n<blank>'], + ['<blank>\nRow Two'], + ['Row Three\n'], + ['\nRow Four'], + ] + assert max_dimensions(table_data) == ([9], [2, 2, 2, 2], [9], [2, 2, 2, 2]) + + +def test_colors_cjk_rtl(): + """Test color text, CJK characters, and RTL characters.""" + table_data = [ + [Color('{blue}Test{/blue}')], + [Fore.BLUE + 'Test' + Fore.RESET], + [colored('Test', 'blue')], + ] + assert max_dimensions(table_data) == ([4], [1, 1, 1], [4], [1, 1, 1]) + + table_data = [ + ['蓝色'], + ['世界你好'], + ] + assert max_dimensions(table_data) == ([8], [1, 1], [8], [1, 1]) + + table_data = [ + ['שלום'], + ['معرب'], + ] + assert max_dimensions(table_data) == ([4], [1, 1], [4], [1, 1]) + + +def test_non_string(): + """Test with non-string values.""" + table_data = [ + [123, 0.9, None, True, False], + ] + assert max_dimensions(table_data) == ([3, 3, 4, 4, 5], [1], [3, 3, 4, 4, 5], [1]) diff --git a/tests/test_width_and_alignment/test_table_width.py b/tests/test_width_and_alignment/test_table_width.py new file mode 100644 index 0000000..5818789 --- /dev/null +++ b/tests/test_width_and_alignment/test_table_width.py @@ -0,0 +1,70 @@ +"""Test function in module.""" + +from terminaltables.width_and_alignment import max_dimensions, table_width + + +def test_empty(): + """Test with zero-length cells.""" + assert table_width(max_dimensions([['']])[2], 0, 0) == 0 + assert table_width(max_dimensions([['', '', '']])[2], 0, 0) == 0 + assert table_width(max_dimensions([['', '', ''], ['', '', '']])[2], 0, 0) == 0 + + assert table_width(max_dimensions([['']], 1, 1)[2], 2, 1) == 4 + assert table_width(max_dimensions([['', '', '']], 1, 1)[2], 2, 1) == 10 + assert table_width(max_dimensions([['', '', ''], ['', '', '']], 1, 1)[2], 2, 1) == 10 + + +def test_single_line(): + """Test with single-line cells.""" + table_data = [ + ['Name', 'Color', 'Type'], + ['Avocado', 'green', 'nut'], + ['Tomato', 'red', 'fruit'], + ['Lettuce', 'green', 'vegetable'], + ] + + # '| Lettuce | green | vegetable |' + outer, inner, outer_widths = 2, 1, max_dimensions(table_data, 1, 1)[2] + assert table_width(outer_widths, outer, inner) == 31 + + # ' Lettuce | green | vegetable ' + outer = 0 + assert table_width(outer_widths, outer, inner) == 29 + + # '| Lettuce green vegetable |' + outer, inner = 2, 0 + assert table_width(outer_widths, outer, inner) == 29 + + # ' Lettuce green vegetable ' + outer = 0 + assert table_width(outer_widths, outer, inner) == 27 + + # '|Lettuce |green |vegetable |' + outer, inner, outer_widths = 2, 1, max_dimensions(table_data, 1)[2] + assert table_width(outer_widths, outer, inner) == 28 + + # '|Lettuce |green |vegetable |' + outer_widths = max_dimensions(table_data, 3, 2)[2] + assert table_width(outer_widths, outer, inner) == 40 + + table_data = [ + ['Name', 'Color', 'Type'], + ['Avocado', 'green', 'nut'], + ['Tomato', 'red', 'fruit'], + ['Lettuce', 'green', 'vegetable'], + ['Watermelon', 'green', 'fruit'], + ] + outer, inner, outer_widths = 2, 1, max_dimensions(table_data, 1, 1)[2] + assert table_width(outer_widths, outer, inner) == 34 + + +def test_multi_line(): + """Test with multi-line cells.""" + table_data = [ + ['Show', 'Characters'], + ['Rugrats', ('Tommy Pickles, Chuckie Finster, Phillip DeVille, Lillian DeVille, Angelica Pickles,\n' + 'Susie Carmichael, Dil Pickles, Kimi Finster, Spike')], + ['South Park', 'Stan Marsh, Kyle Broflovski, Eric Cartman, Kenny McCormick'] + ] + outer, inner, outer_widths = 2, 1, max_dimensions(table_data, 1, 1)[2] + assert table_width(outer_widths, outer, inner) == 100 diff --git a/tests/test_width_and_alignment/test_visible_width.py b/tests/test_width_and_alignment/test_visible_width.py new file mode 100644 index 0000000..79cebcb --- /dev/null +++ b/tests/test_width_and_alignment/test_visible_width.py @@ -0,0 +1,59 @@ +# coding: utf-8 +"""Test function in module.""" + +import pytest +from colorama import Fore +from colorclass import Color +from termcolor import colored + +from terminaltables.width_and_alignment import visible_width + + +@pytest.mark.parametrize('string,expected', [ + # str + ('hello, world', 12), + ('世界你好', 8), + ('蓝色', 4), + ('שלום', 4), + ('معرب', 4), + ('hello 世界', 10), + + # str+ansi + ('\x1b[34mhello, world\x1b[39m', 12), + ('\x1b[34m世界你好\x1b[39m', 8), + ('\x1b[34m蓝色\x1b[39m', 4), + ('\x1b[34mשלום\x1b[39m', 4), + ('\x1b[34mمعرب\x1b[39m', 4), + ('\x1b[34mhello 世界\x1b[39m', 10), + + # colorclass + (Color(u'{blue}hello, world{/blue}'), 12), + (Color(u'{blue}世界你好{/blue}'), 8), + (Color(u'{blue}蓝色{/blue}'), 4), + (Color(u'{blue}שלום{/blue}'), 4), + (Color(u'{blue}معرب{/blue}'), 4), + (Color(u'{blue}hello 世界{/blue}'), 10), + + # colorama + (Fore.BLUE + 'hello, world' + Fore.RESET, 12), + (Fore.BLUE + '世界你好' + Fore.RESET, 8), + (Fore.BLUE + '蓝色' + Fore.RESET, 4), + (Fore.BLUE + 'שלום' + Fore.RESET, 4), + (Fore.BLUE + 'معرب' + Fore.RESET, 4), + (Fore.BLUE + 'hello 世界' + Fore.RESET, 10), + + # termcolor + (colored('hello, world', 'blue'), 12), + (colored('世界你好', 'blue'), 8), + (colored('蓝色', 'blue'), 4), + (colored('שלום', 'blue'), 4), + (colored('معرب', 'blue'), 4), + (colored('hello 世界', 'blue'), 10), +]) +def test(string, expected): + """Test function with different color libraries. + + :param str string: Input string to measure. + :param int expected: Expected visible width of string (some characters are len() == 1 but take up 2 spaces). + """ + assert visible_width(string) == expected |