aboutsummaryrefslogtreecommitdiffstats
path: root/tests/test_edit_config.py
blob: eac30f9aad230fa1af71b814def263ee488b11b7 (plain) (blame)
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
#!/usr/bin/env python3
"""C-a e opens the config in $EDITOR, in a pane of its own.

The point is the loop: edit, save, watch the session change under you, edit
again. Anything that makes you leave the session to do it — another terminal,
or remembering where the file lives — is friction in the middle of the one
workflow the config watcher exists for.

The pane is *ephemeral*: quit the editor and it is gone. You asked for an
editor, not for a record of having had one.
"""

import os
import sys
import tempfile

from harness import Session, check, report

SH = ["/bin/sh", "-c", 'printf "\\033]2;p\\007"; read x']

# An "editor" that shows the file and stays up, and one that quits when told.
SHOWS = "tail -f"
QUITS = "sh -c 'read x; exit' --"


def conf(text='shell "x"\n'):
    f = tempfile.NamedTemporaryFile("w", suffix=".kdl", delete=False)
    f.write(text)
    f.close()
    return f.name


def test_it_opens_the_config_in_a_pane():
    c = conf('shell "a-marker-only-this-test-writes"\n')
    with Session(SH, cols=70, rows=24, config=c, env={"EDITOR": SHOWS}) as s:
        s.settle()
        check("one pane to start", len(s.panes()) == 1)
        s.key("e")
        snap = s.until_text("a-marker-only-this-test-writes")
        check("a second pane opened", len(s.panes()) == 2, str(s.panes()))
        check(
            "with the config file in it",
            "a-marker-only-this-test-writes" in snap.screen(),
            snap.screen(),
        )
    os.unlink(c)


def test_the_editor_pane_goes_when_the_editor_does():
    c = conf()
    with Session(SH, cols=70, rows=24, config=c, env={"EDITOR": QUITS}) as s:
        s.settle()
        s.key("e")
        s.settle(60)
        check("the editor is up", len(s.panes()) == 2, str(s.panes()))
        s.raw("q\\n")
        s.until(lambda _: len(s.panes()) == 1)
        check("quitting it closes the pane", len(s.panes()) == 1, str(s.panes()))
        check(
            "and leaves no corpse to dismiss",
            "[process exited" not in s.snapshot().screen(),
            s.snapshot().screen(),
        )
    os.unlink(c)


def test_the_config_can_name_the_editor():
    """`editor` beats $EDITOR, for the person whose $EDITOR is something they
    do not want a terminal pane to run."""
    c = conf('editor "tail -f"\nshell "picked-by-the-config"\n')
    with Session(
        SH, cols=70, rows=24, config=c, env={"EDITOR": "definitely-not-an-editor"}
    ) as s:
        s.settle()
        s.key("e")
        snap = s.until_text("picked-by-the-config")
        check(
            "the config's editor is used",
            "picked-by-the-config" in snap.screen(),
            snap.screen(),
        )
    os.unlink(c)


def test_it_is_not_written_into_a_dumped_layout():
    """A dump is the session's shape. An editor someone had open while it was
    taken is not part of that shape — restoring it would reopen their editor
    on a file they had finished with."""
    c = conf()
    with Session(SH, cols=70, rows=24, config=c, env={"EDITOR": SHOWS}) as s:
        s.settle()
        s.key("e")
        s.settle(60)
        check("the editor is up", len(s.panes()) == 2, str(s.panes()))
        dumped = s.api("dump-layout")["kdl"]
        check(
            "the dump does not carry the editor command",
            "tail -f" not in dumped,
            dumped,
        )
    os.unlink(c)


def test_no_room_says_so_rather_than_splitting_anyway():
    c = conf()
    with Session(SH, cols=70, rows=10, config=c, env={"EDITOR": SHOWS}) as s:
        s.settle()
        s.key("e")
        s.settle(40)
        check("still one pane", len(s.panes()) == 1, str(s.panes()))
        check(
            "and it said why", "no room" in s.snapshot().screen(), s.snapshot().screen()
        )
    os.unlink(c)


def test_the_cheatsheet_lists_it():
    c = conf()
    with Session(SH, cols=92, rows=40, config=c) as s:
        s.settle()
        s.send(r"\x01?")
        s.settle()
        screen = s.snapshot().screen()
        check("it is on the sheet", "edit the config" in screen, screen)
        check("under a key", "e " in screen, screen)
    os.unlink(c)


def test_the_control_api_can_do_it_too():
    c = conf('shell "from-the-api"\n')
    with Session(SH, cols=70, rows=24, config=c, env={"EDITOR": SHOWS}) as s:
        s.settle()
        r = s.api("edit-config")
        check("the api reports ok", r.get("ok") is True, str(r))
        snap = s.until_text("from-the-api")
        check("and opened it", "from-the-api" in snap.screen(), snap.screen())
    os.unlink(c)


for name, fn in sorted(list(globals().items())):
    if name.startswith("test_"):
        fn()
sys.exit(report())