From c68d6e8d0ed87851e99207febff287b03262a8c9 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Thu, 21 Apr 2022 12:16:05 +0100 Subject: [PATCH] tests for box model --- sandbox/align.css | 4 +- sandbox/uber.css | 2 +- src/textual/box_model.py | 8 +- src/textual/css/styles.py | 8 +- tests/test_box_model.py | 182 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 195 insertions(+), 9 deletions(-) create mode 100644 tests/test_box_model.py diff --git a/sandbox/align.css b/sandbox/align.css index f05ebabca..f976925ed 100644 --- a/sandbox/align.css +++ b/sandbox/align.css @@ -26,7 +26,7 @@ Widget { #thing2 { border: solid white; /* outline: heavy blue; */ - + height: 10; padding: 1 2; box-sizing: border-box; @@ -39,7 +39,7 @@ Widget { #thing3 { - + height: 10; margin: 1; background:blue; align-horizontal: center; diff --git a/sandbox/uber.css b/sandbox/uber.css index 70dc86714..fcc11b51d 100644 --- a/sandbox/uber.css +++ b/sandbox/uber.css @@ -8,7 +8,7 @@ .list-item { height: 8; - width:100%; + min-width: 80; background: dark_blue; } diff --git a/src/textual/box_model.py b/src/textual/box_model.py index 87f9d116d..4d04957d3 100644 --- a/src/textual/box_model.py +++ b/src/textual/box_model.py @@ -40,7 +40,9 @@ def get_box_model( is_content_box = styles.box_sizing == "content-box" gutter = styles.padding + styles.border.spacing - if not has_rule("width") or styles.width.is_auto: + if not has_rule("width"): + width = container_size.width + elif styles.width.is_auto: # When width is auto, we want enough space to always fit the content width = get_content_width(container_size, parent_size) if not is_content_box: @@ -50,7 +52,9 @@ def get_box_model( else: width = styles.width.resolve_dimension(container_size, parent_size) - if not has_rule("height") or styles.height.is_auto: + if not has_rule("height"): + height = container_size.height + elif styles.height.is_auto: height = get_content_height(container_size, parent_size) if not is_content_box: height += gutter.height diff --git a/src/textual/css/styles.py b/src/textual/css/styles.py index 286c0dd2c..d1ecbdd91 100644 --- a/src/textual/css/styles.py +++ b/src/textual/css/styles.py @@ -194,10 +194,10 @@ class StylesBase(ABC): box_sizing = StringEnumProperty(VALID_BOX_SIZING, "border-box") width = ScalarProperty(percent_unit=Unit.WIDTH) height = ScalarProperty(percent_unit=Unit.HEIGHT) - min_width = ScalarProperty(percent_unit=Unit.WIDTH) - min_height = ScalarProperty(percent_unit=Unit.HEIGHT) - max_width = ScalarProperty(percent_unit=Unit.WIDTH) - max_height = ScalarProperty(percent_unit=Unit.HEIGHT) + min_width = ScalarProperty(percent_unit=Unit.WIDTH, allow_auto=False) + min_height = ScalarProperty(percent_unit=Unit.HEIGHT, allow_auto=False) + max_width = ScalarProperty(percent_unit=Unit.WIDTH, allow_auto=False) + max_height = ScalarProperty(percent_unit=Unit.HEIGHT, allow_auto=False) dock = DockProperty() docks = DocksProperty() diff --git a/tests/test_box_model.py b/tests/test_box_model.py new file mode 100644 index 000000000..7020e3b98 --- /dev/null +++ b/tests/test_box_model.py @@ -0,0 +1,182 @@ +from __future__ import annotations + + +from textual.box_model import BoxModel, get_box_model +from textual.css.styles import Styles +from textual.geometry import Size, Spacing + + +def test_content_box(): + styles = Styles() + styles.width = 10 + styles.height = 8 + styles.padding = 1 + styles.border = ("solid", "red") + + # border-box is default + assert styles.box_sizing == "border-box" + + def get_auto_width(container: Size, parent: Size) -> int: + assert False, "must not be called" + + def get_auto_height(container: Size, parent: Size) -> int: + assert False, "must not be called" + + box_model = get_box_model( + styles, Size(60, 20), Size(80, 24), get_auto_width, get_auto_height + ) + # Size should be inclusive of padding / border + assert box_model == BoxModel(Size(10, 8), Spacing(0, 0, 0, 0)) + + # Switch to content-box + styles.box_sizing = "content-box" + + box_model = get_box_model( + styles, Size(60, 20), Size(80, 24), get_auto_width, get_auto_height + ) + # width and height have added padding / border to accommodate content + assert box_model == BoxModel(Size(14, 12), Spacing(0, 0, 0, 0)) + + +def test_width(): + """Test width settings.""" + styles = Styles() + + def get_auto_width(container: Size, parent: Size) -> int: + return 10 + + def get_auto_height(container: Size, parent: Size) -> int: + return 10 + + box_model = get_box_model( + styles, Size(60, 20), Size(80, 24), get_auto_width, get_auto_height + ) + assert box_model == BoxModel(Size(60, 20), Spacing(0, 0, 0, 0)) + + # Add a margin and check that it is reported + styles.margin = (1, 2, 3, 4) + + box_model = get_box_model( + styles, Size(60, 20), Size(80, 24), get_auto_width, get_auto_height + ) + assert box_model == BoxModel(Size(60, 20), Spacing(1, 2, 3, 4)) + + styles.width = "auto" + + box_model = get_box_model( + styles, Size(60, 20), Size(80, 24), get_auto_width, get_auto_height + ) + # Setting width to auto should call get_auto_width + assert box_model == BoxModel(Size(10, 20), Spacing(1, 2, 3, 4)) + + # Set width to 100 vw which should make it the width of the parent + styles.width = "100vw" + + box_model = get_box_model( + styles, Size(60, 20), Size(80, 24), get_auto_width, get_auto_height + ) + assert box_model == BoxModel(Size(80, 20), Spacing(1, 2, 3, 4)) + + # Set the width to 100% should make it fill the container size + styles.width = "100%" + + box_model = get_box_model( + styles, Size(60, 20), Size(80, 24), get_auto_width, get_auto_height + ) + assert box_model == BoxModel(Size(60, 20), Spacing(1, 2, 3, 4)) + + styles.width = "100vw" + styles.max_width = "50%" + + box_model = get_box_model( + styles, Size(60, 20), Size(80, 24), get_auto_width, get_auto_height + ) + assert box_model == BoxModel(Size(30, 20), Spacing(1, 2, 3, 4)) + + +def test_height(): + """Test width settings.""" + styles = Styles() + + def get_auto_width(container: Size, parent: Size) -> int: + return 10 + + def get_auto_height(container: Size, parent: Size) -> int: + return 10 + + box_model = get_box_model( + styles, Size(60, 20), Size(80, 24), get_auto_width, get_auto_height + ) + assert box_model == BoxModel(Size(60, 20), Spacing(0, 0, 0, 0)) + + # Add a margin and check that it is reported + styles.margin = (1, 2, 3, 4) + + box_model = get_box_model( + styles, Size(60, 20), Size(80, 24), get_auto_width, get_auto_height + ) + assert box_model == BoxModel(Size(60, 20), Spacing(1, 2, 3, 4)) + + # Set width to 100 vw which should make it the width of the parent + styles.height = "100vh" + + box_model = get_box_model( + styles, Size(60, 20), Size(80, 24), get_auto_width, get_auto_height + ) + assert box_model == BoxModel(Size(60, 24), Spacing(1, 2, 3, 4)) + + # Set the width to 100% should make it fill the container size + styles.height = "100%" + + box_model = get_box_model( + styles, Size(60, 20), Size(80, 24), get_auto_width, get_auto_height + ) + assert box_model == BoxModel(Size(60, 20), Spacing(1, 2, 3, 4)) + + styles.height = "100vh" + styles.max_height = "50%" + + box_model = get_box_model( + styles, Size(60, 20), Size(80, 24), get_auto_width, get_auto_height + ) + assert box_model == BoxModel(Size(60, 10), Spacing(1, 2, 3, 4)) + + +def test_max(): + """Check that max_width and max_height are respected.""" + styles = Styles() + styles.width = 100 + styles.height = 80 + styles.max_width = 40 + styles.max_height = 30 + + def get_auto_width(container: Size, parent: Size) -> int: + assert False, "must not be called" + + def get_auto_height(container: Size, parent: Size) -> int: + assert False, "must not be called" + + box_model = get_box_model( + styles, Size(40, 30), Size(80, 24), get_auto_width, get_auto_height + ) + assert box_model == BoxModel(Size(40, 30), Spacing(0, 0, 0, 0)) + + +def test_min(): + """Check that min_width and min_height are respected.""" + styles = Styles() + styles.width = 10 + styles.height = 5 + styles.min_width = 40 + styles.min_height = 30 + + def get_auto_width(container: Size, parent: Size) -> int: + assert False, "must not be called" + + def get_auto_height(container: Size, parent: Size) -> int: + assert False, "must not be called" + + box_model = get_box_model( + styles, Size(40, 30), Size(80, 24), get_auto_width, get_auto_height + ) + assert box_model == BoxModel(Size(40, 30), Spacing(0, 0, 0, 0))