Repository: Textualize/textual
Pull request: Fix a crash when setting `DirectoryTree.show_root` before DOM is ready
Fixes #2363.

Because labels can sometimes be needed by the tree before the DOM is ready, this changes `DirectoryTree.render_label` so that it only attempts to *style* the labels if the DOM is up and running.

Problem statement:
Setting `show_root` to `True` raises a `KeyError`
```py
    def compose(self):
        dir_tree = DirectoryTree("./src/routes")
        dir_tree.show_root = True
        yield Header()
        with Horizontal():
            yield Logo()
            with TabbedContent("Config", "Routes", "Server"):
                yield Config(self.config)
                yield dir_tree
                yield Label("hello")
        yield Footer()
```
setting `show_root` to `True`. raises the following error:
`KeyError: "No 'directory-tree--folder' key in COMPONENT_CLASSES"`


Diagnose output:

# Textual Diagnostics

## Versions

| Name    | Value  |
|---------|--------|
| Textual | 0.20.1 |
| Rich    | 13.3.3 |

## Python

| Name           | Value                                                                                                    |
|----------------|----------------------------------------------------------------------------------------------------------|
| Version        | 3.11.0                                                                                                   |
| Implementation | CPython                                                                                                  |
| Compiler       | Clang 14.0.0 (clang-1400.0.29.202)                                                                       |
| Executable     | /Users/felixingram/Library/Application Support/hatch/env/virtual/sanic-kit/1YPPa8fF/sanic-kit/bin/python |

## Operating System

| Name    | Value                                                                                                 |
|---------|-------------------------------------------------------------------------------------------------------|
| System  | Darwin                                                                                                |
| Release | 22.4.0                                                                                                |
| Version | Darwin Kernel Version 22.4.0: Mon Mar  6 21:00:41 PST 2023; root:xnu-8796.101.5~3/RELEASE_ARM64_T8103 |

## Terminal

| Name                 | Value                              |
|----------------------|------------------------------------|
| Terminal Application | WezTerm (20230326-111934-3666303c) |
| TERM                 | xterm-256color                     |
| COLORTERM            | truecolor                          |
| FORCE_COLOR          | *Not set*                          |
| NO_COLOR             | *Not set*                          |

## Rich Console options

| Name           | Value                |
|----------------|----------------------|
| size           | width=203, height=46 |
| legacy_windows | False                |
| min_width      | 1                    |
| max_width      | 203                  |
| is_terminal    | False                |
| encoding       | utf-8                |
| max_height     | 46                   |
| justify        | None                 |
| overflow       | None                 |
| no_wrap        | False                |
| highlight      | None                 |
| markup         | None                 |
| height         | None                 |

Hints from issue discussion:
To narrow it down a wee but, this only happens if `show_root` is changed before the DOM is fully-loaded (for example, doing the same in `on_mount` is fine). Looks like `watch_show_root` is a bit too eager.

Commit message: Update PR.

Patch to review:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3e1da5c856..a21ba6316d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -22,6 +22,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
 - Loading indicators and app notifications overlapped in the wrong order https://github.com/Textualize/textual/issues/3677
 - Widgets being loaded are disabled and have their scrolling explicitly disabled too https://github.com/Textualize/textual/issues/3677
 - Method render on a widget could be called before mounting said widget https://github.com/Textualize/textual/issues/2914
+- Fixed a crash if `DirectoryTree.show_root` was set before the DOM was fully available https://github.com/Textualize/textual/issues/2363
 
 ### Added
 
diff --git a/src/textual/widgets/_directory_tree.py b/src/textual/widgets/_directory_tree.py
index d428939e53..718d59b55e 100644
--- a/src/textual/widgets/_directory_tree.py
+++ b/src/textual/widgets/_directory_tree.py
@@ -297,17 +297,21 @@ def render_label(
 
         if node._allow_expand:
             prefix = ("📂 " if node.is_expanded else "📁 ", base_style + TOGGLE_STYLE)
-            node_label.stylize_before(
-                self.get_component_rich_style("directory-tree--folder", partial=True)
-            )
+            if self.is_mounted:
+                node_label.stylize_before(
+                    self.get_component_rich_style(
+                        "directory-tree--folder", partial=True
+                    )
+                )
         else:
             prefix = (
                 "📄 ",
                 base_style,
             )
-            node_label.stylize_before(
-                self.get_component_rich_style("directory-tree--file", partial=True),
-            )
+            if self.is_mounted:
+                node_label.stylize_before(
+                    self.get_component_rich_style("directory-tree--file", partial=True),
+                )
             node_label.highlight_regex(
                 r"\..+$",
                 self.get_component_rich_style(
@@ -315,7 +319,7 @@ def render_label(
                 ),
             )
 
-        if node_label.plain.startswith("."):
+        if node_label.plain.startswith(".") and self.is_mounted:
             node_label.stylize_before(
                 self.get_component_rich_style("directory-tree--hidden")
             )
diff --git a/tests/directory_tree/test_early_show_root.py b/tests/directory_tree/test_early_show_root.py
new file mode 100644
index 0000000000..73e1995337
--- /dev/null
+++ b/tests/directory_tree/test_early_show_root.py
@@ -0,0 +1,16 @@
+from textual.app import App, ComposeResult
+from textual.widgets import DirectoryTree
+
+
+class DirectoryTreeApp(App[None]):
+    def compose(self) -> ComposeResult:
+        tree = DirectoryTree(".")
+        tree.show_root = True
+        yield tree
+
+
+async def test_managed_to_set_show_root_before_mounted() -> None:
+    """https://github.com/Textualize/textual/issues/2363"""
+    async with DirectoryTreeApp().run_test() as pilot:
+        assert isinstance(pilot.app.query_one(DirectoryTree), DirectoryTree)
+        assert pilot.app.query_one(DirectoryTree).show_root is True


Code review comments:
diff_hunk: @@ -297,25 +297,29 @@ def render_label(
 
         if node._allow_expand:
             prefix = ("📂 " if node.is_expanded else "📁 ", base_style + TOGGLE_STYLE)
-            node_label.stylize_before(
-                self.get_component_rich_style("directory-tree--folder", partial=True)
-            )
+            if self.is_mounted:
path: src/textual/widgets/_directory_tree.py
text: @user1:
Why do we repeat `is_mounted` several times? I don't see any other state modified. Could we not bail at the start of the method if it is not mounted?

@user2:
I think the idea was to build a label as faithful to the intended one as possible.
If you want, we can return early with a simpler version of `node_label`.
I've tested that locally and the tests still pass; do you want me to commit it?

@user1:
Yes please

@user2:
Done in 8087eac.