noctalia-plugin: bar widget, toolbar panel and tile, with Luau tests and a flake check

This commit is contained in:
elwrcl committed 2026-10-04 05:58:08 +03:00
1 parent c8626dbe3e
commit 127d44040e
15 files changed
+530

No files matched your search

+24
View File
@@ -0,0 +1,24 @@
--!nonstrict
-- The bar widget. Runs after stubs.luau and bar.luau.
update()
assert(findAll(rendered, function(n) return n.kind == "glyph" and n.props.name == "camera" end)[1], "camera glyph")
ok("renders")
onClick()
assert(calls.toggled[1] == "elars/valw:toolbar", "left click toggles the panel")
assert(#calls.run == 0)
ok("left click")
reset()
onRightClick()
assert(calls.run[1] == "valw region", calls.run[1])
reset()
fake.answers["valw region"] = { exitCode = 1, stdout = "", stderr = "error: busy\n" }
onRightClick()
assert(calls.notify[1][2] == "error: busy", "failure notified")
reset()
fake.answers["valw region"] = { exitCode = 3, stdout = "", stderr = "" }
onRightClick()
assert(#calls.notify == 0, "cancel is silent")
ok("right click")
+87
View File
@@ -0,0 +1,87 @@
--!nonstrict
-- The toolbar panel. Runs after stubs.luau and panel.luau.
local STATE = { mode = "zoom", timer = 5, cursor = false, preview = true, sound = true, timers = { 0, 5, 10 } }
-- valw missing from Noctalia's PATH: a label and a notification, no panel.
fake.exists = false
onOpen(nil)
local labels = findAll(rendered, function(n) return n.kind == "label" end)
assert(#labels == 1 and labels[1].props.text == "panel.unavailable", "unavailable label")
assert(#calls.notify == 1, "notified once")
assert(#calls.run == 0, "nothing run")
ok("valw missing")
-- --state failing: the same.
reset()
fake.exists = true
fake.answers["valw toolbar --state"] = { exitCode = 1, stdout = "", stderr = "warn\nerror: broken\n" }
onOpen(nil)
assert(findAll(rendered, function(n) return n.kind == "label" and n.props.text == "panel.unavailable" end)[1], "label")
assert(calls.notify[1][2] == "error: broken", "the stderr's last line: " .. tostring(calls.notify[1][2]))
ok("--state failing")
-- A working valw: the remembered mode is primary, the timer selected.
reset()
fake.answers["valw toolbar --state"] = { exitCode = 0, stdout = "{}", stderr = "" }
fake.decoded = STATE
onOpen(nil)
assert(calls.run[1] == "valw toolbar --state", calls.run[1])
assert(button("mode.zoom").props.variant == "primary", "zoom is primary")
for _, m in ipairs({ "mode.screen", "mode.region", "mode.window" }) do
assert(button(m).props.variant == "outline", m .. " is outline")
end
local select = findAll(rendered, function(n) return n.kind == "select" end)[1]
assert(select.props.selectedIndex == 1, "5 s is index 1 (0-based)")
assert(select.props.options[1] == "timer.off" and select.props.options[2] == "5 s", "timer labels")
local toggles = findAll(rendered, function(n) return n.kind == "toggle" end)
assert(#toggles == 3, "three toggles")
assert(toggles[1].props.checked == false and toggles[2].props.checked == true, "cursor off, preview on")
local hex = findAll(rendered, function(n)
for _, v in pairs(n.props) do
if type(v) == "string" and string.find(v, "^#") then return true end
end
return false
end)
assert(#hex == 0, "no hex colours")
ok("renders the state")
-- A mode click closes the panel first, then captures after the wait.
reset()
button("mode.region").props.onClick()
assert(calls.order[1] == "close" and calls.order[2] == "run", "close, then run")
assert(calls.run[1] == "sleep 0.3 && valw toolbar --run region", calls.run[1])
assert(#calls.notify == 0, "success is silent")
ok("mode click")
-- A failed capture notifies; a cancelled one doesn't.
reset()
fake.answers["sleep 0.3 && valw toolbar --run"] = { exitCode = 1, stdout = "", stderr = "error: no outputs\n" }
button("mode.screen").props.onClick()
assert(calls.notify[1][2] == "error: no outputs", "failure notified")
reset()
fake.answers["sleep 0.3 && valw toolbar --run"] = { exitCode = 3, stdout = "", stderr = "" }
button("mode.window").props.onClick()
assert(#calls.notify == 0, "cancel is silent")
ok("capture results")
-- The timer and the toggles write through --set and re-render at once.
reset()
fake.answers["sleep 0.3 && valw toolbar --run"] = nil
select = findAll(rendered, function(n) return n.kind == "select" end)[1]
select.props.onChange("2")
assert(calls.run[1] == "valw toolbar --set timer=10", calls.run[1])
select = findAll(rendered, function(n) return n.kind == "select" end)[1]
assert(select.props.selectedIndex == 2, "re-rendered with 10 s")
reset()
toggles = findAll(rendered, function(n) return n.kind == "toggle" end)
toggles[3].props.onChange("false")
assert(calls.run[1] == "valw toolbar --set sound=false", calls.run[1])
toggles = findAll(rendered, function(n) return n.kind == "toggle" end)
assert(toggles[3].props.checked == false, "re-rendered with sound off")
reset()
fake.answers["valw toolbar --set"] = { exitCode = 1, stdout = "", stderr = "error: read-only\n" }
toggles[1].props.onChange("true")
assert(calls.run[1] == "valw toolbar --set cursor=true", calls.run[1])
assert(calls.notify[1][2] == "error: read-only", "a failed --set notifies")
ok("timer and toggles")
+13
View File
@@ -0,0 +1,13 @@
#!/bin/sh
# Runs each plugin test as one chunk: the stubs, the entry script, the test.
# Needs `luau` (nix shell nixpkgs#luau -c sh noctalia-plugin/tests/run.sh).
set -eu
dir=$(dirname "$0")
tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT
for pair in panel:panel_test bar:bar_test shortcut:shortcut_test; do
entry=${pair%%:*}
test=${pair##*:}
cat "$dir/stubs.luau" "$dir/../$entry.luau" "$dir/$test.luau" > "$tmp/$test.luau"
luau "$tmp/$test.luau"
done
+19
View File
@@ -0,0 +1,19 @@
--!nonstrict
-- The control-center tile. Runs after stubs.luau and shortcut.luau.
onClick()
assert(calls.run[1] == "noctalia msg panel-close; sleep 0.3 && valw region", calls.run[1])
reset()
fake.answers["noctalia msg panel-close"] = { exitCode = 1, stdout = "", stderr = "error: busy\n" }
onClick()
assert(calls.notify[1][2] == "error: busy", "failure notified")
reset()
fake.answers["noctalia msg panel-close"] = { exitCode = 3, stdout = "", stderr = "" }
onClick()
assert(#calls.notify == 0, "cancel is silent")
ok("click")
reset()
onRightClick()
assert(calls.toggled[1] == "elars/valw:toolbar", "right click toggles the panel")
ok("right click")
+85
View File
@@ -0,0 +1,85 @@
--!nonstrict
-- Stand-ins for Noctalia's globals. Each test is run as one chunk:
-- these stubs, then the entry script, then the test (see run.sh).
calls = { run = {}, notify = {}, toggled = {}, closed = 0, order = {} }
fake = {
exists = true,
-- runAsync answers by command prefix: { exitCode, stdout, stderr }
answers = {},
decoded = nil,
}
rendered = nil
local function node(kind)
return function(props, children)
return { kind = kind, props = props or {}, children = children or {} }
end
end
ui = {
row = node("row"), column = node("column"), label = node("label"),
button = node("button"), glyph = node("glyph"), toggle = node("toggle"),
select = node("select"), separator = node("separator"), spacer = node("spacer"),
}
noctalia = {
tr = function(key) return key end,
commandExists = function(_name) return fake.exists end,
runAsync = function(command, callback)
table.insert(calls.run, command)
table.insert(calls.order, "run")
if callback then
local answer = { exitCode = 0, stdout = "", stderr = "" }
for prefix, a in pairs(fake.answers) do
if string.sub(command, 1, #prefix) == prefix then answer = a end
end
callback(answer)
end
return true
end,
notifyError = function(title, body) table.insert(calls.notify, { title, body }) end,
togglePanel = function(id) table.insert(calls.toggled, id) end,
json = { decode = function(_text) return fake.decoded end },
}
panel = {
render = function(tree) rendered = tree end,
close = function()
calls.closed += 1
table.insert(calls.order, "close")
end,
}
barWidget = {
render = function(tree) rendered = tree end,
setTooltip = function(_text) end,
}
shortcut = {
setLabel = function(_text) end,
setIcon = function(_a, _b) end,
}
-- Every node of `tree` that `match` accepts, depth first.
function findAll(tree, match, out)
out = out or {}
if type(tree) ~= "table" then return out end
if tree.kind and match(tree) then table.insert(out, tree) end
for _, child in ipairs(tree.children or {}) do findAll(child, match, out) end
return out
end
function button(text)
local found = findAll(rendered, function(n) return n.kind == "button" and n.props.text == text end)
assert(#found == 1, "one button " .. text .. ", found " .. #found)
return found[1]
end
function reset()
calls = { run = {}, notify = {}, toggled = {}, closed = 0, order = {} }
end
function ok(name)
print("ok - " .. name)
end