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
@@ -9,4 +9,5 @@
|
|||||||
!/src/
|
!/src/
|
||||||
!/docs/
|
!/docs/
|
||||||
!/assets/
|
!/assets/
|
||||||
|
!/noctalia-plugin/
|
||||||
!/README.md
|
!/README.md
|
||||||
@@ -88,6 +88,33 @@
|
|||||||
}/testBuildFailure.log
|
}/testBuildFailure.log
|
||||||
touch $out
|
touch $out
|
||||||
'';
|
'';
|
||||||
|
# The plugin as packaged: manifest, every entry present, Luau that
|
||||||
|
# compiles, translations that parse; and its logic against stubbed
|
||||||
|
# Noctalia globals.
|
||||||
|
noctalia-plugin =
|
||||||
|
pkgs.runCommand "valw-noctalia-plugin"
|
||||||
|
{
|
||||||
|
nativeBuildInputs = [
|
||||||
|
pkgs.jq
|
||||||
|
pkgs.luau
|
||||||
|
];
|
||||||
|
}
|
||||||
|
''
|
||||||
|
dir=${valw}/share/valw/noctalia-plugin
|
||||||
|
grep -qx 'id = "elars/valw"' $dir/plugin.toml
|
||||||
|
test "$(grep -c '^entry = ' $dir/plugin.toml)" -eq 3
|
||||||
|
for entry in $(sed -n 's/^entry = "\(.*\)"$/\1/p' $dir/plugin.toml); do
|
||||||
|
test -f $dir/$entry
|
||||||
|
done
|
||||||
|
test ! -e $dir/tests
|
||||||
|
luau-compile --null $dir/*.luau
|
||||||
|
for f in $dir/translations/*.json; do jq empty $f; done
|
||||||
|
cp -r ${../noctalia-plugin} plugin
|
||||||
|
chmod -R u+w plugin
|
||||||
|
sh plugin/tests/run.sh
|
||||||
|
touch $out
|
||||||
|
'';
|
||||||
|
|
||||||
clippy = cargoCheck "clippy" [ pkgs.clippy ] "cargo clippy --all-targets --offline -- -D warnings";
|
clippy = cargoCheck "clippy" [ pkgs.clippy ] "cargo clippy --all-targets --offline -- -D warnings";
|
||||||
nextest = cargoCheck "nextest" [ pkgs.cargo-nextest ] "cargo nextest run --offline";
|
nextest = cargoCheck "nextest" [ pkgs.cargo-nextest ] "cargo nextest run --offline";
|
||||||
|
|
||||||
|
|||||||
@@ -48,6 +48,10 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
install -Dm644 assets/fonts/LICENSE-DejaVu $out/share/licenses/valw/LICENSE-DejaVu
|
install -Dm644 assets/fonts/LICENSE-DejaVu $out/share/licenses/valw/LICENSE-DejaVu
|
||||||
|
# The Noctalia plugin (programs.valw.noctalia), without its tests.
|
||||||
|
mkdir -p $out/share/valw
|
||||||
|
cp -r noctalia-plugin $out/share/valw/noctalia-plugin
|
||||||
|
rm -r $out/share/valw/noctalia-plugin/tests
|
||||||
'';
|
'';
|
||||||
|
|
||||||
# dlopen()ed at run time: libwayland (winit, valw), libxkbcommon (winit),
|
# dlopen()ed at run time: libwayland (winit, valw), libxkbcommon (winit),
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
# valw for Noctalia
|
||||||
|
|
||||||
|
A bar button, the valw toolbar as a Noctalia panel, and a control-center
|
||||||
|
tile. Noctalia draws everything, so it follows your Noctalia theme.
|
||||||
|
|
||||||
|
- Bar button: left click opens the toolbar, right click takes a region shot.
|
||||||
|
- Toolbar: click a mode to capture; the timer and options are remembered
|
||||||
|
(shared with `valw toolbar`).
|
||||||
|
- Tile: click for a region shot, right click for the toolbar.
|
||||||
|
|
||||||
|
Install it with the home-manager module (`programs.valw.noctalia.enable`),
|
||||||
|
then enable `elars/valw` in Noctalia and place the widget (type
|
||||||
|
`elars/valw:bar`). Toggle the toolbar from a keybind with
|
||||||
|
`noctalia msg panel-toggle elars/valw:toolbar`.
|
||||||
|
|
||||||
|
Tests: `nix shell nixpkgs#luau -c sh noctalia-plugin/tests/run.sh`.
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
--!nonstrict
|
||||||
|
-- valw bar widget: a camera glyph. Left click opens the toolbar panel,
|
||||||
|
-- right click takes a region shot at once (plain `valw region`, like the
|
||||||
|
-- keybind: the toolbar's timer and options don't apply).
|
||||||
|
|
||||||
|
local PANEL = "elars/valw:toolbar"
|
||||||
|
|
||||||
|
local function lastLine(text)
|
||||||
|
local last = ""
|
||||||
|
for line in string.gmatch(text or "", "[^\n]+") do
|
||||||
|
last = line
|
||||||
|
end
|
||||||
|
return last
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Exit 3 is a cancel (Esc): not an error.
|
||||||
|
local function report(result)
|
||||||
|
if result.exitCode ~= 0 and result.exitCode ~= 3 then
|
||||||
|
noctalia.notifyError("valw", lastLine(result.stderr))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function update()
|
||||||
|
barWidget.render(ui.row({ align = "center" }, {
|
||||||
|
ui.glyph({ name = "camera" }),
|
||||||
|
}))
|
||||||
|
barWidget.setTooltip(noctalia.tr("bar.tooltip"))
|
||||||
|
end
|
||||||
|
|
||||||
|
function onClick()
|
||||||
|
noctalia.togglePanel(PANEL)
|
||||||
|
end
|
||||||
|
|
||||||
|
function onRightClick()
|
||||||
|
noctalia.runAsync("valw region", report)
|
||||||
|
end
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
--!nonstrict
|
||||||
|
-- valw toolbar panel: the four modes, the timer and the options, drawn
|
||||||
|
-- with Noctalia's own components. The choices live in valw's toolbar.toml:
|
||||||
|
-- read with `valw toolbar --state`, changed with `valw toolbar --set`.
|
||||||
|
-- A mode click closes the panel, waits for it to go, then captures.
|
||||||
|
|
||||||
|
local MODES = {
|
||||||
|
{ id = "screen", glyph = "device-desktop" },
|
||||||
|
{ id = "region", glyph = "crop" },
|
||||||
|
{ id = "window", glyph = "app-window" },
|
||||||
|
{ id = "zoom", glyph = "zoom-in" },
|
||||||
|
}
|
||||||
|
local OPTIONS = { "cursor", "preview", "sound" }
|
||||||
|
|
||||||
|
-- The decoded `--state` JSON; nil until it arrives.
|
||||||
|
local state = nil
|
||||||
|
local unavailable = false
|
||||||
|
|
||||||
|
local function lastLine(text)
|
||||||
|
local last = ""
|
||||||
|
for line in string.gmatch(text or "", "[^\n]+") do
|
||||||
|
last = line
|
||||||
|
end
|
||||||
|
return last
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Exit 3 is a cancel (Esc): not an error.
|
||||||
|
local function report(result)
|
||||||
|
if result.exitCode ~= 0 and result.exitCode ~= 3 then
|
||||||
|
noctalia.notifyError("valw", lastLine(result.stderr))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local render
|
||||||
|
|
||||||
|
local function capture(mode)
|
||||||
|
panel.close()
|
||||||
|
noctalia.runAsync("sleep 0.3 && valw toolbar --run " .. mode, report)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- `key` and `value` are always one of our own tokens, never user text.
|
||||||
|
local function remember(key, value)
|
||||||
|
state[key] = value
|
||||||
|
render()
|
||||||
|
noctalia.runAsync("valw toolbar --set " .. key .. "=" .. tostring(value), report)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function modeButton(m)
|
||||||
|
return ui.button({
|
||||||
|
text = noctalia.tr("mode." .. m.id),
|
||||||
|
glyph = m.glyph,
|
||||||
|
variant = if state.mode == m.id then "primary" else "outline",
|
||||||
|
onClick = function()
|
||||||
|
capture(m.id)
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
local function optionRow(key)
|
||||||
|
return ui.row({ gap = 8, align = "center", justify = "space_between" }, {
|
||||||
|
ui.label({ text = noctalia.tr("option." .. key), color = "on_surface" }),
|
||||||
|
ui.toggle({
|
||||||
|
checked = state[key],
|
||||||
|
onChange = function(value)
|
||||||
|
remember(key, value == "true")
|
||||||
|
end,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
render = function()
|
||||||
|
if unavailable then
|
||||||
|
panel.render(ui.column({ padding = 16 }, {
|
||||||
|
ui.label({ text = noctalia.tr("panel.unavailable"), color = "error" }),
|
||||||
|
}))
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if state == nil then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local modes = {}
|
||||||
|
for _, m in ipairs(MODES) do
|
||||||
|
table.insert(modes, modeButton(m))
|
||||||
|
end
|
||||||
|
local timers, selected = {}, 0
|
||||||
|
for i, t in ipairs(state.timers) do
|
||||||
|
table.insert(timers, if t == 0 then noctalia.tr("timer.off") else tostring(t) .. " s")
|
||||||
|
if t == state.timer then
|
||||||
|
selected = i - 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local rows = {
|
||||||
|
ui.row({ gap = 8, justify = "space_between" }, modes),
|
||||||
|
ui.separator({}),
|
||||||
|
ui.row({ gap = 8, align = "center", justify = "space_between" }, {
|
||||||
|
ui.label({ text = noctalia.tr("timer.label"), color = "on_surface" }),
|
||||||
|
ui.select({
|
||||||
|
options = timers,
|
||||||
|
selectedIndex = selected,
|
||||||
|
onChange = function(index)
|
||||||
|
remember("timer", state.timers[tonumber(index) + 1])
|
||||||
|
end,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
for _, key in ipairs(OPTIONS) do
|
||||||
|
table.insert(rows, optionRow(key))
|
||||||
|
end
|
||||||
|
panel.render(ui.column({ gap = 12, padding = 16 }, rows))
|
||||||
|
end
|
||||||
|
|
||||||
|
local function fail(message)
|
||||||
|
unavailable = true
|
||||||
|
noctalia.notifyError("valw", message)
|
||||||
|
render()
|
||||||
|
end
|
||||||
|
|
||||||
|
function onOpen(_context)
|
||||||
|
unavailable = false
|
||||||
|
if not noctalia.commandExists("valw") then
|
||||||
|
fail(noctalia.tr("panel.unavailable"))
|
||||||
|
return
|
||||||
|
end
|
||||||
|
noctalia.runAsync("valw toolbar --state", function(result)
|
||||||
|
if result.exitCode ~= 0 then
|
||||||
|
fail(lastLine(result.stderr))
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local decoded = noctalia.json.decode(result.stdout)
|
||||||
|
if type(decoded) ~= "table" then
|
||||||
|
fail(noctalia.tr("panel.unavailable"))
|
||||||
|
return
|
||||||
|
end
|
||||||
|
state = decoded
|
||||||
|
render()
|
||||||
|
end)
|
||||||
|
end
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
# valw for Noctalia: a bar button, the toolbar as a Noctalia panel and a
|
||||||
|
# control-center tile. Thin clients of the valw CLI: the choices live in
|
||||||
|
# valw's own toolbar.toml (`valw toolbar --state` / `--set`), captures run
|
||||||
|
# through `valw toolbar --run` and `valw region`.
|
||||||
|
|
||||||
|
id = "elars/valw"
|
||||||
|
name = "valw"
|
||||||
|
version = "0.1.0"
|
||||||
|
plugin_api = 3
|
||||||
|
author = "elars"
|
||||||
|
license = "MIT"
|
||||||
|
dependencies = ["valw"]
|
||||||
|
tags = ["screenshot", "capture"]
|
||||||
|
icon = "camera"
|
||||||
|
description = "macOS-style screenshots with valw: a bar button, a toolbar panel and a control-center tile."
|
||||||
|
|
||||||
|
# Left click: the toolbar panel. Right click: a region shot.
|
||||||
|
[[widget]]
|
||||||
|
id = "bar"
|
||||||
|
entry = "bar.luau"
|
||||||
|
|
||||||
|
[[panel]]
|
||||||
|
id = "toolbar"
|
||||||
|
entry = "panel.luau"
|
||||||
|
width = 360
|
||||||
|
height = 220
|
||||||
|
placement = "attached"
|
||||||
|
position = "auto"
|
||||||
|
open_near_click = true
|
||||||
|
|
||||||
|
# Click: a region shot. Right click: the toolbar panel.
|
||||||
|
[[shortcut]]
|
||||||
|
id = "tile"
|
||||||
|
entry = "shortcut.luau"
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
--!nonstrict
|
||||||
|
-- valw control-center tile. Click: the control center closes, then a
|
||||||
|
-- region shot (so it isn't in it). Right click: the toolbar panel.
|
||||||
|
|
||||||
|
local PANEL = "elars/valw:toolbar"
|
||||||
|
|
||||||
|
local function lastLine(text)
|
||||||
|
local last = ""
|
||||||
|
for line in string.gmatch(text or "", "[^\n]+") do
|
||||||
|
last = line
|
||||||
|
end
|
||||||
|
return last
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Exit 3 is a cancel (Esc): not an error.
|
||||||
|
local function report(result)
|
||||||
|
if result.exitCode ~= 0 and result.exitCode ~= 3 then
|
||||||
|
noctalia.notifyError("valw", lastLine(result.stderr))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
shortcut.setLabel(noctalia.tr("tile.label"))
|
||||||
|
shortcut.setIcon("camera", "camera")
|
||||||
|
|
||||||
|
function onClick()
|
||||||
|
noctalia.runAsync("noctalia msg panel-close; sleep 0.3 && valw region", report)
|
||||||
|
end
|
||||||
|
|
||||||
|
function onRightClick()
|
||||||
|
noctalia.togglePanel(PANEL)
|
||||||
|
end
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"bar": { "tooltip": "Screenshot" },
|
||||||
|
"tile": { "label": "Screenshot" },
|
||||||
|
"panel": { "unavailable": "valw is not available" },
|
||||||
|
"mode": { "screen": "Screen", "region": "Region", "window": "Window", "zoom": "Zoom" },
|
||||||
|
"timer": { "label": "Timer", "off": "Off" },
|
||||||
|
"option": { "cursor": "Show cursor", "preview": "Show preview", "sound": "Play sound" }
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"bar": { "tooltip": "Ekran görüntüsü" },
|
||||||
|
"tile": { "label": "Ekran görüntüsü" },
|
||||||
|
"panel": { "unavailable": "valw bulunamadı" },
|
||||||
|
"mode": { "screen": "Ekran", "region": "Bölge", "window": "Pencere", "zoom": "Yakınlaştır" },
|
||||||
|
"timer": { "label": "Zamanlayıcı", "off": "Kapalı" },
|
||||||
|
"option": { "cursor": "İmleci göster", "preview": "Önizlemeyi göster", "sound": "Ses çal" }
|
||||||
|
}
|
||||||
Reference in new issue
Block a user