noctalia-plugin: bar widget, toolbar panel and tile, with Luau tests and a flake check
This commit is contained in:
1 parent
c8626dbe3e
commit
127d44040e
15 files changed
+530
No files matched your search
@@ -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")
|
||||
@@ -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")
|
||||
@@ -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
|
||||
@@ -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")
|
||||
@@ -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
|
||||
Reference in new issue
Block a user