import doctest
import math
import os
import unittest
import textcanvas.textcanvas
from textcanvas.color import Color
from textcanvas.textcanvas import Surface, TextCanvas
def load_tests(
loader: unittest.TestLoader, tests: unittest.TestSuite, ignore: str
) -> unittest.TestSuite:
tests.addTests(doctest.DocTestSuite(textcanvas.textcanvas))
return tests
def stroke_line_accros_canvas(canvas: TextCanvas) -> None:
y = 0
for x in range(canvas.screen.width):
canvas.set_pixel(x, y, True)
y += 1
class TestSurface(unittest.TestCase):
def test_size(self):
surface = Surface(width=15, height=9)
self.assertEqual(surface.width, 15)
self.assertEqual(surface.height, 9)
class TestTextCanvas(unittest.TestCase):
def test_output_size(self) -> None:
canvas = TextCanvas(7, 4)
self.assertEqual(canvas.output.width, 7, "Incorrect output width.")
self.assertEqual(canvas.output.height, 4, "Incorrect output height.")
def test_screen_size(self) -> None:
canvas = TextCanvas(7, 4)
self.assertEqual(canvas.screen.width, 7 * 2, "Incorrect output width.")
self.assertEqual(canvas.screen.height, 4 * 4, "Incorrect output height.")
def test_buffer_size(self) -> None:
canvas = TextCanvas(7, 4)
buffer_width = len(canvas.buffer[0])
buffer_height = len(canvas.buffer)
self.assertEqual(buffer_width, 7 * 2, "Incorrect number of rows in buffer.")
self.assertEqual(buffer_height, 4 * 4, "Incorrect number of columns in buffer.")
def test_default_size(self) -> None:
canvas = TextCanvas()
self.assertEqual(canvas.output.width, 80, "Incorrect default width.")
self.assertEqual(canvas.output.height, 24, "Incorrect default height.")
def test_get_default_size(self) -> None:
(width, height) = TextCanvas.get_default_size()
self.assertEqual(width, 80, "Incorrect default width.")
self.assertEqual(height, 24, "Incorrect default width.")
def test_constructor_default_size_eq_get_default_size(self) -> None:
canvas = TextCanvas()
(width, height) = TextCanvas.get_default_size()
self.assertEqual(canvas.output.width, width, "Incorrect default width.")
self.assertEqual(canvas.output.height, height, "Incorrect default height.")
def test_size_zero_raises_error(self) -> None:
with self.assertRaises(ValueError) as ctx:
ctx.msg = "Zero width did not raise error."
TextCanvas(0, 1)
with self.assertRaises(ValueError) as ctx:
ctx.msg = "Zero height did not raise error."
TextCanvas(1, 0)
with self.assertRaises(ValueError) as ctx:
ctx.msg = "Zero width and height did not raise error."
TextCanvas(0, 0)
def test_size_negative_raises_error(self) -> None:
with self.assertRaises(ValueError) as ctx:
ctx.msg = "Negative width did not raise error."
TextCanvas(-1, 1)
with self.assertRaises(ValueError) as ctx:
ctx.msg = "Negative height did not raise error."
TextCanvas(1, -1)
with self.assertRaises(ValueError) as ctx:
ctx.msg = "Negative width and height did not raise error."
TextCanvas(-1, -1)
def test_auto_size(self) -> None:
os.environ["WIDTH"] = "12"
os.environ["HEIGHT"] = "5"
canvas = TextCanvas.auto()
self.assertEqual(canvas.output.width, 12, "Incorrect auto width.")
self.assertEqual(canvas.output.height, 5, "Incorrect auto height.")
self.assertEqual(TextCanvas.get_auto_size(), (12, 5))
def test_auto_size_width_and_height_variables_dont_exist(self) -> None:
os.environ.pop("WIDTH", None)
os.environ.pop("HEIGHT", None)
with self.assertRaises(LookupError) as ctx:
ctx.msg = "`WIDTH` and `HEIGHT` don't exist."
TextCanvas.auto()
with self.assertRaises(LookupError):
TextCanvas.get_auto_size()
def test_auto_size_cannot_parse_width_variable(self) -> None:
os.environ["WIDTH"] = "abc"
os.environ["HEIGHT"] = "1"
with self.assertRaises(LookupError) as ctx:
ctx.msg = "`WIDTH` is not a number."
TextCanvas.auto()
with self.assertRaises(LookupError):
TextCanvas.get_auto_size()
def test_auto_size_cannot_parse_height_variable(self) -> None:
os.environ["WIDTH"] = "1"
os.environ["HEIGHT"] = "abc"
with self.assertRaises(LookupError) as ctx:
ctx.msg = "`HEIGHT` is not a number."
TextCanvas.auto()
with self.assertRaises(LookupError):
TextCanvas.get_auto_size()
def test_string_representation(self) -> None:
canvas = TextCanvas(7, 4)
self.assertEqual(
canvas.to_string(), str(canvas), "Incorrect string representation."
)
self.assertEqual(
repr(canvas),
"Canvas(output=(7×4), screen=(14×16)))",
"Incorrect string representation.",
)
def test_shortcuts(self) -> None:
canvas = TextCanvas(7, 4)
self.assertEqual(canvas.w, 13, "Incorrect screen width.")
self.assertEqual(canvas.h, 15, "Incorrect screen height.")
self.assertEqual(canvas.cx, 7, "Incorrect screen center-X.")
self.assertEqual(canvas.cy, 8, "Incorrect screen center-Y.")
def test_check_output_bounds(self) -> None:
canvas = TextCanvas(7, 4)
self.assertTrue(canvas._check_output_bounds(0, 0))
self.assertTrue(canvas._check_output_bounds(6, 0))
self.assertTrue(canvas._check_output_bounds(6, 3))
self.assertTrue(canvas._check_output_bounds(0, 3))
self.assertFalse(canvas._check_output_bounds(0, -1))
self.assertFalse(canvas._check_output_bounds(7, 0))
self.assertFalse(canvas._check_output_bounds(6, 4))
self.assertFalse(canvas._check_output_bounds(-1, 3))
def test_check_screen_bounds(self) -> None:
canvas = TextCanvas(7, 4)
self.assertTrue(canvas._check_screen_bounds(0, 0))
self.assertTrue(canvas._check_screen_bounds(13, 0))
self.assertTrue(canvas._check_screen_bounds(13, 15))
self.assertTrue(canvas._check_screen_bounds(0, 15))
self.assertFalse(canvas._check_screen_bounds(0, -1))
self.assertFalse(canvas._check_screen_bounds(14, 0))
self.assertFalse(canvas._check_screen_bounds(13, 16))
self.assertFalse(canvas._check_screen_bounds(-1, 15))
def test_turn_all_pixels_on(self) -> None:
canvas = TextCanvas(2, 2)
for x in range(canvas.screen.width):
for y in range(canvas.screen.height):
canvas.set_pixel(x, y, True)
self.assertEqual(canvas.to_string(), "⣿⣿\n⣿⣿\n", "Output not fully on.")
def test_get_pixel(self) -> None:
canvas = TextCanvas(2, 2)
self.assertEqual(canvas.get_pixel(3, 2), False, "Pixel should be turned off.")
canvas.set_pixel(3, 2, True)
self.assertEqual(canvas.get_pixel(3, 2), True, "Pixel should be turned on.")
def test_get_pixel_with_overflow(self) -> None:
canvas = TextCanvas(1, 1)
self.assertIsNone(canvas.get_pixel(-1, 0), "Overflow should be None.")
self.assertIsNone(canvas.get_pixel(0, -1), "Overflow should be None.")
self.assertIsNone(canvas.get_pixel(-1, -1), "Overflow should be None.")
self.assertIsNone(
canvas.get_pixel(canvas.screen.width, 0), "Overflow should be None."
)
self.assertIsNone(
canvas.get_pixel(0, canvas.screen.height), "Overflow should be None."
)
self.assertIsNone(
canvas.get_pixel(canvas.screen.width, canvas.screen.height),
"Overflow should be None.",
)
def test_get_pixel_on_boundaries(self) -> None:
canvas = TextCanvas(1, 1)
canvas.buffer = [
[True, False],
[False, False],
[False, False],
[False, True],
]
self.assertEqual(canvas.get_pixel(0, 0), True, "Incorrect pixel value.")
self.assertEqual(
canvas.get_pixel(canvas.screen.width - 1, canvas.screen.height - 1),
True,
"Incorrect pixel value.",
)
def test_set_pixel(self) -> None:
canvas = TextCanvas(3, 2)
stroke_line_accros_canvas(canvas)
self.assertEqual(
canvas.buffer,
[
[True, False, False, False, False, False],
[False, True, False, False, False, False],
[False, False, True, False, False, False],
[False, False, False, True, False, False],
[False, False, False, False, True, False],
[False, False, False, False, False, True],
[False, False, False, False, False, False],
[False, False, False, False, False, False],
],
"Incorrect buffer content.",
)
def test_set_pixel_with_overflow(self) -> None:
canvas = TextCanvas(1, 1)
canvas.set_pixel(-1, 0, True)
canvas.set_pixel(0, -1, True)
canvas.set_pixel(-1, -1, True)
canvas.set_pixel(canvas.screen.width, 0, True)
canvas.set_pixel(0, canvas.screen.height, True)
canvas.set_pixel(canvas.screen.width, canvas.screen.height, True)
self.assertEqual(
canvas.buffer,
[
[False, False],
[False, False],
[False, False],
[False, False],
],
"No pixel should be turned on.",
)
def test_set_pixel_on_boundaries(self) -> None:
canvas = TextCanvas(1, 1)
canvas.set_pixel(0, 0, True)
canvas.set_pixel(canvas.screen.width - 1, canvas.screen.height - 1, True)
self.assertEqual(
canvas.buffer,
[
[True, False],
[False, False],
[False, False],
[False, True],
],
"Incorrect buffer content.",
)
def test_get_as_string(self) -> None:
canvas = TextCanvas(3, 2)
stroke_line_accros_canvas(canvas)
self.assertEqual(canvas.to_string(), "⠑⢄⠀\n⠀⠀⠑\n", "Incorrect output string.")
def test_clear(self) -> None:
canvas = TextCanvas(2, 2)
canvas.fill()
canvas.clear()
self.assertEqual(canvas.to_string(), "⠀⠀\n⠀⠀\n", "Output not empty.")
def test_clear_edits_buffer_in_place(self) -> None:
canvas = TextCanvas(1, 1)
buffer = canvas.buffer
row_0 = canvas.buffer[0]
row_1 = canvas.buffer[1]
row_2 = canvas.buffer[2]
row_3 = canvas.buffer[3]
canvas.clear()
self.assertIs(buffer, canvas.buffer, "Container should be the same as before.")
self.assertIs(
row_0, canvas.buffer[0], "Container should be the same as before."
)
self.assertIs(
row_1, canvas.buffer[1], "Container should be the same as before."
)
self.assertIs(
row_2, canvas.buffer[2], "Container should be the same as before."
)
self.assertIs(
row_3, canvas.buffer[3], "Container should be the same as before."
)
def test_fill(self) -> None:
canvas = TextCanvas(2, 2)
canvas.fill()
self.assertEqual(canvas.to_string(), "⣿⣿\n⣿⣿\n", "Output not full.")
def test_invert(self) -> None:
canvas = TextCanvas(15, 5)
canvas.fill_rect(6, 3, 20, 15)
self.assertFalse(canvas.is_inverted)
canvas.invert()
canvas.fill_rect(9, 6, 14, 9)
self.assertTrue(canvas.is_inverted)
self.assertEqual(
canvas.to_string(),
"""\
⠀⠀⠀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⠀⠀
⠀⠀⠀⣿⡟⠛⠛⠛⠛⠛⠛⢻⣿⠀⠀
⠀⠀⠀⣿⡇⠀⠀⠀⠀⠀⠀⢸⣿⠀⠀
⠀⠀⠀⣿⣇⣀⣀⣀⣀⣀⣀⣸⣿⠀⠀
⠀⠀⠀⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠀⠀
""",
)
def test_double_invert(self) -> None:
canvas = TextCanvas(15, 5)
self.assertFalse(canvas.is_inverted)
canvas.invert()
self.assertTrue(canvas.is_inverted)
canvas.invert()
self.assertFalse(canvas.is_inverted)
def test_clear_not_affected_by_invert(self) -> None:
canvas = TextCanvas(2, 2)
canvas.invert()
canvas.clear()
self.assertEqual(canvas.to_string(), "⠀⠀\n⠀⠀\n", "Output not empty.")
def test_fill_not_affected_by_invert(self) -> None:
canvas = TextCanvas(2, 2)
canvas.invert()
canvas.fill()
self.assertEqual(canvas.to_string(), "⣿⣿\n⣿⣿\n", "Output not full.")
def test_iter_buffer_by_blocks_lrtb(self) -> None:
canvas = TextCanvas(3, 2)
stroke_line_accros_canvas(canvas)
self.assertEqual(
list(canvas._iter_buffer_by_blocks_lrtb()),
[
(
(True, False),
(False, True),
(False, False),
(False, False),
),
(
(False, False),
(False, False),
(True, False),
(False, True),
),
(
(False, False),
(False, False),
(False, False),
(False, False),
),
(
(False, False),
(False, False),
(False, False),
(False, False),
),
(
(False, False),
(False, False),
(False, False),
(False, False),
),
(
(True, False),
(False, True),
(False, False),
(False, False),
),
],
"Incorrect list of blocks.",
)
def test_iter_buffer(self) -> None:
canvas = TextCanvas(3, 2)
self.assertEqual(list(canvas.iter_buffer()), [
(0, 0), (1, 0), (2, 0), (3, 0), (4, 0), (5, 0),
(0, 1), (1, 1), (2, 1), (3, 1), (4, 1), (5, 1),
(0, 2), (1, 2), (2, 2), (3, 2), (4, 2), (5, 2),
(0, 3), (1, 3), (2, 3), (3, 3), (4, 3), (5, 3),
(0, 4), (1, 4), (2, 4), (3, 4), (4, 4), (5, 4),
(0, 5), (1, 5), (2, 5), (3, 5), (4, 5), (5, 5),
(0, 6), (1, 6), (2, 6), (3, 6), (4, 6), (5, 6),
(0, 7), (1, 7), (2, 7), (3, 7), (4, 7), (5, 7),
], "Incorrect X and Y pairs, or in wrong order.")
class TestTextCanvasColor(unittest.TestCase):
def test_color_buffer_size_at_init(self) -> None:
canvas = TextCanvas(7, 4)
self.assertEqual(len(canvas.color_buffer), 0, "Color buffer should be empty.")
def test_color_buffer_size_with_color(self) -> None:
canvas = TextCanvas(7, 4)
canvas.set_color(Color().bg_bright_blue())
buffer_width = len(canvas.color_buffer[0])
buffer_height = len(canvas.color_buffer)
self.assertEqual(
buffer_width, 7, "Color buffer width should match output buffer width."
)
self.assertEqual(
buffer_height, 4, "Color buffer height should match output buffer height."
)
def test_is_colorized(self) -> None:
canvas = TextCanvas(2, 2)
self.assertFalse(
canvas.is_colorized, "Canvas should not be colorized by default."
)
canvas.set_color(Color().bg_bright_blue())
self.assertTrue(
canvas.is_colorized, "Canvas should be colorized after a color is set."
)
def test_set_color(self) -> None:
canvas = TextCanvas(2, 2)
canvas.set_color(Color().bg_bright_blue())
canvas.set_pixel(3, 3, True)
self.assertEqual(
canvas.color_buffer,
[
[Color(), Color().bg_bright_blue()],
[Color(), Color()],
],
"Incorrect color buffer.",
)
def test_set_color_multiple(self) -> None:
canvas = TextCanvas(2, 2)
canvas.set_color(Color().bg_bright_blue())
canvas.set_pixel(3, 3, True)
canvas.set_pixel(1, 5, True)
self.assertEqual(
canvas.color_buffer,
[
[Color(), Color().bg_bright_blue()],
[Color().bg_bright_blue(), Color()],
],
"Incorrect color buffer.",
)
def test_set_color_override(self) -> None:
canvas = TextCanvas(2, 2)
canvas.set_color(Color().bg_bright_blue())
canvas.set_pixel(3, 3, True)
canvas.set_pixel(1, 5, True)
canvas.set_color(Color().bg_bright_red())
canvas.set_pixel(3, 3, True)
self.assertEqual(
canvas.color_buffer,
[
[Color(), Color().bg_bright_red()],
[Color().bg_bright_blue(), Color()],
],
"Incorrect color buffer.",
)
def test_color_is_reset_if_pixel_turned_off(self) -> None:
canvas = TextCanvas(2, 2)
canvas.set_color(Color().bg_bright_blue())
canvas.set_pixel(3, 3, True)
canvas.set_pixel(1, 5, True)
canvas.set_pixel(3, 3, False)
self.assertEqual(
canvas.color_buffer,
[
[Color(), Color()],
[Color().bg_bright_blue(), Color()],
],
"Incorrect color buffer.",
)
def test_get_as_string_colored(self) -> None:
canvas = TextCanvas(3, 2)
canvas.set_color(Color().bright_green())
stroke_line_accros_canvas(canvas)
self.assertEqual(
canvas.to_string(),
"\x1b[0;92m⠑\x1b[0m\x1b[0;92m⢄\x1b[0m⠀\n⠀⠀\x1b[0;92m⠑\x1b[0m\n",
"Incorrect output string.",
)
def test_clear_clears_color_buffer(self) -> None:
canvas = TextCanvas(2, 1)
self.assertEqual(canvas.color_buffer, [], "Color buffer should be empty.")
canvas.set_color(Color().bright_red())
self.assertEqual(
canvas.color_buffer,
[[Color(), Color()]],
"Color buffer should be full of no-color.",
)
canvas.set_pixel(0, 0, True)
self.assertEqual(
canvas.color_buffer,
[[Color().bright_red(), Color()]],
"First pixel should be red.",
)
canvas.clear()
self.assertEqual(
canvas.color_buffer,
[[Color(), Color()]],
"Color buffer should be full of no-color.",
)
def test_clear_edits_color_buffer_in_place(self) -> None:
canvas = TextCanvas(2, 2)
canvas.set_color(Color().bright_red())
color_buffer = canvas.color_buffer
row_0 = canvas.color_buffer[0]
row_1 = canvas.color_buffer[1]
canvas.clear()
self.assertIs(
color_buffer, canvas.color_buffer, "Container should be the same as before."
)
self.assertIs(
row_0, canvas.color_buffer[0], "Container should be the same as before."
)
self.assertIs(
row_1, canvas.color_buffer[1], "Container should be the same as before."
)
class TestTextCanvasText(unittest.TestCase):
def test_text_buffer_size_at_init(self) -> None:
canvas = TextCanvas(7, 4)
self.assertEqual(len(canvas.text_buffer), 0, "Text buffer should be empty.")
def test_text_buffer_size_with_color(self) -> None:
canvas = TextCanvas(7, 4)
canvas.draw_text("foo", 0, 0)
buffer_width = len(canvas.text_buffer[0])
buffer_height = len(canvas.text_buffer)
self.assertEqual(
buffer_width, 7, "Text buffer width should match output buffer width."
)
self.assertEqual(
buffer_height, 4, "Text buffer height should match output buffer height."
)
def test_is_textual(self) -> None:
canvas = TextCanvas(2, 2)
self.assertFalse(
canvas.is_colorized, "Canvas should not be textual by default."
)
canvas.draw_text("hi", 0, 0)
self.assertTrue(
canvas.is_textual, "Canvas should be textual after text is drawn."
)
def test_draw_text(self) -> None:
canvas = TextCanvas(5, 1)
canvas.draw_text("bar", 1, 0)
self.assertEqual(
canvas.text_buffer, [["", "b", "a", "r", ""]], "Incorrect text buffer."
)
def test_draw_text_multiline(self) -> None:
canvas = TextCanvas(6, 2)
canvas.draw_text("hello\nworld!", 0, 0)
self.assertEqual(
canvas.text_buffer,
[
["h", "e", "l", "l", "o", ""],
["w", "o", "r", "l", "d", "!"],
],
"Incorrect text buffer.",
)
def test_draw_text_vertical(self) -> None:
canvas = TextCanvas(1, 5)
self.assertFalse(canvas.is_textual)
canvas.draw_text_vertical("bar", 0, 1)
self.assertTrue(canvas.is_textual)
self.assertEqual(
canvas.text_buffer,
[
[""],
["b"],
["a"],
["r"],
[""],
],
"Incorrect text buffer.",
)
def test_draw_text_vertical_multiline(self) -> None:
canvas = TextCanvas(2, 6)
canvas.draw_text_vertical("hello\nworld!", 0, 0)
self.assertEqual(
canvas.text_buffer,
[
["h", "w"],
["e", "o"],
["l", "r"],
["l", "l"],
["o", "d"],
["", "!"],
],
"Incorrect text buffer.",
)
def test_draw_text_over_text(self) -> None:
canvas = TextCanvas(5, 1)
canvas.draw_text("bar", 1, 0)
canvas.draw_text("foo", 2, 0)
self.assertEqual(
canvas.text_buffer, [["", "b", "f", "o", "o"]], "Incorrect text buffer."
)
def test_draw_text_space_is_transparent(self) -> None:
canvas = TextCanvas(9, 1)
canvas.draw_text("foo bar", 1, 0)
self.assertEqual(
canvas.text_buffer,
[["", "f", "o", "o", "", "b", "a", "r", ""]],
"Incorrect text buffer.",
)
def test_draw_text_space_clears_text(self) -> None:
canvas = TextCanvas(5, 1)
canvas.draw_text("bar", 1, 0)
canvas.draw_text(" ", 2, 0)
self.assertEqual(
canvas.text_buffer,
[["", "b", "", "", ""]],
"Incorrect text buffer.",
)
def test_draw_text_with_overflow(self) -> None:
canvas = TextCanvas(5, 2)
canvas.draw_text("foo", -1, 0)
canvas.draw_text("bar", 3, 1)
canvas.draw_text("baz1", -10, -1)
canvas.draw_text("baz2", 10, -1)
canvas.draw_text("baz3", -10, 2)
canvas.draw_text("baz4", 10, 2)
self.assertEqual(
canvas.text_buffer,
[
["o", "o", "", "", ""],
["", "", "", "b", "a"],
],
"Incorrect text buffer.",
)
def test_draw_text_on_boundaries(self) -> None:
canvas = TextCanvas(3, 3)
canvas.draw_text("a", 0, 1)
canvas.draw_text("b", 1, 0)
canvas.draw_text("c", 2, 1)
canvas.draw_text("d", 1, 2)
self.assertEqual(
canvas.to_string(),
"⠀b⠀\na⠀c\n⠀d⠀\n",
"Incorrect text output.",
)
def test_draw_text_with_color(self) -> None:
canvas = TextCanvas(3, 1)
self.assertEqual(canvas.text_buffer, [], "Text buffer should be empty.")
canvas.draw_text("hi!", 0, 0)
self.assertEqual(
canvas.text_buffer,
[["h", "i", "!"]],
"Text should not be colorized.",
)
canvas.set_color(Color().bright_red())
canvas.draw_text("o!", 1, 0)
self.assertEqual(
canvas.text_buffer,
[["h", "\x1b[0;91mo\x1b[0m", "\x1b[0;91m!\x1b[0m"]],
"'o!' should be red.",
)
def test_merge_text_space_does_not_clear_text(self) -> None:
canvas = TextCanvas(5, 1)
canvas.merge_text("bar", 1, 0)
canvas.merge_text(" z", 2, 0)
self.assertEqual(
canvas.text_buffer,
[["", "b", "a", "z", ""]],
"Incorrect text buffer.",
)
def test_merge_text_multiline(self) -> None:
canvas = TextCanvas(6, 2)
canvas.merge_text("@@@@@@\n@@@@@@", 0, 0)
canvas.merge_text("h ll \nw rld!", 0, 0)
self.assertEqual(
canvas.text_buffer,
[
["h", "@", "l", "l", "@", "@"],
["w", "@", "r", "l", "d", "!"],
],
"Incorrect text buffer.",
)
def test_merge_text_vertical(self) -> None:
canvas = TextCanvas(1, 5)
self.assertFalse(canvas.is_textual)
canvas.merge_text_vertical("bar", 0, 1)
canvas.merge_text_vertical(" z", 0, 2)
self.assertTrue(canvas.is_textual)
self.assertEqual(
canvas.text_buffer,
[
[""],
["b"],
["a"],
["z"],
[""],
],
"Incorrect text buffer.",
)
def test_merge_text_vertical_multiline(self) -> None:
canvas = TextCanvas(2, 6)
canvas.merge_text_vertical("@@@@@@\n@@@@@@", 0, 0)
canvas.merge_text_vertical("h ll \nw rld!", 0, 0)
self.assertEqual(
canvas.text_buffer,
[
["h", "w"],
["@", "@"],
["l", "r"],
["l", "l"],
["@", "d"],
["@", "!"],
],
"Incorrect text buffer.",
)
def test_get_text_as_string(self) -> None:
canvas = TextCanvas(5, 3)
canvas.draw_text("foo", 1, 1)
self.assertEqual(
canvas.to_string(), "⠀⠀⠀⠀⠀\n⠀foo⠀\n⠀⠀⠀⠀⠀\n", "Incorrect output string."
)
def test_get_text_as_string_colored(self) -> None:
canvas = TextCanvas(5, 3)
canvas.set_color(Color().bright_green())
canvas.draw_text("foo", 1, 1)
self.assertEqual(
canvas.to_string(),
"⠀⠀⠀⠀⠀\n⠀\x1b[0;92mf\x1b[0m\x1b[0;92mo\x1b[0m\x1b[0;92mo\x1b[0m⠀\n⠀⠀⠀⠀⠀\n",
"Incorrect output string.",
)
def test_clear_clears_text_buffer(self) -> None:
canvas = TextCanvas(2, 1)
self.assertEqual(canvas.text_buffer, [], "Text buffer should be empty.")
canvas.set_color(Color().bright_red())
canvas.draw_text("hi", 0, 0)
self.assertEqual(
canvas.text_buffer,
[["\x1b[0;91mh\x1b[0m", "\x1b[0;91mi\x1b[0m"]],
"Text should be colorized.",
)
canvas.clear()
self.assertEqual(
canvas.text_buffer,
[["", ""]],
"Text buffer should be full of no-colored empty chars.",
)
def test_clear_edits_text_buffer_in_place(self) -> None:
canvas = TextCanvas(2, 2)
canvas.draw_text("hi", 0, 0)
text_buffer = canvas.text_buffer
row_0 = canvas.text_buffer[0]
row_1 = canvas.text_buffer[1]
canvas.clear()
self.assertIs(
text_buffer, canvas.text_buffer, "Container should be the same as before."
)
self.assertIs(
row_0, canvas.text_buffer[0], "Container should be the same as before."
)
self.assertIs(
row_1, canvas.text_buffer[1], "Container should be the same as before."
)
class TestTextCanvasDrawingPrimitives(unittest.TestCase):
def test_stroke_line(self) -> None:
canvas = TextCanvas(15, 5)
top_left = (0, 0)
top_right = (canvas.w, 0)
bottom_right = (canvas.w, canvas.h)
bottom_left = (0, canvas.h)
center = (canvas.cx, canvas.cy)
center_top = (canvas.cx, 0)
center_right = (canvas.w, canvas.cy)
center_bottom = (canvas.cx, canvas.h)
center_left = (0, canvas.cy)
canvas.stroke_line(*center, *top_left)
canvas.stroke_line(*center, *top_right)
canvas.stroke_line(*center, *bottom_right)
canvas.stroke_line(*center, *bottom_left)
canvas.stroke_line(*center, *center_top)
canvas.stroke_line(*center, *center_right)
canvas.stroke_line(*center, *center_bottom)
canvas.stroke_line(*center, *center_left)
self.assertEqual(
canvas.to_string(),
"""\
⠑⠢⣀⠀⠀⠀⠀⢸⠀⠀⠀⠀⢀⠔⠊
⠀⠀⠀⠑⠢⣀⠀⢸⠀⢀⠤⠊⠁⠀⠀
⠤⠤⠤⠤⠤⠤⢵⣾⣶⠥⠤⠤⠤⠤⠤
⠀⠀⠀⣀⠤⠊⠁⢸⠀⠑⠢⣀⠀⠀⠀
⡠⠔⠊⠀⠀⠀⠀⢸⠀⠀⠀⠀⠉⠢⢄
""",
"Lines not drawn correctly.",
)
def test_stroke_line_from_outside_to_outside(self) -> None:
canvas = TextCanvas(15, 5)
canvas.stroke_line(-10, -10, canvas.w + 10, canvas.h + 10)
self.assertEqual(
canvas.to_string(),
"""\
⠀⠉⠢⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠈⠑⢄⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠉⠢⣀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠑⢄⡀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠢⣀⠀
""",
"Line not drawn correctly.",
)
def test_erase_line(self) -> None:
canvas = TextCanvas(15, 5)
canvas.fill()
top_left = (0, 0)
bottom_right = (canvas.w, canvas.h)
canvas.invert()
canvas.stroke_line(*top_left, *bottom_right)
self.assertEqual(
canvas.to_string(),
"""\
⣮⣝⠿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
⣿⣿⣿⣮⣝⠿⣿⣿⣿⣿⣿⣿⣿⣿⣿
⣿⣿⣿⣿⣿⣿⣮⣝⡻⣿⣿⣿⣿⣿⣿
⣿⣿⣿⣿⣿⣿⣿⣿⣿⣶⣝⡻⣿⣿⣿
⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣶⣝⡻
""",
"Line not erased correctly.",
)
def test_stroke_rect(self) -> None:
canvas = TextCanvas(15, 5)
canvas.stroke_rect(6, 3, 20, 15)
self.assertEqual(
canvas.to_string(),
"""\
⠀⠀⠀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⠀⠀
⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀
⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀
⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀
⠀⠀⠀⠓⠒⠒⠒⠒⠒⠒⠒⠒⠚⠀⠀
""",
)
def test_frame(self) -> None:
canvas = TextCanvas(15, 5)
canvas.frame()
self.assertEqual(
canvas.to_string(),
"""\
⡏⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⢹
⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸
⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸
⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸
⣇⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣸
""",
)
def test_fill_rect(self) -> None:
canvas = TextCanvas(15, 5)
canvas.fill_rect(6, 3, 20, 15)
self.assertEqual(
canvas.to_string(),
"""\
⠀⠀⠀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⠀⠀
⠀⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠀⠀
⠀⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠀⠀
⠀⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠀⠀
⠀⠀⠀⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠀⠀
""",
)
def test_stroke_triangle(self) -> None:
canvas = TextCanvas(15, 5)
canvas.stroke_triangle(6, 3, 20, 2, 23, 18)
self.assertEqual(
canvas.to_string(),
"""\
⠀⠀⠀⣀⣀⣀⡠⠤⠤⠤⡄⠀⠀⠀⠀
⠀⠀⠀⠈⠢⣀⠀⠀⠀⠀⢱⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠑⢄⠀⠀⠘⡄⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠑⠤⡀⡇⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠺⠀⠀⠀
""",
)
def test_fill_triangle(self) -> None:
canvas = TextCanvas(15, 5)
canvas.fill_triangle(6, 3, 20, 2, 23, 18)
self.assertEqual(
canvas.to_string(),
"""\
⠀⠀⠀⣀⣀⣀⣠⣤⣤⣤⡄⠀⠀⠀⠀
⠀⠀⠀⠈⠻⣿⣿⣿⣿⣿⣷⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠙⢿⣿⣿⣿⡄⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠙⠿⣿⡇⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠻⠀⠀⠀
""",
)
def test_stroke_circle(self) -> None:
canvas = TextCanvas(15, 5)
canvas.stroke_circle(15, 10, 7)
self.assertEqual(
canvas.to_string(),
"""\
⠀⠀⠀⠀⠀⠀⣀⣀⣀⡀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⡠⠊⠀⠀⠀⠈⠢⡀⠀⠀⠀
⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀
⠀⠀⠀⠀⠣⡀⠀⠀⠀⠀⡠⠃⠀⠀⠀
⠀⠀⠀⠀⠀⠈⠒⠒⠒⠊⠀⠀⠀⠀⠀
""",
)
def test_fill_circle(self) -> None:
canvas = TextCanvas(15, 5)
canvas.fill_circle(15, 10, 7)
self.assertEqual(
canvas.to_string(),
"""\
⠀⠀⠀⠀⠀⠀⣀⣀⣀⡀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⣠⣾⣿⣿⣿⣿⣦⡀⠀⠀⠀
⠀⠀⠀⠀⣿⣿⣿⣿⣿⣿⣿⡇⠀⠀⠀
⠀⠀⠀⠀⠻⣿⣿⣿⣿⣿⡿⠃⠀⠀⠀
⠀⠀⠀⠀⠀⠈⠛⠛⠛⠋⠀⠀⠀⠀⠀
""",
)
def test_stroke_ngon(self) -> None:
canvas = TextCanvas(15, 5)
canvas.stroke_ngon(canvas.cx, canvas.cy, 7, 6, 0.0)
self.assertEqual(
canvas.to_string(),
"""\
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⡰⠉⠉⠉⠱⡀⠀⠀⠀⠀
⠀⠀⠀⠀⢜⠀⠀⠀⠀⠀⢘⠄⠀⠀⠀
⠀⠀⠀⠀⠈⢆⠀⠀⠀⢠⠊⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠈⠉⠉⠉⠁⠀⠀⠀⠀⠀
""",
)
def test_stroke_ngon_at_angle(self) -> None:
canvas = TextCanvas(15, 5)
canvas.stroke_ngon(canvas.cx, canvas.cy, 7, 6, math.pi / 2.0)
self.assertEqual(
canvas.to_string(),
"""\
⠀⠀⠀⠀⠀⠀⠀⢀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⢠⠔⠊⠁⠉⠢⢄⠀⠀⠀⠀
⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀
⠀⠀⠀⠀⠘⠤⡀⠀⠀⣀⠼⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠈⠑⠉⠀⠀⠀⠀⠀⠀
""",
)
def test_stroke_ngon_radius_matches_circle(self) -> None:
canvas = TextCanvas(15, 5)
canvas.stroke_ngon(canvas.cx, canvas.cy, 7, 3, math.pi / 2.0)
canvas.stroke_circle(15, 10, 7)
self.assertEqual(
canvas.to_string(),
"""\
⠀⠀⠀⠀⠀⠀⣀⣀⣀⡀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⡠⠊⢠⠃⢣⠈⠢⡀⠀⠀⠀
⠀⠀⠀⠀⡇⡰⠁⠀⠀⢣⠀⡇⠀⠀⠀
⠀⠀⠀⠀⠳⡓⠒⠢⠤⠤⡧⠃⠀⠀⠀
⠀⠀⠀⠀⠀⠈⠒⠒⠒⠊⠀⠀⠀⠀⠀
""",
)
def test_fill_ngon(self) -> None:
canvas = TextCanvas(15, 5)
canvas.fill_ngon(canvas.cx, canvas.cy, 7, 6, 0.0)
self.assertEqual(
canvas.to_string(),
"""\
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⣰⣿⣿⣿⣷⡀⠀⠀⠀⠀
⠀⠀⠀⠀⢼⣿⣿⣿⣿⣿⣿⠄⠀⠀⠀
⠀⠀⠀⠀⠈⢿⣿⣿⣿⣿⠋⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠈⠉⠉⠉⠁⠀⠀⠀⠀⠀
""",
)
def test_fill_ngon_not_enough_sides(self) -> None:
canvas = TextCanvas(15, 5)
with self.assertRaises(ValueError) as ctx:
ctx.msg = "Number of sides less than 3 did not raise error."
canvas.fill_ngon(canvas.cx, canvas.cy, 7, 2, 0.0)
def test_draw_canvas(self) -> None:
canvas = TextCanvas(15, 5)
canvas.stroke_line(0, 0, canvas.w, canvas.h)
canvas.frame()
overlay = TextCanvas(7, 3)
overlay.stroke_line(0, overlay.h, overlay.w, 0)
overlay.frame()
canvas.draw_canvas(overlay, 8, 4)
self.assertEqual(
canvas.to_string(),
"""\
⡟⠫⣉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⢹
⡇⠀⠀⠑⡏⠉⠉⠉⢉⡩⢻⠀⠀⠀⢸
⡇⠀⠀⠀⡇⠀⢀⠔⠁⠀⢸⠀⠀⠀⢸
⡇⠀⠀⠀⣧⣊⣁⣀⣀⣀⣸⢄⠀⠀⢸
⣇⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣉⣢⣼
""",
)
def test_draw_canvas_with_overflow(self) -> None:
canvas = TextCanvas(15, 5)
canvas.stroke_line(0, 0, canvas.w, canvas.h)
canvas.frame()
overlay = TextCanvas(7, 3)
overlay.stroke_line(0, overlay.h, overlay.w, 0)
overlay.frame()
canvas.draw_canvas(overlay, -8, -4)
canvas.draw_canvas(overlay, 24, -4)
canvas.draw_canvas(overlay, 24, 12)
canvas.draw_canvas(overlay, -8, 12)
self.assertEqual(
canvas.to_string(),
"""\
⠁⠀⢸⠉⠉⠉⠉⠉⠉⠉⠉⠉⡇⠀⢀
⣀⣀⣸⠑⠢⣀⠀⠀⠀⠀⠀⠀⣧⣊⣁
⡇⠀⠀⠀⠀⠀⠑⠢⢄⠀⠀⠀⠀⠀⢸
⢉⡩⢻⠀⠀⠀⠀⠀⠀⠉⠢⢄⡏⠉⠉
⠁⠀⢸⣀⣀⣀⣀⣀⣀⣀⣀⣀⡇⠀⢀
""",
)
def test_draw_canvas_with_color(self) -> None:
canvas = TextCanvas(15, 5)
canvas.set_color(Color().red())
canvas.fill_rect(3, 3, 10, 10)
overlay = TextCanvas(15, 5)
overlay.set_color(Color().green())
overlay.fill_rect(8, 8, 10, 10)
canvas.draw_canvas(overlay, 8, 8)
self.assertEqual(
canvas.to_string(),
"""\
⠀\x1b[0;31m⢀\x1b[0m\x1b[0;31m⣀\x1b[0m\x1b[0;31m⣀\x1b[0m\x1b[0;31m⣀\x1b[0m\x1b[0;31m⣀\x1b[0m\x1b[0;31m⡀\x1b[0m⠀⠀⠀⠀⠀⠀⠀⠀
⠀\x1b[0;31m⢸\x1b[0m\x1b[0;31m⣿\x1b[0m\x1b[0;31m⣿\x1b[0m\x1b[0;31m⣿\x1b[0m\x1b[0;31m⣿\x1b[0m\x1b[0;31m⡇\x1b[0m⠀⠀⠀⠀⠀⠀⠀⠀
⠀\x1b[0;31m⢸\x1b[0m\x1b[0;31m⣿\x1b[0m\x1b[0;31m⣿\x1b[0m⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀\x1b[0;31m⠈\x1b[0m\x1b[0;31m⠉\x1b[0m\x1b[0;31m⠉\x1b[0m⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀\x1b[0;32m⣿\x1b[0m\x1b[0;32m⣿\x1b[0m\x1b[0;32m⣿\x1b[0m\x1b[0;32m⣿\x1b[0m\x1b[0;32m⣿\x1b[0m⠀⠀
""",
)
def test_draw_canvas_with_color_onto_non_colorized_canvas(self) -> None:
canvas = TextCanvas(15, 5)
canvas.fill_rect(3, 3, 10, 10)
self.assertFalse(canvas.is_colorized)
overlay = TextCanvas(15, 5)
overlay.set_color(Color().green())
overlay.fill_rect(8, 8, 10, 10)
canvas.draw_canvas(overlay, 8, 8)
self.assertTrue(canvas.is_colorized)
self.assertEqual(
canvas.to_string(),
"""\
⠀⢀⣀⣀⣀⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⢸⣿⣿⣿⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀
⠀⢸⣿⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠈⠉⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀\x1b[0;32m⣿\x1b[0m\x1b[0;32m⣿\x1b[0m\x1b[0;32m⣿\x1b[0m\x1b[0;32m⣿\x1b[0m\x1b[0;32m⣿\x1b[0m⠀⠀
""",
)
def test_draw_canvas_with_text(self) -> None:
canvas = TextCanvas(7, 3)
canvas.draw_text("abcde", 1, 1)
overlay = TextCanvas(7, 3)
overlay.draw_text("012", 2, 1)
canvas.draw_canvas(overlay, 5, 0)
self.assertEqual(
canvas.to_string(),
"""\
⠀⠀⠀⠀⠀⠀⠀
⠀a⠀⠀012
⠀⠀⠀⠀⠀⠀⠀
""",
)
def test_draw_canvas_with_colored_text(self) -> None:
canvas = TextCanvas(7, 3)
canvas.set_color(Color().red())
canvas.draw_text("abcde", 1, 1)
overlay = TextCanvas(7, 3)
overlay.set_color(Color().green())
overlay.draw_text("012", 2, 1)
canvas.draw_canvas(overlay, 5, 0)
self.assertEqual(
canvas.to_string(),
"""\
⠀⠀⠀⠀⠀⠀⠀
⠀\x1b[0;31ma\x1b[0m⠀⠀\x1b[0;32m0\x1b[0m\x1b[0;32m1\x1b[0m\x1b[0;32m2\x1b[0m
⠀⠀⠀⠀⠀⠀⠀
""",
)
def test_draw_canvas_with_colored_text_onto_non_textual_canvas(self) -> None:
canvas = TextCanvas(7, 3)
self.assertFalse(canvas.is_colorized)
self.assertFalse(canvas.is_textual)
overlay = TextCanvas(7, 3)
overlay.set_color(Color().green())
overlay.draw_text("012", 2, 1)
canvas.draw_canvas(overlay, 5, 0)
self.assertTrue(canvas.is_colorized)
self.assertTrue(canvas.is_textual)
self.assertEqual(
canvas.to_string(),
"""\
⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀\x1b[0;32m0\x1b[0m\x1b[0;32m1\x1b[0m\x1b[0;32m2\x1b[0m
⠀⠀⠀⠀⠀⠀⠀
""",
)
def test_draw_canvas_with_colored_text_onto_non_colorized_canvas(self) -> None:
canvas = TextCanvas(7, 3)
canvas.draw_text("abcde", 1, 1)
self.assertFalse(canvas.is_colorized)
self.assertTrue(canvas.is_textual)
overlay = TextCanvas(7, 3)
overlay.set_color(Color().green())
overlay.draw_text("012", 2, 1)
canvas.draw_canvas(overlay, 5, 0)
self.assertTrue(canvas.is_colorized)
self.assertTrue(canvas.is_textual)
self.assertEqual(
canvas.to_string(),
"""\
⠀⠀⠀⠀⠀⠀⠀
⠀a⠀⠀\x1b[0;32m0\x1b[0m\x1b[0;32m1\x1b[0m\x1b[0;32m2\x1b[0m
⠀⠀⠀⠀⠀⠀⠀
""",
)
def test_merge_canvas(self) -> None:
canvas = TextCanvas(15, 5)
canvas.stroke_line(0, 0, canvas.w, canvas.h)
canvas.frame()
overlay = TextCanvas(7, 3)
overlay.stroke_line(0, overlay.h, overlay.w, 0)
overlay.frame()
canvas.merge_canvas(overlay, 8, 4)
self.assertEqual(
canvas.to_string(),
"""\
⡟⠫⣉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⢹
⡇⠀⠀⠑⡯⣉⠉⠉⢉⡩⢻⠀⠀⠀⢸
⡇⠀⠀⠀⡇⠀⢑⠶⢅⠀⢸⠀⠀⠀⢸
⡇⠀⠀⠀⣧⣊⣁⣀⣀⣉⣺⢄⠀⠀⢸
⣇⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣉⣢⣼
""",
)
def test_merge_canvas_with_overflow(self) -> None:
canvas = TextCanvas(15, 5)
canvas.stroke_line(0, 0, canvas.w, canvas.h)
canvas.frame()
overlay = TextCanvas(7, 3)
overlay.stroke_line(0, overlay.h, overlay.w, 0)
overlay.frame()
canvas.merge_canvas(overlay, -8, -4)
canvas.merge_canvas(overlay, 24, -4)
canvas.merge_canvas(overlay, 24, 12)
canvas.merge_canvas(overlay, -8, 12)
self.assertEqual(
canvas.to_string(),
"""\
⡟⠫⣹⠉⠉⠉⠉⠉⠉⠉⠉⠉⡏⠉⢹
⣇⣀⣸⠑⠢⣀⠀⠀⠀⠀⠀⠀⣧⣊⣹
⡇⠀⠀⠀⠀⠀⠑⠢⢄⠀⠀⠀⠀⠀⢸
⣏⡩⢻⠀⠀⠀⠀⠀⠀⠉⠢⢄⡏⠉⢹
⣇⣀⣸⣀⣀⣀⣀⣀⣀⣀⣀⣀⣏⣢⣼
""",
)
def test_merge_canvas_with_color(self) -> None:
canvas = TextCanvas(15, 5)
canvas.set_color(Color().red())
canvas.fill_rect(3, 3, 10, 10)
overlay = TextCanvas(15, 5)
overlay.set_color(Color().green())
overlay.fill_rect(8, 8, 10, 10)
canvas.merge_canvas(overlay, 0, 0)
self.assertEqual(
canvas.to_string(),
"""\
⠀\x1b[0;31m⢀\x1b[0m\x1b[0;31m⣀\x1b[0m\x1b[0;31m⣀\x1b[0m\x1b[0;31m⣀\x1b[0m\x1b[0;31m⣀\x1b[0m\x1b[0;31m⡀\x1b[0m⠀⠀⠀⠀⠀⠀⠀⠀
⠀\x1b[0;31m⢸\x1b[0m\x1b[0;31m⣿\x1b[0m\x1b[0;31m⣿\x1b[0m\x1b[0;31m⣿\x1b[0m\x1b[0;31m⣿\x1b[0m\x1b[0;31m⡇\x1b[0m⠀⠀⠀⠀⠀⠀⠀⠀
⠀\x1b[0;31m⢸\x1b[0m\x1b[0;31m⣿\x1b[0m\x1b[0;31m⣿\x1b[0m\x1b[0;32m⣿\x1b[0m\x1b[0;32m⣿\x1b[0m\x1b[0;32m⣿\x1b[0m\x1b[0;32m⣿\x1b[0m\x1b[0;32m⣿\x1b[0m⠀⠀⠀⠀⠀⠀
⠀\x1b[0;31m⠈\x1b[0m\x1b[0;31m⠉\x1b[0m\x1b[0;31m⠉\x1b[0m\x1b[0;32m⣿\x1b[0m\x1b[0;32m⣿\x1b[0m\x1b[0;32m⣿\x1b[0m\x1b[0;32m⣿\x1b[0m\x1b[0;32m⣿\x1b[0m⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀\x1b[0;32m⠛\x1b[0m\x1b[0;32m⠛\x1b[0m\x1b[0;32m⠛\x1b[0m\x1b[0;32m⠛\x1b[0m\x1b[0;32m⠛\x1b[0m⠀⠀⠀⠀⠀⠀
""",
)
def test_merge_canvas_with_color_onto_non_colorized_canvas(self) -> None:
canvas = TextCanvas(15, 5)
canvas.fill_rect(3, 3, 10, 10)
self.assertFalse(canvas.is_colorized)
overlay = TextCanvas(15, 5)
overlay.set_color(Color().green())
overlay.fill_rect(8, 8, 10, 10)
canvas.merge_canvas(overlay, 0, 0)
self.assertTrue(canvas.is_colorized)
self.assertEqual(
canvas.to_string(),
"""\
⠀⢀⣀⣀⣀⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⢸⣿⣿⣿⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀
⠀⢸⣿⣿\x1b[0;32m⣿\x1b[0m\x1b[0;32m⣿\x1b[0m\x1b[0;32m⣿\x1b[0m\x1b[0;32m⣿\x1b[0m\x1b[0;32m⣿\x1b[0m⠀⠀⠀⠀⠀⠀
⠀⠈⠉⠉\x1b[0;32m⣿\x1b[0m\x1b[0;32m⣿\x1b[0m\x1b[0;32m⣿\x1b[0m\x1b[0;32m⣿\x1b[0m\x1b[0;32m⣿\x1b[0m⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀\x1b[0;32m⠛\x1b[0m\x1b[0;32m⠛\x1b[0m\x1b[0;32m⠛\x1b[0m\x1b[0;32m⠛\x1b[0m\x1b[0;32m⠛\x1b[0m⠀⠀⠀⠀⠀⠀
""",
)
def test_merge_canvas_with_text(self) -> None:
canvas = TextCanvas(7, 3)
canvas.draw_text("abcde", 1, 1)
overlay = TextCanvas(7, 3)
overlay.draw_text("012", 2, 1)
canvas.merge_canvas(overlay, 0, 0)
self.assertEqual(
canvas.to_string(),
"""\
⠀⠀⠀⠀⠀⠀⠀
⠀a012e⠀
⠀⠀⠀⠀⠀⠀⠀
""",
)
def test_merge_canvas_with_colored_text(self) -> None:
canvas = TextCanvas(7, 3)
canvas.set_color(Color().red())
canvas.draw_text("abcde", 1, 1)
overlay = TextCanvas(7, 3)
overlay.set_color(Color().green())
overlay.draw_text("012", 2, 1)
canvas.merge_canvas(overlay, 5, 0)
self.assertEqual(
canvas.to_string(),
"""\
⠀⠀⠀⠀⠀⠀⠀
⠀\x1b[0;31ma\x1b[0m\x1b[0;31mb\x1b[0m\x1b[0;31mc\x1b[0m\x1b[0;32m0\x1b[0m\x1b[0;32m1\x1b[0m\x1b[0;32m2\x1b[0m
⠀⠀⠀⠀⠀⠀⠀
""",
)
def test_merge_canvas_with_colored_text_onto_non_textual_canvas(self) -> None:
canvas = TextCanvas(7, 3)
self.assertFalse(canvas.is_colorized)
self.assertFalse(canvas.is_textual)
overlay = TextCanvas(7, 3)
overlay.set_color(Color().green())
overlay.draw_text("012", 2, 1)
canvas.merge_canvas(overlay, 5, 0)
self.assertTrue(canvas.is_colorized)
self.assertTrue(canvas.is_textual)
self.assertEqual(
canvas.to_string(),
"""\
⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀\x1b[0;32m0\x1b[0m\x1b[0;32m1\x1b[0m\x1b[0;32m2\x1b[0m
⠀⠀⠀⠀⠀⠀⠀
""",
)
def test_merge_canvas_with_colored_text_onto_non_colorized_canvas(self) -> None:
canvas = TextCanvas(7, 3)
canvas.draw_text("abcde", 1, 1)
self.assertFalse(canvas.is_colorized)
self.assertTrue(canvas.is_textual)
overlay = TextCanvas(7, 3)
overlay.set_color(Color().green())
overlay.draw_text("012", 2, 1)
canvas.merge_canvas(overlay, 5, 0)
self.assertTrue(canvas.is_colorized)
self.assertTrue(canvas.is_textual)
self.assertEqual(
canvas.to_string(),
"""\
⠀⠀⠀⠀⠀⠀⠀
⠀abc\x1b[0;32m0\x1b[0m\x1b[0;32m1\x1b[0m\x1b[0;32m2\x1b[0m
⠀⠀⠀⠀⠀⠀⠀
""",
)
def test_merge_canvas_with_pixels_color_and_text(self) -> None:
canvas = TextCanvas(7, 5)
canvas.set_color(Color().red())
canvas.draw_text("abcdefg", 0, 2)
canvas.set_color(Color().blue())
canvas.stroke_line(0, 13, canvas.w, 13)
overlay = TextCanvas(7, 5)
overlay.set_color(Color().green())
overlay.draw_text_vertical("012", canvas.cx // 2, 1)
overlay.set_color(Color().yellow())
overlay.stroke_line(overlay.cx, 0, overlay.cx, overlay.h)
canvas.merge_canvas(overlay, 0, 0)
self.assertEqual(
canvas.to_string(),
"""\
⠀⠀⠀\x1b[0;33m⢸\x1b[0m⠀⠀⠀
⠀⠀⠀\x1b[0;32m0\x1b[0m⠀⠀⠀
\x1b[0;31ma\x1b[0m\x1b[0;31mb\x1b[0m\x1b[0;31mc\x1b[0m\x1b[0;32m1\x1b[0m\x1b[0;31me\x1b[0m\x1b[0;31mf\x1b[0m\x1b[0;31mg\x1b[0m
\x1b[0;34m⠒\x1b[0m\x1b[0;34m⠒\x1b[0m\x1b[0;34m⠒\x1b[0m\x1b[0;32m2\x1b[0m\x1b[0;34m⠒\x1b[0m\x1b[0;34m⠒\x1b[0m\x1b[0;34m⠒\x1b[0m
⠀⠀⠀\x1b[0;33m⢸\x1b[0m⠀⠀⠀
""",
)
if __name__ == "__main__":
unittest.main()