* Stop pipefail from turning grep -q SIGPIPE exits into false negatives grep -q exits at the first match, and when the producer is still writing it dies with SIGPIPE. Under pipefail that 141 becomes the pipeline's status, so hardware checks like lspci | grep -q read as "not found" on exactly the machines they target. The T2 defaults migration hit this and silently skipped real T2 Macs (#6608). Redirect grep to /dev/null instead of -q wherever a pipeline feeds grep in a pipefail context, so grep reads all input and the producer never gets killed. The install-time T2 checks aren't run under pipefail today but are switched too, since they're the same detection line the issue calls out. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Re-run the T2 defaults migration its broken hardware check skipped The SIGPIPE bug marked 1785944594 as applied without doing anything on affected T2 Macs. The original migration is idempotent, so a fresh migration can just source it now that the guard is fixed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Address Copilot review: fix OCR grep pipeline and prove the T2 repair screen_contains piped tesseract into grep -Fqi under the acceptance suite's pipefail, the same SIGPIPE false negative the rest of the branch fixes. The T2 test's lspci stub now keeps writing past the pipe buffer after the match so every scenario exercises the SIGPIPE case, and a new case runs the rerun migration against fixtures a bitten install would have. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
408 lines
16 KiB
Bash
408 lines
16 KiB
Bash
#!/bin/bash
|
|
|
|
set -euo pipefail
|
|
|
|
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
|
|
|
|
run_node_test <<'JS'
|
|
const fs = require('fs')
|
|
const clipboard = requireFromRoot('shell/plugins/clipboard/ClipboardHistory.js')
|
|
const clipboardQml = fs.readFileSync(path.join(root, 'shell/plugins/clipboard/Clipboard.qml'), 'utf8')
|
|
|
|
assertDeepEqual(
|
|
clipboard.normalizeEntry('hello'),
|
|
{ type: 'text', text: 'hello' },
|
|
'clipboard normalizes string entries'
|
|
)
|
|
|
|
assertDeepEqual(
|
|
clipboard.normalizeEntry({ kind: 'image', path: '/tmp/a.png' }),
|
|
{ type: 'image', path: '/tmp/a.png', mime: 'image/png' },
|
|
'clipboard normalizes image entries with default mime'
|
|
)
|
|
|
|
assertDeepEqual(
|
|
clipboard.normalizeEntry({ type: 'image', path: '/tmp/a.png', mime: 'image/png', capturedAt: 'Friday 14:42' }),
|
|
{ type: 'image', path: '/tmp/a.png', mime: 'image/png', capturedAt: 'Friday 14:42' },
|
|
'clipboard keeps image capture timestamps'
|
|
)
|
|
|
|
assertDeepEqual(
|
|
clipboard.parseHistory(JSON.stringify(['one', '', { type: 'text', text: 'two' }, { type: 'image', path: '/tmp/a.jpg', mime: 'image/jpeg' }])),
|
|
[
|
|
{ type: 'text', text: 'one' },
|
|
{ type: 'text', text: 'two' },
|
|
{ type: 'image', path: '/tmp/a.jpg', mime: 'image/jpeg' }
|
|
],
|
|
'clipboard history parser drops invalid entries'
|
|
)
|
|
|
|
assertDeepEqual(clipboard.parseHistory(JSON.stringify([' ', '\n', { type: 'text', text: '\t' }])), [], 'clipboard history parser drops whitespace-only text')
|
|
|
|
const history = [
|
|
{ type: 'text', text: 'old' },
|
|
{ type: 'text', text: 'new' },
|
|
{ type: 'image', path: '/tmp/a.png', mime: 'image/png' }
|
|
]
|
|
|
|
assertDeepEqual(
|
|
clipboard.addEntry(history, { type: 'text', text: 'new' }, 100),
|
|
[
|
|
{ type: 'text', text: 'new' },
|
|
{ type: 'text', text: 'old' },
|
|
{ type: 'image', path: '/tmp/a.png', mime: 'image/png' }
|
|
],
|
|
'clipboard addEntry moves duplicate text to front'
|
|
)
|
|
|
|
assertDeepEqual(
|
|
clipboard.removeEntryAt(history, 1),
|
|
[
|
|
{ type: 'text', text: 'old' },
|
|
{ type: 'image', path: '/tmp/a.png', mime: 'image/png' }
|
|
],
|
|
'clipboard removeEntryAt removes the requested entry'
|
|
)
|
|
|
|
assertDeepEqual(clipboard.removeEntryAt(history, 10), history, 'clipboard removeEntryAt ignores invalid indexes')
|
|
assertDeepEqual(clipboard.clearHistory(), [], 'clipboard clearHistory returns an empty history')
|
|
|
|
assertDeepEqual(
|
|
clipboard.displayRows(history, 'image', 50).map(row => ({ type: row.entryType, preview: row.previewText, mime: row.mime })),
|
|
[{ type: 'image', preview: 'Image', mime: 'image/png' }],
|
|
'clipboard display rows search image metadata'
|
|
)
|
|
|
|
assertDeepEqual(
|
|
clipboard.displayRows([{ type: 'image', path: '/tmp/a.png', mime: 'image/png', capturedAt: 'Friday 14:42' }], '', 50)[0].previewText,
|
|
'Screenshot from Friday 14:42',
|
|
'clipboard labels timestamped png image entries as screenshots'
|
|
)
|
|
|
|
assertDeepEqual(
|
|
clipboard.displayRows([{ type: 'image', path: '/tmp/a.jpg', mime: 'image/jpeg', capturedAt: 'Friday 14:42' }], '', 50)[0].previewText,
|
|
'Image from Friday 14:42',
|
|
'clipboard labels timestamped non-png image entries as images'
|
|
)
|
|
|
|
assertDeepEqual(
|
|
clipboard.displayRows(history, 'image', 50).map(row => row.index),
|
|
[2],
|
|
'clipboard display rows preserve original history indexes'
|
|
)
|
|
|
|
assertDeepEqual(
|
|
clipboard.displayRows([{ type: 'text', text: 'line one\nline two' }], '', 50)[0].previewText,
|
|
'line one line two',
|
|
'clipboard display rows collapse text whitespace'
|
|
)
|
|
|
|
assertDeepEqual(
|
|
clipboard.displayRows([{ type: 'text', text: 'file:///home/dhh/Videos/screenrecording-2026-05-29_13-56-43-720p.gif\n' }], '', 50)[0],
|
|
{
|
|
entryType: 'file',
|
|
fullText: '/home/dhh/Videos/screenrecording-2026-05-29_13-56-43-720p.gif',
|
|
previewText: 'screenrecording-2026-05-29_13-56-43-720p.gif',
|
|
previewImage: '/home/dhh/Videos/screenrecording-2026-05-29_13-56-43-720p.gif',
|
|
path: '/home/dhh/Videos/screenrecording-2026-05-29_13-56-43-720p.gif',
|
|
mime: 'text/plain',
|
|
index: 0
|
|
},
|
|
'clipboard display rows show file uri entries as files'
|
|
)
|
|
|
|
assertDeepEqual(
|
|
clipboard.displayRows([{ type: 'text', text: 'file:///home/dhh/One.txt\nfile:///home/dhh/Two.txt\n' }], '', 50)[0].previewText,
|
|
'2 files',
|
|
'clipboard display rows summarize multiple file uri entries'
|
|
)
|
|
|
|
assertDeepEqual(
|
|
clipboard.displayRows([{ type: 'text', text: 'file:///home/dhh/Videos/demo.mp4\n' }], '', 50)[0].previewImage,
|
|
'',
|
|
'clipboard display rows do not preview video file uri entries inline'
|
|
)
|
|
|
|
assertDeepEqual(clipboard.displayRows(history, '', 0), [], 'clipboard display rows supports zero result limit')
|
|
assertDeepEqual(clipboard.addEntry(history, 'next', 0), [], 'clipboard addEntry supports zero history limit')
|
|
|
|
assert(
|
|
/function select\(delta\)[\s\S]*root\.disarmPointer\(\)[\s\S]*selectedIndex =/.test(clipboardQml),
|
|
'clipboard keyboard navigation disarms pointer selection'
|
|
)
|
|
assert(
|
|
/function selectAbsolute\(index\)[\s\S]*root\.disarmPointer\(\)[\s\S]*root\.selectedIndex = Math\.max\(0, Math\.min\(index, displayModel\.count - 1\)\)/.test(clipboardQml),
|
|
'clipboard absolute navigation disarms pointer selection'
|
|
)
|
|
assert(
|
|
/event\.key === Qt\.Key_Home[\s\S]*root\.selectAbsolute\(0\)[\s\S]*event\.accepted = true/.test(clipboardQml),
|
|
'clipboard Home selects the first entry'
|
|
)
|
|
assert(
|
|
/event\.key === Qt\.Key_End[\s\S]*root\.selectAbsolute\(displayModel\.count - 1\)[\s\S]*event\.accepted = true/.test(clipboardQml),
|
|
'clipboard End selects the last entry'
|
|
)
|
|
assert(
|
|
/PointerMoveGate\s*\{[\s\S]*id: pointerGate[\s\S]*referenceItem: card[\s\S]*\}/.test(clipboardQml),
|
|
'clipboard uses shared pointer movement gate in card coordinates'
|
|
)
|
|
assert(
|
|
/function disarmPointer\(\)[\s\S]*pointerGate\.reset\(\)/.test(clipboardQml),
|
|
'clipboard resets pointer movement gate when pointer selection is disarmed'
|
|
)
|
|
assert(
|
|
/function selectFromPointer\(index, item, mouse\)[\s\S]*pointerGate\.moved\(item, mouse\)[\s\S]*root\.selectedIndex = index/.test(clipboardQml),
|
|
'clipboard only selects from pointer after real movement'
|
|
)
|
|
assert(
|
|
/onPositionChanged: function\(mouse\) \{\s*root\.selectFromPointer\(row\.index, row, mouse\)\s*\}/.test(clipboardQml),
|
|
'clipboard row hover routes through pointer movement gate'
|
|
)
|
|
assert(
|
|
!/onContainsMouseChanged:[\s\S]*root\.selectedIndex/.test(clipboardQml),
|
|
'clipboard does not select rows from containsMouse'
|
|
)
|
|
assert(
|
|
clipboardQml.includes('command: ["setpriv", "--pdeathsig", "TERM", "wl-paste", "--type", "text", "--watch", root.captureScript, "text"]'),
|
|
'clipboard text watcher dies with the shell via pdeathsig'
|
|
)
|
|
assert(
|
|
clipboardQml.includes('command: ["setpriv", "--pdeathsig", "TERM", "wl-paste", "--type", "image/png", "--watch", root.captureScript, "image/png"]'),
|
|
'clipboard image watcher dies with the shell via pdeathsig'
|
|
)
|
|
assert(
|
|
clipboardQml.includes('command: ["pkill", "-f", "wl-paste .*--watch .*/shell/plugins/clipboard/capture\\\\.sh"]'),
|
|
'clipboard init reaps stale watchers before starting new ones'
|
|
)
|
|
assertEqual(
|
|
(clipboardQml.match(/onExited: watchRestartTimer\.restart\(\)/g) || []).length,
|
|
2,
|
|
'clipboard respawns both watchers when they die'
|
|
)
|
|
JS
|
|
|
|
TMPDIR=$(mktemp -d)
|
|
PIDS_TO_KILL=()
|
|
|
|
cleanup() {
|
|
local pid
|
|
|
|
for pid in "${PIDS_TO_KILL[@]}"; do
|
|
kill "$pid" 2>/dev/null || true
|
|
wait "$pid" 2>/dev/null || true
|
|
done
|
|
|
|
rm -rf "$TMPDIR"
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
process_gone() {
|
|
local pid=$1
|
|
local stat
|
|
|
|
for _ in {1..40}; do
|
|
stat=$(ps -o stat= -p "$pid" 2>/dev/null || true)
|
|
if [[ -z $stat || $stat == Z* ]]; then
|
|
return 0
|
|
fi
|
|
sleep 0.1
|
|
done
|
|
|
|
return 1
|
|
}
|
|
|
|
process_alive() {
|
|
local pid=$1
|
|
local stat
|
|
|
|
stat=$(ps -o stat= -p "$pid" 2>/dev/null || true)
|
|
[[ -n $stat && $stat != Z* ]]
|
|
}
|
|
|
|
mkdir -p "$TMPDIR/bin" "$TMPDIR/home/.local/state/omarchy"
|
|
|
|
cat >"$TMPDIR/bin/wl-copy" <<'SH'
|
|
#!/bin/bash
|
|
cat >"$WL_COPY_OUT"
|
|
SH
|
|
|
|
cat >"$TMPDIR/bin/wl-paste" <<'SH'
|
|
#!/bin/bash
|
|
if [[ $1 == "--list-types" ]]; then
|
|
printf '%b' "${WL_PASTE_TYPES:-text/plain\n}"
|
|
elif [[ $1 == "--type" && $2 == "text" ]]; then
|
|
printf '%s' "${WL_PASTE_TEXT:-terminal copy}"
|
|
fi
|
|
SH
|
|
|
|
cat >"$TMPDIR/bin/wtype" <<'SH'
|
|
#!/bin/bash
|
|
printf '%s\n' "$*" >"$WTYPE_OUT"
|
|
SH
|
|
|
|
cat >"$TMPDIR/bin/omarchy-launch-browser" <<'SH'
|
|
#!/bin/bash
|
|
printf '%s\n' "$*" >"$BROWSER_OUT"
|
|
SH
|
|
|
|
cat >"$TMPDIR/bin/omarchy-launch-editor" <<'SH'
|
|
#!/bin/bash
|
|
printf '%s\n' "$1" >"$EDITOR_PATH_OUT"
|
|
cat "$1" >"$EDITOR_TEXT_OUT"
|
|
SH
|
|
|
|
cat >"$TMPDIR/bin/tensaku-edit" <<'SH'
|
|
#!/bin/bash
|
|
printf '%s\n' "$*" >"$TENSAKU_OUT"
|
|
SH
|
|
|
|
chmod +x "$TMPDIR/bin/wl-copy" "$TMPDIR/bin/wl-paste" "$TMPDIR/bin/wtype" "$TMPDIR/bin/omarchy-launch-browser" "$TMPDIR/bin/omarchy-launch-editor" "$TMPDIR/bin/tensaku-edit"
|
|
|
|
capture_output=$(XDG_RUNTIME_DIR="$TMPDIR" XDG_STATE_HOME="$TMPDIR/state" PATH="$TMPDIR/bin:$PATH" "$ROOT/shell/plugins/clipboard/capture.sh")
|
|
[[ $capture_output == '{"type":"text","text":"terminal copy"}' ]] || fail "clipboard capture records normal text events"
|
|
pass "clipboard capture records normal text events"
|
|
|
|
capture_output=$(printf 'closing app copy' | WL_PASTE_TEXT="stale read" XDG_RUNTIME_DIR="$TMPDIR" XDG_STATE_HOME="$TMPDIR/state" PATH="$TMPDIR/bin:$PATH" "$ROOT/shell/plugins/clipboard/capture.sh" text)
|
|
[[ $capture_output == '{"type":"text","text":"closing app copy"}' ]] || fail "clipboard capture records watched text from stdin"
|
|
pass "clipboard capture records watched text from stdin"
|
|
|
|
capture_output=$(printf 'png-data' | XDG_RUNTIME_DIR="$TMPDIR" XDG_STATE_HOME="$TMPDIR/state" PATH="$TMPDIR/bin:$PATH" "$ROOT/shell/plugins/clipboard/capture.sh" image/png)
|
|
image_path=$(jq -r '.path' <<<"$capture_output")
|
|
jq -e '.type == "image" and .mime == "image/png" and (.capturedAt | type == "string")' <<<"$capture_output" >/dev/null || fail "clipboard capture records watched png images"
|
|
[[ -s $image_path && $(<"$image_path") == "png-data" ]] || fail "clipboard capture stores watched png image data"
|
|
pass "clipboard capture records watched png images"
|
|
|
|
capture_output=$(printf 'jpg-data' | XDG_RUNTIME_DIR="$TMPDIR" XDG_STATE_HOME="$TMPDIR/state" PATH="$TMPDIR/bin:$PATH" "$ROOT/shell/plugins/clipboard/capture.sh" image/jpeg)
|
|
image_path=$(jq -r '.path' <<<"$capture_output")
|
|
jq -e '.mime == "image/jpeg"' <<<"$capture_output" >/dev/null && [[ $image_path == *.jpg ]] || fail "clipboard capture stores watched jpeg images with jpg extension"
|
|
pass "clipboard capture stores watched jpeg images with jpg extension"
|
|
|
|
capture_output=$(printf 'secret' | CLIPBOARD_STATE=sensitive XDG_RUNTIME_DIR="$TMPDIR" XDG_STATE_HOME="$TMPDIR/state" PATH="$TMPDIR/bin:$PATH" "$ROOT/shell/plugins/clipboard/capture.sh" text)
|
|
[[ -z $capture_output ]] || fail "clipboard capture ignores sensitive watched text"
|
|
pass "clipboard capture ignores sensitive watched text"
|
|
|
|
capture_output=$(CLIPBOARD_STATE=sensitive XDG_RUNTIME_DIR="$TMPDIR" XDG_STATE_HOME="$TMPDIR/state" PATH="$TMPDIR/bin:$PATH" "$ROOT/shell/plugins/clipboard/capture.sh")
|
|
[[ -z $capture_output ]] || fail "clipboard capture ignores sensitive clipboard events"
|
|
pass "clipboard capture ignores sensitive clipboard events"
|
|
|
|
capture_output=$(WL_PASTE_TYPES="text/plain\nx-kde-passwordManagerHint\n" XDG_RUNTIME_DIR="$TMPDIR" XDG_STATE_HOME="$TMPDIR/state" PATH="$TMPDIR/bin:$PATH" "$ROOT/shell/plugins/clipboard/capture.sh")
|
|
[[ -z $capture_output ]] || fail "clipboard capture ignores password manager hint"
|
|
pass "clipboard capture ignores password manager hint"
|
|
|
|
cat >"$TMPDIR/bin/wl-paste" <<'SH'
|
|
#!/bin/bash
|
|
printf '%s\t%s\n' "$BASHPID" "$*" >>"${WL_PASTE_LOG:-/dev/null}"
|
|
sleep_pid=""
|
|
|
|
cleanup() {
|
|
[[ -n $sleep_pid ]] && kill "$sleep_pid" 2>/dev/null || true
|
|
exit 0
|
|
}
|
|
trap cleanup HUP INT TERM
|
|
|
|
while true; do
|
|
sleep 10 &
|
|
sleep_pid=$!
|
|
wait "$sleep_pid"
|
|
done
|
|
SH
|
|
chmod +x "$TMPDIR/bin/wl-paste"
|
|
|
|
clipboard_lifecycle_dir="$TMPDIR/clipboard-lifecycle"
|
|
current_script="$clipboard_lifecycle_dir/current/shell/plugins/clipboard/capture.sh"
|
|
mkdir -p "$(dirname "$current_script")"
|
|
cp "$ROOT/shell/plugins/clipboard/capture.sh" "$current_script"
|
|
chmod +x "$current_script"
|
|
|
|
PATH="$TMPDIR/bin:$PATH" wl-paste --type text --watch "$current_script" text &
|
|
stale_pid=$!
|
|
PIDS_TO_KILL+=("$stale_pid")
|
|
sleep 0.2
|
|
pgrep -f 'wl-paste .*--watch .*/shell/plugins/clipboard/capture\.sh' | grep -x "$stale_pid" >/dev/null || fail "clipboard reaper pattern matches running watchers"
|
|
kill "$stale_pid" 2>/dev/null || true
|
|
wait "$stale_pid" 2>/dev/null || true
|
|
pass "clipboard reaper pattern matches running watchers"
|
|
|
|
watch_owner="$clipboard_lifecycle_dir/watch-owner.sh"
|
|
watch_pid_file="$clipboard_lifecycle_dir/watch.pid"
|
|
cat >"$watch_owner" <<SH
|
|
#!/bin/bash
|
|
PATH="$TMPDIR/bin:\$PATH" setpriv --pdeathsig TERM wl-paste --type text --watch "$current_script" text &
|
|
printf '%s\n' "\$!" >"$watch_pid_file"
|
|
wait
|
|
SH
|
|
chmod +x "$watch_owner"
|
|
|
|
"$watch_owner" &
|
|
owner_pid=$!
|
|
PIDS_TO_KILL+=("$owner_pid")
|
|
|
|
for _ in {1..40}; do
|
|
[[ -s $watch_pid_file ]] && break
|
|
sleep 0.1
|
|
done
|
|
|
|
watch_pid=$(<"$watch_pid_file")
|
|
[[ -n $watch_pid ]] && process_alive "$watch_pid" || fail "clipboard watcher starts under setpriv"
|
|
PIDS_TO_KILL+=("$watch_pid")
|
|
|
|
kill "$owner_pid" 2>/dev/null || true
|
|
process_gone "$watch_pid" || fail "clipboard watcher dies with its owner via pdeathsig"
|
|
pass "clipboard watcher dies with its owner via pdeathsig"
|
|
|
|
jq -n --arg text "$(printf 'large block line 1\nlarge block line 2\n')" '[{type:"text", text:"ignored"}, {type:"text", text:$text}]' >"$TMPDIR/home/.local/state/omarchy/clipboard-history.json"
|
|
|
|
WL_COPY_OUT="$TMPDIR/copied" WTYPE_OUT="$TMPDIR/wtype" HOME="$TMPDIR/home" PATH="$TMPDIR/bin:$PATH" \
|
|
"$ROOT/bin/omarchy-clipboard-paste-text" --shift-insert --history-index 1
|
|
|
|
[[ $(<"$TMPDIR/copied") == "$(printf 'large block line 1\nlarge block line 2')" ]] || fail "clipboard paste helper copies history entry text"
|
|
pass "clipboard paste helper copies history entry text"
|
|
|
|
[[ $(<"$TMPDIR/wtype") == "-M shift -k Insert -m shift" ]] || fail "clipboard paste helper pastes history entries with shift insert"
|
|
pass "clipboard paste helper pastes history entries with shift insert"
|
|
|
|
rm -f "$TMPDIR/wtype"
|
|
WL_COPY_OUT="$TMPDIR/copied" WTYPE_OUT="$TMPDIR/wtype" HOME="$TMPDIR/home" PATH="$TMPDIR/bin:$PATH" \
|
|
"$ROOT/bin/omarchy-clipboard-paste-text" --copy-only --history-index 1
|
|
|
|
[[ $(<"$TMPDIR/copied") == "$(printf 'large block line 1\nlarge block line 2')" ]] || fail "clipboard paste helper copy-only copies history entry text"
|
|
pass "clipboard paste helper copy-only copies history entry text"
|
|
|
|
[[ ! -e "$TMPDIR/wtype" ]] || fail "clipboard paste helper copy-only skips typing"
|
|
pass "clipboard paste helper copy-only skips typing"
|
|
|
|
printf 'image-data' >"$TMPDIR/image.png"
|
|
rm -f "$TMPDIR/wtype"
|
|
WL_COPY_OUT="$TMPDIR/copied" WTYPE_OUT="$TMPDIR/wtype" PATH="$TMPDIR/bin:$PATH" \
|
|
"$ROOT/bin/omarchy-clipboard-paste-file" --copy-only image/png "$TMPDIR/image.png"
|
|
|
|
[[ $(<"$TMPDIR/copied") == "image-data" ]] || fail "clipboard file paste helper copy-only copies file content"
|
|
pass "clipboard file paste helper copy-only copies file content"
|
|
|
|
[[ ! -e "$TMPDIR/wtype" ]] || fail "clipboard file paste helper copy-only skips paste keystroke"
|
|
pass "clipboard file paste helper copy-only skips paste keystroke"
|
|
|
|
jq -n --arg url 'https://example.com/docs' --arg text "$(printf 'plain text\nsecond line')" --arg image "$TMPDIR/image.png" \
|
|
'[{type:"text", text:$url}, {type:"text", text:$text}, {type:"image", mime:"image/png", path:$image}]' >"$TMPDIR/home/.local/state/omarchy/clipboard-history.json"
|
|
|
|
BROWSER_OUT="$TMPDIR/browser" HOME="$TMPDIR/home" PATH="$TMPDIR/bin:$PATH" \
|
|
"$ROOT/bin/omarchy-clipboard-open" --history-index 0
|
|
|
|
[[ $(<"$TMPDIR/browser") == "https://example.com/docs" ]] || fail "clipboard open helper opens URL entries in browser"
|
|
pass "clipboard open helper opens URL entries in browser"
|
|
|
|
EDITOR_PATH_OUT="$TMPDIR/editor-path" EDITOR_TEXT_OUT="$TMPDIR/editor-text" HOME="$TMPDIR/home" XDG_STATE_HOME="$TMPDIR/state" PATH="$TMPDIR/bin:$PATH" \
|
|
"$ROOT/bin/omarchy-clipboard-open" --history-index 1
|
|
|
|
[[ $(<"$TMPDIR/editor-text") == "$(printf 'plain text\nsecond line')" ]] || fail "clipboard open helper opens text entries in editor"
|
|
pass "clipboard open helper opens text entries in editor"
|
|
|
|
[[ $(<"$TMPDIR/editor-path") == "$TMPDIR"/state/omarchy/clipboard-open/clipboard.*.txt ]] || fail "clipboard open helper writes text entries to a temporary file"
|
|
pass "clipboard open helper writes text entries to a temporary file"
|
|
|
|
TENSAKU_OUT="$TMPDIR/tensaku" HOME="$TMPDIR/home" PATH="$TMPDIR/bin:$PATH" \
|
|
"$ROOT/bin/omarchy-clipboard-open" --history-index 2
|
|
|
|
[[ $(<"$TMPDIR/tensaku") == "$TMPDIR/image.png" ]] || fail "clipboard open helper opens image entries in Tensaku"
|
|
pass "clipboard open helper opens image entries in Tensaku"
|