aboutsummaryrefslogtreecommitdiff
path: root/tests/e2e.sh
diff options
context:
space:
mode:
Diffstat (limited to 'tests/e2e.sh')
-rwxr-xr-xtests/e2e.sh146
1 files changed, 136 insertions, 10 deletions
diff --git a/tests/e2e.sh b/tests/e2e.sh
index f9fcf18..20ce5dc 100755
--- a/tests/e2e.sh
+++ b/tests/e2e.sh
@@ -43,11 +43,13 @@ cleanup() {
trap cleanup EXIT
# Notifications are on, delivered by a fake notifier that takes 3s like the
-# real one can; the UI must not wait for it.
+# real one can; the UI must not wait for it. Sounds are on, played by a fake
+# player that logs them.
NOTES="$DIR/notifications"
+SOUNDS="$DIR/sounds"
start_daemon() {
- DINGGER_NOTIFIER="sleep 3; echo \"\$0|\$1\" >> $NOTES" RUST_LOG=debug \
- "$DINGGER" watch --interval-secs 30 --no-sounds >>"$DIR/daemon.log" 2>&1 &
+ DINGGER_NOTIFIER="sleep 3; echo \"\$0|\$1\" >> $NOTES" DINGGER_SOUND="echo \"\$0\" >> $SOUNDS" RUST_LOG=debug \
+ "$DINGGER" watch --interval-secs 30 >>"$DIR/daemon.log" 2>&1 &
DAEMON_PID=$!
for _ in $(seq 50); do [ -S "$HOOK" ] && return; sleep 0.1; done
echo "daemon did not start"; cat "$DIR/daemon.log"; exit 1
@@ -223,7 +225,12 @@ hook "$A1" working; hook "$A1" idle
expect "A2 blocked" "opt $A2 @agent_state" blocked
expect "A1 done" "opt $A1 @agent_state" done
t source-file "$ROOT/packaging/tmux/dingger.tmux"
-segment=$(t display -p -c "$CLIENT" '#{T:status-right}' | sed 's/#\[[^]]*\]//g')
+# The counts are published right after the state: poll, like expect.
+for _ in $(seq 40); do
+ segment=$(t display -p -c "$CLIENT" '#{T:status-right}' | sed 's/#\[[^]]*\]//g')
+ case "$segment" in *"×1 ✓1") break ;; esac
+ sleep 0.1
+done
case "$segment" in *"×1"*"✓1"*) pass "segment shows ×1 ✓1 ($segment)";; *) fail "segment: [$segment]";; esac
expect "mouse binding installed" "t list-keys -T root | grep MouseDown1StatusRight | grep -c 'dingger focus'" 1
expect "focus hook installed: learns the terminal window" "t show-hooks -g client-focus-in | grep -c 'dingger terminal focused'" 1
@@ -272,13 +279,43 @@ fi
# --- 12. the shipped pi extension reports for its own pane -------------------
echo "12. pi extension"
cat >"$DIR/ext-harness.mjs" <<'JS'
-// Loads the shipped extension with a fake pi and fires lifecycle events.
+// Loads the shipped extension with a fake pi and fires lifecycle events
+// ("agent_start"), runs commands ("/importance high"), and asks for argument
+// completions ("complete:importance h"). Custom session entries persist in
+// $SESSION_ENTRIES, so a later run reads them back as the session; what the
+// extension shows and emits goes to $EXT_LOG.
+import fs from "node:fs";
const ext = await import(process.env.EXTENSION);
-const on = {};
-ext.default({ on: (name, fn) => (on[name] = fn), events: { on: () => {} } });
-const ctx = { mode: "tui", isIdle: () => true };
-for (const event of process.argv.slice(2)) {
- await on[event]?.({ type: event, reason: "reload" }, ctx);
+const on = {}, commands = {};
+const store = process.env.SESSION_ENTRIES;
+const entries = store && fs.existsSync(store) ? JSON.parse(fs.readFileSync(store, "utf8")) : [];
+const log = (line) => process.env.EXT_LOG && fs.appendFileSync(process.env.EXT_LOG, line + "\n");
+ext.default({
+ on: (name, fn) => (on[name] = fn),
+ registerCommand: (name, command) => (commands[name] = command),
+ appendEntry: (customType, data) => {
+ entries.push({ type: "custom", customType, data });
+ if (store) fs.writeFileSync(store, JSON.stringify(entries));
+ },
+ events: { on: () => {}, emit: (name, data) => log(`event ${name} ${JSON.stringify(data)}`) },
+});
+const ctx = {
+ mode: "tui",
+ isIdle: () => true,
+ sessionManager: { getEntries: () => entries, getSessionFile: () => undefined },
+ ui: { setStatus: (key, text) => log(`status ${key} ${text ?? ""}`), notify: (text) => log(`notify ${text}`) },
+};
+for (const arg of process.argv.slice(2)) {
+ if (arg.startsWith("/")) {
+ const [name, ...rest] = arg.slice(1).split(" ");
+ await commands[name]?.handler(rest.join(" "), ctx);
+ } else if (arg.startsWith("complete:")) {
+ const [name, ...rest] = arg.slice("complete:".length).split(" ");
+ const items = await commands[name]?.getArgumentCompletions?.(rest.join(" "));
+ log(`complete ${(items ?? []).map((item) => item.value).join(" ")}`);
+ } else {
+ await on[arg]?.({ type: arg, reason: "reload" }, ctx);
+ }
await new Promise((r) => setTimeout(r, 300));
}
JS
@@ -793,6 +830,95 @@ renv "$DINGGER" focus --client "$TTY1" --pane "$(r display -p -t '=grp:agent' '#
expect "a window on a session group stays on its own session" "client_session $TTY1" grp-b
expect "showing the agent" "r display -p -c $TTY1 '#{window_name}'" agent
+# --- 21. importance --------------------------------------------------------
+# Fresh agents on the first server (its daemon polls every 30 s: updates
+# arrive by reports and pokes). Nobody looks at session imp.
+echo "21. importance"
+report() { # pane state importance (either may be empty)
+ python3 - "$HOOK" "$1" "$2" "$3" <<'PY'
+import json, socket, sys
+params = {"pane_id": sys.argv[2], "agent": "pi"}
+if sys.argv[3]: params["state"] = sys.argv[3]
+if sys.argv[4]: params["importance"] = sys.argv[4]
+s = socket.socket(socket.AF_UNIX)
+s.connect(sys.argv[1])
+s.sendall((json.dumps({"method": "pane.report_agent", "params": params}) + "\n").encode())
+s.close()
+PY
+}
+t new-session -d -s imp -n i1 -c "$DIR" -x 120 -y 40 "$DIR/bin/pi"
+t new-window -d -t imp: -n i2 -c "$DIR" "$DIR/bin/pi"
+t new-window -d -t imp: -n i3 -c "$DIR" "$DIR/bin/pi"
+t new-window -d -t imp: -n i4 -c "$DIR" "$DIR/bin/pi"
+I1=$(t display -p -t imp:i1 '#{pane_id}'); I2=$(t display -p -t imp:i2 '#{pane_id}')
+I3=$(t display -p -t imp:i3 '#{pane_id}'); I4=$(t display -p -t imp:i4 '#{pane_id}')
+iqueue() { gopt @dingger_queue | tr ',' '\n' | grep -xE "$I1|$I2|$I3" | tr '\n' ' '; }
+for p in "$I1" "$I2" "$I3"; do report "$p" working ""; expect "imp: $p working" "opt $p @agent_state" working; done
+for p in "$I1" "$I2" "$I3"; do report "$p" idle ""; expect "imp: $p done" "opt $p @agent_state" done; sleep 0.05; done
+expect "same importance: oldest done first" "iqueue" "$I1 $I2 $I3 "
+report "$I3" "" high
+expect "a high agent's pane option" "opt $I3 @dingger_importance" high
+expect "importance before time: the high agent first" "iqueue" "$I3 $I1 $I2 "
+expect "and its status unchanged (a report without state)" "opt $I3 @agent_state" done
+report "$I1" "" low
+expect "a low agent last" "iqueue" "$I3 $I2 $I1 "
+report "$I3" "" normal
+expect "normal leaves the option unset" "opt $I3 @dingger_importance" ""
+expect "and the order by time again" "iqueue" "$I2 $I3 $I1 "
+report "$I3" "" high
+expect "imp: high again" "iqueue" "$I3 $I2 $I1 "
+report "$I1" blocked ""
+expect "imp: a low agent blocked" "opt $I1 @agent_state" blocked
+expect "status before importance: the blocked low agent first" "iqueue" "$I1 $I3 $I2 "
+report "$I1" working ""
+expect "imp: working again" "opt $I1 @agent_state" working
+# Let every alert so far land (the fake notifier takes 3 s).
+sleep 3.5; : >"$SOUNDS"; : >"$NOTES"
+report "$I1" working ""; report "$I1" idle ""
+expect "a low agent finishing" "opt $I1 @agent_state" done
+sleep 0.5
+expect "makes no sound" "cat $SOUNDS" ""
+expect "but still notifies" "grep -c '^pi finished|imp:i1' $NOTES" 1 60
+report "$I2" working ""; report "$I2" idle ""
+expect "a normal agent finishing sounds" "cat $SOUNDS" done
+mb() { DINGGER_TMUX_SOCKET="$SOCK" DINGGER_BIN="$DINGGER" "$MENUBAR" "$@"; }
+expect "menu: a high agent is marked" "mb --print | grep -F 'imp · i3' | cut -f2" "! pi"
+expect "menu: a low agent says so" "mb --print | grep -F 'imp · i1' | cut -f3" "imp · i1 · pi · low · done now"
+expect "menu: high, normal, low among the done" \
+ "mb --print | grep -F 'imp · ' | grep '^✓' | cut -f3 | cut -d' ' -f3 | tr '\n' ' '" "i3 i2 i1 "
+expect "dingger importance shows it" "$DINGGER importance --pane $I1" low
+expect "a pi that reports itself is set in pi" \
+ "$DINGGER importance high --pane $I1 2>&1" "dingger: this pi reports to dingger: use /importance in it (pi saves it with the session)"
+expect "dingger importance needs an agent" "$DINGGER importance high --pane \$(t display -p -t viewer: '#{pane_id}') 2>&1 | cut -c1-24" "dingger: no agent in pan"
+$DINGGER poke; expect "imp: the plain pi is identified" "opt $I4 @agent" pi
+expect "dingger importance sets another agent's" "$DINGGER importance high --pane $I4" "importance: high"
+expect "the daemon writes it" "opt $I4 @dingger_importance" high
+t respawn-pane -k -t "$I4" "sleep 100000"; $DINGGER poke
+expect "it goes with the agent" "opt $I4 @dingger_importance" ""
+kill "$DAEMON_PID"; sleep 0.3; start_daemon; $DINGGER poke
+sleep 1
+expect "a daemon restart keeps it" "opt $I3 @dingger_importance" high
+expect "and the order" "iqueue" "$I3 $I2 $I1 "
+if command -v node >/dev/null; then
+ ENTRIES="$DIR/entries.json"; EXTLOG="$DIR/ext.log"
+ iext() { SESSION_ENTRIES="$ENTRIES" EXT_LOG="$EXTLOG" ext "$@"; }
+ iext "$I2" session_start "/importance high"
+ expect "/importance high reaches dingger" "opt $I2 @dingger_importance" high
+ expect "saved in the session" "grep -c '\"customType\":\"dingger-importance\",\"data\":{\"level\":\"high\"}' $ENTRIES" 1
+ expect "shown in pi's footer" "grep -c '^status dingger-importance importance: high$' $EXTLOG" 1
+ expect "announced to other extensions" "grep -c '^event dingger:importance {\"level\":\"high\"}$' $EXTLOG" 1
+ report "$I2" "" normal
+ expect "imp: set back by hand" "opt $I2 @dingger_importance" ""
+ iext "$I2" session_start
+ expect "a new pi on the session reports it again" "opt $I2 @dingger_importance" high
+ # pi starts a session before any command runs.
+ : >"$EXTLOG"; iext "$I2" session_start "/importance urgent" "complete:importance h" "/importance normal"
+ expect "an unknown level is refused" "grep -c '^notify importance is high, normal, or low$' $EXTLOG" 1
+ expect "levels complete" "grep '^complete' $EXTLOG" "complete high"
+ expect "normal reaches dingger" "opt $I2 @dingger_importance" ""
+ expect "and leaves the footer" "grep -c '^status dingger-importance $' $EXTLOG" 1
+fi
+
# Every daemon this run started must stop with it.
kill "$DAEMON_PID" "$DAEMON2_PID" 2>/dev/null
expect "the test daemons stop" "ps -o pid= -p $DAEMON_PID,$DAEMON2_PID | wc -l | tr -d ' '" 0