diff --git a/config/dot/quickshell/services/Notifs.qml b/config/dot/quickshell/services/Notifs.qml index a4cf51f..368ef77 100644 --- a/config/dot/quickshell/services/Notifs.qml +++ b/config/dot/quickshell/services/Notifs.qml @@ -88,7 +88,11 @@ Singleton { } function applicationLabel(appId: string, entries: var, remembered: var): string { - const entry = DesktopEntries.byId(appId) || DesktopEntries.heuristicLookup(appId); + const desktopId = appId.endsWith(".desktop") ? appId.slice(0, -8) : appId; + const entry = DesktopEntries.byId(appId) + || DesktopEntries.byId(desktopId) + || DesktopEntries.heuristicLookup(appId) + || DesktopEntries.heuristicLookup(desktopId); return entry?.name || remembered[appId]?.name || appId; } diff --git a/tests/quickshell/NotificationAppRulesHarness.qml b/tests/quickshell/NotificationAppRulesHarness.qml index f4bdf8f..35c87f2 100644 --- a/tests/quickshell/NotificationAppRulesHarness.qml +++ b/tests/quickshell/NotificationAppRulesHarness.qml @@ -55,6 +55,11 @@ ShellRoot { const appId = Notifs.notificationAppId(signal); const initialRules = DesktopPreferences.get("notificationAppRules"); + const fallback = root.notification(6, "", "Fallback Terminal"); + Notifs.handleNotification(fallback); + const fallbackId = Notifs.notificationAppId(fallback); + const fallbackApplication = Notifs.applications.find(app => app.id === fallbackId); + root.resetNotifications(); Notifs.setAppRule(appId, { enabled: false }); const muted = root.notification(2, "org.signal.Signal.desktop", "Signal"); @@ -80,6 +85,10 @@ ShellRoot { return JSON.stringify({ appId: appId, initialRules: initialRules, + fallback: { + id: fallbackId, + application: fallbackApplication + }, muted: mutedResult, dnd: { tracked: dnd.tracked, @@ -109,5 +118,9 @@ ShellRoot { function restored(): string { return JSON.stringify(DesktopPreferences.get("notificationAppRules")); } + + function applications(): string { + return JSON.stringify(Notifs.applications); + } } } diff --git a/tests/quickshell/fixtures/org.persist.App.desktop b/tests/quickshell/fixtures/org.persist.App.desktop new file mode 100644 index 0000000..4f986f2 --- /dev/null +++ b/tests/quickshell/fixtures/org.persist.App.desktop @@ -0,0 +1,5 @@ +[Desktop Entry] +Type=Application +Name=Persisted Fixture App +Exec=/usr/bin/true +Icon=applications-system diff --git a/tests/quickshell/notification-app-rules-contract.sh b/tests/quickshell/notification-app-rules-contract.sh index 440aced..5568454 100755 --- a/tests/quickshell/notification-app-rules-contract.sh +++ b/tests/quickshell/notification-app-rules-contract.sh @@ -6,6 +6,7 @@ repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" service="$repo_dir/config/dot/quickshell/services/Notifs.qml" page="$repo_dir/config/dot/quickshell/modules/settings/NotificationsPage.qml" harness_fixture="$repo_dir/tests/quickshell/NotificationAppRulesHarness.qml" +desktop_entry_fixture="$repo_dir/tests/quickshell/fixtures/org.persist.App.desktop" fail() { printf 'notification application rules contract: %s\n' "$1" >&2 @@ -15,6 +16,7 @@ fail() { [[ -f "$service" ]] || fail 'notification service is missing' [[ -f "$page" ]] || fail 'notification settings page is missing' [[ -f "$harness_fixture" ]] || fail 'runtime harness fixture is missing' +[[ -f "$desktop_entry_fixture" ]] || fail 'runtime desktop entry fixture is missing' SERVICE_PATH="$service" PAGE_PATH="$page" bun -e ' const source = await Bun.file(process.env.SERVICE_PATH).text(); @@ -109,6 +111,7 @@ console.log("notification application rules contract: PASS"); state_home="$(mktemp -d /tmp/panama-notification-rules-state.XXXXXX)" config_home="$(mktemp -d /tmp/panama-notification-rules-config.XXXXXX)" +data_home="$(mktemp -d /tmp/panama-notification-rules-data.XXXXXX)" config_path="$state_home/quickshell" harness="$config_path/notification-app-rules-harness.qml" shell_log="$state_home/notification-app-rules.log" @@ -117,12 +120,14 @@ cleanup() { if [[ -n "${bus_pid:-}" ]]; then kill "$bus_pid" >/dev/null 2>&1 || true fi - rm -rf "$state_home" "$config_home" + rm -rf "$state_home" "$config_home" "$data_home" } trap cleanup EXIT cp -a "$repo_dir/config/dot/quickshell" "$config_path" cp "$harness_fixture" "$harness" +mkdir -p "$data_home/applications" +cp "$desktop_entry_fixture" "$data_home/applications/org.persist.App.desktop" # This is intentionally a copy-local integration dependency. The production # schema is Claude's change; the runtime contract proves persistence only once @@ -140,6 +145,7 @@ bus_pid="${dbus_info[1]:-}" qs_for_test() { DBUS_SESSION_BUS_ADDRESS="$bus_address" \ XDG_STATE_HOME="$state_home" XDG_CONFIG_HOME="$config_home" \ + XDG_DATA_HOME="$data_home" XDG_DATA_DIRS="$data_home" \ qs -p "$harness" "$@" } @@ -175,7 +181,11 @@ jq -e ' } and .muted == { tracked: false, history: 0, popups: 0, unread: 0 } and .dnd == { tracked: true, history: 1, popups: 0, unread: 1 } and - .privacy == { visible: false, content: false } + .privacy == { visible: false, content: false } and + .fallback == { + id: "Fallback Terminal", + application: { id: "Fallback Terminal", name: "Fallback Terminal" } + } ' <<<"$exercise" >/dev/null || fail "runtime notification policy fixture failed: $exercise" persisted="$(qs_for_test ipc call notification-app-rules-test persist)" @@ -194,10 +204,31 @@ for _ in $(seq 1 40); do done [[ -f "$settings_file" ]] || fail 'runtime persistence fixture did not write settings.json' +wait_for_persisted_application() { + local expected="$1" + local applications="" + for _ in $(seq 1 80); do + applications="$(qs_for_test ipc call notification-app-rules-test applications)" + if jq -e '.[] | select(.id == "org.persist.App.desktop" and .name == "Persisted Fixture App")' \ + <<<"$applications" >/dev/null; then + [[ "$applications" == *"$expected"* ]] && printf '%s' "$applications" && return + fi + sleep 0.1 + done + fail "persisted desktop entry did not resolve to a friendly name: $applications" +} + +before_restart_applications="$(wait_for_persisted_application 'Persisted Fixture App')" + stop_harness start_harness restored="$(qs_for_test ipc call notification-app-rules-test restored)" [[ "$restored" == "$persisted" ]] || fail "notification rules did not survive isolated restart: $restored" +after_restart_applications="$(wait_for_persisted_application 'Persisted Fixture App')" +[[ "$before_restart_applications" == *'"id":"org.persist.App.desktop","name":"Persisted Fixture App"'* ]] \ + || fail "persisted application name was wrong before restart: $before_restart_applications" +[[ "$after_restart_applications" == *'"id":"org.persist.App.desktop","name":"Persisted Fixture App"'* ]] \ + || fail "persisted application name was wrong after restart: $after_restart_applications" stop_harness printf 'notification application rules runtime contract: PASS\n'