Avoid visual Phone harness teardown

This commit is contained in:
Gabriel Brown
2026-08-17 19:42:15 -04:00
parent 7ac5355a77
commit 7f540a58e5
6 changed files with 59 additions and 24 deletions
@@ -0,0 +1,24 @@
import QtQuick
import qs.services
// Nonvisual owner of Phone Controls' stable action model and enablement rules.
// Keeping this logic free of delegates and icons makes it safe to exercise in
// disposable diagnostic engines.
QtObject {
id: root
readonly property var actionModels: [
{ id: "share", glyph: "\u{F0142}", label: "Send file" },
{ id: "clipboard", glyph: "\u{F014C}", label: "Clipboard" },
{ id: "ring", glyph: "\u{F009A}", label: "Ring" },
{ id: "messages", glyph: "\u{F0365}", label: "Messages" }
]
function actionEnabled(action: string): bool {
if (action === "messages")
return SystemSettings.bluebubblesAvailable;
return KdeConnect.phoneReachable
&& !KdeConnect.transferActive
&& KdeConnect.supports(action);
}
}
@@ -10,14 +10,11 @@ Item {
signal toggleExpanded signal toggleExpanded
readonly property var phone: KdeConnect.preferredPhone readonly property var phone: KdeConnect.preferredPhone
// Keep the four slots stable. The KDE actions can become unavailable when readonly property var actionModels: phoneActions.actionModels
// the phone disconnects, but the layout should not shift beneath Messages.
readonly property var actionModels: [ PhoneActions {
{ id: "share", glyph: "\u{F0142}", label: "Send file" }, id: phoneActions
{ id: "clipboard", glyph: "\u{F014C}", label: "Clipboard" }, }
{ id: "ring", glyph: "\u{F009A}", label: "Ring" },
{ id: "messages", glyph: "\u{F0365}", label: "Messages" }
]
implicitHeight: content.implicitHeight implicitHeight: content.implicitHeight
@@ -288,11 +285,7 @@ Item {
} }
function actionEnabled(action: string): bool { function actionEnabled(action: string): bool {
if (action === "messages") return phoneActions.actionEnabled(action);
return SystemSettings.bluebubblesAvailable;
return KdeConnect.phoneReachable
&& !KdeConnect.transferActive
&& KdeConnect.supports(action);
} }
function actionAccessibleDescription(action: string): string { function actionAccessibleDescription(action: string): string {
@@ -8,6 +8,7 @@ HomeBrightnessSlider 1.0 HomeBrightnessSlider.qml
HomeControls 1.0 HomeControls.qml HomeControls 1.0 HomeControls.qml
HomeTile 1.0 HomeTile.qml HomeTile 1.0 HomeTile.qml
IconButton 1.0 IconButton.qml IconButton 1.0 IconButton.qml
PhoneActions 1.0 PhoneActions.qml
PhoneControls 1.0 PhoneControls.qml PhoneControls 1.0 PhoneControls.qml
QuickSettings 1.0 QuickSettings.qml QuickSettings 1.0 QuickSettings.qml
QuickSettingsPanel 1.0 QuickSettingsPanel.qml QuickSettingsPanel 1.0 QuickSettingsPanel.qml
@@ -8,9 +8,8 @@ import qs.services
// Component-only diagnostic surface. The contract replaces the two services in // Component-only diagnostic surface. The contract replaces the two services in
// its copied configuration with fixture singletons before this file is run. // its copied configuration with fixture singletons before this file is run.
ShellRoot { ShellRoot {
PhoneControls { PhoneActions {
id: phoneControls id: phoneActions
width: 408
} }
IpcHandler { IpcHandler {
@@ -46,9 +45,9 @@ ShellRoot {
function status(): string { function status(): string {
return JSON.stringify({ return JSON.stringify({
actions: phoneControls.actionModels.map(action => ({ actions: phoneActions.actionModels.map(action => ({
id: action.id, id: action.id,
enabled: phoneControls.actionEnabled(action.id) enabled: phoneActions.actionEnabled(action.id)
})), })),
bluebubblesAvailable: SystemSettings.bluebubblesAvailable, bluebubblesAvailable: SystemSettings.bluebubblesAvailable,
appLaunches: SystemSettings.launchCount, appLaunches: SystemSettings.launchCount,
+6 -3
View File
@@ -85,16 +85,16 @@ rg -Fq 'visible: KdeConnect.phoneReachable' \
"$source_config_path/modules/bar/StatusCluster.qml" \ "$source_config_path/modules/bar/StatusCluster.qml" \
|| fail 'reachable phone state is absent from the bar' || fail 'reachable phone state is absent from the bar'
for component in ControlSectionHeader HomeBrightnessSlider HomeControls HomeTile PhoneControls RecentExchange; do for component in ControlSectionHeader HomeBrightnessSlider HomeControls HomeTile PhoneActions PhoneControls RecentExchange; do
[[ -f "$quicksettings_path/$component.qml" ]] \ [[ -f "$quicksettings_path/$component.qml" ]] \
|| fail "$component is missing" || fail "$component is missing"
done done
[[ "$(rg -c 'id: "(share|clipboard|ring|messages)"' "$quicksettings_path/PhoneControls.qml")" -eq 4 ]] \ [[ "$(rg -c 'id: "(share|clipboard|ring|messages)"' "$quicksettings_path/PhoneActions.qml")" -eq 4 ]] \
|| fail 'Phone controls do not expose four stable action models' || fail 'Phone controls do not expose four stable action models'
rg -Fq 'columns: 4' "$quicksettings_path/PhoneControls.qml" \ rg -Fq 'columns: 4' "$quicksettings_path/PhoneControls.qml" \
|| fail 'Phone actions are not arranged in four equal columns' || fail 'Phone actions are not arranged in four equal columns'
if rg -Fq '.filter(' "$quicksettings_path/PhoneControls.qml"; then if rg -Fq '.filter(' "$quicksettings_path/PhoneActions.qml"; then
fail 'Phone action columns change when a KDE capability is unavailable' fail 'Phone action columns change when a KDE capability is unavailable'
fi fi
rg -Fq 'SystemSettings.bluebubblesAvailable' "$quicksettings_path/PhoneControls.qml" \ rg -Fq 'SystemSettings.bluebubblesAvailable' "$quicksettings_path/PhoneControls.qml" \
@@ -107,6 +107,9 @@ rg -Fq 'KdeConnect.phoneReachable' "$quicksettings_path/PhoneControls.qml" \
rg -Fq 'HomeBrightnessSlider 1.0 HomeBrightnessSlider.qml' \ rg -Fq 'HomeBrightnessSlider 1.0 HomeBrightnessSlider.qml' \
"$quicksettings_path/qmldir" \ "$quicksettings_path/qmldir" \
|| fail 'brightness slider is not registered in the quick-settings module' || fail 'brightness slider is not registered in the quick-settings module'
rg -Fq 'PhoneActions 1.0 PhoneActions.qml' \
"$quicksettings_path/qmldir" \
|| fail 'Phone actions model is not registered in the quick-settings module'
[[ "$(rg -c '^[[:space:]]*columns: 2$' "$quicksettings_path/HomeControls.qml")" -ge 2 ]] \ [[ "$(rg -c '^[[:space:]]*columns: 2$' "$quicksettings_path/HomeControls.qml")" -ge 2 ]] \
|| fail 'resting and expanded Home shelves are not both two-column grids' || fail 'resting and expanded Home shelves are not both two-column grids'
+18 -3
View File
@@ -4,6 +4,8 @@ set -euo pipefail
project_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" project_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
phone_controls="$project_root/config/dot/quickshell/modules/quicksettings/PhoneControls.qml" phone_controls="$project_root/config/dot/quickshell/modules/quicksettings/PhoneControls.qml"
phone_actions="$project_root/config/dot/quickshell/modules/quicksettings/PhoneActions.qml"
quicksettings_qmldir="$project_root/config/dot/quickshell/modules/quicksettings/qmldir"
system_settings="$project_root/config/dot/quickshell/services/SystemSettings.qml" system_settings="$project_root/config/dot/quickshell/services/SystemSettings.qml"
harness="$project_root/config/dot/quickshell/phone-controls-harness.qml" harness="$project_root/config/dot/quickshell/phone-controls-harness.qml"
kde_fixture="$project_root/tests/quickshell/fixtures/PhoneControlsKdeConnect.qml" kde_fixture="$project_root/tests/quickshell/fixtures/PhoneControlsKdeConnect.qml"
@@ -17,11 +19,24 @@ fail() {
} }
[[ -f "$phone_controls" ]] || fail 'Phone controls component is missing' [[ -f "$phone_controls" ]] || fail 'Phone controls component is missing'
[[ -f "$phone_actions" ]] || fail 'Nonvisual Phone actions component is missing'
[[ -f "$system_settings" ]] || fail 'System settings service is missing' [[ -f "$system_settings" ]] || fail 'System settings service is missing'
[[ -f "$harness" ]] || fail 'Phone controls runtime harness is missing' [[ -f "$harness" ]] || fail 'Phone controls runtime harness is missing'
[[ -f "$kde_fixture" ]] || fail 'Phone controls KDE fixture is missing' [[ -f "$kde_fixture" ]] || fail 'Phone controls KDE fixture is missing'
[[ -f "$settings_fixture" ]] || fail 'Phone controls System Settings fixture is missing' [[ -f "$settings_fixture" ]] || fail 'Phone controls System Settings fixture is missing'
rg -Fq 'PhoneActions 1.0 PhoneActions.qml' "$quicksettings_qmldir" \
|| fail 'Phone actions component is not registered'
rg -Fq 'PhoneActions {' "$phone_controls" \
|| fail 'Phone controls do not consume the shared action model'
rg -Fq 'readonly property var actionModels: phoneActions.actionModels' "$phone_controls" \
|| fail 'Phone controls do not render the shared action models'
rg -Fq 'PhoneActions {' "$harness" \
|| fail 'Runtime harness does not use the nonvisual action model'
if rg -Fq 'PhoneControls {' "$harness"; then
fail 'Runtime harness still instantiates the visual Phone controls tree'
fi
cleanup() { cleanup() {
XDG_STATE_HOME="$state_home" QS_DISABLE_CRASH_HANDLER=1 \ XDG_STATE_HOME="$state_home" QS_DISABLE_CRASH_HANDLER=1 \
qs -p "$config_path/phone-controls-harness.qml" kill >/dev/null 2>&1 || true qs -p "$config_path/phone-controls-harness.qml" kill >/dev/null 2>&1 || true
@@ -76,11 +91,11 @@ rg -Fq 'command: ["flatpak", "info", "app.bluebubbles.BlueBubbles"]' "$system_se
|| fail 'BlueBubbles installed-state probe is missing' || fail 'BlueBubbles installed-state probe is missing'
for action in share clipboard ring messages; do for action in share clipboard ring messages; do
rg -Fq "id: \"$action\"" "$phone_controls" \ rg -Fq "id: \"$action\"" "$phone_actions" \
|| fail "Phone actions do not include $action" || fail "Phone actions do not include $action"
done done
if rg -Fq '.filter(' "$phone_controls"; then if rg -Fq '.filter(' "$phone_actions"; then
fail 'Phone actions are filtered instead of keeping four stable columns' fail 'Phone actions are filtered instead of keeping four stable columns'
fi fi
rg -Fq 'columns: 4' "$phone_controls" \ rg -Fq 'columns: 4' "$phone_controls" \
@@ -93,7 +108,7 @@ rg -Fq 'KdeConnect.phoneReachable' "$phone_controls" \
|| fail 'KDE action reachability semantics are missing' || fail 'KDE action reachability semantics are missing'
rg -Fq 'function actionEnabled(action: string): bool' "$phone_controls" \ rg -Fq 'function actionEnabled(action: string): bool' "$phone_controls" \
|| fail 'Phone controls do not expose action enablement' || fail 'Phone controls do not expose action enablement'
rg -Fq 'KdeConnect.supports(action)' "$phone_controls" \ rg -Fq 'KdeConnect.supports(action)' "$phone_actions" \
|| fail 'KDE action capability semantics are missing' || fail 'KDE action capability semantics are missing'
rg -Fq 'else if (action === "messages")' "$phone_controls" \ rg -Fq 'else if (action === "messages")' "$phone_controls" \
|| fail 'Messages has no independent invocation branch' || fail 'Messages has no independent invocation branch'