ElyDoc, ElyTable, ElyShow (LibreOfficeKit), ElyPDF (MuPDF), ElyTree (Git), ElyMark (Milkdown and CodeMirror in CEF). ElyMail and ElyAgent follow.
231 lines
8.9 KiB
Bash
Executable File
231 lines
8.9 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Settings: one app's change reaches another; bad input is refused.
|
|
set -euo pipefail
|
|
|
|
root=$(cd "$(dirname "$0")/../.." && pwd)
|
|
[ "$(uname)" = Darwin ] || { echo "SKIP: macOS only"; exit 0; }
|
|
work=$(mktemp -d)
|
|
pids=()
|
|
cleanup() {
|
|
for pid in "${pids[@]}"; do kill "$pid" 2>/dev/null || true; done
|
|
rm -rf "$work"
|
|
}
|
|
trap cleanup EXIT
|
|
fail() {
|
|
echo "FAIL: $*" >&2
|
|
exit 1
|
|
}
|
|
|
|
cargo build -q -p ely-pdf -p ely-tree --manifest-path "$root/Cargo.toml"
|
|
for tool in ax-values ax-press click window-shade; do
|
|
swiftc -O -o "$work/$tool" "$root/tests/scripts/$tool.swift"
|
|
done
|
|
home="$work/home"
|
|
support="$home/Library/Application Support/ElyOffice"
|
|
mkdir -p "$support"
|
|
printf '{"version":1,"appearance":"light"}' >"$support/settings.json"
|
|
launch() {
|
|
ELY_SMOKE=hold ELY_LOCALE=en ELY_NO_DIALOG=1 HOME="$home" \
|
|
perl -e 'alarm 300; exec @ARGV' "$root/target/debug/$1" >"$work/$1.out" 2>&1 &
|
|
pids+=($!)
|
|
}
|
|
ax() { "$work/ax-values" "$1"; }
|
|
[ "$("$work/ax-values" $$ | head -n 1)" != untrusted ] || { echo "SKIP: needs Accessibility permission"; exit 0; }
|
|
# Waits until $1's AX tree matches $2.
|
|
seen() {
|
|
for _ in $(seq 100); do
|
|
ax "$1" | grep -q -- "$2" && return 0
|
|
sleep 0.2
|
|
done
|
|
return 1
|
|
}
|
|
front() {
|
|
osascript -e "tell application \"System Events\" to set frontmost of (first process whose unix id is $1) to true" >/dev/null
|
|
sleep 0.5
|
|
}
|
|
keys() { osascript -e 'tell application "System Events"' -e "$1" -e 'end tell' >/dev/null; }
|
|
# Another process holds the lock file $1 for $2 seconds.
|
|
hold() {
|
|
python3 -c 'import fcntl, sys, time; f = open(sys.argv[1], "a"); fcntl.flock(f, fcntl.LOCK_EX); time.sleep(float(sys.argv[2]))' "$1" "$2" &
|
|
pids+=($!)
|
|
sleep 0.3
|
|
}
|
|
# Clicks the field AX names $2 in app $1.
|
|
field() {
|
|
local at
|
|
# A page that just opened may still be moving.
|
|
sleep 0.5
|
|
at=$(ax "$1" | grep -m 1 "^AXTextField.*title=$2" | sed 's/.*at=//')
|
|
[ -n "$at" ] || fail "no field $2"
|
|
"$work/click" "${at%,*}" "${at#*,}"
|
|
sleep 0.3
|
|
}
|
|
|
|
launch ely-pdf
|
|
pdf=${pids[${#pids[@]}-1]}
|
|
launch ely-tree
|
|
tree=${pids[${#pids[@]}-1]}
|
|
seen "$pdf" AXWindow || fail "ElyPDF opened no window"
|
|
seen "$tree" AXWindow || fail "ElyTree opened no window"
|
|
sleep 2
|
|
shade=$("$work/window-shade" "$tree")
|
|
[ "$shade" != denied ] || { echo "SKIP: needs Screen Recording permission"; exit 0; }
|
|
[ "${shade%% *}" -gt 150 ] || fail "ElyTree starts light, not at ${shade%% *}"
|
|
|
|
# ElyPDF's Dark reaches ElyTree without a restart.
|
|
front "$pdf"
|
|
keys 'keystroke "," using command down'
|
|
seen "$pdf" "title=Settings" || fail "Cmd+, opened no settings window"
|
|
"$work/ax-press" "$pdf" Dark >/dev/null
|
|
for _ in $(seq 50); do grep -q '"dark"' "$support/settings.json" && break; sleep 0.2; done
|
|
grep -q '"appearance": "dark"' "$support/settings.json" || fail "Dark is not saved: $(cat "$support/settings.json")"
|
|
for _ in $(seq 25); do
|
|
shade=$("$work/window-shade" "$tree")
|
|
[ "${shade%% *}" -lt 80 ] && break
|
|
sleep 0.2
|
|
done
|
|
[ "${shade%% *}" -lt 80 ] || fail "ElyTree did not turn dark: ${shade%% *}; $(grep -E 'settings: changed|mode ->' "$support/ely-tree/ely.log" | tail -3)"
|
|
|
|
# The author name is saved for every app.
|
|
"$work/ax-press" "$pdf" Documents >/dev/null
|
|
seen "$pdf" "title=Author name" || fail "no author field"
|
|
field "$pdf" "Author name"
|
|
keys 'keystroke "Ana Test"'
|
|
keys 'key code 36'
|
|
for _ in $(seq 50); do grep -q 'Ana Test' "$support/settings.json" && break; sleep 0.2; done
|
|
grep -q '"author": "Ana Test"' "$support/settings.json" || fail "the author is not saved"
|
|
grep -q '"appearance": "dark"' "$support/settings.json" || fail "the author write lost the appearance"
|
|
|
|
# A name from elsewhere fills the unedited field.
|
|
python3 - "$support/settings.json" <<'PY'
|
|
import json, sys
|
|
path = sys.argv[1]
|
|
fields = json.load(open(path))
|
|
fields["author"] = "Bob"
|
|
open(path, "w").write(json.dumps(fields))
|
|
PY
|
|
seen "$pdf" "value=Bob.title=Author name" || fail "the field kept the old name"
|
|
keys 'key code 48'
|
|
sleep 1
|
|
grep -q '"author": "Bob"' "$support/settings.json" || fail "leaving the field wrote the old name back"
|
|
|
|
# A failed write is shown, not only logged.
|
|
mv "$support/.settings.lock" "$support/.settings.lock.file"
|
|
mkdir "$support/.settings.lock"
|
|
field "$pdf" "Author name"
|
|
keys 'keystroke "a" using command down'
|
|
keys 'keystroke "Carol"'
|
|
keys 'key code 36'
|
|
seen "$pdf" "was not saved" || fail "the failed write is not shown"
|
|
sleep 1
|
|
seen "$pdf" "value=Carol.title=Author name" || fail "the failed name was not kept for a retry"
|
|
grep -q '"author": "Bob"' "$support/settings.json" || fail "the failed name reached the file"
|
|
rmdir "$support/.settings.lock" && mv "$support/.settings.lock.file" "$support/.settings.lock"
|
|
keys 'key code 36'
|
|
for _ in $(seq 50); do grep -q Carol "$support/settings.json" && break; sleep 0.2; done
|
|
grep -q '"author": "Carol"' "$support/settings.json" || fail "the retry did not save"
|
|
|
|
# Changing back while a save waits: last choice stays.
|
|
hold "$support/.settings.lock" 4
|
|
keys 'keystroke "a" using command down'
|
|
keys 'keystroke "Dave"'
|
|
keys 'key code 36'
|
|
sleep 0.5
|
|
keys 'keystroke "a" using command down'
|
|
keys 'keystroke "Carol"'
|
|
keys 'key code 36'
|
|
sleep 5
|
|
grep -q '"author": "Carol"' "$support/settings.json" || fail "a waiting save overtook the last choice: $(grep author "$support/settings.json")"
|
|
seen "$pdf" "value=Carol.title=Author name" || fail "the field shows a waiting save"
|
|
|
|
# ElyTree shows its own unreadable file.
|
|
mkdir -p "$support/ely-tree"
|
|
printf '{' >"$support/ely-tree/settings.json"
|
|
front "$tree"
|
|
keys 'keystroke "," using command down'
|
|
seen "$tree" "title=Settings" || fail "ElyTree opened no settings window"
|
|
"$work/ax-press" "$tree" Git >/dev/null
|
|
seen "$tree" "title=Git program" || fail "no Git field"
|
|
seen "$tree" "cannot be read" || fail "ElyTree's unreadable file is not shown"
|
|
printf '{}' >"$support/ely-tree/settings.json"
|
|
|
|
# ElyTree refuses a program that is not Git.
|
|
field "$tree" "Git program"
|
|
keys 'keystroke "/bin/echo"'
|
|
keys 'key code 36'
|
|
seen "$tree" "is not Git 2.40 or later" || fail "a non-Git program was not refused: $(ax "$tree" | grep -i "git\|echo")"
|
|
! grep -q echo "$support/ely-tree/settings.json" 2>/dev/null || fail "the refused program was saved"
|
|
|
|
# A slow check finishing last never overwrites a later choice.
|
|
cat >"$work/slow-git" <<'SH'
|
|
#!/bin/sh
|
|
sleep 4
|
|
echo "git version 2.56.0"
|
|
SH
|
|
chmod +x "$work/slow-git"
|
|
bundled="$root/engines/git/bin/git"
|
|
[ -x "$bundled" ] || fail "no bundled Git at $bundled"
|
|
field "$tree" "Git program"
|
|
keys 'keystroke "a" using command down'
|
|
keys "keystroke \"$work/slow-git\""
|
|
keys 'key code 36'
|
|
sleep 0.5
|
|
keys 'keystroke "a" using command down'
|
|
keys "keystroke \"$bundled\""
|
|
keys 'key code 36'
|
|
sleep 6
|
|
grep -q "\"git\": \"$bundled\"" "$support/ely-tree/settings.json" || fail "the slow check won: $(cat "$support/ely-tree/settings.json")"
|
|
! ax "$tree" | grep -q "is not Git" || fail "a stale refusal stayed on screen"
|
|
|
|
# Choosing the saved Git again outdates a slow check.
|
|
field "$tree" "Git program"
|
|
keys 'keystroke "a" using command down'
|
|
keys "keystroke \"$work/slow-git\""
|
|
keys 'key code 36'
|
|
sleep 0.5
|
|
keys 'keystroke "a" using command down'
|
|
keys "keystroke \"$bundled\""
|
|
keys 'key code 36'
|
|
sleep 6
|
|
grep -q "\"git\": \"$bundled\"" "$support/ely-tree/settings.json" || fail "the slow check overwrote the saved choice"
|
|
|
|
# A Git write waiting for the lock loses.
|
|
cat >"$work/fast-git" <<'SH'
|
|
#!/bin/sh
|
|
echo "git version 2.56.0"
|
|
SH
|
|
chmod +x "$work/fast-git"
|
|
hold "$support/ely-tree/.settings.lock" 4
|
|
field "$tree" "Git program"
|
|
keys 'keystroke "a" using command down'
|
|
keys "keystroke \"$work/fast-git\""
|
|
keys 'key code 36'
|
|
sleep 1
|
|
keys 'keystroke "a" using command down'
|
|
keys "keystroke \"$bundled\""
|
|
keys 'key code 36'
|
|
sleep 5
|
|
grep -q "\"git\": \"$bundled\"" "$support/ely-tree/settings.json" || fail "a waiting Git write overtook the last choice"
|
|
|
|
# An unreadable file is shown, and never overwritten.
|
|
printf '{' >"$support/settings.json"
|
|
front "$pdf"
|
|
seen "$pdf" "cannot be read" || fail "the unreadable file is not shown: $(grep settings "$support/ely-pdf/ely.log" | tail -n 3)"
|
|
"$work/ax-press" "$pdf" General >/dev/null
|
|
"$work/ax-press" "$pdf" Light >/dev/null
|
|
sleep 1
|
|
[ "$(cat "$support/settings.json")" = "{" ] || fail "the unreadable file was overwritten"
|
|
# The main window reopens where it was left.
|
|
printf '{"version":1}' >"$support/settings.json"
|
|
osascript -e "tell application \"System Events\" to set position of (first window of (first process whose unix id is $pdf) whose name is \"ElyPDF\") to {120, 140}" >/dev/null
|
|
for _ in $(seq 50); do grep -q '"x": 120' "$support/ely-pdf/settings.json" 2>/dev/null && break; sleep 0.2; done
|
|
grep -q '"x": 120' "$support/ely-pdf/settings.json" || fail "the window place is not saved: $(cat "$support/ely-pdf/settings.json" 2>/dev/null)"
|
|
kill "$pdf"
|
|
wait "$pdf" 2>/dev/null || true
|
|
launch ely-pdf
|
|
pdf=${pids[${#pids[@]}-1]}
|
|
seen "$pdf" "AXWindow.*title=ElyPDF" || fail "ElyPDF did not reopen"
|
|
place=$(osascript -e "tell application \"System Events\" to get position of (first window of (first process whose unix id is $pdf))")
|
|
[ "$place" = "120, 140" ] || fail "ElyPDF reopened at $place"
|
|
echo "ok: settings"
|