#!/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" 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') // 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') assert( /BarModel\.pickDrawnSlot\(candidates\)/.test(barSource), 'bar routes panels through the drawn-slot picker' ) // 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') 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