Co-authored-by: Rodrigo Girão Serrão <5621605+rodrigogiraoserrao@users.noreply.github.com>
1228 lines
35 KiB
Python
1228 lines
35 KiB
Python
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
from textual.color import Color
|
|
from textual.css.errors import UnresolvedVariableError
|
|
from textual.css.parse import substitute_references
|
|
from textual.css.scalar import Scalar, Unit
|
|
from textual.css.stylesheet import Stylesheet, StylesheetParseError
|
|
from textual.css.tokenize import tokenize
|
|
from textual.css.tokenizer import ReferencedBy, Token, TokenError
|
|
from textual.css.transition import Transition
|
|
from textual.geometry import Spacing
|
|
from textual.layouts.vertical import VerticalLayout
|
|
|
|
|
|
class TestVariableReferenceSubstitution:
|
|
def test_simple_reference(self):
|
|
css = "$x: 1; #some-widget{border: $x;}"
|
|
variables = substitute_references(tokenize(css, ""))
|
|
assert list(variables) == [
|
|
Token(
|
|
name="variable_name",
|
|
value="$x:",
|
|
path="",
|
|
code=css,
|
|
location=(0, 0),
|
|
referenced_by=None,
|
|
),
|
|
Token(
|
|
name="whitespace",
|
|
value=" ",
|
|
path="",
|
|
code=css,
|
|
location=(0, 3),
|
|
referenced_by=None,
|
|
),
|
|
Token(
|
|
name="number",
|
|
value="1",
|
|
path="",
|
|
code=css,
|
|
location=(0, 4),
|
|
referenced_by=None,
|
|
),
|
|
Token(
|
|
name="variable_value_end",
|
|
value=";",
|
|
path="",
|
|
code=css,
|
|
location=(0, 5),
|
|
referenced_by=None,
|
|
),
|
|
Token(
|
|
name="whitespace",
|
|
value=" ",
|
|
path="",
|
|
code=css,
|
|
location=(0, 6),
|
|
referenced_by=None,
|
|
),
|
|
Token(
|
|
name="selector_start_id",
|
|
value="#some-widget",
|
|
path="",
|
|
code=css,
|
|
location=(0, 7),
|
|
referenced_by=None,
|
|
),
|
|
Token(
|
|
name="declaration_set_start",
|
|
value="{",
|
|
path="",
|
|
code=css,
|
|
location=(0, 19),
|
|
referenced_by=None,
|
|
),
|
|
Token(
|
|
name="declaration_name",
|
|
value="border:",
|
|
path="",
|
|
code=css,
|
|
location=(0, 20),
|
|
referenced_by=None,
|
|
),
|
|
Token(
|
|
name="whitespace",
|
|
value=" ",
|
|
path="",
|
|
code=css,
|
|
location=(0, 27),
|
|
referenced_by=None,
|
|
),
|
|
Token(
|
|
name="number",
|
|
value="1",
|
|
path="",
|
|
code=css,
|
|
location=(0, 4),
|
|
referenced_by=ReferencedBy(
|
|
name="x", location=(0, 28), length=2, code=css
|
|
),
|
|
),
|
|
Token(
|
|
name="declaration_end",
|
|
value=";",
|
|
path="",
|
|
code=css,
|
|
location=(0, 30),
|
|
referenced_by=None,
|
|
),
|
|
Token(
|
|
name="declaration_set_end",
|
|
value="}",
|
|
path="",
|
|
code=css,
|
|
location=(0, 31),
|
|
referenced_by=None,
|
|
),
|
|
]
|
|
|
|
def test_simple_reference_no_whitespace(self):
|
|
css = "$x:1; #some-widget{border: $x;}"
|
|
variables = substitute_references(tokenize(css, ""))
|
|
assert list(variables) == [
|
|
Token(
|
|
name="variable_name",
|
|
value="$x:",
|
|
path="",
|
|
code=css,
|
|
location=(0, 0),
|
|
referenced_by=None,
|
|
),
|
|
Token(
|
|
name="number",
|
|
value="1",
|
|
path="",
|
|
code=css,
|
|
location=(0, 3),
|
|
referenced_by=None,
|
|
),
|
|
Token(
|
|
name="variable_value_end",
|
|
value=";",
|
|
path="",
|
|
code=css,
|
|
location=(0, 4),
|
|
referenced_by=None,
|
|
),
|
|
Token(
|
|
name="whitespace",
|
|
value=" ",
|
|
path="",
|
|
code=css,
|
|
location=(0, 5),
|
|
referenced_by=None,
|
|
),
|
|
Token(
|
|
name="selector_start_id",
|
|
value="#some-widget",
|
|
path="",
|
|
code=css,
|
|
location=(0, 6),
|
|
referenced_by=None,
|
|
),
|
|
Token(
|
|
name="declaration_set_start",
|
|
value="{",
|
|
path="",
|
|
code=css,
|
|
location=(0, 18),
|
|
referenced_by=None,
|
|
),
|
|
Token(
|
|
name="declaration_name",
|
|
value="border:",
|
|
path="",
|
|
code=css,
|
|
location=(0, 19),
|
|
referenced_by=None,
|
|
),
|
|
Token(
|
|
name="whitespace",
|
|
value=" ",
|
|
path="",
|
|
code=css,
|
|
location=(0, 26),
|
|
referenced_by=None,
|
|
),
|
|
Token(
|
|
name="number",
|
|
value="1",
|
|
path="",
|
|
code=css,
|
|
location=(0, 3),
|
|
referenced_by=ReferencedBy(
|
|
name="x", location=(0, 27), length=2, code=css
|
|
),
|
|
),
|
|
Token(
|
|
name="declaration_end",
|
|
value=";",
|
|
path="",
|
|
code=css,
|
|
location=(0, 29),
|
|
referenced_by=None,
|
|
),
|
|
Token(
|
|
name="declaration_set_end",
|
|
value="}",
|
|
path="",
|
|
code=css,
|
|
location=(0, 30),
|
|
referenced_by=None,
|
|
),
|
|
]
|
|
|
|
def test_undefined_variable(self):
|
|
css = ".thing { border: $not-defined; }"
|
|
with pytest.raises(UnresolvedVariableError):
|
|
list(substitute_references(tokenize(css, "")))
|
|
|
|
def test_transitive_reference(self):
|
|
css = "$x: 1\n$y: $x\n.thing { border: $y }"
|
|
assert list(substitute_references(tokenize(css, ""))) == [
|
|
Token(
|
|
name="variable_name",
|
|
value="$x:",
|
|
path="",
|
|
code=css,
|
|
location=(0, 0),
|
|
referenced_by=None,
|
|
),
|
|
Token(
|
|
name="whitespace",
|
|
value=" ",
|
|
path="",
|
|
code=css,
|
|
location=(0, 3),
|
|
referenced_by=None,
|
|
),
|
|
Token(
|
|
name="number",
|
|
value="1",
|
|
path="",
|
|
code=css,
|
|
location=(0, 4),
|
|
referenced_by=None,
|
|
),
|
|
Token(
|
|
name="variable_value_end",
|
|
value="\n",
|
|
path="",
|
|
code=css,
|
|
location=(0, 5),
|
|
referenced_by=None,
|
|
),
|
|
Token(
|
|
name="variable_name",
|
|
value="$y:",
|
|
path="",
|
|
code=css,
|
|
location=(1, 0),
|
|
referenced_by=None,
|
|
),
|
|
Token(
|
|
name="whitespace",
|
|
value=" ",
|
|
path="",
|
|
code=css,
|
|
location=(1, 3),
|
|
referenced_by=None,
|
|
),
|
|
Token(
|
|
name="number",
|
|
value="1",
|
|
path="",
|
|
code=css,
|
|
location=(0, 4),
|
|
referenced_by=ReferencedBy(
|
|
name="x", location=(1, 4), length=2, code=css
|
|
),
|
|
),
|
|
Token(
|
|
name="variable_value_end",
|
|
value="\n",
|
|
path="",
|
|
code=css,
|
|
location=(1, 6),
|
|
referenced_by=None,
|
|
),
|
|
Token(
|
|
name="selector_start_class",
|
|
value=".thing",
|
|
path="",
|
|
code=css,
|
|
location=(2, 0),
|
|
referenced_by=None,
|
|
),
|
|
Token(
|
|
name="whitespace",
|
|
value=" ",
|
|
path="",
|
|
code=css,
|
|
location=(2, 6),
|
|
referenced_by=None,
|
|
),
|
|
Token(
|
|
name="declaration_set_start",
|
|
value="{",
|
|
path="",
|
|
code=css,
|
|
location=(2, 7),
|
|
referenced_by=None,
|
|
),
|
|
Token(
|
|
name="whitespace",
|
|
value=" ",
|
|
path="",
|
|
code=css,
|
|
location=(2, 8),
|
|
referenced_by=None,
|
|
),
|
|
Token(
|
|
name="declaration_name",
|
|
value="border:",
|
|
path="",
|
|
code=css,
|
|
location=(2, 9),
|
|
referenced_by=None,
|
|
),
|
|
Token(
|
|
name="whitespace",
|
|
value=" ",
|
|
path="",
|
|
code=css,
|
|
location=(2, 16),
|
|
referenced_by=None,
|
|
),
|
|
Token(
|
|
name="number",
|
|
value="1",
|
|
path="",
|
|
code=css,
|
|
location=(0, 4),
|
|
referenced_by=ReferencedBy(
|
|
name="y", location=(2, 17), length=2, code=css
|
|
),
|
|
),
|
|
Token(
|
|
name="whitespace",
|
|
value=" ",
|
|
path="",
|
|
code=css,
|
|
location=(2, 19),
|
|
referenced_by=None,
|
|
),
|
|
Token(
|
|
name="declaration_set_end",
|
|
value="}",
|
|
path="",
|
|
code=css,
|
|
location=(2, 20),
|
|
referenced_by=None,
|
|
),
|
|
]
|
|
|
|
def test_multi_value_variable(self):
|
|
css = "$x: 2 4\n$y: 6 $x 2\n.thing { border: $y }"
|
|
assert list(substitute_references(tokenize(css, ""))) == [
|
|
Token(
|
|
name="variable_name",
|
|
value="$x:",
|
|
path="",
|
|
code=css,
|
|
location=(0, 0),
|
|
referenced_by=None,
|
|
),
|
|
Token(
|
|
name="whitespace",
|
|
value=" ",
|
|
path="",
|
|
code=css,
|
|
location=(0, 3),
|
|
referenced_by=None,
|
|
),
|
|
Token(
|
|
name="number",
|
|
value="2",
|
|
path="",
|
|
code=css,
|
|
location=(0, 4),
|
|
referenced_by=None,
|
|
),
|
|
Token(
|
|
name="whitespace",
|
|
value=" ",
|
|
path="",
|
|
code=css,
|
|
location=(0, 5),
|
|
referenced_by=None,
|
|
),
|
|
Token(
|
|
name="number",
|
|
value="4",
|
|
path="",
|
|
code=css,
|
|
location=(0, 6),
|
|
referenced_by=None,
|
|
),
|
|
Token(
|
|
name="variable_value_end",
|
|
value="\n",
|
|
path="",
|
|
code=css,
|
|
location=(0, 7),
|
|
referenced_by=None,
|
|
),
|
|
Token(
|
|
name="variable_name",
|
|
value="$y:",
|
|
path="",
|
|
code=css,
|
|
location=(1, 0),
|
|
referenced_by=None,
|
|
),
|
|
Token(
|
|
name="whitespace",
|
|
value=" ",
|
|
path="",
|
|
code=css,
|
|
location=(1, 3),
|
|
referenced_by=None,
|
|
),
|
|
Token(
|
|
name="number",
|
|
value="6",
|
|
path="",
|
|
code=css,
|
|
location=(1, 4),
|
|
referenced_by=None,
|
|
),
|
|
Token(
|
|
name="whitespace",
|
|
value=" ",
|
|
path="",
|
|
code=css,
|
|
location=(1, 5),
|
|
referenced_by=None,
|
|
),
|
|
Token(
|
|
name="number",
|
|
value="2",
|
|
path="",
|
|
code=css,
|
|
location=(0, 4),
|
|
referenced_by=ReferencedBy(
|
|
name="x", location=(1, 6), length=2, code=css
|
|
),
|
|
),
|
|
Token(
|
|
name="whitespace",
|
|
value=" ",
|
|
path="",
|
|
code=css,
|
|
location=(0, 5),
|
|
referenced_by=ReferencedBy(
|
|
name="x", location=(1, 6), length=2, code=css
|
|
),
|
|
),
|
|
Token(
|
|
name="number",
|
|
value="4",
|
|
path="",
|
|
code=css,
|
|
location=(0, 6),
|
|
referenced_by=ReferencedBy(
|
|
name="x", location=(1, 6), length=2, code=css
|
|
),
|
|
),
|
|
Token(
|
|
name="whitespace",
|
|
value=" ",
|
|
path="",
|
|
code=css,
|
|
location=(1, 8),
|
|
referenced_by=None,
|
|
),
|
|
Token(
|
|
name="number",
|
|
value="2",
|
|
path="",
|
|
code=css,
|
|
location=(1, 9),
|
|
referenced_by=None,
|
|
),
|
|
Token(
|
|
name="variable_value_end",
|
|
value="\n",
|
|
path="",
|
|
code=css,
|
|
location=(1, 10),
|
|
referenced_by=None,
|
|
),
|
|
Token(
|
|
name="selector_start_class",
|
|
value=".thing",
|
|
path="",
|
|
code=css,
|
|
location=(2, 0),
|
|
referenced_by=None,
|
|
),
|
|
Token(
|
|
name="whitespace",
|
|
value=" ",
|
|
path="",
|
|
code=css,
|
|
location=(2, 6),
|
|
referenced_by=None,
|
|
),
|
|
Token(
|
|
name="declaration_set_start",
|
|
value="{",
|
|
path="",
|
|
code=css,
|
|
location=(2, 7),
|
|
referenced_by=None,
|
|
),
|
|
Token(
|
|
name="whitespace",
|
|
value=" ",
|
|
path="",
|
|
code=css,
|
|
location=(2, 8),
|
|
referenced_by=None,
|
|
),
|
|
Token(
|
|
name="declaration_name",
|
|
value="border:",
|
|
path="",
|
|
code=css,
|
|
location=(2, 9),
|
|
referenced_by=None,
|
|
),
|
|
Token(
|
|
name="whitespace",
|
|
value=" ",
|
|
path="",
|
|
code=css,
|
|
location=(2, 16),
|
|
referenced_by=None,
|
|
),
|
|
Token(
|
|
name="number",
|
|
value="6",
|
|
path="",
|
|
code=css,
|
|
location=(1, 4),
|
|
referenced_by=ReferencedBy(
|
|
name="y", location=(2, 17), length=2, code=css
|
|
),
|
|
),
|
|
Token(
|
|
name="whitespace",
|
|
value=" ",
|
|
path="",
|
|
code=css,
|
|
location=(1, 5),
|
|
referenced_by=ReferencedBy(
|
|
name="y", location=(2, 17), length=2, code=css
|
|
),
|
|
),
|
|
Token(
|
|
name="number",
|
|
value="2",
|
|
path="",
|
|
code=css,
|
|
location=(0, 4),
|
|
referenced_by=ReferencedBy(
|
|
name="y", location=(2, 17), length=2, code=css
|
|
),
|
|
),
|
|
Token(
|
|
name="whitespace",
|
|
value=" ",
|
|
path="",
|
|
code=css,
|
|
location=(0, 5),
|
|
referenced_by=ReferencedBy(
|
|
name="y", location=(2, 17), length=2, code=css
|
|
),
|
|
),
|
|
Token(
|
|
name="number",
|
|
value="4",
|
|
path="",
|
|
code=css,
|
|
location=(0, 6),
|
|
referenced_by=ReferencedBy(
|
|
name="y", location=(2, 17), length=2, code=css
|
|
),
|
|
),
|
|
Token(
|
|
name="whitespace",
|
|
value=" ",
|
|
path="",
|
|
code=css,
|
|
location=(1, 8),
|
|
referenced_by=ReferencedBy(
|
|
name="y", location=(2, 17), length=2, code=css
|
|
),
|
|
),
|
|
Token(
|
|
name="number",
|
|
value="2",
|
|
path="",
|
|
code=css,
|
|
location=(1, 9),
|
|
referenced_by=ReferencedBy(
|
|
name="y", location=(2, 17), length=2, code=css
|
|
),
|
|
),
|
|
Token(
|
|
name="whitespace",
|
|
value=" ",
|
|
path="",
|
|
code=css,
|
|
location=(2, 19),
|
|
referenced_by=None,
|
|
),
|
|
Token(
|
|
name="declaration_set_end",
|
|
value="}",
|
|
path="",
|
|
code=css,
|
|
location=(2, 20),
|
|
referenced_by=None,
|
|
),
|
|
]
|
|
|
|
def test_variable_used_inside_property_value(self):
|
|
css = "$x: red\n.thing { border: on $x; }"
|
|
assert list(substitute_references(tokenize(css, ""))) == [
|
|
Token(
|
|
name="variable_name",
|
|
value="$x:",
|
|
path="",
|
|
code=css,
|
|
location=(0, 0),
|
|
referenced_by=None,
|
|
),
|
|
Token(
|
|
name="whitespace",
|
|
value=" ",
|
|
path="",
|
|
code=css,
|
|
location=(0, 3),
|
|
referenced_by=None,
|
|
),
|
|
Token(
|
|
name="token",
|
|
value="red",
|
|
path="",
|
|
code=css,
|
|
location=(0, 4),
|
|
referenced_by=None,
|
|
),
|
|
Token(
|
|
name="variable_value_end",
|
|
value="\n",
|
|
path="",
|
|
code=css,
|
|
location=(0, 7),
|
|
referenced_by=None,
|
|
),
|
|
Token(
|
|
name="selector_start_class",
|
|
value=".thing",
|
|
path="",
|
|
code=css,
|
|
location=(1, 0),
|
|
referenced_by=None,
|
|
),
|
|
Token(
|
|
name="whitespace",
|
|
value=" ",
|
|
path="",
|
|
code=css,
|
|
location=(1, 6),
|
|
referenced_by=None,
|
|
),
|
|
Token(
|
|
name="declaration_set_start",
|
|
value="{",
|
|
path="",
|
|
code=css,
|
|
location=(1, 7),
|
|
referenced_by=None,
|
|
),
|
|
Token(
|
|
name="whitespace",
|
|
value=" ",
|
|
path="",
|
|
code=css,
|
|
location=(1, 8),
|
|
referenced_by=None,
|
|
),
|
|
Token(
|
|
name="declaration_name",
|
|
value="border:",
|
|
path="",
|
|
code=css,
|
|
location=(1, 9),
|
|
referenced_by=None,
|
|
),
|
|
Token(
|
|
name="whitespace",
|
|
value=" ",
|
|
path="",
|
|
code=css,
|
|
location=(1, 16),
|
|
referenced_by=None,
|
|
),
|
|
Token(
|
|
name="token",
|
|
value="on",
|
|
path="",
|
|
code=css,
|
|
location=(1, 17),
|
|
referenced_by=None,
|
|
),
|
|
Token(
|
|
name="whitespace",
|
|
value=" ",
|
|
path="",
|
|
code=css,
|
|
location=(1, 19),
|
|
referenced_by=None,
|
|
),
|
|
Token(
|
|
name="token",
|
|
value="red",
|
|
path="",
|
|
code=css,
|
|
location=(0, 4),
|
|
referenced_by=ReferencedBy(
|
|
name="x", location=(1, 20), length=2, code=css
|
|
),
|
|
),
|
|
Token(
|
|
name="declaration_end",
|
|
value=";",
|
|
path="",
|
|
code=css,
|
|
location=(1, 22),
|
|
referenced_by=None,
|
|
),
|
|
Token(
|
|
name="whitespace",
|
|
value=" ",
|
|
path="",
|
|
code=css,
|
|
location=(1, 23),
|
|
referenced_by=None,
|
|
),
|
|
Token(
|
|
name="declaration_set_end",
|
|
value="}",
|
|
path="",
|
|
code=css,
|
|
location=(1, 24),
|
|
referenced_by=None,
|
|
),
|
|
]
|
|
|
|
def test_variable_definition_eof(self):
|
|
css = "$x: 1"
|
|
assert list(substitute_references(tokenize(css, ""))) == [
|
|
Token(
|
|
name="variable_name",
|
|
value="$x:",
|
|
path="",
|
|
code=css,
|
|
location=(0, 0),
|
|
referenced_by=None,
|
|
),
|
|
Token(
|
|
name="whitespace",
|
|
value=" ",
|
|
path="",
|
|
code=css,
|
|
location=(0, 3),
|
|
referenced_by=None,
|
|
),
|
|
Token(
|
|
name="number",
|
|
value="1",
|
|
path="",
|
|
code=css,
|
|
location=(0, 4),
|
|
referenced_by=None,
|
|
),
|
|
]
|
|
|
|
def test_variable_reference_whitespace_trimming(self):
|
|
css = "$x: 123;.thing{border: $x}"
|
|
assert list(substitute_references(tokenize(css, ""))) == [
|
|
Token(
|
|
name="variable_name",
|
|
value="$x:",
|
|
path="",
|
|
code=css,
|
|
location=(0, 0),
|
|
referenced_by=None,
|
|
),
|
|
Token(
|
|
name="whitespace",
|
|
value=" ",
|
|
path="",
|
|
code=css,
|
|
location=(0, 3),
|
|
referenced_by=None,
|
|
),
|
|
Token(
|
|
name="number",
|
|
value="123",
|
|
path="",
|
|
code=css,
|
|
location=(0, 7),
|
|
referenced_by=None,
|
|
),
|
|
Token(
|
|
name="variable_value_end",
|
|
value=";",
|
|
path="",
|
|
code=css,
|
|
location=(0, 10),
|
|
referenced_by=None,
|
|
),
|
|
Token(
|
|
name="selector_start_class",
|
|
value=".thing",
|
|
path="",
|
|
code=css,
|
|
location=(0, 11),
|
|
referenced_by=None,
|
|
),
|
|
Token(
|
|
name="declaration_set_start",
|
|
value="{",
|
|
path="",
|
|
code=css,
|
|
location=(0, 17),
|
|
referenced_by=None,
|
|
),
|
|
Token(
|
|
name="declaration_name",
|
|
value="border:",
|
|
path="",
|
|
code=css,
|
|
location=(0, 18),
|
|
referenced_by=None,
|
|
),
|
|
Token(
|
|
name="whitespace",
|
|
value=" ",
|
|
path="",
|
|
code=css,
|
|
location=(0, 25),
|
|
referenced_by=None,
|
|
),
|
|
Token(
|
|
name="number",
|
|
value="123",
|
|
path="",
|
|
code=css,
|
|
location=(0, 7),
|
|
referenced_by=ReferencedBy(
|
|
name="x", location=(0, 26), length=2, code=css
|
|
),
|
|
),
|
|
Token(
|
|
name="declaration_set_end",
|
|
value="}",
|
|
path="",
|
|
code=css,
|
|
location=(0, 28),
|
|
referenced_by=None,
|
|
),
|
|
]
|
|
|
|
|
|
class TestParseLayout:
|
|
def test_valid_layout_name(self):
|
|
css = "#some-widget { layout: vertical; }"
|
|
|
|
stylesheet = Stylesheet()
|
|
stylesheet.add_source(css)
|
|
|
|
styles = stylesheet.rules[0].styles
|
|
assert isinstance(styles.layout, VerticalLayout)
|
|
|
|
def test_invalid_layout_name(self):
|
|
css = "#some-widget { layout: invalidlayout; }"
|
|
|
|
stylesheet = Stylesheet()
|
|
with pytest.raises(StylesheetParseError) as ex:
|
|
stylesheet.add_source(css)
|
|
stylesheet.parse()
|
|
|
|
assert ex.value.errors is not None
|
|
|
|
|
|
class TestParseText:
|
|
def test_foreground(self):
|
|
css = """#some-widget {
|
|
color: green;
|
|
}
|
|
"""
|
|
stylesheet = Stylesheet()
|
|
stylesheet.add_source(css)
|
|
|
|
styles = stylesheet.rules[0].styles
|
|
assert styles.color == Color.parse("green")
|
|
|
|
def test_background(self):
|
|
css = """#some-widget {
|
|
background: red;
|
|
}
|
|
"""
|
|
stylesheet = Stylesheet()
|
|
stylesheet.add_source(css)
|
|
|
|
styles = stylesheet.rules[0].styles
|
|
assert styles.background == Color.parse("red")
|
|
|
|
|
|
class TestParseColor:
|
|
"""More in-depth tests around parsing of CSS colors"""
|
|
|
|
@pytest.mark.parametrize(
|
|
"value,result",
|
|
[
|
|
("rgb(1,255,50)", Color(1, 255, 50)),
|
|
("rgb( 1, 255,50 )", Color(1, 255, 50)),
|
|
("rgba( 1, 255,50,0.3 )", Color(1, 255, 50, 0.3)),
|
|
("rgba( 1, 255,50, 1.3 )", Color(1, 255, 50, 1.0)),
|
|
("hsl( 180, 50%, 50% )", Color(64, 191, 191)),
|
|
("hsl(180,50%,50%)", Color(64, 191, 191)),
|
|
("hsla(180,50%,50%,0.25)", Color(64, 191, 191, 0.25)),
|
|
("hsla( 180, 50% ,50%,0.25 )", Color(64, 191, 191, 0.25)),
|
|
("hsla( 180, 50% , 50% , 1.5 )", Color(64, 191, 191)),
|
|
],
|
|
)
|
|
def test_rgb_and_hsl(self, value, result):
|
|
css = f""".box {{
|
|
color: {value};
|
|
}}
|
|
"""
|
|
stylesheet = Stylesheet()
|
|
stylesheet.add_source(css)
|
|
|
|
styles = stylesheet.rules[0].styles
|
|
assert styles.color == result
|
|
|
|
|
|
class TestParseOffset:
|
|
@pytest.mark.parametrize(
|
|
"offset_x, parsed_x, offset_y, parsed_y",
|
|
[
|
|
[
|
|
"-5.5%",
|
|
Scalar(-5.5, Unit.PERCENT, Unit.WIDTH),
|
|
"-30%",
|
|
Scalar(-30, Unit.PERCENT, Unit.HEIGHT),
|
|
],
|
|
[
|
|
"5%",
|
|
Scalar(5, Unit.PERCENT, Unit.WIDTH),
|
|
"40%",
|
|
Scalar(40, Unit.PERCENT, Unit.HEIGHT),
|
|
],
|
|
[
|
|
"10",
|
|
Scalar(10, Unit.CELLS, Unit.WIDTH),
|
|
"40",
|
|
Scalar(40, Unit.CELLS, Unit.HEIGHT),
|
|
],
|
|
],
|
|
)
|
|
def test_composite_rule(self, offset_x, parsed_x, offset_y, parsed_y):
|
|
css = f"""#some-widget {{
|
|
offset: {offset_x} {offset_y};
|
|
}}
|
|
"""
|
|
stylesheet = Stylesheet()
|
|
stylesheet.add_source(css)
|
|
|
|
styles = stylesheet.rules[0].styles
|
|
|
|
assert len(stylesheet.rules) == 1
|
|
assert stylesheet.rules[0].errors == []
|
|
assert styles.offset.x == parsed_x
|
|
assert styles.offset.y == parsed_y
|
|
|
|
@pytest.mark.parametrize(
|
|
"offset_x, parsed_x, offset_y, parsed_y",
|
|
[
|
|
[
|
|
"-5.5%",
|
|
Scalar(-5.5, Unit.PERCENT, Unit.WIDTH),
|
|
"-30%",
|
|
Scalar(-30, Unit.PERCENT, Unit.HEIGHT),
|
|
],
|
|
[
|
|
"5%",
|
|
Scalar(5, Unit.PERCENT, Unit.WIDTH),
|
|
"40%",
|
|
Scalar(40, Unit.PERCENT, Unit.HEIGHT),
|
|
],
|
|
[
|
|
"-10",
|
|
Scalar(-10, Unit.CELLS, Unit.WIDTH),
|
|
"40",
|
|
Scalar(40, Unit.CELLS, Unit.HEIGHT),
|
|
],
|
|
],
|
|
)
|
|
def test_separate_rules(self, offset_x, parsed_x, offset_y, parsed_y):
|
|
css = f"""#some-widget {{
|
|
offset-x: {offset_x};
|
|
offset-y: {offset_y};
|
|
}}
|
|
"""
|
|
stylesheet = Stylesheet()
|
|
stylesheet.add_source(css)
|
|
|
|
styles = stylesheet.rules[0].styles
|
|
|
|
assert len(stylesheet.rules) == 1
|
|
assert stylesheet.rules[0].errors == []
|
|
assert styles.offset.x == parsed_x
|
|
assert styles.offset.y == parsed_y
|
|
|
|
|
|
class TestParseOverflow:
|
|
def test_multiple_enum(self):
|
|
css = "#some-widget { overflow: hidden auto; }"
|
|
stylesheet = Stylesheet()
|
|
stylesheet.add_source(css)
|
|
|
|
styles = stylesheet.rules[0].styles
|
|
|
|
assert len(stylesheet.rules) == 1
|
|
assert styles.overflow_x == "hidden"
|
|
assert styles.overflow_y == "auto"
|
|
|
|
|
|
class TestParseTransition:
|
|
@pytest.mark.parametrize(
|
|
"duration, parsed_duration",
|
|
[
|
|
["5.57s", 5.57],
|
|
["0.5s", 0.5],
|
|
["1200ms", 1.2],
|
|
["0.5ms", 0.0005],
|
|
["20", 20.0],
|
|
["0.1", 0.1],
|
|
],
|
|
)
|
|
def test_various_duration_formats(self, duration, parsed_duration):
|
|
easing = "in_out_cubic"
|
|
transition_property = "offset"
|
|
delay = duration
|
|
css = f"""#some-widget {{
|
|
transition: {transition_property} {duration} {easing} {delay};
|
|
}}
|
|
"""
|
|
stylesheet = Stylesheet()
|
|
stylesheet.add_source(css)
|
|
|
|
styles = stylesheet.rules[0].styles
|
|
|
|
assert len(stylesheet.rules) == 1
|
|
assert stylesheet.rules[0].errors == []
|
|
assert styles.transitions == {
|
|
"offset": Transition(
|
|
duration=parsed_duration, easing=easing, delay=parsed_duration
|
|
)
|
|
}
|
|
|
|
def test_no_delay_specified(self):
|
|
css = f"#some-widget {{ transition: offset-x 1 in_out_cubic; }}"
|
|
stylesheet = Stylesheet()
|
|
stylesheet.add_source(css)
|
|
|
|
styles = stylesheet.rules[0].styles
|
|
|
|
assert stylesheet.rules[0].errors == []
|
|
assert styles.transitions == {
|
|
"offset-x": Transition(duration=1, easing="in_out_cubic", delay=0)
|
|
}
|
|
|
|
def test_unknown_easing_function(self):
|
|
invalid_func_name = "invalid_easing_function"
|
|
css = f"#some-widget {{ transition: offset 1 {invalid_func_name} 1; }}"
|
|
|
|
stylesheet = Stylesheet()
|
|
with pytest.raises(StylesheetParseError) as ex:
|
|
stylesheet.add_source(css)
|
|
stylesheet.parse()
|
|
|
|
rules = stylesheet._parse_rules(css, "foo")
|
|
stylesheet_errors = rules[0].errors
|
|
|
|
assert len(stylesheet_errors) == 1
|
|
assert stylesheet_errors[0][0].value == invalid_func_name
|
|
assert ex.value.errors is not None
|
|
|
|
|
|
class TestParseOpacity:
|
|
@pytest.mark.parametrize(
|
|
"css_value, styles_value",
|
|
[
|
|
["-0.2", 0.0],
|
|
["0.4", 0.4],
|
|
["1.3", 1.0],
|
|
["-20%", 0.0],
|
|
["25%", 0.25],
|
|
["128%", 1.0],
|
|
],
|
|
)
|
|
def test_opacity_to_styles(self, css_value, styles_value):
|
|
css = f"#some-widget {{ text-opacity: {css_value} }}"
|
|
stylesheet = Stylesheet()
|
|
stylesheet.add_source(css)
|
|
|
|
assert stylesheet.rules[0].styles.text_opacity == styles_value
|
|
assert not stylesheet.rules[0].errors
|
|
|
|
def test_opacity_invalid_value(self):
|
|
css = "#some-widget { text-opacity: 123x }"
|
|
stylesheet = Stylesheet()
|
|
|
|
with pytest.raises(StylesheetParseError):
|
|
stylesheet.add_source(css)
|
|
stylesheet.parse()
|
|
rules = stylesheet._parse_rules(css, "foo")
|
|
assert rules[0].errors
|
|
|
|
|
|
class TestParseMargin:
|
|
def test_margin_partial(self):
|
|
css = "#foo {margin: 1; margin-top: 2; margin-right: 3; margin-bottom: -1;}"
|
|
stylesheet = Stylesheet()
|
|
stylesheet.add_source(css)
|
|
assert stylesheet.rules[0].styles.margin == Spacing(2, 3, -1, 1)
|
|
|
|
|
|
class TestParsePadding:
|
|
def test_padding_partial(self):
|
|
css = "#foo {padding: 1; padding-top: 2; padding-right: 3; padding-bottom: -1;}"
|
|
stylesheet = Stylesheet()
|
|
stylesheet.add_source(css)
|
|
assert stylesheet.rules[0].styles.padding == Spacing(2, 3, -1, 1)
|
|
|
|
|
|
class TestParseTextAlign:
|
|
@pytest.mark.parametrize(
|
|
"valid_align", ["left", "start", "center", "right", "end", "justify"]
|
|
)
|
|
def test_text_align(self, valid_align):
|
|
css = f"#foo {{ text-align: {valid_align} }}"
|
|
stylesheet = Stylesheet()
|
|
stylesheet.add_source(css)
|
|
assert stylesheet.rules[0].styles.text_align == valid_align
|
|
|
|
def test_text_align_invalid(self):
|
|
css = "#foo { text-align: invalid-value; }"
|
|
stylesheet = Stylesheet()
|
|
with pytest.raises(StylesheetParseError):
|
|
stylesheet.add_source(css)
|
|
stylesheet.parse()
|
|
rules = stylesheet._parse_rules(css, "foo")
|
|
assert rules[0].errors
|
|
|
|
def test_text_align_empty_uses_default(self):
|
|
css = "#foo { text-align: ; }"
|
|
stylesheet = Stylesheet()
|
|
stylesheet.add_source(css)
|
|
assert stylesheet.rules[0].styles.text_align == "start"
|
|
|
|
|
|
class TestTypeNames:
|
|
def test_type_no_number(self):
|
|
stylesheet = Stylesheet()
|
|
stylesheet.add_source("TestType {}")
|
|
assert len(stylesheet.rules) == 1
|
|
|
|
def test_type_with_number(self):
|
|
stylesheet = Stylesheet()
|
|
stylesheet.add_source("TestType1 {}")
|
|
assert len(stylesheet.rules) == 1
|
|
|
|
def test_type_starts_with_number(self):
|
|
stylesheet = Stylesheet()
|
|
stylesheet.add_source("1TestType {}")
|
|
with pytest.raises(TokenError):
|
|
stylesheet.parse()
|
|
|
|
def test_combined_type_no_number(self):
|
|
for separator in " >,":
|
|
stylesheet = Stylesheet()
|
|
stylesheet.add_source(f"StartType {separator} TestType {{}}")
|
|
assert len(stylesheet.rules) == 1
|
|
|
|
def test_combined_type_with_number(self):
|
|
for separator in " >,":
|
|
stylesheet = Stylesheet()
|
|
stylesheet.add_source(f"StartType {separator} TestType1 {{}}")
|
|
assert len(stylesheet.rules) == 1
|
|
|
|
def test_combined_type_starts_with_number(self):
|
|
for separator in " >,":
|
|
stylesheet = Stylesheet()
|
|
stylesheet.add_source(f"StartType {separator} 1TestType {{}}")
|
|
with pytest.raises(TokenError):
|
|
stylesheet.parse()
|