Files
omarchy/test/shell.d/elsewhen-default-migration-test.sh
T
Spencer BullandCodex XHigh e8a8236a22 Preserve local Elsewhen work and fallback bar layouts
Retire only checkouts whose refs and reflogs are reachable from recorded origin history, and preserve ignored files. Leave configs without an explicit supported bar layout untouched so migration does not replace the shell fallback with an almost empty bar.

Co-Authored-By: Codex XHigh <noreply@openai.com>
2026-09-17 22:11:46 -05:00

364 lines
14 KiB
Bash

#!/bin/bash
set -euo pipefail
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
require_command jq
require_command git
migration="$ROOT/migrations/1789581661.sh"
test_dir=$(mktemp -d)
trap 'rm -rf "$test_dir"' EXIT
mkdir -p "$test_dir/bin"
export INSTALLED_PACKAGES="$test_dir/installed" CALL_LOG="$test_dir/calls" SHELL_CALLS="$test_dir/shell-calls"
# The checkout cases use the real git; keep the developer's config (signing,
# hooks, safe.directory) out of what it sees.
export GIT_CONFIG_GLOBAL=/dev/null GIT_CONFIG_NOSYSTEM=1
export GIT_AUTHOR_NAME=test GIT_AUTHOR_EMAIL=test@example.com
export GIT_COMMITTER_NAME=test GIT_COMMITTER_EMAIL=test@example.com
# Keep the real package helpers, but contain every pacman transaction here.
cat >"$test_dir/bin/pacman" <<'SH'
#!/bin/bash
case "$1" in
-Q) grep -Fxq -- "$2" "$INSTALLED_PACKAGES" ;;
-S)
[[ ${FAIL_INSTALL:-0} == 0 ]] || exit 1
shift 3 # -S --noconfirm --needed
printf '%s\n' "$@" >>"$INSTALLED_PACKAGES"
printf '%s\n' "$@" >>"$CALL_LOG"
;;
*) exit 1 ;;
esac
SH
cat >"$test_dir/bin/sudo" <<'SH'
#!/bin/bash
[[ $1 == "pacman" ]] || exit 1
"$@"
SH
# The migration runs under a shell that predates the packaged root, or under
# none at all, so it must only ever ask the shell for best-effort refreshes.
cat >"$test_dir/bin/omarchy-shell" <<'SH'
#!/bin/bash
printf '%s\n' "$*" >>"$SHELL_CALLS"
[[ $1 != "-q" ]] || exit 0
exit "${SHELL_STATUS:-0}"
SH
chmod +x "$test_dir/bin/"*
home="$test_dir/home"
config="$home/.config/omarchy/shell.json"
checkout="$home/.config/omarchy/plugins/omacom.elsewhen"
output="$test_dir/output"
mkdir -p "$home/.config/omarchy"
run_migration() {
: >"$CALL_LOG"
: >"$SHELL_CALLS"
HOME="$home" OMARCHY_PATH="$ROOT" PATH="$test_dir/bin:$ROOT/bin:$PATH" \
bash -euo pipefail "$migration" >"$output"
}
ids() {
jq -c ".bar.layout.$1 | map(if type == \"object\" then .id else . end)" "$config"
}
# ------------------------------------------------------------ package install
: >"$INSTALLED_PACKAGES"
cat >"$config" <<'JSON'
{
"version": 1,
"bar": {
"layout": {
"left": [{ "id": "omarchy.menu" }],
"center": [{ "id": "omarchy.clock", "format": "HH:mm" }],
"right": [
{ "id": "omarchy.tray" },
{ "id": "omarchy.agents", "syncMode": "On" },
{ "id": "omarchy.power" }
]
}
},
"plugins": []
}
JSON
run_migration
grep -Fxq elsewhen "$CALL_LOG" || fail "migration installs the elsewhen package" "$(cat "$CALL_LOG")"
pass "migration installs the elsewhen package"
[[ $(ids center) == '["omacom.elsewhen","omarchy.clock"]' && $(ids right) == '["omarchy.tray","omarchy.agents","omarchy.power"]' ]] ||
fail "migration puts the widget just before the center clock" "$(cat "$config")"
pass "migration puts the widget just before the center clock"
[[ $(jq -c '.bar.layout.right[1]' "$config") == '{"id":"omarchy.agents","syncMode":"On"}' ]] ||
fail "migration keeps the settings of its neighbours" "$(cat "$config")"
[[ $(jq -c '.bar.layout.center[1]' "$config") == '{"format":"HH:mm","id":"omarchy.clock"}' ]] ||
fail "migration keeps the clock settings" "$(cat "$config")"
pass "migration leaves the rest of the layout alone"
grep -q 'shell rescanPlugins' "$SHELL_CALLS" && grep -q 'shell reloadConfig' "$SHELL_CALLS" ||
fail "migration asks the running shell to rescan and reload" "$(cat "$SHELL_CALLS")"
pass "migration asks the running shell to rescan and reload"
before=$(sha256sum "$config")
run_migration
[[ $before == $(sha256sum "$config") ]] || fail "migration is idempotent" "$(cat "$config")"
[[ ! -s $CALL_LOG ]] || fail "migration does not reinstall a present package" "$(cat "$CALL_LOG")"
pass "migration is idempotent"
# ------------------------------------------------------------- placements
cat >"$config" <<'JSON'
{
"version": 1,
"bar": { "layout": { "left": [], "center": ["omarchy.clock", "omarchy.tray"], "right": ["omarchy.power"] } }
}
JSON
run_migration
[[ $(ids center) == '["omacom.elsewhen","omarchy.clock","omarchy.tray"]' && $(ids right) == '["omarchy.power"]' ]] ||
fail "migration reads string clock entries" "$(cat "$config")"
pass "migration reads string clock entries"
cat >"$config" <<'JSON'
{ "version": 1, "bar": { "layout": { "right": [{ "id": "omarchy.agents" }, { "id": "omarchy.power" }] } } }
JSON
run_migration
[[ $(ids center) == '["omacom.elsewhen"]' && $(ids right) == '["omarchy.agents","omarchy.power"]' ]] ||
fail "migration prepends to the center section when the clock is off the bar" "$(cat "$config")"
pass "migration prepends to the center section when the clock is off the bar"
cat >"$config" <<'JSON'
{
"version": 1,
"bar": { "layout": { "left": [{ "id": "omacom.elsewhen" }], "center": [], "right": [{ "id": "omarchy.tray" }, { "id": "omarchy.agents" }] } }
}
JSON
before=$(sha256sum "$config")
run_migration
[[ $before == $(sha256sum "$config") ]] ||
fail "migration leaves a widget the user already placed where it is" "$(cat "$config")"
pass "migration leaves a widget the user already placed where it is"
# A customized clock keeps its section and settings; insert only its neighbour.
for section in left right; do
jq -n --arg section "$section" '{version: 1, bar: {layout: {center: ["omarchy.weather"], ($section): ["omarchy.menu", {id: "omarchy.clock", format: "HH:mm"}]}}}' >"$config"
run_migration
[[ $(ids "$section") == '["omarchy.menu","omacom.elsewhen","omarchy.clock"]' && $(ids center) == '["omarchy.weather"]' ]] ||
fail "migration follows a clock customized into $section" "$(cat "$config")"
pass "migration follows a clock customized into $section"
done
# ------------------------------------------------------------- edge cases
rm -f "$config"
run_migration
[[ ! -e $config ]] || fail "migration writes no shell.json where the defaults apply" "$(cat "$config")"
pass "migration writes no shell.json where the defaults apply"
for partial in \
'{"version":1,"idle":{"lock":600}}' \
'{"version":1,"bar":null,"idle":{"lock":600}}' \
'{"version":1,"bar":{"position":"bottom"}}' \
'{"bar":{"layout":{"center":["omarchy.clock"]}}}'; do
printf '%s\n' "$partial" >"$config"
before=$(sha256sum "$config")
run_migration
[[ $before == $(sha256sum "$config") ]] || fail "migration preserves fallback configuration" "$(cat "$config")"
pass "migration preserves fallback configuration: $partial"
done
printf '{ not json' >"$config"
run_migration
[[ $(cat "$config") == '{ not json' ]] || fail "migration leaves an unparsable config untouched" "$(cat "$config")"
pass "migration leaves an unparsable config untouched"
cat >"$config" <<'JSON'
{ "version": 1, "bar": { "layout": { "right": [{ "id": "omarchy.tray" }, { "id": "omarchy.agents" }] } } }
JSON
SHELL_STATUS=1 run_migration
[[ $(ids center) == '["omacom.elsewhen"]' && $(ids right) == '["omarchy.tray","omarchy.agents"]' ]] ||
fail "migration places the widget with no shell running" "$(cat "$config")"
pass "migration places the widget with no shell running"
# A package the mirror does not carry yet leaves the migration pending rather
# than half-done: the layout must not name a widget nothing can install.
: >"$INSTALLED_PACKAGES"
cat >"$config" <<'JSON'
{ "version": 1, "bar": { "layout": { "right": [{ "id": "omarchy.tray" }, { "id": "omarchy.agents" }] } } }
JSON
before=$(sha256sum "$config")
if FAIL_INSTALL=1 run_migration 2>/dev/null; then
fail "a failed package installation must leave the migration pending"
fi
[[ $before == $(sha256sum "$config") ]] ||
fail "a failed package installation leaves the layout untouched" "$(cat "$config")"
pass "a failed package installation is propagated and changes nothing"
# ------------------------------------------------- pre-package checkouts
# Users who followed the README before the package existed hold a git clone
# under ~/.config/omarchy/plugins, which the packaged copy now shadows. Only a
# clone of the upstream repo with nothing local in it is retired; everything
# else is the user's and stays, and shell.json gets the widget either way.
# A clone made by `omarchy plugin add`: one commit, the given origin, nothing
# local. Built with init rather than clone so the origin can be any URL form.
make_checkout() {
local origin="$1"
rm -rf "$checkout"
mkdir -p "$checkout"
git -C "$checkout" init -q
git -C "$checkout" remote add origin "$origin"
printf '{ "id": "omacom.elsewhen" }\n' >"$checkout/manifest.json"
printf 'import QtQuick\nItem {}\n' >"$checkout/Panel.qml"
git -C "$checkout" add -A
git -C "$checkout" commit -q -m "Elsewhen"
git -C "$checkout" update-ref refs/remotes/origin/main HEAD
}
reset_layout() {
cat >"$config" <<'JSON'
{ "version": 1, "bar": { "layout": { "right": [{ "id": "omarchy.tray" }, { "id": "omarchy.power" }] } } }
JSON
}
assert_widget_placed() {
[[ $(ids center) == '["omacom.elsewhen"]' && $(ids right) == '["omarchy.tray","omarchy.power"]' ]] ||
fail "$1 still gets the widget" "$(cat "$config")"
}
assert_retired() {
local description="$1"
[[ ! -e $checkout && ! -L $checkout ]] || fail "$description is retired" "$(ls -la "$checkout")"
grep -q 'Retired the omacom.elsewhen checkout' "$output" || fail "$description is reported as retired" "$(cat "$output")"
assert_widget_placed "$description"
pass "$description is retired"
}
assert_kept() {
local description="$1"
[[ -e $checkout || -L $checkout ]] || fail "$description is kept"
grep -q 'takes precedence over' "$output" || fail "$description is reported as shadowed" "$(cat "$output")"
assert_widget_placed "$description"
pass "$description is kept"
}
for origin in \
https://github.com/omacom/elsewhen.git \
https://github.com/omacom/elsewhen \
git@github.com:omacom/elsewhen.git \
ssh://git@github.com/omacom/elsewhen.git \
https://GitHub.com/Omacom/Elsewhen/; do
make_checkout "$origin"
reset_layout
run_migration
assert_retired "a pristine clone with origin $origin"
done
make_checkout https://github.com/omacom/elsewhen.git
printf 'import QtQuick\nItem { id: mine }\n' >"$checkout/Panel.qml"
reset_layout
run_migration
assert_kept "a clone with a modified tracked file"
[[ $(cat "$checkout/Panel.qml") == $'import QtQuick\nItem { id: mine }' ]] ||
fail "a kept clone keeps its local change" "$(cat "$checkout/Panel.qml")"
pass "a kept clone keeps its local change"
make_checkout https://github.com/omacom/elsewhen.git
printf 'notes\n' >"$checkout/NOTES.md"
reset_layout
run_migration
assert_kept "a clone with an untracked file"
make_checkout https://github.com/omacom/elsewhen.git
printf 'import QtQuick\nItem { id: mine }\n' >"$checkout/Panel.qml"
git -C "$checkout" add Panel.qml
git -C "$checkout" commit -q -m "Local customization"
local_commit=$(git -C "$checkout" rev-parse HEAD)
reset_layout
run_migration
assert_kept "a clean clone with an unpublished commit"
[[ $(git -C "$checkout" rev-parse HEAD) == "$local_commit" ]] || fail "local commit survives"
make_checkout https://github.com/omacom/elsewhen.git
git -C "$checkout" checkout -qb local-work
printf 'local branch\n' >"$checkout/Panel.qml"
git -C "$checkout" commit -qam "Unpublished branch"
git -C "$checkout" checkout -q --detach refs/remotes/origin/main
reset_layout
run_migration
assert_kept "an unpublished commit on another branch"
make_checkout https://github.com/omacom/elsewhen.git
printf 'stashed work\n' >"$checkout/Panel.qml"
git -C "$checkout" stash push -q
reset_layout
run_migration
assert_kept "a clean clone with stashed work"
make_checkout https://github.com/omacom/elsewhen.git
git -C "$checkout" config status.showUntrackedFiles no
printf 'private-notes\n' >"$checkout/.git/info/exclude"
printf 'ignored work\n' >"$checkout/private-notes"
reset_layout
run_migration
assert_kept "a clone with an ignored file"
[[ $(cat "$checkout/private-notes") == "ignored work" ]] || fail "ignored file survives"
make_checkout https://github.com/omacom/elsewhen.git
printf 'recoverable work\n' >"$checkout/Panel.qml"
git -C "$checkout" commit -qam "Recoverable local commit"
git -C "$checkout" reset -q --hard refs/remotes/origin/main
reset_layout
run_migration
assert_kept "a local commit retained only by the reflog"
make_checkout https://github.com/omacom/elsewhen.git
git -C "$checkout" update-ref -d refs/remotes/origin/main
reset_layout
run_migration
assert_kept "a clone without recorded upstream history"
make_checkout https://github.com/omacom/elsewhen.git
git -C "$checkout" config status.showUntrackedFiles no
printf 'untracked work\n' >"$checkout/NOTES.md"
reset_layout
run_migration
assert_kept "untracked files hidden by the user Git configuration"
make_checkout https://github.com/someone/elsewhen-fork.git
reset_layout
run_migration
assert_kept "a clone of another repository"
make_checkout https://github.com/omacom/elsewhen.git
git -C "$checkout" remote remove origin
reset_layout
run_migration
assert_kept "a clone without an origin remote"
rm -rf "$checkout"
mkdir -p "$checkout"
printf '{ "id": "omacom.elsewhen" }\n' >"$checkout/manifest.json"
reset_layout
run_migration
assert_kept "a plain directory without .git"
rm -rf "$checkout"
make_checkout https://github.com/omacom/elsewhen.git
mv "$checkout" "$test_dir/elsewhen-src"
ln -s "$test_dir/elsewhen-src" "$checkout"
reset_layout
run_migration
assert_kept "a symlinked checkout"
[[ -L $checkout && -f $test_dir/elsewhen-src/manifest.json ]] ||
fail "a symlinked checkout stays a symlink with its target intact" "$(ls -la "$checkout" "$test_dir/elsewhen-src")"
pass "a symlinked checkout stays a symlink with its target intact"
rm -f "$checkout"
reset_layout
run_migration
grep -q 'omacom.elsewhen checkout\|takes precedence' "$output" && fail "no checkout means no checkout report" "$(cat "$output")"
assert_widget_placed "a machine without a checkout"
pass "a machine without a checkout is left alone"