mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
Merge branch 'css' into validate-id
This commit is contained in:
@@ -106,16 +106,18 @@ def test_stylesheet_apply_user_css_over_widget_css():
|
||||
user_css = ".a {color: red; tint: yellow;}"
|
||||
|
||||
class MyWidget(Widget):
|
||||
CSS = ".a {color: blue !important; background: lime;}"
|
||||
DEFAULT_CSS = ".a {color: blue !important; background: lime;}"
|
||||
|
||||
node = MyWidget()
|
||||
node.add_class("a")
|
||||
|
||||
stylesheet = _make_user_stylesheet(user_css)
|
||||
stylesheet.add_source(MyWidget.CSS, "widget.py:MyWidget", is_default_css=True)
|
||||
stylesheet.add_source(
|
||||
MyWidget.DEFAULT_CSS, "widget.py:MyWidget", is_default_css=True
|
||||
)
|
||||
stylesheet.apply(node)
|
||||
|
||||
# The node is red because user CSS overrides Widget.CSS
|
||||
# The node is red because user CSS overrides Widget.DEFAULT_CSS
|
||||
assert node.styles.color == Color(255, 0, 0)
|
||||
# The background colour defined in the Widget still applies, since user CSS doesn't override it
|
||||
assert node.styles.background == Color(0, 255, 0)
|
||||
|
||||
@@ -27,8 +27,3 @@ async def devtools(aiohttp_client, server):
|
||||
yield devtools
|
||||
await devtools.disconnect()
|
||||
await client.close()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def in_memory_logfile():
|
||||
yield StringIO()
|
||||
|
||||
@@ -37,6 +37,9 @@ def test_log_message_render(console):
|
||||
path="abc/hello.py",
|
||||
line_number=123,
|
||||
unix_timestamp=TIMESTAMP,
|
||||
group=0,
|
||||
verbosity=0,
|
||||
severity=0,
|
||||
)
|
||||
table = next(iter(message.__rich_console__(console, console.options)))
|
||||
|
||||
@@ -52,7 +55,7 @@ def test_log_message_render(console):
|
||||
local_time = datetime.fromtimestamp(TIMESTAMP)
|
||||
string_timestamp = local_time.time()
|
||||
|
||||
assert left == f"[dim]{string_timestamp}"
|
||||
assert left.plain == f"[{string_timestamp}] UNDEFINED"
|
||||
assert right.align == "right"
|
||||
assert "hello.py:123" in right.renderable
|
||||
|
||||
|
||||
@@ -36,15 +36,20 @@ async def test_devtools_log_places_encodes_and_queues_message(devtools):
|
||||
queued_log = await devtools.log_queue.get()
|
||||
queued_log_data = msgpack.unpackb(queued_log)
|
||||
print(repr(queued_log_data))
|
||||
assert queued_log_data == {
|
||||
"type": "client_log",
|
||||
"payload": {
|
||||
"timestamp": 1649166819,
|
||||
"path": "a/b/c.py",
|
||||
"line_number": 123,
|
||||
"segments": b"\x80\x04\x95B\x00\x00\x00\x00\x00\x00\x00]\x94(\x8c\x0crich.segment\x94\x8c\x07Segment\x94\x93\x94\x8c\rHello, world!\x94NN\x87\x94\x81\x94h\x03\x8c\x01\n\x94NN\x87\x94\x81\x94e.",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
{
|
||||
"type": "client_log",
|
||||
"payload": {
|
||||
"group": 0,
|
||||
"verbosity": 0,
|
||||
"severity": 0,
|
||||
"timestamp": 1649166819,
|
||||
"path": "a/b/c.py",
|
||||
"line_number": 123,
|
||||
"segments": b"\x80\x04\x95@\x00\x00\x00\x00\x00\x00\x00]\x94(\x8c\x0crich.segment\x94\x8c\x07Segment\x94\x93\x94\x8c\x0bhello world\x94NN\x87\x94\x81\x94h\x03\x8c\x01\n\x94NN\x87\x94\x81\x94e.",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@time_machine.travel(datetime.utcfromtimestamp(TIMESTAMP))
|
||||
@@ -57,6 +62,9 @@ async def test_devtools_log_places_encodes_and_queues_many_logs_as_string(devtoo
|
||||
assert queued_log_data == {
|
||||
"type": "client_log",
|
||||
"payload": {
|
||||
"group": 0,
|
||||
"verbosity": 0,
|
||||
"severity": 0,
|
||||
"timestamp": 1649166819,
|
||||
"path": "a/b/c.py",
|
||||
"line_number": 123,
|
||||
|
||||
@@ -14,7 +14,7 @@ TIMESTAMP = 1649166819
|
||||
async def test_print_redirect_to_devtools_only(devtools):
|
||||
await devtools._stop_log_queue_processing()
|
||||
|
||||
with redirect_stdout(StdoutRedirector(devtools, None)): # type: ignore
|
||||
with redirect_stdout(StdoutRedirector(devtools)): # type: ignore
|
||||
print("Hello, world!")
|
||||
|
||||
assert devtools.log_queue.qsize() == 1
|
||||
@@ -32,28 +32,26 @@ async def test_print_redirect_to_devtools_only(devtools):
|
||||
)
|
||||
|
||||
|
||||
async def test_print_redirect_to_logfile_only(devtools, in_memory_logfile):
|
||||
async def test_print_redirect_to_logfile_only(devtools):
|
||||
await devtools.disconnect()
|
||||
with redirect_stdout(StdoutRedirector(devtools, in_memory_logfile)): # type: ignore
|
||||
with redirect_stdout(StdoutRedirector(devtools)): # type: ignore
|
||||
print("Hello, world!")
|
||||
assert in_memory_logfile.getvalue() == "Hello, world!\n"
|
||||
|
||||
|
||||
async def test_print_redirect_to_devtools_and_logfile(devtools, in_memory_logfile):
|
||||
async def test_print_redirect_to_devtools_and_logfile(devtools):
|
||||
await devtools._stop_log_queue_processing()
|
||||
|
||||
with redirect_stdout(StdoutRedirector(devtools, in_memory_logfile)): # type: ignore
|
||||
with redirect_stdout(StdoutRedirector(devtools)): # type: ignore
|
||||
print("Hello, world!")
|
||||
|
||||
assert devtools.log_queue.qsize() == 1
|
||||
assert in_memory_logfile.getvalue() == "Hello, world!\n"
|
||||
|
||||
|
||||
@time_machine.travel(datetime.fromtimestamp(TIMESTAMP))
|
||||
async def test_print_without_flush_not_sent_to_devtools(devtools, in_memory_logfile):
|
||||
async def test_print_without_flush_not_sent_to_devtools(devtools):
|
||||
await devtools._stop_log_queue_processing()
|
||||
|
||||
with redirect_stdout(StdoutRedirector(devtools, in_memory_logfile)): # type: ignore
|
||||
with redirect_stdout(StdoutRedirector(devtools)): # type: ignore
|
||||
# End is no longer newline character, so print will no longer
|
||||
# flush the output buffer by default.
|
||||
print("Hello, world!", end="")
|
||||
@@ -62,44 +60,19 @@ async def test_print_without_flush_not_sent_to_devtools(devtools, in_memory_logf
|
||||
|
||||
|
||||
@time_machine.travel(datetime.fromtimestamp(TIMESTAMP))
|
||||
async def test_print_forced_flush_sent_to_devtools(devtools, in_memory_logfile):
|
||||
async def test_print_forced_flush_sent_to_devtools(devtools):
|
||||
await devtools._stop_log_queue_processing()
|
||||
|
||||
with redirect_stdout(StdoutRedirector(devtools, in_memory_logfile)): # type: ignore
|
||||
with redirect_stdout(StdoutRedirector(devtools)): # type: ignore
|
||||
print("Hello, world!", end="", flush=True)
|
||||
|
||||
assert devtools.log_queue.qsize() == 1
|
||||
|
||||
|
||||
@time_machine.travel(datetime.fromtimestamp(TIMESTAMP))
|
||||
async def test_print_multiple_args_batched_as_one_log(devtools, in_memory_logfile):
|
||||
async def test_print_multiple_args_batched_as_one_log(devtools):
|
||||
await devtools._stop_log_queue_processing()
|
||||
|
||||
with redirect_stdout(StdoutRedirector(devtools, in_memory_logfile)): # type: ignore
|
||||
# We call print with multiple arguments here, but it
|
||||
# results in a single log added to the log queue.
|
||||
print("Hello", "world", "multiple")
|
||||
|
||||
assert devtools.log_queue.qsize() == 1
|
||||
|
||||
queued_log = await devtools.log_queue.get()
|
||||
queued_log_json = json.loads(queued_log)
|
||||
payload = queued_log_json["payload"]
|
||||
|
||||
assert queued_log_json["type"] == "client_log"
|
||||
assert payload["timestamp"] == TIMESTAMP
|
||||
assert (
|
||||
payload["encoded_segments"]
|
||||
== "gANdcQAoY3JpY2guc2VnbWVudApTZWdtZW50CnEBWBQAAABIZWxsbyB3b3JsZCBtdWx0aXBsZXECTk6HcQOBcQRoAVgBAAAACnEFTk6HcQaBcQdlLg=="
|
||||
)
|
||||
assert len(payload["path"]) > 0
|
||||
assert payload["line_number"] != 0
|
||||
|
||||
|
||||
@time_machine.travel(datetime.fromtimestamp(TIMESTAMP))
|
||||
async def test_print_multiple_args_batched_as_one_log(devtools, in_memory_logfile):
|
||||
await devtools._stop_log_queue_processing()
|
||||
redirector = StdoutRedirector(devtools, in_memory_logfile)
|
||||
redirector = StdoutRedirector(devtools)
|
||||
with redirect_stdout(redirector): # type: ignore
|
||||
# This print adds 3 messages to the buffer that can be batched
|
||||
print("The first", "batch", "of logs", end="")
|
||||
@@ -111,10 +84,10 @@ async def test_print_multiple_args_batched_as_one_log(devtools, in_memory_logfil
|
||||
|
||||
|
||||
@time_machine.travel(datetime.fromtimestamp(TIMESTAMP))
|
||||
async def test_print_strings_containing_newline_flushed(devtools, in_memory_logfile):
|
||||
async def test_print_strings_containing_newline_flushed(devtools):
|
||||
await devtools._stop_log_queue_processing()
|
||||
|
||||
with redirect_stdout(StdoutRedirector(devtools, in_memory_logfile)): # type: ignore
|
||||
with redirect_stdout(StdoutRedirector(devtools)): # type: ignore
|
||||
# Flushing is disabled since end="", but the first
|
||||
# string will be flushed since it contains a newline
|
||||
print("Hel\nlo", end="")
|
||||
@@ -124,10 +97,10 @@ async def test_print_strings_containing_newline_flushed(devtools, in_memory_logf
|
||||
|
||||
|
||||
@time_machine.travel(datetime.fromtimestamp(TIMESTAMP))
|
||||
async def test_flush_flushes_buffered_logs(devtools, in_memory_logfile):
|
||||
async def test_flush_flushes_buffered_logs(devtools):
|
||||
await devtools._stop_log_queue_processing()
|
||||
|
||||
redirector = StdoutRedirector(devtools, in_memory_logfile)
|
||||
redirector = StdoutRedirector(devtools)
|
||||
with redirect_stdout(redirector): # type: ignore
|
||||
print("x", end="")
|
||||
|
||||
|
||||
@@ -110,7 +110,7 @@ async def test_composition_of_vertical_container_with_children(
|
||||
expected_placeholders_offset_x: int,
|
||||
):
|
||||
class VerticalContainer(Widget):
|
||||
CSS = (
|
||||
DEFAULT_CSS = (
|
||||
"""
|
||||
VerticalContainer {
|
||||
layout: vertical;
|
||||
@@ -304,7 +304,7 @@ async def test_scrollbar_size_impact_on_the_layout(
|
||||
class LargeWidgetContainer(Widget):
|
||||
# TODO: Once textual#581 ("Default versus User CSS") is solved the following CSS should just use the
|
||||
# "LargeWidgetContainer" selector, without having to use a more specific one to be able to override Widget's CSS:
|
||||
CSS = """
|
||||
DEFAULT_CSS = """
|
||||
#large-widget-container {
|
||||
width: 20;
|
||||
height: 20;
|
||||
|
||||
@@ -48,7 +48,7 @@ async def test_scroll_to_widget(
|
||||
last_screen_expected_placeholder_ids: Sequence[int],
|
||||
):
|
||||
class VerticalContainer(Widget):
|
||||
CSS = """
|
||||
DEFAULT_CSS = """
|
||||
VerticalContainer {
|
||||
layout: vertical;
|
||||
overflow: hidden auto;
|
||||
@@ -60,7 +60,7 @@ async def test_scroll_to_widget(
|
||||
"""
|
||||
|
||||
class MyTestApp(AppTest):
|
||||
CSS = """
|
||||
DEFAULT_CSS = """
|
||||
Placeholder {
|
||||
height: 5; /* minimal height to see the name of a Placeholder */
|
||||
}
|
||||
|
||||
@@ -25,20 +25,11 @@ from textual.geometry import Size, Region
|
||||
|
||||
|
||||
class AppTest(App):
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
test_name: str,
|
||||
size: Size,
|
||||
log_verbosity: int = 2,
|
||||
):
|
||||
def __init__(self, *, test_name: str, size: Size):
|
||||
# Tests will log in "/tests/test.[test name].log":
|
||||
log_path = Path(__file__).parent.parent / f"test.{test_name}.log"
|
||||
super().__init__(
|
||||
driver_class=DriverTest,
|
||||
log_path=log_path,
|
||||
log_verbosity=log_verbosity,
|
||||
log_color_system="256",
|
||||
)
|
||||
|
||||
# Let's disable all features by default
|
||||
@@ -169,7 +160,7 @@ class AppTest(App):
|
||||
|
||||
await let_asyncio_process_some_events()
|
||||
|
||||
def on_exception(self, error: Exception) -> None:
|
||||
def _handle_exception(self, error: Exception) -> None:
|
||||
# In tests we want the errors to be raised, rather than printed to a Console
|
||||
raise error
|
||||
|
||||
|
||||
Reference in New Issue
Block a user