summaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2022-09-16 09:09:35 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2022-09-16 09:09:35 +0000
commit0dfe1c9e2780469e3a4696e8fb3e6f717a7ebeb7 (patch)
treea0b651b55ea02e3b00bbc5eedba566fdd6bd7c08 /tests
parentInitial commit. (diff)
downloadterminaltables-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')
-rw-r--r--tests/__init__.py5
-rw-r--r--tests/screenshot.py292
-rw-r--r--tests/test_all_tables_e2e/__init__.py1
-rw-r--r--tests/test_all_tables_e2e/sub_ascii_win10.bmpbin0 -> 36870 bytes
-rw-r--r--tests/test_all_tables_e2e/sub_ascii_winxp.bmpbin0 -> 49350 bytes
-rw-r--r--tests/test_all_tables_e2e/sub_double_win10.bmpbin0 -> 36090 bytes
-rw-r--r--tests/test_all_tables_e2e/sub_double_win10b.bmpbin0 -> 36090 bytes
-rw-r--r--tests/test_all_tables_e2e/sub_double_winxp.bmpbin0 -> 48726 bytes
-rw-r--r--tests/test_all_tables_e2e/sub_single_win10.bmpbin0 -> 34854 bytes
-rw-r--r--tests/test_all_tables_e2e/sub_single_win10b.bmpbin0 -> 34854 bytes
-rw-r--r--tests/test_all_tables_e2e/sub_single_winxp.bmpbin0 -> 45954 bytes
-rw-r--r--tests/test_all_tables_e2e/test_ascii_table.py145
-rw-r--r--tests/test_all_tables_e2e/test_double_table.py245
-rw-r--r--tests/test_all_tables_e2e/test_github_table.py77
-rw-r--r--tests/test_all_tables_e2e/test_porcelain_table.py59
-rw-r--r--tests/test_all_tables_e2e/test_single_table.py171
-rw-r--r--tests/test_all_tables_e2e/test_single_table_windows.py246
-rw-r--r--tests/test_ascii_table.py108
-rw-r--r--tests/test_base_table/test_gen_row_lines.py86
-rw-r--r--tests/test_base_table/test_gen_table.py225
-rw-r--r--tests/test_base_table/test_horizontal_border.py98
-rw-r--r--tests/test_base_table/test_table.py196
-rw-r--r--tests/test_build/test_build_border.py312
-rw-r--r--tests/test_build/test_build_row.py104
-rw-r--r--tests/test_build/test_combine.py37
-rw-r--r--tests/test_build/test_flatten.py25
-rw-r--r--tests/test_examples.py32
-rw-r--r--tests/test_terminal_io/__init__.py45
-rw-r--r--tests/test_terminal_io/sub_title_ascii_win10.bmpbin0 -> 1998 bytes
-rw-r--r--tests/test_terminal_io/sub_title_ascii_win2012.bmpbin0 -> 3048 bytes
-rw-r--r--tests/test_terminal_io/sub_title_ascii_winxp.bmpbin0 -> 2070 bytes
-rw-r--r--tests/test_terminal_io/sub_title_cjk_win10.bmpbin0 -> 4278 bytes
-rw-r--r--tests/test_terminal_io/sub_title_cjk_win2012.bmpbin0 -> 6896 bytes
-rw-r--r--tests/test_terminal_io/sub_title_cjk_winxp.bmpbin0 -> 4322 bytes
-rw-r--r--tests/test_terminal_io/test_get_console_info.py28
-rw-r--r--tests/test_terminal_io/test_set_terminal_title.py110
-rw-r--r--tests/test_terminal_io/test_terminal_size.py54
-rw-r--r--tests/test_width_and_alignment/test_align_and_pad_cell.py202
-rw-r--r--tests/test_width_and_alignment/test_column_max_width.py107
-rw-r--r--tests/test_width_and_alignment/test_max_dimensions.py100
-rw-r--r--tests/test_width_and_alignment/test_table_width.py70
-rw-r--r--tests/test_width_and_alignment/test_visible_width.py59
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
new file mode 100644
index 0000000..fe21fa7
--- /dev/null
+++ b/tests/test_all_tables_e2e/sub_ascii_win10.bmp
Binary files differ
diff --git a/tests/test_all_tables_e2e/sub_ascii_winxp.bmp b/tests/test_all_tables_e2e/sub_ascii_winxp.bmp
new file mode 100644
index 0000000..4105d11
--- /dev/null
+++ b/tests/test_all_tables_e2e/sub_ascii_winxp.bmp
Binary files differ
diff --git a/tests/test_all_tables_e2e/sub_double_win10.bmp b/tests/test_all_tables_e2e/sub_double_win10.bmp
new file mode 100644
index 0000000..e6b00ae
--- /dev/null
+++ b/tests/test_all_tables_e2e/sub_double_win10.bmp
Binary files differ
diff --git a/tests/test_all_tables_e2e/sub_double_win10b.bmp b/tests/test_all_tables_e2e/sub_double_win10b.bmp
new file mode 100644
index 0000000..a527959
--- /dev/null
+++ b/tests/test_all_tables_e2e/sub_double_win10b.bmp
Binary files differ
diff --git a/tests/test_all_tables_e2e/sub_double_winxp.bmp b/tests/test_all_tables_e2e/sub_double_winxp.bmp
new file mode 100644
index 0000000..aae7b24
--- /dev/null
+++ b/tests/test_all_tables_e2e/sub_double_winxp.bmp
Binary files differ
diff --git a/tests/test_all_tables_e2e/sub_single_win10.bmp b/tests/test_all_tables_e2e/sub_single_win10.bmp
new file mode 100644
index 0000000..ff6f272
--- /dev/null
+++ b/tests/test_all_tables_e2e/sub_single_win10.bmp
Binary files differ
diff --git a/tests/test_all_tables_e2e/sub_single_win10b.bmp b/tests/test_all_tables_e2e/sub_single_win10b.bmp
new file mode 100644
index 0000000..c8d1e36
--- /dev/null
+++ b/tests/test_all_tables_e2e/sub_single_win10b.bmp
Binary files differ
diff --git a/tests/test_all_tables_e2e/sub_single_winxp.bmp b/tests/test_all_tables_e2e/sub_single_winxp.bmp
new file mode 100644
index 0000000..c4f5873
--- /dev/null
+++ b/tests/test_all_tables_e2e/sub_single_winxp.bmp
Binary files differ
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
new file mode 100644
index 0000000..638d0a3
--- /dev/null
+++ b/tests/test_terminal_io/sub_title_ascii_win10.bmp
Binary files differ
diff --git a/tests/test_terminal_io/sub_title_ascii_win2012.bmp b/tests/test_terminal_io/sub_title_ascii_win2012.bmp
new file mode 100644
index 0000000..04f0f2a
--- /dev/null
+++ b/tests/test_terminal_io/sub_title_ascii_win2012.bmp
Binary files differ
diff --git a/tests/test_terminal_io/sub_title_ascii_winxp.bmp b/tests/test_terminal_io/sub_title_ascii_winxp.bmp
new file mode 100644
index 0000000..c40a2d2
--- /dev/null
+++ b/tests/test_terminal_io/sub_title_ascii_winxp.bmp
Binary files differ
diff --git a/tests/test_terminal_io/sub_title_cjk_win10.bmp b/tests/test_terminal_io/sub_title_cjk_win10.bmp
new file mode 100644
index 0000000..052e6b5
--- /dev/null
+++ b/tests/test_terminal_io/sub_title_cjk_win10.bmp
Binary files differ
diff --git a/tests/test_terminal_io/sub_title_cjk_win2012.bmp b/tests/test_terminal_io/sub_title_cjk_win2012.bmp
new file mode 100644
index 0000000..ec48a85
--- /dev/null
+++ b/tests/test_terminal_io/sub_title_cjk_win2012.bmp
Binary files differ
diff --git a/tests/test_terminal_io/sub_title_cjk_winxp.bmp b/tests/test_terminal_io/sub_title_cjk_winxp.bmp
new file mode 100644
index 0000000..349f685
--- /dev/null
+++ b/tests/test_terminal_io/sub_title_cjk_winxp.bmp
Binary files differ
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