From 1abe8f933db7e90fd0ea572a1d65b9d882ccca3a Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Wed, 27 Jul 2022 15:37:33 +0100 Subject: [PATCH] test fixes, tests for partition --- sandbox/basic.css | 231 ------------------- sandbox/basic.py | 185 --------------- sandbox/will/pred.py | 50 ---- src/textual/color.py | 2 +- tests/css/test_help_text.py | 6 - tests/css/test_parse.py | 6 +- tests/layouts/test_common_layout_features.py | 2 - tests/test_box_model.py | 2 +- tests/test_integration_layout.py | 2 +- tests/test_partition.py | 15 ++ tests/test_view.py | 1 - 11 files changed, 21 insertions(+), 481 deletions(-) delete mode 100644 sandbox/basic.css delete mode 100644 sandbox/basic.py delete mode 100644 sandbox/will/pred.py create mode 100644 tests/test_partition.py diff --git a/sandbox/basic.css b/sandbox/basic.css deleted file mode 100644 index eed5e0b5e..000000000 --- a/sandbox/basic.css +++ /dev/null @@ -1,231 +0,0 @@ -/* CSS file for basic.py */ - - - -* { - transition: color 300ms linear, background 300ms linear; -} - -* { - scrollbar-background: $panel-darken-2; - scrollbar-background-hover: $panel-darken-3; - scrollbar-color: $system; - scrollbar-color-active: $accent-darken-1; - scrollbar-size-horizontal: 1; - scrollbar-size-vertical: 2; -} - -App > Screen { - layout: dock; - docks: side=left/1; - background: $surface; - color: $text-surface; -} - - -#sidebar { - color: $text-primary; - background: $primary-background; - dock: side; - width: 30; - offset-x: -100%; - layout: dock; - transition: offset 500ms in_out_cubic; -} - -#sidebar.-active { - offset-x: 0; -} - -#sidebar .title { - height: 3; - background: $primary-background-darken-2; - color: $text-primary-darken-2 ; - border-right: outer $primary-darken-3; - content-align: center middle; -} - -#sidebar .user { - height: 8; - background: $primary-background-darken-1; - color: $text-primary-darken-1; - border-right: outer $primary-background-darken-3; - content-align: center middle; -} - -#sidebar .content { - background: $primary-background; - color: $text-primary-background; - border-right: outer $primary-background-darken-3; - content-align: center middle; -} - -#header { - color: $text-primary-darken-1; - background: $primary-darken-1; - height: 3; - content-align: center middle; -} - -#content { - color: $text-background; - background: $background; - layout: vertical; - overflow-y: scroll; -} - - -Tweet { - height:12; - width: 100%; - - margin: 1 3; - background: $panel; - color: $text-panel; - layout: vertical; - /* border: outer $primary; */ - padding: 1; - border: wide $panel-darken-2; - overflow: auto; - /* scrollbar-gutter: stable; */ - align-horizontal: center; - box-sizing: border-box; -} - - -.scrollable { - - overflow-y: scroll; - margin: 1 2; - height: 20; - align-horizontal: center; - layout: vertical; -} - -.code { - - height: auto; - -} - - -TweetHeader { - height:1; - background: $accent; - color: $text-accent -} - -TweetBody { - width: 100%; - background: $panel; - color: $text-panel; - height: auto; - padding: 0 1 0 0; -} - -Tweet.scroll-horizontal TweetBody { - width: 350; -} - -.button { - background: $accent; - color: $text-accent; - width:20; - height: 3; - /* border-top: hidden $accent-darken-3; */ - border: tall $accent-darken-2; - /* border-left: tall $accent-darken-1; */ - - - /* padding: 1 0 0 0 ; */ - - transition: background 400ms in_out_cubic, color 400ms in_out_cubic; - -} - -.button:hover { - background: $accent-lighten-1; - color: $text-accent-lighten-1; - width: 20; - height: 3; - border: tall $accent-darken-1; - /* border-left: tall $accent-darken-3; */ - - - - -} - -#footer { - color: $text-accent; - background: $accent; - height: 1; - border-top: hkey $accent-darken-2; - content-align: center middle; -} - - -#sidebar .content { - layout: vertical -} - -OptionItem { - height: 3; - background: $primary-background; - border-right: outer $primary-background-darken-2; - border-left: blank; - content-align: center middle; -} - -OptionItem:hover { - height: 3; - color: $accent; - background: $primary-background-darken-1; - /* border-top: hkey $accent2-darken-3; - border-bottom: hkey $accent2-darken-3; */ - text-style: bold; - border-left: outer $accent-darken-2; -} - -Error { - width: 100%; - height:3; - background: $error; - color: $text-error; - border-top: hkey $error-darken-2; - border-bottom: hkey $error-darken-2; - margin: 1 3; - - text-style: bold; - align-horizontal: center; -} - -Warning { - width: 100%; - height:3; - background: $warning; - color: $text-warning-fade-1; - border-top: hkey $warning-darken-2; - border-bottom: hkey $warning-darken-2; - margin: 1 2; - text-style: bold; - align-horizontal: center; -} - -Success { - width: 100%; - height:3; - box-sizing: border-box; - background: $success-lighten-3; - color: $text-success-lighten-3-fade-1; - border-top: hkey $success; - border-bottom: hkey $success; - margin: 1 2; - text-style: bold; - align-horizontal: center; -} - - -.horizontal { - layout: horizontal -} diff --git a/sandbox/basic.py b/sandbox/basic.py deleted file mode 100644 index acae838d7..000000000 --- a/sandbox/basic.py +++ /dev/null @@ -1,185 +0,0 @@ -from rich.console import RenderableType -from rich.style import Style -from rich.syntax import Syntax -from rich.text import Text - -from textual.app import App -from textual.reactive import Reactive -from textual.widget import Widget -from textual.widgets import Static - -CODE = ''' -class Offset(NamedTuple): - """A point defined by x and y coordinates.""" - - x: int = 0 - y: int = 0 - - @property - def is_origin(self) -> bool: - """Check if the point is at the origin (0, 0)""" - return self == (0, 0) - - def __bool__(self) -> bool: - return self != (0, 0) - - def __add__(self, other: object) -> Offset: - if isinstance(other, tuple): - _x, _y = self - x, y = other - return Offset(_x + x, _y + y) - return NotImplemented - - def __sub__(self, other: object) -> Offset: - if isinstance(other, tuple): - _x, _y = self - x, y = other - return Offset(_x - x, _y - y) - return NotImplemented - - def __mul__(self, other: object) -> Offset: - if isinstance(other, (float, int)): - x, y = self - return Offset(int(x * other), int(y * other)) - return NotImplemented -''' - - -lorem_short = """Lorem ipsum dolor sit amet, consectetur adipiscing elit. In velit liber a a a, volutpat nec hendrerit at, faucibus in odio. Aliquam hendrerit nibh sed quam volutpat maximus. Nullam suscipit convallis lorem quis sodales. In tristique lobortis ante et dictum. Ut at finibus ipsum.""" -lorem = ( - lorem_short - + """ In urna dolor, placerat et mi facilisis, congue sollicitudin massa. Phasellus felis turpis, cursus eu lectus et, porttitor malesuada augue. Sed feugiat volutpat velit, sollicitudin fringilla velit bibendum faucibus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. In velit libero, volutpat nec hendrerit at, faucibus in odio. Aliquam hendrerit nibh sed quam volutpat maximus. Nullam suscipit convallis lorem quis sodales. In tristique lobortis ante et dictum. Ut at finibus ipsum. In urna dolor, placerat et mi facilisis, congue sollicitudin massa. Phasellus felis turpis, cursus eu lectus et, porttitor malesuada augue. Sed feugiat volutpat velit, sollicitudin fringilla velit bibendum faucibus. """ -) - -lorem_short_text = Text.from_markup(lorem_short) -lorem_long_text = Text.from_markup(lorem * 2) - - -class TweetHeader(Widget): - def render(self) -> RenderableType: - return Text("Lorem Impsum", justify="center") - - -class TweetBody(Widget): - short_lorem = Reactive(False) - - def render(self) -> Text: - return lorem_short_text if self.short_lorem else lorem_long_text - - -class Tweet(Widget): - pass - - -class OptionItem(Widget): - def render(self) -> Text: - return Text("Option") - - -class Error(Widget): - def render(self) -> Text: - return Text("This is an error message", justify="center") - - -class Warning(Widget): - def render(self) -> Text: - return Text("This is a warning message", justify="center") - - -class Success(Widget): - def render(self) -> Text: - return Text("This is a success message", justify="center") - - -class BasicApp(App, css_path="basic.css"): - """A basic app demonstrating CSS""" - - def on_load(self): - """Bind keys here.""" - self.bind("s", "toggle_class('#sidebar', '-active')") - - def on_mount(self): - """Build layout here.""" - - self.scroll_to_target = Tweet(TweetBody()) - self.mount( - header=Static( - Text.from_markup( - "[b]This is a [u]Textual[/u] app, running in the terminal" - ), - ), - content=Widget( - Tweet(TweetBody()), - Widget( - Static(Syntax(CODE, "python"), classes="code"), - classes="scrollable", - ), - Error(), - Tweet(TweetBody(), classes="scrollbar-size-custom"), - Warning(), - Tweet(TweetBody(), classes="scroll-horizontal"), - Success(), - Tweet(TweetBody(), classes="scroll-horizontal"), - Tweet(TweetBody(), classes="scroll-horizontal"), - Tweet(TweetBody(), classes="scroll-horizontal"), - Tweet(TweetBody(), classes="scroll-horizontal"), - Tweet(TweetBody(), classes="scroll-horizontal"), - ), - footer=Widget(), - sidebar=Widget( - Widget(classes="title"), - Widget(classes="user"), - OptionItem(), - OptionItem(), - OptionItem(), - Widget(classes="content"), - ), - ) - - async def on_key(self, event) -> None: - await self.dispatch_key(event) - - def key_d(self): - self.dark = not self.dark - - async def key_q(self): - await self.shutdown() - - def key_x(self): - self.panic(self.tree) - - def key_escape(self): - self.app.bell() - - def key_t(self): - # Pressing "t" toggles the content of the TweetBody widget, from a long "Lorem ipsum..." to a shorter one. - tweet_body = self.query("TweetBody").first() - tweet_body.short_lorem = not tweet_body.short_lorem - - def key_v(self): - self.get_child(id="content").scroll_to_widget(self.scroll_to_target) - - def key_space(self): - self.bell() - - -app = BasicApp() - -if __name__ == "__main__": - app.run() - - from textual.geometry import Region - from textual.color import Color - - print(Region.intersection.cache_info()) - print(Region.overlaps.cache_info()) - print(Region.union.cache_info()) - print(Region.split_vertical.cache_info()) - print(Region.__contains__.cache_info()) - from textual.css.scalar import Scalar - - print(Scalar.resolve_dimension.cache_info()) - - from rich.style import Style - - print(Style._add.cache_info()) diff --git a/sandbox/will/pred.py b/sandbox/will/pred.py deleted file mode 100644 index 95f8cd50f..000000000 --- a/sandbox/will/pred.py +++ /dev/null @@ -1,50 +0,0 @@ -def partition_will(pred, values): - if not values: - return [], [] - if len(values) == 1: - return ([], values) if pred(values[0]) else (values, []) - values = sorted(values, key=pred) - lower = 0 - upper = len(values) - 1 - index = (lower + upper) // 2 - while True: - value = pred(values[index]) - if value and not pred(values[index - 1]): - return values[:index], values[index:] - if value: - upper = index - else: - lower = index - - index = (lower + upper) // 2 - - -def partition_more_iter(pred, iterable): - """ - Returns a 2-tuple of iterables derived from the input iterable. - The first yields the items that have ``pred(item) == False``. - The second yields the items that have ``pred(item) == True``. - - >>> is_odd = lambda x: x % 2 != 0 - >>> iterable = range(10) - >>> even_items, odd_items = partition(is_odd, iterable) - >>> list(even_items), list(odd_items) - ([0, 2, 4, 6, 8], [1, 3, 5, 7, 9]) - - If *pred* is None, :func:`bool` is used. - - >>> iterable = [0, 1, False, True, '', ' '] - >>> false_items, true_items = partition(None, iterable) - >>> list(false_items), list(true_items) - ([0, False, ''], [1, True, ' ']) - - """ - if pred is None: - pred = bool - - evaluations = ((pred(x), x) for x in iterable) - t1, t2 = tee(evaluations) - return ( - (x for (cond, x) in t1 if not cond), - (x for (cond, x) in t2 if cond), - ) diff --git a/src/textual/color.py b/src/textual/color.py index 813d6bcc6..3d7458451 100644 --- a/src/textual/color.py +++ b/src/textual/color.py @@ -167,7 +167,7 @@ class Color(NamedTuple): """This color encoded in Rich's Color class.""" r, g, b, _a = self return RichColor( - f"#{r:02X}{g:02X}{b:02X}", _TRUECOLOR, None, ColorTriplet(r, g, b) + f"#{r:02x}{g:02x}{b:02x}", _TRUECOLOR, None, ColorTriplet(r, g, b) ) @property diff --git a/tests/css/test_help_text.py b/tests/css/test_help_text.py index f5818a11d..1114961d8 100644 --- a/tests/css/test_help_text.py +++ b/tests/css/test_help_text.py @@ -98,12 +98,6 @@ def test_docks_property_help_text(styling_context): assert "docks" in rendered -def test_dock_property_help_text(styling_context): - rendered = render(dock_property_help_text("dock", styling_context)) - assert "Invalid value for" in rendered - assert "dock" in rendered - - def test_fractional_property_help_text(styling_context): rendered = render(fractional_property_help_text("opacity", styling_context)) assert "Invalid value for" in rendered diff --git a/tests/css/test_parse.py b/tests/css/test_parse.py index bf5cc4505..5b11472cf 100644 --- a/tests/css/test_parse.py +++ b/tests/css/test_parse.py @@ -12,7 +12,7 @@ from textual.css.tokenize import tokenize from textual.css.tokenizer import Token, ReferencedBy from textual.css.transition import Transition from textual.geometry import Spacing -from textual.layouts.dock import DockLayout +from textual.layouts.vertical import VerticalLayout class TestVariableReferenceSubstitution: @@ -861,13 +861,13 @@ class TestVariableReferenceSubstitution: class TestParseLayout: def test_valid_layout_name(self): - css = "#some-widget { layout: dock; }" + css = "#some-widget { layout: vertical; }" stylesheet = Stylesheet() stylesheet.add_source(css) styles = stylesheet.rules[0].styles - assert isinstance(styles.layout, DockLayout) + assert isinstance(styles.layout, VerticalLayout) def test_invalid_layout_name(self): css = "#some-widget { layout: invalidlayout; }" diff --git a/tests/layouts/test_common_layout_features.py b/tests/layouts/test_common_layout_features.py index e8cc5ee19..7fba1efb7 100644 --- a/tests/layouts/test_common_layout_features.py +++ b/tests/layouts/test_common_layout_features.py @@ -7,10 +7,8 @@ from textual.widget import Widget @pytest.mark.parametrize( "layout,display,expected_in_displayed_children", [ - ("dock", "block", True), ("horizontal", "block", True), ("vertical", "block", True), - ("dock", "none", False), ("horizontal", "none", False), ("vertical", "none", False), ], diff --git a/tests/test_box_model.py b/tests/test_box_model.py index 31bfc3212..1c82620ff 100644 --- a/tests/test_box_model.py +++ b/tests/test_box_model.py @@ -137,7 +137,7 @@ def test_height(): box_model = get_box_model( styles, Size(60, 20), Size(80, 24), one, get_auto_width, get_auto_height ) - assert box_model == BoxModel(Fraction(54), Fraction(20), Spacing(1, 2, 3, 4)) + assert box_model == BoxModel(Fraction(54), Fraction(16), Spacing(1, 2, 3, 4)) styles.height = "auto" styles.margin = 2 diff --git a/tests/test_integration_layout.py b/tests/test_integration_layout.py index 85990497f..0c5994ca1 100644 --- a/tests/test_integration_layout.py +++ b/tests/test_integration_layout.py @@ -228,7 +228,7 @@ async def test_border_edge_types_impact_on_widget_size( top_left_edge_style = app.screen.get_style_at(0, 0) top_left_edge_color = top_left_edge_style.color.name - assert top_left_edge_color == expected_top_left_edge_color + assert top_left_edge_color.upper() == expected_top_left_edge_color.upper() top_left_edge_char = app.get_char_at(0, 0) top_left_edge_char_is_a_visible_one = top_left_edge_char != " " diff --git a/tests/test_partition.py b/tests/test_partition.py new file mode 100644 index 000000000..cbd98333b --- /dev/null +++ b/tests/test_partition.py @@ -0,0 +1,15 @@ +from textual._partition import partition + + +def test_partition(): + def is_odd(value: int) -> bool: + return bool(value % 2) + + assert partition(is_odd, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) == ( + [2, 4, 6, 8, 10], + [1, 3, 5, 7, 9], + ) + + assert partition(is_odd, [1, 2]) == ([2], [1]) + assert partition(is_odd, [1]) == ([], [1]) + assert partition(is_odd, []) == ([], []) diff --git a/tests/test_view.py b/tests/test_view.py index fad4484bf..7aa51d33a 100644 --- a/tests/test_view.py +++ b/tests/test_view.py @@ -1,6 +1,5 @@ import pytest -from textual.layouts.dock import DockLayout from textual.layouts.grid import GridLayout from textual.layouts.horizontal import HorizontalLayout from textual.layouts.vertical import VerticalLayout