mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
Add tests / changelog.
This commit is contained in:
@@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
- Mapping of ANSI colors to hex codes configurable via `App.ansi_theme_dark` and `App.ansi_theme_light` https://github.com/Textualize/textual/pull/4192
|
||||
- `Pilot.resize_terminal` to resize the terminal in testing https://github.com/Textualize/textual/issues/4212
|
||||
- Support for pseudo-classes in nested TCSS https://github.com/Textualize/textual/issues/4039
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -30,6 +31,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
### Changed
|
||||
|
||||
- Clicking a non focusable widget focus ancestors https://github.com/Textualize/textual/pull/4236
|
||||
- BREAKING: Querying and TCSS expect widget class names to start with a capital letter or an underscore `_` https://github.com/Textualize/textual/pull/4252
|
||||
|
||||
## [0.52.1] - 2024-02-20
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ from textual.color import Color
|
||||
from textual.containers import Vertical
|
||||
from textual.css.parse import parse
|
||||
from textual.css.tokenizer import EOFError, TokenError
|
||||
from textual.widgets import Label
|
||||
from textual.widgets import Button, Label
|
||||
|
||||
|
||||
class NestedApp(App):
|
||||
@@ -110,3 +110,76 @@ def test_parse_errors(css: str, exception: type[Exception]) -> None:
|
||||
"""Check some CSS which should fail."""
|
||||
with pytest.raises(exception):
|
||||
list(parse("", css, ("foo", "")))
|
||||
|
||||
|
||||
class PseudoClassesInNestedApp(App[None]):
|
||||
CSS = """
|
||||
Vertical {
|
||||
Button:light, Button:dark {
|
||||
background: red;
|
||||
}
|
||||
|
||||
min-height: 3; # inconsequential rule to add entropy.
|
||||
|
||||
#two, *:focus {
|
||||
background: green !important;
|
||||
}
|
||||
|
||||
height: auto; # inconsequential rule to add entropy.
|
||||
|
||||
Label {
|
||||
background: yellow;
|
||||
|
||||
&:light, &:dark {
|
||||
background: red;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: green !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
AUTO_FOCUS = "Button"
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
with Vertical():
|
||||
yield Button(id="one", classes="first_half")
|
||||
yield Button(id="two", classes="first_half")
|
||||
with Vertical():
|
||||
yield Label("Hello, world!", id="three", classes="first_half")
|
||||
yield Label("Hello, world!", id="four", classes="first_half")
|
||||
with Vertical():
|
||||
yield Button(id="five", classes="second_half")
|
||||
yield Button(id="six", classes="second_half")
|
||||
yield Label("Hello, world!", id="seven", classes="second_half")
|
||||
yield Label("Hello, world!", id="eight", classes="second_half")
|
||||
|
||||
|
||||
async def test_pseudo_classes_work_in_nested_css() -> None:
|
||||
"""Makes sure pseudo-classes are correctly understood in nested TCSS.
|
||||
|
||||
Regression test for https://github.com/Textualize/textual/issues/4039.
|
||||
"""
|
||||
|
||||
app = PseudoClassesInNestedApp()
|
||||
green = Color.parse("green")
|
||||
red = Color.parse("red")
|
||||
async with app.run_test() as pilot:
|
||||
assert app.query_one("#one").styles.background == green
|
||||
assert app.query_one("#two").styles.background == green
|
||||
assert app.query_one("#five").styles.background == red
|
||||
assert app.query_one("#six").styles.background == red
|
||||
|
||||
assert app.query_one("#three").styles.background == red
|
||||
assert app.query_one("#four").styles.background == red
|
||||
assert app.query_one("#seven").styles.background == red
|
||||
assert app.query_one("#eight").styles.background == red
|
||||
|
||||
await pilot.hover("#eight")
|
||||
|
||||
assert app.query_one("#three").styles.background == red
|
||||
assert app.query_one("#four").styles.background == red
|
||||
assert app.query_one("#seven").styles.background == red
|
||||
assert app.query_one("#eight").styles.background == green
|
||||
|
||||
@@ -207,6 +207,55 @@ def test_did_you_mean_for_css_property_names(
|
||||
assert help_text.summary == expected_summary
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"css_property_name,expected_property_name_suggestion",
|
||||
[
|
||||
["backgroundu", "background"],
|
||||
["bckgroundu", "background"],
|
||||
["ofset-x", "offset-x"],
|
||||
["ofst_y", "offset-y"],
|
||||
["colr", "color"],
|
||||
["colour", "color"],
|
||||
["wdth", "width"],
|
||||
["wth", "width"],
|
||||
["wh", None],
|
||||
["xkcd", None],
|
||||
],
|
||||
)
|
||||
def test_did_you_mean_for_property_names_in_nested_css(
|
||||
css_property_name: str, expected_property_name_suggestion: "str | None"
|
||||
) -> None:
|
||||
"""Test that we get nice errors with mistyped declaractions in nested CSS.
|
||||
|
||||
When implementing pseudo-class support in nested TCSS
|
||||
(https://github.com/Textualize/textual/issues/4039), the first iterations didn't
|
||||
preserve this so we add these tests to make sure we don't take this feature away
|
||||
unintentionally.
|
||||
"""
|
||||
stylesheet = Stylesheet()
|
||||
css = """
|
||||
Screen {
|
||||
* {
|
||||
border: blue;
|
||||
${PROPERTY}: red;
|
||||
}
|
||||
}
|
||||
""".replace(
|
||||
"${PROPERTY}", css_property_name
|
||||
)
|
||||
|
||||
stylesheet.add_source(css)
|
||||
with pytest.raises(StylesheetParseError) as err:
|
||||
stylesheet.parse()
|
||||
|
||||
_, help_text = err.value.errors.rules[1].errors[0]
|
||||
displayed_css_property_name = css_property_name.replace("_", "-")
|
||||
expected_summary = f"Invalid CSS property {displayed_css_property_name!r}"
|
||||
if expected_property_name_suggestion:
|
||||
expected_summary += f". Did you mean '{expected_property_name_suggestion}'?"
|
||||
assert help_text.summary == expected_summary
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"css_property_name,css_property_value,expected_color_suggestion",
|
||||
[
|
||||
|
||||
@@ -898,3 +898,83 @@ def test_allow_new_lines():
|
||||
),
|
||||
]
|
||||
assert list(tokenize(css, ("", ""))) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
["pseudo_class", "expected"],
|
||||
[
|
||||
("blue", "blur"),
|
||||
("br", "blur"),
|
||||
("canfocus", "can-focus"),
|
||||
("can_focus", "can-focus"),
|
||||
("can-foc", "can-focus"),
|
||||
("drk", "dark"),
|
||||
("ark", "dark"),
|
||||
("disssabled", "disabled"),
|
||||
("enalbed", "enabled"),
|
||||
("focoswithin", "focus-within"),
|
||||
("focus_whitin", "focus-within"),
|
||||
("fcus", "focus"),
|
||||
("huver", "hover"),
|
||||
("LIght", "light"),
|
||||
],
|
||||
)
|
||||
def test_did_you_mean_pseudo_classes(pseudo_class: str, expected: str) -> None:
|
||||
"""Make sure we get the correct suggestion for pseudo-classes with typos."""
|
||||
|
||||
css = f"""
|
||||
Button:{pseudo_class} {{
|
||||
background: red;
|
||||
}}
|
||||
"""
|
||||
|
||||
with pytest.raises(TokenError) as err:
|
||||
list(tokenize(css, ("", "")))
|
||||
|
||||
assert f"unknown pseudo-class {pseudo_class!r}" in str(err.value)
|
||||
assert f"did you mean {expected!r}" in str(err.value)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
["pseudo_class", "expected"],
|
||||
[
|
||||
("blue", "blur"),
|
||||
("br", "blur"),
|
||||
("canfocus", "can-focus"),
|
||||
("can_focus", "can-focus"),
|
||||
("can-foc", "can-focus"),
|
||||
("drk", "dark"),
|
||||
("ark", "dark"),
|
||||
("disssabled", "disabled"),
|
||||
("enalbed", "enabled"),
|
||||
("focoswithin", "focus-within"),
|
||||
("focus_whitin", "focus-within"),
|
||||
("fcus", "focus"),
|
||||
("huver", "hover"),
|
||||
("LIght", "light"),
|
||||
],
|
||||
)
|
||||
def test_did_you_mean_pseudo_classes_in_nested_css(
|
||||
pseudo_class: str, expected: str
|
||||
) -> None:
|
||||
"""Test that we get nice errors for pseudo-classes with typos in nested TCSS.
|
||||
|
||||
When implementing pseudo-class support in nested TCSS
|
||||
(https://github.com/Textualize/textual/issues/4039), the first iterations didn't
|
||||
preserve this so we add these tests to make sure we don't take this feature away
|
||||
unintentionally.
|
||||
"""
|
||||
|
||||
css = f"""
|
||||
Screen {{
|
||||
Button:{pseudo_class} {{
|
||||
background: red;
|
||||
}}
|
||||
}}
|
||||
"""
|
||||
|
||||
with pytest.raises(TokenError) as err:
|
||||
list(tokenize(css, ("", "")))
|
||||
|
||||
assert f"unknown pseudo-class {pseudo_class!r}" in str(err.value)
|
||||
assert f"did you mean {expected!r}" in str(err.value)
|
||||
|
||||
@@ -149,12 +149,12 @@ def test_no_focus_empty_selector(screen: Screen):
|
||||
|
||||
screen.set_focus(screen.query_one("#foo"))
|
||||
assert screen.focused is not None
|
||||
assert screen.focus_next("bananas") is None
|
||||
assert screen.focus_next("#bananas") is None
|
||||
assert screen.focused is None
|
||||
|
||||
screen.set_focus(screen.query_one("#foo"))
|
||||
assert screen.focused is not None
|
||||
assert screen.focus_previous("bananas") is None
|
||||
assert screen.focus_previous("#bananas") is None
|
||||
assert screen.focused is None
|
||||
|
||||
|
||||
|
||||
@@ -244,7 +244,7 @@ async def test_query_set_styles_invalid_css_raises_error():
|
||||
app = App()
|
||||
async with app.run_test():
|
||||
with pytest.raises(DeclarationError):
|
||||
app.query(Widget).set_styles(css="random_rule: 1fr;")
|
||||
app.query(Widget).set_styles(css="random-rule: 1fr;")
|
||||
|
||||
|
||||
async def test_query_set_styles_kwds():
|
||||
|
||||
Reference in New Issue
Block a user