update
This commit is contained in:
@@ -4,6 +4,11 @@ DIR=$(cd "$(dirname "$0")" && pwd)
|
||||
"$DIR/stop.sh"
|
||||
pgrep -f "^gnome-shell --(nested|headless)" | xargs -r kill 2>/dev/null
|
||||
rm -rf /tmp/desktop-app-nested/data/desktop-app
|
||||
# Mit SYNC_URL bekommt die Test-Shell einen Server-Zugang (Token "test", alle 2 Sekunden)
|
||||
if [ -n "$SYNC_URL" ]; then
|
||||
mkdir -p /tmp/desktop-app-nested/data/desktop-app
|
||||
echo "{\"url\": \"$SYNC_URL\", \"token\": \"test\", \"interval\": 2}" > /tmp/desktop-app-nested/data/desktop-app/sync.json
|
||||
fi
|
||||
sleep 2
|
||||
(nohup "$DIR/nested.sh" "$1" > /tmp/desktop-app-nested.log 2>&1 &)
|
||||
sleep 9
|
||||
|
||||
@@ -82,7 +82,7 @@ rclick 'cal._blocks.find(b => b.event.title === "Sport").block'
|
||||
check "Kontextmenü offen" "$(val 'labels(grabbed()).join("|")')" "Löschen"
|
||||
shot "03-context-menu"
|
||||
click 'find(grabbed(), "Löschen")'
|
||||
check "Termin gelöscht" "$(val 'store.events.filter(e => e.title === "Sport").length')" "0"
|
||||
check "Termin gelöscht (Tombstone bleibt)" "$(val 'store.events.filter(e => e.title === "Sport").map(e => e.deleted).join(",") + " " + store.eventsOn("2026-09-18").filter(e => e.title === "Sport").length')" "true 0"
|
||||
|
||||
echo "=== Woche: ganztägiger Termin, Klick daneben schließt"
|
||||
click 'cal._alldaySlots[6]'
|
||||
@@ -118,7 +118,7 @@ check "Todo bearbeiten zeigt Text" "$(val 'entries(grabbed())[0]')" "Milch kaufe
|
||||
key Escape
|
||||
rclick 'find(todos, "Milch kaufen")'
|
||||
click 'find(grabbed(), "Löschen")'
|
||||
check "Todo gelöscht" "$(val 'store.todos.filter(t => t.title === "Milch kaufen").length')" "0"
|
||||
check "Todo gelöscht (Tombstone bleibt)" "$(val 'store.todos.filter(t => t.title === "Milch kaufen").map(t => t.deleted).join(",") + " " + store.sortedTodos().filter(t => t.title === "Milch kaufen").length')" "true 0"
|
||||
rclickb 'todos'
|
||||
check "Rechtsklick Todo-Fläche" "$(val 'labels(grabbed()).join("|")')" "Erstellen"
|
||||
click 'find(grabbed(), "Erstellen")'
|
||||
|
||||
130
dev/tests/sync.sh
Executable file
130
dev/tests/sync.sh
Executable file
@@ -0,0 +1,130 @@
|
||||
#!/bin/bash
|
||||
# Sync-Test: lokaler Server plus headless Shell, Änderungen in beide Richtungen: dev/tests/sync.sh
|
||||
DIR=$(cd "$(dirname "$0")/.." && pwd)
|
||||
ROOT=$(cd "$DIR/.." && pwd)
|
||||
E="$DIR/eval.sh"
|
||||
OUT=/tmp/desktop-app-nested/out.txt
|
||||
PORT=8041
|
||||
URL="http://localhost:$PORT"
|
||||
SERVER_DATA=/tmp/desktop-app-sync-server
|
||||
AUTH="Authorization: Bearer test"
|
||||
FAILED=0
|
||||
|
||||
PRE='const ext = Main.extensionManager.lookup("desktop@marha.de").stateObj;
|
||||
const notes = ext._root.get_child_at_index(0);
|
||||
const todos = ext._root.get_child_at_index(1).get_child_at_index(1);
|
||||
const store = ext._store;
|
||||
const read = p => new TextDecoder().decode(GLib.file_get_contents(p)[1]);
|
||||
const exists = p => GLib.file_test(p, GLib.FileTest.EXISTS);
|
||||
const state = () => JSON.parse(read(store.dir + "/sync-state.json"));
|
||||
const St = imports.gi.St;
|
||||
const labels = a => { const out = []; const walk = n => { if (n instanceof St.Label && n.text) out.push(n.text); n.get_children().forEach(walk); }; walk(a); return out; };
|
||||
'
|
||||
val() {
|
||||
"$E" "$PRE GLib.file_set_contents('$OUT', String($1)); 'w'" | grep -v "^OK '\"w\"')$"
|
||||
cat "$OUT"
|
||||
}
|
||||
api() { curl -s -H "$AUTH" -H "Content-Type: application/json" "$@"; }
|
||||
now() { date -u +%Y-%m-%dT%H:%M:%S.000Z; }
|
||||
push() { api -X POST "$URL/api/sync" -d "$1" >/dev/null; }
|
||||
check() {
|
||||
local name=$1 actual=$2 expected=$3
|
||||
if [ "$actual" == "$expected" ]; then
|
||||
echo "ok $name"
|
||||
else
|
||||
echo "FAIL $name"; echo " ist: $actual"; echo " soll: $expected"
|
||||
FAILED=$((FAILED + 1))
|
||||
fi
|
||||
}
|
||||
cleanup() {
|
||||
kill $SERVER_PID 2>/dev/null
|
||||
"$DIR/stop.sh"
|
||||
pkill -f "^gnome-shell --(nested|headless)" 2>/dev/null
|
||||
wait 2>/dev/null
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
echo "=== Server starten"
|
||||
rm -rf "$SERVER_DATA"
|
||||
(cd "$ROOT/backend" && DATA_DIR="$SERVER_DATA" SYNC_TOKEN=test .venv/bin/uvicorn app.main:app --port $PORT > /tmp/desktop-app-sync-server.log 2>&1 &)
|
||||
for i in $(seq 1 30); do curl -s "$URL/api/health" >/dev/null && break; sleep 0.5; done
|
||||
SERVER_PID=$(pgrep -f "uvicorn app.main:app --port $PORT")
|
||||
check "Server läuft" "$(curl -s $URL/api/health)" '{"ok":true}'
|
||||
|
||||
echo "=== Shell mit Sync starten"
|
||||
SYNC_URL="$URL" "$DIR/restart.sh" headless >/dev/null
|
||||
check "Sync aktiv" "$(val 'ext._sync.active')" "true"
|
||||
|
||||
echo "=== Lokal → Server"
|
||||
"$E" "$PRE store.saveEvent({title: 'Zahnarzt', date: '2026-09-21', start: '09:00', end: '10:00', endDate: null}); store.saveTodo({title: 'Einkaufen', date: null, time: null}); 'ok'" >/dev/null
|
||||
sleep 3
|
||||
check "Termin auf dem Server" "$(api "$URL/api/events" | python3 -c 'import json,sys; print([e["title"] for e in json.load(sys.stdin)])')" "['Zahnarzt']"
|
||||
check "Todo auf dem Server" "$(api "$URL/api/todos" | python3 -c 'import json,sys; print([t["title"] for t in json.load(sys.stdin)])')" "['Einkaufen']"
|
||||
check "Stand gemerkt" "$(val 'state().cursor > 0')" "true"
|
||||
|
||||
echo "=== Server → lokal"
|
||||
push '{"todos": [{"id": "r1", "updated": "2026-09-19T10:00:00.000Z", "title": "Vom Handy", "date": null, "time": null, "done": false}], "notes": [{"id": "Remote/Idee.md", "updated": "2026-09-19T10:00:00.000Z", "content": "# Vom Server"}]}'
|
||||
sleep 3
|
||||
check "Todo vom Server in der Liste" "$(val 'store.sortedTodos().map(t => t.title).sort().join(",")')" "Einkaufen,Vom Handy"
|
||||
check "Todo vom Server im Panel" "$(val 'labels(todos).includes("Vom Handy")')" "true"
|
||||
check "Notiz vom Server als Datei" "$(val 'exists(store.notesDir + "/Remote/Idee.md") + " " + JSON.stringify(read(store.notesDir + "/Remote/Idee.md"))')" 'true "# Vom Server"'
|
||||
check "Notiz vom Server im Baum" "$(val 'labels(notes).includes("▸ Remote")')" "true"
|
||||
check "Übernommene Notiz wird nicht zurückgeschickt" "$(val 'state().pushed.notes["Remote/Idee.md"]')" "2026-09-19T10:00:00.000Z"
|
||||
|
||||
echo "=== Konflikt: neuester gewinnt"
|
||||
push '{"todos": [{"id": "r1", "updated": "2026-09-19T09:00:00.000Z", "title": "Älter", "date": null, "time": null, "done": false}]}'
|
||||
sleep 3
|
||||
check "Älterer Stand verliert" "$(val 'store.todos.find(t => t.id === "r1").title')" "Vom Handy"
|
||||
push '{"todos": [{"id": "r1", "updated": "'$(now)'", "title": "Neuer", "date": null, "time": null, "done": false}]}'
|
||||
sleep 3
|
||||
check "Neuerer Stand gewinnt" "$(val 'store.todos.find(t => t.id === "r1").title')" "Neuer"
|
||||
sleep 1
|
||||
"$E" "$PRE store.toggleTodo('r1'); 'ok'" >/dev/null
|
||||
sleep 3
|
||||
check "Lokale Änderung ist neuer und landet auf dem Server" "$(api "$URL/api/todos" | python3 -c 'import json,sys; print([t["done"] for t in json.load(sys.stdin) if t["id"] == "r1"])')" "[True]"
|
||||
push '{"todos": [{"id": "r1", "updated": "2026-09-19T09:30:00.000Z", "title": "Verspätet", "date": null, "time": null, "done": false}]}'
|
||||
sleep 3
|
||||
check "Verspäteter älterer Stand ändert nichts" "$(val 'store.todos.find(t => t.id === "r1").title + " " + store.todos.find(t => t.id === "r1").done')" "Neuer true"
|
||||
|
||||
echo "=== Löschen in beide Richtungen"
|
||||
"$E" "$PRE store.removeTodo(store.todos.find(t => t.title === 'Einkaufen').id); store.deleteNote(store.notesDir + '/Remote/Idee.md'); 'ok'" >/dev/null
|
||||
sleep 3
|
||||
check "Gelöschtes Todo auf dem Server weg" "$(api "$URL/api/todos" | python3 -c 'import json,sys; print(sorted(t["title"] for t in json.load(sys.stdin)))')" "['Neuer']"
|
||||
check "Gelöschte Notiz auf dem Server weg" "$(api "$URL/api/notes" | python3 -c 'import json,sys; print([n["id"] for n in json.load(sys.stdin)])')" "[]"
|
||||
"$E" "$PRE store.createNote(store.notesDir, 'Lokal'); store.writeNote(store.notesDir + '/Lokal.md', 'lokal'); 'ok'" >/dev/null
|
||||
sleep 3
|
||||
check "Lokale Notiz auf dem Server" "$(api "$URL/api/notes" | python3 -c 'import json,sys; print([(n["id"], n["content"]) for n in json.load(sys.stdin)])')" "[('Lokal.md', 'lokal')]"
|
||||
push '{"notes": [{"id": "Lokal.md", "updated": "'$(now)'", "deleted": true}]}'
|
||||
sleep 3
|
||||
check "Vom Server gelöschte Notiz lokal weg" "$(val 'exists(store.notesDir + "/Lokal.md")')" "false"
|
||||
|
||||
echo "=== Offene Notiz bleibt unangetastet"
|
||||
"$E" "$PRE store.createNote(store.notesDir, 'Offen'); store.writeNote(store.notesDir + '/Offen.md', 'meins'); notes._select({type: 'note', path: store.notesDir + '/Offen.md', name: 'Offen'}); 'ok'" >/dev/null
|
||||
sleep 3
|
||||
"$E" "$PRE notes._startEdit(); 'ok'" >/dev/null
|
||||
CURSOR=$(val 'state().cursor')
|
||||
push '{"notes": [{"id": "Offen.md", "updated": "'$(now)'", "content": "vom server"}]}'
|
||||
sleep 3
|
||||
check "Datei während des Tippens unverändert" "$(val 'read(store.notesDir + "/Offen.md") + " " + (state().cursor === '"$CURSOR"')')" "meins true"
|
||||
"$E" "$PRE notes._stopEdit(); 'ok'" >/dev/null
|
||||
sleep 3
|
||||
check "Nach dem Schließen übernommen" "$(val 'read(store.notesDir + "/Offen.md") + " " + notes._content.text')" "vom server vom server"
|
||||
|
||||
echo "=== Umbenennen"
|
||||
sleep 1
|
||||
"$E" "$PRE store.renameNote(store.notesDir + '/Offen.md', 'Neu'); 'ok'" >/dev/null
|
||||
sleep 3
|
||||
check "Umbenannt auf dem Server" "$(api "$URL/api/notes" | python3 -c 'import json,sys; print([n["id"] for n in json.load(sys.stdin)])')" "['Neu.md']"
|
||||
|
||||
echo "=== Server weg und wieder da"
|
||||
kill $SERVER_PID; sleep 1
|
||||
"$E" "$PRE store.saveTodo({title: 'Offline', date: null, time: null}); 'ok'" >/dev/null
|
||||
sleep 3
|
||||
(cd "$ROOT/backend" && DATA_DIR="$SERVER_DATA" SYNC_TOKEN=test .venv/bin/uvicorn app.main:app --port $PORT >> /tmp/desktop-app-sync-server.log 2>&1 &)
|
||||
for i in $(seq 1 30); do curl -s "$URL/api/health" >/dev/null && break; sleep 0.5; done
|
||||
SERVER_PID=$(pgrep -f "uvicorn app.main:app --port $PORT")
|
||||
sleep 4
|
||||
check "Offline-Änderung nachgeliefert" "$(api "$URL/api/todos" | python3 -c 'import json,sys; print(sorted(t["title"] for t in json.load(sys.stdin)))')" "['Neuer', 'Offline']"
|
||||
check "Keine JS-Fehler" "$(grep -c 'JS ERROR' /tmp/desktop-app-nested.log)" "0"
|
||||
echo
|
||||
if [ $FAILED -eq 0 ]; then echo "alle ok"; else echo "$FAILED FEHLER"; grep -n "JS ERROR" -A5 /tmp/desktop-app-nested.log | head -30; grep "desktop-app sync" /tmp/desktop-app-nested.log | tail -5; fi
|
||||
Reference in New Issue
Block a user