mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
[css] Use a HelpText for unknown CSS property name errors
This commit is contained in:
@@ -128,7 +128,32 @@ def _spacing_examples(property_name: str) -> ContextSpecificBullets:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def spacing_wrong_number_of_values(
|
def property_invalid_value_help_text(
|
||||||
|
property_name: str, context: StylingContext, *, suggested_property_name: str = None
|
||||||
|
) -> HelpText:
|
||||||
|
"""Help text to show when the user supplies an invalid value for CSS property
|
||||||
|
property.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
property_name (str): The name of the property
|
||||||
|
context (StylingContext | None): The context the spacing property is being used in.
|
||||||
|
Keyword Args:
|
||||||
|
suggested_property_name (str | None): A suggested name for the property (e.g. "width" for "wdth"). Defaults to None.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
HelpText: Renderable for displaying the help text for this property
|
||||||
|
"""
|
||||||
|
property_name = _contextualize_property_name(property_name, context)
|
||||||
|
bullets = []
|
||||||
|
if suggested_property_name:
|
||||||
|
suggested_property_name = _contextualize_property_name(
|
||||||
|
suggested_property_name, context
|
||||||
|
)
|
||||||
|
bullets.append(Bullet(f"Did you mean [i]{suggested_property_name}[/]?"))
|
||||||
|
return HelpText(f"Invalid CSS property [i]{property_name}[/]", bullets=bullets)
|
||||||
|
|
||||||
|
|
||||||
|
def spacing_wrong_number_of_values_help_text(
|
||||||
property_name: str,
|
property_name: str,
|
||||||
num_values_supplied: int,
|
num_values_supplied: int,
|
||||||
context: StylingContext,
|
context: StylingContext,
|
||||||
@@ -159,7 +184,7 @@ def spacing_wrong_number_of_values(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def spacing_invalid_value(
|
def spacing_invalid_value_help_text(
|
||||||
property_name: str,
|
property_name: str,
|
||||||
context: StylingContext,
|
context: StylingContext,
|
||||||
) -> HelpText:
|
) -> HelpText:
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ from ._help_text import (
|
|||||||
style_flags_property_help_text,
|
style_flags_property_help_text,
|
||||||
)
|
)
|
||||||
from ._help_text import (
|
from ._help_text import (
|
||||||
spacing_wrong_number_of_values,
|
spacing_wrong_number_of_values_help_text,
|
||||||
scalar_help_text,
|
scalar_help_text,
|
||||||
string_enum_help_text,
|
string_enum_help_text,
|
||||||
color_property_help_text,
|
color_property_help_text,
|
||||||
@@ -415,7 +415,7 @@ class SpacingProperty:
|
|||||||
except ValueError as error:
|
except ValueError as error:
|
||||||
raise StyleValueError(
|
raise StyleValueError(
|
||||||
str(error),
|
str(error),
|
||||||
help_text=spacing_wrong_number_of_values(
|
help_text=spacing_wrong_number_of_values_help_text(
|
||||||
property_name=self.name,
|
property_name=self.name,
|
||||||
num_values_supplied=len(spacing),
|
num_values_supplied=len(spacing),
|
||||||
context="inline",
|
context="inline",
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import rich.repr
|
|||||||
from ._error_tools import friendly_list
|
from ._error_tools import friendly_list
|
||||||
from ._help_renderables import HelpText
|
from ._help_renderables import HelpText
|
||||||
from ._help_text import (
|
from ._help_text import (
|
||||||
spacing_invalid_value,
|
spacing_invalid_value_help_text,
|
||||||
spacing_wrong_number_of_values,
|
spacing_wrong_number_of_values_help_text,
|
||||||
scalar_help_text,
|
scalar_help_text,
|
||||||
color_property_help_text,
|
color_property_help_text,
|
||||||
string_enum_help_text,
|
string_enum_help_text,
|
||||||
@@ -23,6 +23,7 @@ from ._help_text import (
|
|||||||
offset_property_help_text,
|
offset_property_help_text,
|
||||||
offset_single_axis_help_text,
|
offset_single_axis_help_text,
|
||||||
style_flags_property_help_text,
|
style_flags_property_help_text,
|
||||||
|
property_invalid_value_help_text,
|
||||||
)
|
)
|
||||||
from .constants import (
|
from .constants import (
|
||||||
VALID_ALIGN_HORIZONTAL,
|
VALID_ALIGN_HORIZONTAL,
|
||||||
@@ -86,14 +87,17 @@ class StylesBuilder:
|
|||||||
process_method = getattr(self, f"process_{rule_name}", None)
|
process_method = getattr(self, f"process_{rule_name}", None)
|
||||||
|
|
||||||
if process_method is None:
|
if process_method is None:
|
||||||
error_message = f"unknown declaration {declaration.name!r}"
|
suggested_property_name = self._suggested_property_name_for_rule(
|
||||||
did_you_mean_rule_name = self._did_you_mean_for_rule_name(declaration.name)
|
declaration.name
|
||||||
if did_you_mean_rule_name:
|
)
|
||||||
error_message += f"; did you mean {did_you_mean_rule_name!r}?"
|
|
||||||
self.error(
|
self.error(
|
||||||
declaration.name,
|
declaration.name,
|
||||||
declaration.token,
|
declaration.token,
|
||||||
error_message,
|
property_invalid_value_help_text(
|
||||||
|
declaration.name,
|
||||||
|
"css",
|
||||||
|
suggested_property_name=suggested_property_name,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -345,14 +349,20 @@ class StylesBuilder:
|
|||||||
try:
|
try:
|
||||||
append(int(value))
|
append(int(value))
|
||||||
except ValueError:
|
except ValueError:
|
||||||
self.error(name, token, spacing_invalid_value(name, context="css"))
|
self.error(
|
||||||
|
name,
|
||||||
|
token,
|
||||||
|
spacing_invalid_value_help_text(name, context="css"),
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
self.error(name, token, spacing_invalid_value(name, context="css"))
|
self.error(
|
||||||
|
name, token, spacing_invalid_value_help_text(name, context="css")
|
||||||
|
)
|
||||||
if len(space) not in (1, 2, 4):
|
if len(space) not in (1, 2, 4):
|
||||||
self.error(
|
self.error(
|
||||||
name,
|
name,
|
||||||
tokens[0],
|
tokens[0],
|
||||||
spacing_wrong_number_of_values(
|
spacing_wrong_number_of_values_help_text(
|
||||||
name, num_values_supplied=len(space), context="css"
|
name, num_values_supplied=len(space), context="css"
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -361,7 +371,9 @@ class StylesBuilder:
|
|||||||
def _process_space_partial(self, name: str, tokens: list[Token]) -> None:
|
def _process_space_partial(self, name: str, tokens: list[Token]) -> None:
|
||||||
"""Process granular margin / padding declarations."""
|
"""Process granular margin / padding declarations."""
|
||||||
if len(tokens) != 1:
|
if len(tokens) != 1:
|
||||||
self.error(name, tokens[0], spacing_invalid_value(name, context="css"))
|
self.error(
|
||||||
|
name, tokens[0], spacing_invalid_value_help_text(name, context="css")
|
||||||
|
)
|
||||||
|
|
||||||
_EDGE_SPACING_MAP = {"top": 0, "right": 1, "bottom": 2, "left": 3}
|
_EDGE_SPACING_MAP = {"top": 0, "right": 1, "bottom": 2, "left": 3}
|
||||||
token = tokens[0]
|
token = tokens[0]
|
||||||
@@ -369,7 +381,9 @@ class StylesBuilder:
|
|||||||
if token_name == "number":
|
if token_name == "number":
|
||||||
space = int(value)
|
space = int(value)
|
||||||
else:
|
else:
|
||||||
self.error(name, token, spacing_invalid_value(name, context="css"))
|
self.error(
|
||||||
|
name, token, spacing_invalid_value_help_text(name, context="css")
|
||||||
|
)
|
||||||
style_name, _, edge = name.replace("-", "_").partition("_")
|
style_name, _, edge = name.replace("-", "_").partition("_")
|
||||||
|
|
||||||
current_spacing = cast(
|
current_spacing = cast(
|
||||||
@@ -731,7 +745,7 @@ class StylesBuilder:
|
|||||||
process_content_align_horizontal = process_align_horizontal
|
process_content_align_horizontal = process_align_horizontal
|
||||||
process_content_align_vertical = process_align_vertical
|
process_content_align_vertical = process_align_vertical
|
||||||
|
|
||||||
def _did_you_mean_for_rule_name(self, rule_name: str) -> str | None:
|
def _suggested_property_name_for_rule(self, rule_name: str) -> str | None:
|
||||||
possible_matches = get_close_matches(
|
possible_matches = get_close_matches(
|
||||||
rule_name, self._processable_rule_names(), n=1
|
rule_name, self._processable_rule_names(), n=1
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,10 +1,22 @@
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from tests.utilities.render import render
|
from tests.utilities.render import render
|
||||||
from textual.css._help_text import spacing_wrong_number_of_values, spacing_invalid_value, scalar_help_text, \
|
from textual.css._help_text import (
|
||||||
string_enum_help_text, color_property_help_text, border_property_help_text, layout_property_help_text, \
|
spacing_wrong_number_of_values_help_text,
|
||||||
docks_property_help_text, dock_property_help_text, fractional_property_help_text, offset_property_help_text, \
|
spacing_invalid_value_help_text,
|
||||||
align_help_text, offset_single_axis_help_text, style_flags_property_help_text
|
scalar_help_text,
|
||||||
|
string_enum_help_text,
|
||||||
|
color_property_help_text,
|
||||||
|
border_property_help_text,
|
||||||
|
layout_property_help_text,
|
||||||
|
docks_property_help_text,
|
||||||
|
dock_property_help_text,
|
||||||
|
fractional_property_help_text,
|
||||||
|
offset_property_help_text,
|
||||||
|
align_help_text,
|
||||||
|
offset_single_axis_help_text,
|
||||||
|
style_flags_property_help_text,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(params=["css", "inline"])
|
@pytest.fixture(params=["css", "inline"])
|
||||||
@@ -15,22 +27,24 @@ def styling_context(request):
|
|||||||
def test_help_text_examples_are_contextualized():
|
def test_help_text_examples_are_contextualized():
|
||||||
"""Ensure that if the user is using CSS, they see CSS-specific examples
|
"""Ensure that if the user is using CSS, they see CSS-specific examples
|
||||||
and if they're using inline styles they see inline-specific examples."""
|
and if they're using inline styles they see inline-specific examples."""
|
||||||
rendered_inline = render(spacing_invalid_value("padding", "inline"))
|
rendered_inline = render(spacing_invalid_value_help_text("padding", "inline"))
|
||||||
assert "widget.styles.padding" in rendered_inline
|
assert "widget.styles.padding" in rendered_inline
|
||||||
|
|
||||||
rendered_css = render(spacing_invalid_value("padding", "css"))
|
rendered_css = render(spacing_invalid_value_help_text("padding", "css"))
|
||||||
assert "padding:" in rendered_css
|
assert "padding:" in rendered_css
|
||||||
|
|
||||||
|
|
||||||
def test_spacing_wrong_number_of_values(styling_context):
|
def test_spacing_wrong_number_of_values(styling_context):
|
||||||
rendered = render(spacing_wrong_number_of_values("margin", 3, styling_context))
|
rendered = render(
|
||||||
|
spacing_wrong_number_of_values_help_text("margin", 3, styling_context)
|
||||||
|
)
|
||||||
assert "Invalid number of values" in rendered
|
assert "Invalid number of values" in rendered
|
||||||
assert "margin" in rendered
|
assert "margin" in rendered
|
||||||
assert "3" in rendered
|
assert "3" in rendered
|
||||||
|
|
||||||
|
|
||||||
def test_spacing_invalid_value(styling_context):
|
def test_spacing_invalid_value(styling_context):
|
||||||
rendered = render(spacing_invalid_value("padding", styling_context))
|
rendered = render(spacing_invalid_value_help_text("padding", styling_context))
|
||||||
assert "Invalid value for" in rendered
|
assert "Invalid value for" in rendered
|
||||||
assert "padding" in rendered
|
assert "padding" in rendered
|
||||||
|
|
||||||
@@ -47,7 +61,9 @@ def test_scalar_help_text(styling_context):
|
|||||||
|
|
||||||
|
|
||||||
def test_string_enum_help_text(styling_context):
|
def test_string_enum_help_text(styling_context):
|
||||||
rendered = render(string_enum_help_text("display", ["none", "hidden"], styling_context))
|
rendered = render(
|
||||||
|
string_enum_help_text("display", ["none", "hidden"], styling_context)
|
||||||
|
)
|
||||||
assert "Invalid value for" in rendered
|
assert "Invalid value for" in rendered
|
||||||
|
|
||||||
# Ensure property name is mentioned
|
# Ensure property name is mentioned
|
||||||
@@ -113,7 +129,9 @@ def test_offset_single_axis_help_text():
|
|||||||
|
|
||||||
|
|
||||||
def test_style_flags_property_help_text(styling_context):
|
def test_style_flags_property_help_text(styling_context):
|
||||||
rendered = render(style_flags_property_help_text("text-style", "notavalue b", styling_context))
|
rendered = render(
|
||||||
|
style_flags_property_help_text("text-style", "notavalue b", styling_context)
|
||||||
|
)
|
||||||
assert "Invalid value" in rendered
|
assert "Invalid value" in rendered
|
||||||
assert "notavalue" in rendered
|
assert "notavalue" in rendered
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
from contextlib import nullcontext as does_not_raise
|
from contextlib import nullcontext as does_not_raise
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from textual.color import Color
|
from textual.color import Color
|
||||||
|
from textual.css._help_renderables import HelpText
|
||||||
from textual.css.stylesheet import Stylesheet, StylesheetParseError
|
from textual.css.stylesheet import Stylesheet, StylesheetParseError
|
||||||
from textual.css.tokenizer import TokenizeError
|
from textual.css.tokenizer import TokenizeError
|
||||||
|
|
||||||
@@ -60,6 +63,7 @@ def test_color_property_parsing(css_value, expectation, expected_color):
|
|||||||
[
|
[
|
||||||
["backgroundu", "background"],
|
["backgroundu", "background"],
|
||||||
["bckgroundu", "background"],
|
["bckgroundu", "background"],
|
||||||
|
["ofset-x", "offset-x"],
|
||||||
["colr", "color"],
|
["colr", "color"],
|
||||||
["colour", "color"],
|
["colour", "color"],
|
||||||
["wdth", "width"],
|
["wdth", "width"],
|
||||||
@@ -81,12 +85,17 @@ def test_did_you_mean_for_css_property_names(
|
|||||||
"${PROPERTY}", css_property_name
|
"${PROPERTY}", css_property_name
|
||||||
)
|
)
|
||||||
|
|
||||||
|
stylesheet.add_source(css)
|
||||||
with pytest.raises(StylesheetParseError) as err:
|
with pytest.raises(StylesheetParseError) as err:
|
||||||
stylesheet.parse(css)
|
stylesheet.parse()
|
||||||
|
|
||||||
error_token, error_message = err.value.errors.stylesheet.rules[0].errors[0]
|
_, help_text = err.value.errors.rules[0].errors[0] # type: Any, HelpText
|
||||||
if expected_property_name_suggestion is None:
|
assert help_text.summary == f"Invalid CSS property [i]{css_property_name}[/]"
|
||||||
assert "did you mean" not in error_message
|
|
||||||
else:
|
expected_bullets_length = 1 if expected_property_name_suggestion else 0
|
||||||
expected_did_you_mean_error_message = f"unknown declaration '{css_property_name}'; did you mean '{expected_property_name_suggestion}'?"
|
assert len(help_text.bullets) == expected_bullets_length
|
||||||
assert expected_did_you_mean_error_message == error_message
|
if expected_property_name_suggestion is not None:
|
||||||
|
expected_suggestion_message = (
|
||||||
|
f"Did you mean [i]{expected_property_name_suggestion}[/]?"
|
||||||
|
)
|
||||||
|
assert help_text.bullets[0].markup == expected_suggestion_message
|
||||||
|
|||||||
Reference in New Issue
Block a user