File: test_directory_tree.py

package info (click to toggle)
textual 2.1.2-1.1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 55,080 kB
  • sloc: python: 85,423; lisp: 1,669; makefile: 101
file content (203 lines) | stat: -rw-r--r-- 6,743 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
from __future__ import annotations

from pathlib import Path

from rich.text import Text

from textual import on
from textual.app import App, ComposeResult
from textual.widgets import DirectoryTree


class DirectoryTreeApp(App[None]):
    """DirectoryTree test app."""

    def __init__(self, path):
        super().__init__()
        self._tmp_path = path
        self.messages = []

    def compose(self) -> ComposeResult:
        yield DirectoryTree(self._tmp_path)

    @on(DirectoryTree.FileSelected)
    @on(DirectoryTree.DirectorySelected)
    def record(
        self, event: DirectoryTree.FileSelected | DirectoryTree.DirectorySelected
    ) -> None:
        self.messages.append(event.__class__.__name__)


async def test_directory_tree_file_selected_message(tmp_path: Path) -> None:
    """Selecting a file should result in a file selected message being emitted."""

    FILE_NAME = "hello.txt"

    # Creating one file under root
    file = tmp_path / FILE_NAME
    file.touch()
    async with DirectoryTreeApp(tmp_path).run_test() as pilot:
        tree = pilot.app.query_one(DirectoryTree)
        await pilot.pause()

        # Sanity check - file is the only child of root
        assert len(tree.root.children) == 1
        node = tree.root.children[0]
        assert node.label == Text(FILE_NAME)

        # Navigate to the file and select it
        await pilot.press("down", "enter")
        await pilot.pause()
        assert pilot.app.messages == ["FileSelected"]


async def test_directory_tree_directory_selected_message(tmp_path: Path) -> None:
    """Selecting a directory should result in a directory selected message being emitted."""

    SUBDIR = "subdir"
    FILE_NAME = "hello.txt"

    # Creating node with one file as its child
    subdir = tmp_path / SUBDIR
    subdir.mkdir()
    file = subdir / FILE_NAME
    file.touch()
    async with DirectoryTreeApp(tmp_path).run_test() as pilot:
        tree = pilot.app.query_one(DirectoryTree)
        await pilot.pause()

        # Sanity check - subdirectory is the only child of root
        assert len(tree.root.children) == 1
        node = tree.root.children[0]
        assert node.label == Text(SUBDIR)

        # Navigate to the subdirectory and select it
        await pilot.press("down", "enter")
        await pilot.pause()
        assert pilot.app.messages == ["DirectorySelected"]

        # Select the subdirectory again
        await pilot.press("enter")
        await pilot.pause()
        assert pilot.app.messages == ["DirectorySelected", "DirectorySelected"]


async def test_directory_tree_reload_node(tmp_path: Path) -> None:
    """Reloading a node of a directory tree should display newly created file inside the directory."""

    RELOADED_DIRECTORY = "parentdir"
    FILE1_NAME = "log.txt"
    FILE2_NAME = "hello.txt"

    # Creating node with one file as its child
    reloaded_dir = tmp_path / RELOADED_DIRECTORY
    reloaded_dir.mkdir()
    file1 = reloaded_dir / FILE1_NAME
    file1.touch()

    async with DirectoryTreeApp(tmp_path).run_test() as pilot:
        tree = pilot.app.query_one(DirectoryTree)
        await pilot.pause()

        # Sanity check - node is the only child of root
        assert len(tree.root.children) == 1
        node = tree.root.children[0]
        assert node.label == Text(RELOADED_DIRECTORY)
        node.expand()
        await pilot.pause()

        # Creating new file under the node
        file2 = reloaded_dir / FILE2_NAME
        file2.touch()

        # Without reloading the node, the newly created file does not show up as its child
        assert len(node.children) == 1
        assert node.children[0].label == Text(FILE1_NAME)

        tree.reload_node(node)
        node.collapse()
        node.expand()
        await pilot.pause()

        # After reloading the node, both files show up as children
        assert len(node.children) == 2
        assert [child.label for child in node.children] == [
            Text(filename) for filename in sorted({FILE1_NAME, FILE2_NAME})
        ]


async def test_directory_tree_reload_other_node(tmp_path: Path) -> None:
    """Reloading a node of a directory tree should not reload content of other directory."""

    RELOADED_DIRECTORY = "parentdir"
    NOT_RELOADED_DIRECTORY = "otherdir"
    FILE1_NAME = "log.txt"
    NOT_RELOADED_FILE3_NAME = "demo.txt"
    NOT_RELOADED_FILE4_NAME = "unseen.txt"

    # Creating two nodes, each having one file as child
    reloaded_dir = tmp_path / RELOADED_DIRECTORY
    reloaded_dir.mkdir()
    file1 = reloaded_dir / FILE1_NAME
    file1.touch()
    non_reloaded_dir = tmp_path / NOT_RELOADED_DIRECTORY
    non_reloaded_dir.mkdir()
    file3 = non_reloaded_dir / NOT_RELOADED_FILE3_NAME
    file3.touch()

    async with DirectoryTreeApp(tmp_path).run_test() as pilot:
        tree = pilot.app.query_one(DirectoryTree)
        await pilot.pause()

        # Sanity check - the root has the two nodes as its children (in alphabetical order)
        assert len(tree.root.children) == 2
        unaffected_node = tree.root.children[0]
        node = tree.root.children[1]
        assert unaffected_node.label == Text(NOT_RELOADED_DIRECTORY)
        assert node.label == Text(RELOADED_DIRECTORY)
        unaffected_node.expand()
        node.expand()
        await pilot.pause()
        assert len(unaffected_node.children) == 1
        assert unaffected_node.children[0].label == Text(NOT_RELOADED_FILE3_NAME)

        # Creating new file under the node that won't be reloaded
        file4 = non_reloaded_dir / NOT_RELOADED_FILE4_NAME
        file4.touch()

        tree.reload_node(node)
        node.collapse()
        node.expand()
        unaffected_node.collapse()
        unaffected_node.expand()
        await pilot.pause()

        # After reloading one node, the new file under the other one does not show up
        assert len(unaffected_node.children) == 1
        assert unaffected_node.children[0].label == Text(NOT_RELOADED_FILE3_NAME)


async def test_directory_tree_reloading_preserves_state(tmp_path: Path) -> None:
    """Regression test for https://github.com/Textualize/textual/issues/4122.

    Ensures `clear_node` does clear the node specified.
    """
    ROOT = "root"
    structure = [
        ROOT,
        "root/file1.txt",
        "root/file2.txt",
    ]

    for path in structure:
        if path.endswith(".txt"):
            (tmp_path / path).touch()
        else:
            (tmp_path / path).mkdir()

    app = DirectoryTreeApp(tmp_path / ROOT)
    async with app.run_test() as pilot:
        directory_tree = app.query_one(DirectoryTree)
        directory_tree.clear_node(directory_tree.root)
        await pilot.pause()
        assert not directory_tree.root.children