Bar widgets propagate their composed press-and-hold down to the center gesture area without handing over the grab, so the gesture area started a bar move and then received neither a release nor a cancel to end it. The move ghost stayed on screen for the rest of the session. Ignore the gesture unless we hold the press. Closes #6881 Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
483 lines
23 KiB
Bash
483 lines
23 KiB
Bash
#!/bin/bash
|
|
|
|
set -euo pipefail
|
|
|
|
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
|
|
|
|
if perl -0ne 'exit(/drag\s*\.\s*target\s*:\s*[^;]*\bslot\b/s ? 0 : 1)' "$ROOT/shell/plugins/bar/Bar.qml"; then
|
|
fail "bar module dragging must not mutate ModuleSlot positions"
|
|
fi
|
|
pass "bar module dragging leaves layout-managed slots in place"
|
|
|
|
if rg -q 'barMoveSettling|barMoveSettleTimer' "$ROOT/shell/plugins/bar/Bar.qml"; then
|
|
fail "bar move outline must clear when the pointer is released"
|
|
fi
|
|
pass "bar move outline has no post-release settling state"
|
|
|
|
# A widget above the gesture area propagates its composed press-and-hold down
|
|
# without handing over the grab, so the resulting move gets neither a release
|
|
# nor a cancel and the ghost stays up for the session. Only the grabbing area
|
|
# reports pressed, which is what separates the two, so the guard has to stay
|
|
# ahead of the drag.
|
|
if ! perl -0ne 'exit(/onPressAndHold:\s*function[^{]*\{[^}]*?\bpressed\b[^}]*?\bstartDrag\b/s ? 0 : 1)' \
|
|
"$ROOT/shell/plugins/bar/Bar.qml"; then
|
|
fail "bar move ignores a press-and-hold the gesture area does not hold the press for"
|
|
fi
|
|
pass "bar move ignores a press-and-hold propagated from a widget above"
|
|
|
|
run_node_test <<'JS'
|
|
const fs = require('fs')
|
|
const bar = requireFromRoot('shell/plugins/bar/BarModel.js')
|
|
const barSource = fs.readFileSync(root + '/shell/plugins/bar/Bar.qml', 'utf8')
|
|
const shellSource = fs.readFileSync(root + '/shell/shell.qml', 'utf8')
|
|
|
|
assert(/function toggleBarTransparency\(\): string \{[\s\S]*?shell\.bar\.toggleTransparency\(\)/.test(shellSource), 'shell exposes the bar transparency toggle over IPC')
|
|
|
|
// put tolerates a placement target the bar does not carry, so the IPC call
|
|
// must reach the registry's put rather than route back through enable.
|
|
assert(
|
|
/function putBarWidget\(id: string, placementJson: string\): string \{[\s\S]*?shell\.pluginRegistry\.putBarWidget\(/.test(shellSource),
|
|
'putting a bar widget over IPC goes through the registry put'
|
|
)
|
|
|
|
// Hiding must not unmap the bar. An unmapped layer surface has to be rebuilt on
|
|
// every reveal, which measured ~150ms against ~20ms to tear it down; parking it
|
|
// past the screen edge keeps show and hide symmetric at ~12ms.
|
|
assert(
|
|
/visible: !remapGuard\.remapping/.test(barSource),
|
|
'bar stays mapped while hidden so revealing it does not rebuild the surface'
|
|
)
|
|
assert(
|
|
/exclusionMode: root\.barHidden \? ExclusionMode\.Ignore : ExclusionMode\.Auto/.test(barSource),
|
|
'a hidden bar reserves no space for itself'
|
|
)
|
|
for (const edge of ['top', 'bottom', 'left', 'right']) {
|
|
assert(
|
|
new RegExp(`${edge}: root\\.barHidden && root\\.position === "${edge}" \\? -root\\.barSize : 0`).test(barSource),
|
|
`a hidden bar parks past the ${edge} edge`
|
|
)
|
|
}
|
|
|
|
// The center section declares two arrangements and shows one; the hidden one
|
|
// must not build its modules or every center widget exists twice.
|
|
const moduleList = barSource.slice(barSource.indexOf('component ModuleList'), barSource.indexOf('component ModuleSlot'))
|
|
assert(
|
|
/active: visible && entries\.length > 0/.test(moduleList),
|
|
'bar builds only the module list it is showing'
|
|
)
|
|
|
|
// A center module is mounted twice — drawn copy plus zero-size placeholder —
|
|
// and the order they register in is not stable across a live reconfiguration,
|
|
// so panel routing has to pick the one that is actually on screen.
|
|
const drawn = { moduleName: 'omarchy.clock', visible: true, width: 28, height: 81 }
|
|
const placeholder = { moduleName: 'omarchy.clock', visible: false, width: 0, height: 0 }
|
|
assertEqual(bar.isDrawnSlot(drawn), true, 'bar recognises a drawn slot')
|
|
assertEqual(bar.isDrawnSlot(placeholder), false, 'bar recognises a layout placeholder')
|
|
assertEqual(bar.pickDrawnSlot([placeholder, drawn]), drawn, 'bar picks the drawn slot when the placeholder registers first')
|
|
assertEqual(bar.pickDrawnSlot([drawn, placeholder]), drawn, 'bar picks the drawn slot when it registers first')
|
|
assertEqual(bar.pickDrawnSlot([placeholder]), placeholder, 'bar falls back to the placeholder when nothing is drawn')
|
|
assertEqual(bar.pickDrawnSlot([]), null, 'bar reports no slot when there are none')
|
|
assertEqual(bar.pickDrawnSlot(null), null, 'bar tolerates a missing slot list')
|
|
|
|
// Revealing the indicators can slide a neighbouring widget under a stationary
|
|
// pointer; collapsing the peek on that un-hover re-opens it and stutters the
|
|
// bar, so the peek stays held while the pointer is anywhere on the bar.
|
|
const revealTimer = barSource.slice(barSource.indexOf('id: centerSectionRevealTimer'))
|
|
const revealTimerBody = revealTimer.slice(0, revealTimer.indexOf('\n }'))
|
|
assert(
|
|
/!root\.centerSectionHovered && !root\.barHovered/.test(revealTimerBody),
|
|
'the indicator peek stays held while the pointer is anywhere on the bar'
|
|
)
|
|
|
|
// The timer runs on a delay, so it can fire for a pointer that has already come
|
|
// back. Letting it assign the held state outright would then reveal indicators
|
|
// from bar hover alone; it may only close what the center section opened.
|
|
assert(
|
|
!/centerSectionRevealHeld = (?!false)/.test(revealTimerBody),
|
|
'the delayed collapse can only close the peek, never open it'
|
|
)
|
|
|
|
// The whole-bar hover has to come from an ancestor of the sections. A sibling
|
|
// loses hover to whichever section the pointer moved onto, which is the very
|
|
// signal the peek must not collapse on.
|
|
const barLoader = barSource.slice(barSource.indexOf('sourceComponent: root.vertical ? verticalBar : horizontalBar'))
|
|
const barLoaderBody = barLoader.slice(0, barLoader.indexOf('\n }'))
|
|
assert(
|
|
/setBarHovered\(hovered\)/.test(barLoaderBody),
|
|
'the whole-bar hover handler is a child of the bar loader, above both orientations'
|
|
)
|
|
|
|
// Unplugging a monitor tears its bar down mid-hover with no leave event, which
|
|
// would leave that surface counted forever and the peek stuck open.
|
|
assert(
|
|
/Component\.onDestruction: if \(hovered\) root\.setBarHovered\(false\)/.test(barLoaderBody),
|
|
'a bar torn down while hovered gives its hover back'
|
|
)
|
|
|
|
// The helper has to record the state it is handed and re-run the collapse once
|
|
// the pointer leaves. It counts rather than assigns because every monitor's bar
|
|
// reports here: a slide from one bar to the next can deliver the enter before
|
|
// the leave, and a shared bool would read as un-hovered under a live pointer.
|
|
const setBarHovered = barSource.slice(barSource.indexOf('function setBarHovered'))
|
|
const setBarHoveredBody = setBarHovered.slice(0, setBarHovered.indexOf('\n }'))
|
|
assert(
|
|
/barHoverCount = Math\.max\(0, barHoverCount \+ \(hovered \? 1 : -1\)\)/.test(setBarHoveredBody),
|
|
'each bar surface adds to a hover tally instead of overwriting a shared flag'
|
|
)
|
|
assert(
|
|
/if \(barHoverCount === 0\) centerSectionRevealTimer\.restart\(\)/.test(setBarHoveredBody),
|
|
'the peek collapse re-runs once the pointer has left the last bar'
|
|
)
|
|
|
|
// Opening the peek stays the center section's own gesture: pointing straight at
|
|
// a widget reveals nothing. Checking that only inside setBarHovered proves
|
|
// nothing, since the shared reveal timer is the path a bar hover leaks through.
|
|
const opensPeek = barSource.split('\n').filter(line => /centerSectionRevealHeld = true/.test(line))
|
|
assertEqual(opensPeek.length, 1, 'exactly one line in the bar opens the indicator peek')
|
|
const setCenterSectionHovered = barSource.slice(barSource.indexOf('function setCenterSectionHovered'))
|
|
assert(
|
|
setCenterSectionHovered.slice(0, setCenterSectionHovered.indexOf('\n }')).includes(opensPeek[0].trim()),
|
|
'hovering the bar never opens the peek on its own'
|
|
)
|
|
|
|
// A bar surface is built per monitor, so a panel hotkey has one live copy of
|
|
// the widget per screen to choose between.
|
|
const internal = { moduleName: 'omarchy.audio', visible: true, width: 28, height: 81 }
|
|
const external = { moduleName: 'omarchy.audio', visible: true, width: 28, height: 81 }
|
|
const row = (slot, screenName, opened) => ({ slot, screenName, opened: opened === true })
|
|
const copies = [row(internal, 'eDP-1'), row(external, 'DP-1')]
|
|
assertEqual(
|
|
bar.pickPanelSlot(copies, 'DP-1'),
|
|
external,
|
|
'bar summons a panel on the focused monitor'
|
|
)
|
|
assertEqual(
|
|
bar.pickPanelSlot(copies, 'eDP-1'),
|
|
internal,
|
|
'bar summons a panel on the focused monitor whichever one it is'
|
|
)
|
|
assertEqual(
|
|
bar.pickPanelSlot(copies, 'HDMI-A-1'),
|
|
internal,
|
|
'bar falls back to any live copy when the focused monitor has no bar'
|
|
)
|
|
assertEqual(
|
|
bar.pickPanelSlot(copies, ''),
|
|
internal,
|
|
'bar falls back to any live copy before Hyprland reports a focused monitor'
|
|
)
|
|
assertEqual(
|
|
bar.pickPanelSlot([row(internal, 'eDP-1', true), row(external, 'DP-1')], 'DP-1'),
|
|
internal,
|
|
'bar hides the panel that is open rather than the focused monitor copy'
|
|
)
|
|
assertEqual(
|
|
bar.pickPanelSlot(
|
|
[row(placeholder, 'DP-1'), row(drawn, 'DP-1'), row(internal, 'eDP-1')],
|
|
'DP-1'
|
|
),
|
|
drawn,
|
|
'bar still picks the drawn slot among the focused monitor copies'
|
|
)
|
|
assertEqual(bar.pickPanelSlot([], 'DP-1'), null, 'bar reports no panel slot when there are none')
|
|
assertEqual(bar.pickPanelSlot(null, 'DP-1'), null, 'bar tolerates a missing panel slot list')
|
|
assert(
|
|
/BarModel\.pickPanelSlot\(candidates, focusedScreenName\(\)\)/.test(barSource),
|
|
'bar routes panel hotkeys through the focused-monitor picker'
|
|
)
|
|
assert(
|
|
/function focusedScreenName\(\) \{[\s\S]*?Hyprland\.focusedMonitor/.test(barSource),
|
|
'bar reads the focused monitor from Hyprland'
|
|
)
|
|
assert(
|
|
/var slots = panelNavigationSlots\(currentSlot\.region, slotWindow\(currentSlot\)\)/.test(barSource),
|
|
'bar tabs between panels within one bar surface'
|
|
)
|
|
|
|
// A positional hotkey means "the third panel in this section", so it counts the
|
|
// panels the bar actually draws. Reusing the tab-order walk is what keeps the
|
|
// count honest: a widget with no panel and a hidden one are already passed over
|
|
// there, and reading the layout config a second time would count both.
|
|
assert(
|
|
/function panelWidgetIdAt\(region, index\) \{[\s\S]*?panelNavigationSlots\(String\(region \|\| ""\), null\)/.test(barSource),
|
|
'bar counts positional panels off the drawn tab order'
|
|
)
|
|
assert(
|
|
/var slot = slots\[Math\.round\(Number\(index\)\) - 1\]/.test(barSource),
|
|
'positional panels are one-based, and anything off the end lands on no slot'
|
|
)
|
|
assert(
|
|
/function togglePanelAt\(section: string, index: string\): string \{[\s\S]*?shell\.bar\.panelWidgetIdAt\(section, index\)[\s\S]*?shell\.toggle\(id, "\{\}"\)/.test(shellSource),
|
|
'shell toggles a bar panel by its position over IPC'
|
|
)
|
|
|
|
const clockSlot = { id: 'clock' }
|
|
const traySlot = { id: 'tray' }
|
|
const horizontalTargets = [
|
|
{ slot: clockSlot, x: 100, y: 0, width: 100, height: 26 },
|
|
{ slot: traySlot, x: 500, y: 0, width: 50, height: 26 }
|
|
]
|
|
assertDeepEqual(
|
|
bar.nearestDropTarget(horizontalTargets, { x: 240, y: 13 }, false),
|
|
{ slot: clockSlot, after: true },
|
|
'bar resolves free space beside a widget to its nearest insertion edge'
|
|
)
|
|
assertDeepEqual(
|
|
bar.nearestDropTarget(horizontalTargets, { x: 460, y: 13 }, false),
|
|
{ slot: traySlot, after: false },
|
|
'bar resolves free space before a widget to its nearest insertion edge'
|
|
)
|
|
assertDeepEqual(
|
|
bar.nearestDropTarget(horizontalTargets, { x: 125, y: 13 }, false),
|
|
{ slot: clockSlot, after: false },
|
|
'bar resolves the first half of a widget before it'
|
|
)
|
|
assertDeepEqual(
|
|
bar.nearestDropTarget(horizontalTargets, { x: 175, y: 13 }, false),
|
|
{ slot: clockSlot, after: true },
|
|
'bar resolves the second half of a widget after it'
|
|
)
|
|
assertDeepEqual(
|
|
bar.nearestDropTarget([
|
|
{ slot: clockSlot, x: 0, y: 100, width: 26, height: 80 }
|
|
], { x: 13, y: 220 }, true),
|
|
{ slot: clockSlot, after: true },
|
|
'vertical bars resolve free space along their vertical axis'
|
|
)
|
|
assertEqual(bar.nearestDropTarget([], { x: 10, y: 10 }, false), null, 'bar reports no insertion edge without targets')
|
|
assert(
|
|
/contentItem\.mapFromItem\(null, scenePoint\.x, scenePoint\.y\)[\s\S]*?return null/.test(barSource),
|
|
'bar rejects free-space drops after the pointer leaves the bar'
|
|
)
|
|
assert(
|
|
/BarModel\.nearestDropTarget\(candidates, scenePoint, root\.vertical\)/.test(barSource),
|
|
'bar uses nearest insertion targeting for widget and free-space drops'
|
|
)
|
|
assert(
|
|
/component DragGhostPanel:[\s\S]*?readonly property var targetRect: root\.barDragTargetGeometry[\s\S]*?color: Color\.accent/.test(barSource),
|
|
'bar draws the insertion marker above the bar in the drag overlay'
|
|
)
|
|
|
|
// The open-panel mark sits on the module's desktop-facing edge at every
|
|
// position: under a top bar, over a bottom one, inward from left and right.
|
|
const indicator = barSource.slice(barSource.indexOf('id: openPanelIndicator'), barSource.indexOf('id: openPanelIndicator') + 1600)
|
|
assert(
|
|
/x: root\.vertical\s*\n\s*\? \(root\.position === "left" \? parent\.width - width - inset : inset\)/.test(indicator),
|
|
'bar pins the open-panel mark to the desktop-facing edge on vertical bars'
|
|
)
|
|
assert(
|
|
/root\.position === "top" \? parent\.height - height - inset : inset/.test(indicator),
|
|
'bar pins the open-panel mark to the desktop-facing edge on horizontal bars'
|
|
)
|
|
assert(
|
|
/key in activeItem/.test(barSource),
|
|
'bar asks whether a widget declares an indicator hint before reading it'
|
|
)
|
|
assert(
|
|
/width: root\.vertical \? Style\.space\(2\) : slot\.panelIndicatorExtent/.test(indicator) &&
|
|
/height: root\.vertical \? slot\.panelIndicatorExtent : Style\.space\(2\)/.test(indicator),
|
|
'bar sizes the open-panel mark from the same content hint on both axes'
|
|
)
|
|
|
|
assertEqual(bar.normalizePosition('left'), 'left', 'bar accepts valid positions')
|
|
assertEqual(bar.normalizePosition('sideways'), 'top', 'bar defaults invalid positions')
|
|
assertDeepEqual(bar.entrySettings({ id: 'omarchy.clock', format: 'HH:mm' }), { format: 'HH:mm' }, 'bar extracts entry settings')
|
|
assertEqual(bar.entryId({ id: 'omarchy.clock' }), 'omarchy.clock', 'bar extracts object entry ids')
|
|
assertEqual(bar.entryId('omarchy.clock'), 'omarchy.clock', 'bar extracts string entry ids')
|
|
|
|
const entries = [{ id: 'a' }, { id: 'omarchy.tray' }, { id: 'b' }]
|
|
assertDeepEqual(bar.pinTrayToInner(entries, 'left').map(bar.entryId), ['a', 'b', 'omarchy.tray'], 'bar pins tray to left inner edge')
|
|
assertDeepEqual(bar.pinTrayToInner(entries, 'right').map(bar.entryId), ['omarchy.tray', 'a', 'b'], 'bar pins tray to right inner edge')
|
|
|
|
// A settings-only shell.json write must patch the live bar, not rebuild it:
|
|
// the module Repeaters recreate every widget when their array model changes.
|
|
const settingsLayout = { left: [{ id: 'omarchy.power' }], center: [{ id: 'omarchy.clock', format: 'HH:mm' }], right: [] }
|
|
assertDeepEqual(
|
|
bar.inlineSettingsDelta(settingsLayout, { left: [{ id: 'omarchy.power', showPercentage: true }], center: [{ id: 'omarchy.clock', format: 'HH:mm' }], right: [] }),
|
|
[{ region: 'left', index: 0, entry: { id: 'omarchy.power', showPercentage: true } }],
|
|
'bar reports a settings-only change as an inline delta'
|
|
)
|
|
assertDeepEqual(
|
|
bar.inlineSettingsDelta(settingsLayout, JSON.parse(JSON.stringify(settingsLayout))),
|
|
[],
|
|
'bar reports an unchanged layout as an empty delta'
|
|
)
|
|
assertEqual(
|
|
bar.inlineSettingsDelta(settingsLayout, { left: [{ id: 'omarchy.clock', format: 'HH:mm' }], center: [{ id: 'omarchy.power' }], right: [] }),
|
|
null,
|
|
'bar treats reordered entries as structural'
|
|
)
|
|
assertEqual(
|
|
bar.inlineSettingsDelta(settingsLayout, { left: [{ id: 'omarchy.power' }, { id: 'omarchy.battery' }], center: settingsLayout.center, right: [] }),
|
|
null,
|
|
'bar treats added entries as structural'
|
|
)
|
|
assertEqual(
|
|
bar.inlineSettingsDelta(
|
|
{ left: [{ id: 'local.status', exec: 'date' }], center: [], right: [] },
|
|
{ left: [{ id: 'local.status', exec: 'uptime' }], center: [], right: [] }
|
|
),
|
|
null,
|
|
'bar rebuilds for custom modules, which read their entry directly'
|
|
)
|
|
assertEqual(
|
|
bar.inlineSettingsDelta(
|
|
{ left: [{ id: 'x' }], center: [], right: [{ id: 'x' }] },
|
|
{ left: [{ id: 'x', a: 1 }], center: [], right: [{ id: 'x' }] }
|
|
),
|
|
null,
|
|
'bar rebuilds when a changed id appears more than once in the layout'
|
|
)
|
|
assert(
|
|
/BarModel\.inlineSettingsDelta\(layoutConfig, next\)/.test(barSource),
|
|
'bar consults the inline settings delta before rebuilding the layout'
|
|
)
|
|
|
|
assertEqual(bar.moduleString({ id: 'custom', label: 42 }, 'label', 'fallback'), '42', 'bar stringifies module settings')
|
|
assertEqual(bar.entryIndex(entries, 'b'), 2, 'bar finds entry indexes')
|
|
assertDeepEqual(bar.entriesBefore(entries, 'b').map(bar.entryId), ['a', 'omarchy.tray'], 'bar returns entries before target')
|
|
assertDeepEqual(bar.entriesAfter(entries, 'a').map(bar.entryId), ['omarchy.tray', 'b'], 'bar returns entries after target')
|
|
|
|
assertEqual(bar.expandPath('~/module.qml', '/home/dhh'), '/home/dhh/module.qml', 'bar expands tilde paths')
|
|
assertEqual(bar.expandPath('$HOME/module.qml', '/home/dhh'), '/home/dhh/module.qml', 'bar expands HOME paths')
|
|
assert(bar.customModuleSafeName('local.weather'), 'bar accepts safe custom module names')
|
|
assert(!bar.customModuleSafeName('../escape'), 'bar rejects path traversal custom module names')
|
|
assertEqual(bar.customModuleType({ id: 'custom', exec: 'date' }), 'command', 'bar infers command custom modules')
|
|
assertEqual(bar.customModuleType({ id: 'custom', source: '~/Custom.qml' }), 'qml', 'bar infers qml custom modules')
|
|
assertEqual(
|
|
bar.customModulePath({ id: 'local.weather' }, '/home/dhh', '/home/dhh/.config/omarchy'),
|
|
'/home/dhh/.config/omarchy/bar/modules/local.weather.qml',
|
|
'bar builds default custom module paths'
|
|
)
|
|
JS
|
|
|
|
put_tmp=$(mktemp -d)
|
|
trap 'rm -rf "$put_tmp"' EXIT
|
|
mkdir -p "$put_tmp/bin"
|
|
ln -s "$ROOT/bin/omarchy-shell-config" "$put_tmp/bin/omarchy-shell-config"
|
|
|
|
cat >"$put_tmp/bin/omarchy-shell" <<'STUB'
|
|
#!/bin/bash
|
|
case ${OMARCHY_TEST_SHELL_STATE:-ready} in
|
|
missing)
|
|
echo "omarchy-shell is not running" >&2
|
|
exit 1
|
|
;;
|
|
starting)
|
|
echo "omarchy-shell is not ready" >&2
|
|
exit 1
|
|
;;
|
|
crashing)
|
|
# Seen coming up, then gone.
|
|
if [[ -e $OMARCHY_TEST_SHELL_MARKER ]]; then
|
|
echo "omarchy-shell is not running" >&2
|
|
else
|
|
touch "$OMARCHY_TEST_SHELL_MARKER"
|
|
echo "omarchy-shell is not ready" >&2
|
|
fi
|
|
exit 1
|
|
;;
|
|
oldshell)
|
|
# A shell from before put learned to fall back.
|
|
if [[ $4 == *"after"* ]]; then
|
|
echo "could not find target widget omarchy.clock"
|
|
else
|
|
echo "ok"
|
|
fi
|
|
exit 0
|
|
;;
|
|
spawning)
|
|
# Launched, but with no socket to answer on yet.
|
|
if [[ ! -e $OMARCHY_TEST_SHELL_MARKER ]]; then
|
|
touch "$OMARCHY_TEST_SHELL_MARKER"
|
|
echo "omarchy-shell is not running" >&2
|
|
exit 1
|
|
fi
|
|
;;
|
|
vanishing)
|
|
# Answers the first ask, then is gone before the fallback lands.
|
|
if [[ ! -e $OMARCHY_TEST_SHELL_MARKER ]]; then
|
|
touch "$OMARCHY_TEST_SHELL_MARKER"
|
|
echo "could not find target widget omarchy.clock"
|
|
exit 0
|
|
fi
|
|
echo "omarchy-shell is not running" >&2
|
|
exit 1
|
|
;;
|
|
unsupported)
|
|
# An older shell that predates this call.
|
|
echo "Function not found." >&2
|
|
exit 1
|
|
;;
|
|
scanning)
|
|
# Answering IPC, but has not read the plugins yet.
|
|
if [[ ! -e $OMARCHY_TEST_SHELL_MARKER ]]; then
|
|
touch "$OMARCHY_TEST_SHELL_MARKER"
|
|
echo "not ready"
|
|
exit 0
|
|
fi
|
|
;;
|
|
esac
|
|
echo "ok"
|
|
STUB
|
|
chmod +x "$put_tmp/bin/omarchy-shell"
|
|
|
|
put_output=$(PATH="$put_tmp/bin:$ROOT/bin:$PATH" OMARCHY_TEST_SHELL_STATE=missing \
|
|
OMARCHY_SHELL_ABSENT_ATTEMPTS=2 \
|
|
"$ROOT/bin/omarchy-bar" put omarchy.keyboard-layout --after omarchy.clock 2>&1) ||
|
|
fail "put carries on when no shell is running" "$put_output"
|
|
[[ $put_output == *"is not running"* ]] || fail "put says why it placed nothing" "$put_output"
|
|
pass "put carries on when no shell is running"
|
|
|
|
put_output=$(PATH="$put_tmp/bin:$ROOT/bin:$PATH" OMARCHY_TEST_SHELL_STATE=spawning \
|
|
OMARCHY_TEST_SHELL_MARKER="$put_tmp/spawned" \
|
|
"$ROOT/bin/omarchy-bar" put omarchy.keyboard-layout --after omarchy.clock 2>&1) ||
|
|
fail "put waits for a shell that is being spawned" "$put_output"
|
|
[[ $put_output == "omarchy.keyboard-layout is on the bar" ]] || fail "put places once the shell answers" "$put_output"
|
|
pass "put waits for a shell that is being spawned"
|
|
|
|
put_output=$(PATH="$put_tmp/bin:$ROOT/bin:$PATH" OMARCHY_TEST_SHELL_STATE=starting OMARCHY_SHELL_READY_ATTEMPTS=2 \
|
|
"$ROOT/bin/omarchy-bar" put omarchy.keyboard-layout --after omarchy.clock 2>&1) &&
|
|
fail "put fails when the shell never becomes ready" "$put_output"
|
|
[[ $put_output == *"did not become ready"* ]] || fail "put says the shell never became ready" "$put_output"
|
|
pass "put fails when the shell never becomes ready"
|
|
|
|
put_output=$(PATH="$put_tmp/bin:$ROOT/bin:$PATH" OMARCHY_TEST_SHELL_STATE=crashing \
|
|
OMARCHY_TEST_SHELL_MARKER="$put_tmp/started" \
|
|
"$ROOT/bin/omarchy-bar" put omarchy.keyboard-layout --after omarchy.clock 2>&1) &&
|
|
fail "put fails when a starting shell disappears" "$put_output"
|
|
[[ $put_output == *"did not become ready"* ]] || fail "put keeps a lost shell retryable" "$put_output"
|
|
pass "put fails when a starting shell disappears"
|
|
|
|
put_output=$(PATH="$put_tmp/bin:$ROOT/bin:$PATH" OMARCHY_TEST_SHELL_STATE=oldshell \
|
|
"$ROOT/bin/omarchy-bar" put omarchy.keyboard-layout --after omarchy.clock 2>&1) ||
|
|
fail "put falls back against a shell that has not restarted yet" "$put_output"
|
|
[[ $put_output == "omarchy.keyboard-layout is on the bar" ]] || fail "put places without the missing neighbour" "$put_output"
|
|
pass "put falls back against a shell that has not restarted yet"
|
|
|
|
put_output=$(PATH="$put_tmp/bin:$ROOT/bin:$PATH" OMARCHY_TEST_SHELL_STATE=vanishing \
|
|
OMARCHY_TEST_SHELL_MARKER="$put_tmp/vanished" \
|
|
"$ROOT/bin/omarchy-bar" put omarchy.keyboard-layout --after omarchy.clock 2>&1) &&
|
|
fail "put fails when the shell goes away mid-fallback" "$put_output"
|
|
[[ $put_output == *"did not become ready"* ]] || fail "put remembers the shell answered once" "$put_output"
|
|
pass "put fails when the shell goes away mid-fallback"
|
|
|
|
put_output=$(PATH="$put_tmp/bin:$ROOT/bin:$PATH" OMARCHY_TEST_SHELL_STATE=unsupported \
|
|
"$ROOT/bin/omarchy-bar" put omarchy.keyboard-layout --after omarchy.clock 2>&1) &&
|
|
fail "put fails when the shell cannot answer the call" "$put_output"
|
|
[[ $put_output == *"Function not found"* ]] || fail "put passes on what the shell said" "$put_output"
|
|
pass "put fails when the shell cannot answer the call"
|
|
|
|
put_output=$(PATH="$put_tmp/bin:$ROOT/bin:$PATH" OMARCHY_TEST_SHELL_STATE=scanning \
|
|
OMARCHY_TEST_SHELL_MARKER="$put_tmp/scanned" \
|
|
"$ROOT/bin/omarchy-bar" put omarchy.keyboard-layout --after omarchy.clock 2>&1) ||
|
|
fail "put asks again while the shell is still reading its plugins" "$put_output"
|
|
[[ $put_output == "omarchy.keyboard-layout is on the bar" ]] || fail "put places once the plugins are read" "$put_output"
|
|
pass "put asks again while the shell is still reading its plugins"
|
|
|
|
put_output=$(PATH="$put_tmp/bin:$ROOT/bin:$PATH" \
|
|
"$ROOT/bin/omarchy-bar" put omarchy.keyboard-layout --after omarchy.clock 2>&1) ||
|
|
fail "put places a widget through a ready shell" "$put_output"
|
|
[[ $put_output == "omarchy.keyboard-layout is on the bar" ]] || fail "put reports the placed widget" "$put_output"
|
|
pass "put places a widget through a ready shell"
|