#!/usr/bin/env bash # Every role the settings page offers must actually be settable. # # The service kept its own list of which roles it would accept. When the helper # and the page grew documents, text and archives, that list stayed at seven -- # so choosing a PDF viewer set an error string and changed nothing. There is no # crash and no log line; the row simply does not update, which reads as "the # settings app does not work". # # This drives the REAL service through every role the helper reports, setting # each to the handler it already has. Nothing on the machine changes, and the # whole path is still exercised: the QML guard, the desktop-id lookup against # DesktopEntries, the helper, and the refresh afterwards. set -uo pipefail repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" harness="$repo_dir/tests/quickshell/DefaultAppsHarness.qml" helper="$repo_dir/config/dot/quickshell/scripts/panama-default-apps" service="$repo_dir/config/dot/quickshell/services/DefaultApps.qml" page="$repo_dir/config/dot/quickshell/modules/settings/ApplicationsPage.qml" fail() { printf 'default apps roles contract: %s\n' "$1" >&2 exit 1 } for path in "$harness" "$helper" "$service" "$page"; do [[ -r "$path" ]] || fail "missing $path" done # ── Static: the service must not keep its own copy of the role list ───────── grep -qE 'supportedRoles:\s*\[' "$service" \ && fail 'the service hardcodes its own role list again; derive it from the snapshot instead' # ── Category matching must survive a QML list ─────────────────────────────── # DesktopEntries returns a QML list for `categories`, and Array.isArray is false # for it. Treating that as a string yields "Network,WebBrowser", which split on # ";" alone becomes one token matching nothing -- so roles that rely on # categories silently offered no applications to choose from, and the row could # only ever show what it already had. grep -q 'String(entry.categories ?? "")' "$page" \ && fail 'category matching stringifies a QML list again; join the list instead' grep -qE 'split\(/\[;,\]/\)' "$page" \ || fail 'categories are not split on both ";" and "," so a stringified list still matches nothing' # ── The page and the helper must agree ────────────────────────────────────── helper_roles="$(python3 - "$helper" <<'PYTHON' import ast, re, sys source = open(sys.argv[1]).read() table = ast.literal_eval(re.search(r"ROLE_TARGETS = (\{.*?\n\})", source, re.S).group(1)) print("\n".join(sorted(table))) PYTHON )" || fail 'could not read the helper roles' page_roles="$(grep -oE 'key: "[a-z]+"' "$page" | sed 's/key: "//; s/"//' | sort -u)" missing="$(comm -23 <(printf '%s\n' "$helper_roles") <(printf '%s\n' "$page_roles") | tr '\n' ' ')" [[ -z "${missing// }" ]] || fail "the helper supports roles the page never offers: $missing" # ── Live: a change made through the service must stick and be seen ────────── command -v qs >/dev/null 2>&1 || { printf 'default apps roles contract: SKIP (no quickshell)\n'; exit 0; } [[ -n "${WAYLAND_DISPLAY:-}" ]] || { printf 'default apps roles contract: SKIP (no Wayland session)\n'; exit 0; } command -v jq >/dev/null 2>&1 || { printf 'default apps roles contract: SKIP (no jq)\n'; exit 0; } # The value this test moves, captured before anything runs and restored on the # way out no matter how the harness ends. A test that changes someone's browser # and leaves it changed is worse than no test. original_browser="$("$helper" snapshot | jq -r '.handlers.browser // ""')" work="$(mktemp -d /tmp/panama-default-roles.XXXXXX)" staged="$repo_dir/config/dot/quickshell/default-apps-roles-harness.qml" cleanup() { if [[ -n "$original_browser" ]]; then current="$("$helper" snapshot 2>/dev/null | jq -r '.handlers.browser // ""')" [[ "$current" == "$original_browser" ]] \ || "$helper" set-default browser "$original_browser" >/dev/null 2>&1 fi rm -rf "$work" rm -f "$staged" } trap cleanup EXIT # The harness has to run from inside the shell tree, or `import qs.services` # does not resolve. cp "$harness" "$staged" out="$work/result.json" PANAMA_HARNESS_OUT="$out" timeout 75 qs -p "$staged" >/dev/null 2>&1 [[ -s "$out" ]] || fail 'the harness produced no result; the service never reported a handler list' outcome="$(jq -r '.outcome' "$out")" case "$outcome" in ok) ;; skipped-no-second-browser) printf 'default apps roles contract: PASS (static only; this machine has one browser)\n' exit 0 ;; change-not-observed) jq -r '.log[] | " \(.)"' "$out" >&2 fail 'a change made through the service never reached the settings page: the write lands and the refresh afterwards does nothing, so the row keeps showing the old application' ;; restore-not-observed) jq -r '.log[] | " \(.)"' "$out" >&2 fail 'the service did not observe the value being changed back' ;; *) jq -r '.log[] | " \(.)"' "$out" >&2 fail "the harness ended as \"$outcome\"" ;; esac # And it really is back where it started. final="$("$helper" snapshot | jq -r '.handlers.browser // ""')" [[ "$final" == "$original_browser" ]] \ || fail "the default browser was left as $final instead of $original_browser" printf 'default apps roles contract: PASS (%d roles offered; a change through the service sticks and is observed)\n' \ "$(grep -c . <<<"$helper_roles")"