Files
Classroom_Desktop/tests/test-classroom-desktop-layout.sh
T
ekklesiaandClaude Opus 5 a5c7d1d057 fix: an application a device does not offer does not start either
Taking the launcher off the bar is half of a feature switch. The command
has to refuse as well, or a device that does not offer an application can
still be talked into running it by a stale desktop entry, a leftover
window rule or a technician's muscle memory.

Wonder Ink already did this. Parent Call now does the same, through the
same helper and with the same default: absent means yes, so every
existing room is unaffected.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-20 21:19:41 -05:00

231 lines
8.6 KiB
Bash
Executable File

#!/bin/bash
# Which applications a device offers, and the bar that comes out of it.
#
# Two halves. The first is the decision — one setting per application, and
# never the shape of the machine. The second actually RUNS the layout
# script the package ships, in node, with Plasma's API stubbed, so the
# assertions are about the real file rather than about a copy of its text
# that could drift away from it.
set -uo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
PYTHONPATH="$ROOT/lib" python3 - "$ROOT" <<'PY'
import importlib.util
import json
import os
import shutil
import subprocess
import sys
import tempfile
from importlib.machinery import SourceFileLoader
from pathlib import Path
root = Path(sys.argv[1])
loader = SourceFileLoader("layout", str(root / "scripts/classroom-desktop-layout"))
spec = importlib.util.spec_from_loader("layout", loader)
module = importlib.util.module_from_spec(spec)
loader.exec_module(module)
LAYOUT = root / "config/plasma/lookandfeel/contents/layouts/org.kde.plasma.desktop-layout.js"
passed = failed = 0
def check(label, condition, detail=""):
global passed, failed
if condition:
print(f" ok {label}")
passed += 1
else:
print(f" FAIL {label}")
if detail:
print(f" {detail}")
failed += 1
BLOCK = """/* CLASSROOM-FEATURES-BEGIN */
var WONDERINK = true;
var DVD = true;
var PARENT_CALL = true;
/* CLASSROOM-FEATURES-END */"""
print("1. one line per application, rewritten in place")
check("a feature can be switched off",
"var WONDERINK = false;" in module.rewrite(BLOCK, "WONDERINK", False))
check("and back on again",
"var WONDERINK = true;"
in module.rewrite(module.rewrite(BLOCK, "WONDERINK", False),
"WONDERINK", True))
check("switching one leaves the others alone",
"var DVD = true;" in module.rewrite(BLOCK, "WONDERINK", False)
and "var PARENT_CALL = true;" in module.rewrite(BLOCK, "WONDERINK", False))
check("rewriting twice changes nothing the second time",
module.rewrite(module.rewrite(BLOCK, "DVD", False), "DVD", False)
== module.rewrite(BLOCK, "DVD", False))
print("2. the shape of the machine never decides what it is for")
# This is the architecture rule, and it is the one worth a test: a laptop
# must be able to run children's church and a tower must be able to be a
# Bible study.
module.has_optical_drive = lambda: True
laptop_with_ink = module.wanted({"DEVICE_TYPE": "laptop"})
check("a laptop still offers Wonder Ink unless told otherwise",
laptop_with_ink["WONDERINK"] is True, str(laptop_with_ink))
check("a laptop still offers Parent Call unless told otherwise",
laptop_with_ink["PARENT_CALL"] is True)
desktop_no_ink = module.wanted(
{"DEVICE_TYPE": "desktop", "WONDERINK_ENABLED": "false"})
check("a desktop can have Wonder Ink taken away",
desktop_no_ink["WONDERINK"] is False)
check("and keeps everything else",
desktop_no_ink["DVD"] is True and desktop_no_ink["PARENT_CALL"] is True)
check("an existing room that has never heard of these keeps them all",
all(module.wanted({}).values()), str(module.wanted({})))
print("3. a drive can only ever take the player away")
module.has_optical_drive = lambda: False
check("no drive means no player, whatever the setting says",
module.wanted({"DVD_ENABLED": "true"})["DVD"] is False)
module.has_optical_drive = lambda: True
check("a drive plus DVD_ENABLED=false is still no player",
module.wanted({"DVD_ENABLED": "false"})["DVD"] is False)
check("a drive plus DVD_ENABLED=true is the player",
module.wanted({"DVD_ENABLED": "true"})["DVD"] is True)
print("4. the script rewrites the layout the package actually ships")
module.has_optical_drive = lambda: True
def run(config):
"""Run the real script over a copy of the real layout."""
work = Path(tempfile.mkdtemp(prefix="classroom-layout-"))
copy = work / "layout.js"
shutil.copy(LAYOUT, copy)
module.read_device_config = lambda: config
module.main.__globals__["sys"].argv = ["classroom-desktop-layout", str(copy)]
module.main()
return copy.read_text(), work
text, work = run({})
check("the shipped layout carries the feature block",
module.MARKER in text)
check("and an unconfigured machine keeps every application",
"var WONDERINK = true;" in text and "var DVD = true;" in text
and "var PARENT_CALL = true;" in text)
print("5. the bar that comes out, evaluated in node")
STUB = r"""
const fs = require("fs");
const captured = {widgets: [], config: {}};
let current = null;
function makeWidget(type) {
const w = {
type: type,
currentConfigGroup: [],
writeConfig: function (k, v) { captured.config[type + "." + k] = String(v); },
reloadConfig: function () {}
};
captured.widgets.push(type);
return w;
}
globalThis.desktops = () => [makeWidget("desktop")];
globalThis.Panel = function () {
this.addWidget = makeWidget;
return this;
};
eval(fs.readFileSync(process.argv[2], "utf8"));
console.log(JSON.stringify(captured));
"""
def bar(config):
text, work = run(config)
stub = work / "stub.js"
stub.write_text(STUB)
result = subprocess.run(
["node", str(stub), str(work / "layout.js")],
capture_output=True, text=True, timeout=60,
)
if result.returncode != 0:
return None, result.stderr
return json.loads(result.stdout), ""
if shutil.which("node") is None:
print(" SKIP node is not installed; the layout was not evaluated")
else:
LAUNCH = "org.kde.plasma.icontasks.launchers"
INK = "applications:ekklesia-wonderink.desktop"
WEB = "applications:google-chrome.desktop"
DISC = "applications:ekklesia-dvd.desktop"
every, why = bar({})
check("a classroom gets Wonder Ink, the browser and the player, in order",
every and every["config"].get(LAUNCH) == f"{INK},{WEB},{DISC}",
(every or {}).get("config", {}).get(LAUNCH, why))
check("and Parent Call in the corner",
every and "org.kde.plasma.quicklaunch" in every["widgets"])
no_ink, why = bar({"WONDERINK_ENABLED": "false"})
check("a Bible study desktop has the browser first and no gap",
no_ink and no_ink["config"].get(LAUNCH) == f"{WEB},{DISC}",
(no_ink or {}).get("config", {}).get(LAUNCH, why))
module.has_optical_drive = lambda: False
bible, why = bar({"WONDERINK_ENABLED": "false",
"PARENT_CALL_ENABLED": "false",
"DEVICE_TYPE": "laptop"})
check("the laptop's bar is the browser alone",
bible and bible["config"].get(LAUNCH) == WEB,
(bible or {}).get("config", {}).get(LAUNCH, why))
check("with no Parent Call button on it",
bible and "org.kde.plasma.quicklaunch" not in bible["widgets"],
str((bible or {}).get("widgets")))
check("and the status corner is untouched",
bible and "org.kde.plasma.volume" in bible["widgets"]
and "org.kde.plasma.digitalclock" in bible["widgets"]
and "org.kde.plasma.networkmanagement" in bible["widgets"])
module.has_optical_drive = lambda: True
ink_laptop, why = bar({"DEVICE_TYPE": "laptop"})
check("a laptop taking children's church gets the same bar as a tower",
ink_laptop and ink_laptop["config"].get(LAUNCH) == f"{INK},{WEB},{DISC}",
(ink_laptop or {}).get("config", {}).get(LAUNCH, why))
print("6. the bar cannot be rearranged by dragging")
check("sorting is off, which is what refuses a reordering drop",
every and every["config"].get(
"org.kde.plasma.icontasks.sortingStrategy") == "0",
str((every or {}).get("config", {}).get(
"org.kde.plasma.icontasks.sortingStrategy")))
check("and launchers stay ahead of running windows",
every and every["config"].get(
"org.kde.plasma.icontasks.separateLaunchers") == "true")
print("7. a disabled application does not start either")
# Taking the launcher off the bar is half of it. The command itself has
# to refuse, so a device that does not offer an application cannot be
# talked into running it by a stale desktop entry or a technician's
# muscle memory.
for script, setting in (("classroom-wonderink", "WONDERINK_ENABLED"),
("classroom-parent-call", "PARENT_CALL_ENABLED")):
text = (root / "scripts" / script).read_text()
check(f"{script} refuses when {setting} is false",
setting in text and "not starting" in text)
check(f"{script} defaults to starting, so existing rooms are unaffected",
'default=True' in text or 'feature(' in text)
print()
print(f"{passed} passed, {failed} failed")
raise SystemExit(1 if failed else 0)
PY