Files
NexusOS/tests/test_kde_theme.py
janvanwan 42eaed647a feat: sync with upstream — v1.2.0, in-app updates, Projects, modules
Brings the public tree back in line with the development repo after several
weeks of drift caused by a stale publish include list.

New:
- In-app update path: GET /update/check compares the checkout against
  origin/main and POST /update/apply runs `ncp upgrade` detached (pull,
  rebuild, restart). The sidebar shows the version, checks on click, and
  offers an "update available" pill.
- Projects: a project workspace groups chats and RAG documents, with
  per-project instructions and document retrieval scoped to the active
  project. Replaces the standalone Documents page.
- modules/: auto-discovered feature plugins (mail, network) with their
  frontend counterparts and tests.
- Memory curation runs in-process (synapse/memory/curator.py) on the chat
  model when a conversation goes idle. The separate memory service on :8001
  is gone, along with the launcher lines that started it.

Also: the KDE theme, panel and Promethean terminal assets, the full test
suite, and VERSION 1.2.0.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-08-25 09:13:55 -05:00

215 lines
10 KiB
Python

"""Guards for the KDE theme's silent-failure modes.
Every check here corresponds to something that broke without producing an error
message. They are static reads of the scripts and packages because the failures
are configuration-shaped -- there is nothing to import and nothing that raises.
"""
import json
import re
from pathlib import Path
REPO = Path(__file__).resolve().parents[1]
KDE = REPO / "assets" / "themes" / "KDE"
INSTALLER = KDE / "install-plasma.sh"
LNF = KDE / "look-and-feel" / "com.nexusos.desktop"
ICONS = REPO / "assets" / "themes" / "NexusOS-icons"
THEME_INSTALLER = REPO / "assets" / "themes" / "install-theme.sh"
def test_look_and_feel_is_copied_never_symlinked():
"""KPackage skips symlinked package directories without a word, so a
symlinked Global Theme simply never appears in System Settings."""
text = INSTALLER.read_text()
assert "cp -rL" in text, "look-and-feel/wallpaper must be copied into place"
for line in text.splitlines():
if line.strip().startswith("ln -s"):
assert "look-and-feel" not in line and "wallpapers" not in line, \
f"KPackage package dir must not be symlinked: {line.strip()}"
def test_plasmashell_restart_is_detached_from_the_callers_stdout():
"""The restarted shell outlives the script. Inheriting stdout keeps the
caller's pipe open forever, which hangs `ncp restore` after a successful
apply."""
text = INSTALLER.read_text()
restart = [l for l in text.splitlines()
if "kstart5 plasmashell" in l and not l.strip().startswith("#")]
assert restart, "no plasmashell restart found"
for line in restart:
assert "setsid" in line, f"restart not detached: {line.strip()}"
assert "</dev/null" in line and ">/dev/null" in line, \
f"restart still holds the caller's stdio: {line.strip()}"
def test_splash_renders_without_the_stage_signal():
"""A splash gated on `stage == 2` shows a blank coloured screen if that
signal never arrives -- what `ksplashqml --test` does."""
qml = (LNF / "contents" / "splash" / "Splash.qml").read_text()
content = qml[qml.index("id: content"):]
body = content[:content.index("OpacityAnimator")]
assert "opacity: 0" not in body, "splash content starts invisible"
assert "introAnimation" not in qml, "visibility still gated on a stage change"
def test_sddm_theme_is_configured_in_exactly_one_place():
"""boot-branding.sh and install-plasma.sh both deploy the SDDM theme; two
different config files meant the setting could disagree with itself."""
for script in (INSTALLER, REPO / "bin" / "boot-branding.sh"):
text = script.read_text()
stray = re.findall(r"/etc/sddm\.conf(?!\.d)", text)
assert not stray, f"{script.name} writes bare /etc/sddm.conf; use conf.d"
def test_restore_desktop_stage_covers_plasma_as_well_as_xfce():
"""The desktop stage used to bail out entirely without xfconf-query, so a
Plasma box got no theme back from `ncp restore` at all."""
text = (REPO / "bin" / "restore-linux.sh").read_text()
assert "install-plasma.sh" in text, "restore never invokes the Plasma installer"
assert "--no-sddm" in text, "restore should leave SDDM to boot-branding.sh"
# The XFCE check must not be able to skip the Plasma branch or the branding.
assert "exit 0\nfi\n" not in text.split("desktop stage")[1][:900], \
"XFCE guard still exits the whole stage"
def test_global_theme_package_is_well_formed():
meta = json.loads((LNF / "metadata.json").read_text())
assert meta["KPlugin"]["Id"] == LNF.name, "package Id must match its directory"
assert "Plasma/LookAndFeel" in meta["KPlugin"]["ServiceTypes"]
defaults = (LNF / "contents" / "defaults").read_text()
# Every component the Global Theme selects has to exist in the repo.
assert "ColorScheme=NexusOS" in defaults
assert (KDE / "plasma" / "NexusOS").is_dir()
assert (KDE / "aurorae" / "NexusOS").is_dir()
assert (KDE / "wallpaper" / "NexusOS" / "metadata.json").is_file()
assert "Theme=com.nexusos.desktop" in defaults, "splash not wired to this package"
def test_patterned_backgrounds_are_referenced_as_raster_not_svg():
"""QtSvg is SVG Tiny 1.2 and has no <pattern>, so the brushed-metal and
machine-line textures vanish and the gradient renders flat. Every QML that
shows that artwork must load the rasterized PNG."""
qml_files = [
KDE / "sddm" / "NexusOS-QML" / "Main.qml",
LNF / "contents" / "splash" / "Splash.qml",
]
for f in qml_files:
# Only the source: lines -- the comments deliberately mention the SVG,
# since that is the file you edit and re-rasterize.
sources = [l for l in f.read_text().splitlines()
if "source:" in l and not l.strip().startswith("//")]
bg = [l for l in sources if "background" in l]
assert bg, f"{f.name} loads no background"
for line in bg:
assert "background.png" in line, \
f"{f.name} loads a patterned SVG through QtSvg: {line.strip()}"
def _defaults_sections():
"""Parse the look-and-feel defaults into {section: {key: value}}."""
out, section = {}, None
for line in (LNF / "contents" / "defaults").read_text().splitlines():
line = line.strip()
if line.startswith("["):
section = line
out[section] = {}
elif line and "=" in line and section:
k, v = line.split("=", 1)
out[section][k] = v
return out
def test_lock_screen_theme_names_a_look_and_feel_package():
"""Plasma 5.27 draws the lock screen from a look-and-feel package, and
[Greeter]Theme names that package. A bare theme name (the old "NexusOS")
is not one, so Plasma falls back to Breeze without saying anything."""
greeter = _defaults_sections()["[kscreenlockerrc][Greeter]"]
assert greeter["Theme"].endswith(".desktop"), \
f"lock theme must be a look-and-feel package id, got {greeter['Theme']!r}"
installer = INSTALLER.read_text()
lock_lines = [l for l in installer.splitlines()
if "kscreenlockerrc" in l and "--key Theme" in l]
assert lock_lines, "installer never sets the lock screen theme"
for line in lock_lines:
assert ".desktop" in line, f"installer sets a non-package lock theme: {line.strip()}"
# And the lock wallpaper is the raster metal background.
assert "background.png" in installer, "lock wallpaper not set to the metal raster"
def _index_theme():
"""Parse index.theme into (header dict, list of declared directories)."""
header, section, dirs = {}, None, []
for line in (ICONS / "index.theme").read_text().splitlines():
line = line.strip()
if line.startswith("[") and line != "[Icon Theme]":
section = line.strip("[]")
dirs.append(section)
elif "=" in line and not line.startswith("#") and section is None:
k, v = line.split("=", 1)
header[k.strip()] = v.strip()
return header, dirs
def test_every_declared_icon_directory_exists():
"""index.theme declared 124 directories against 21 real ones, most copied
from Papirus. A declared-but-missing directory is dead weight the icon
loader walks on every lookup."""
_, dirs = _index_theme()
missing = [d for d in dirs if not (ICONS / d).is_dir()]
assert not missing, f"index.theme declares directories that do not exist: {missing}"
listed = set(_index_theme()[0].get("Directories", "").split(","))
assert listed == set(dirs), "Directories= and the [section] list disagree"
def test_icon_theme_inherits_a_recolourable_parent():
"""Papirus hardcodes its blues, so nothing the colour scheme does can reach
them and the un-themed surface stayed blue forever. Breeze's icons carry
ColorScheme-* classes that Plasma recolours from the active scheme."""
header, _ = _index_theme()
parents = [p.strip() for p in header["Inherits"].split(",")]
assert not parents[0].lower().startswith("papirus"), \
"primary parent cannot recolour from the colour scheme"
assert parents[0] == "breeze-dark", f"expected breeze-dark first, got {parents[0]!r}"
assert parents[-1] == "hicolor", "hicolor must remain the last-resort fallback"
def test_icon_theme_is_installed_where_qt_looks():
"""~/.icons is the GTK/XFCE legacy path. Qt/KF5 searches XDG data dirs only,
so installing there alone meant Plasma never found the theme and every icon
fell back to Breeze without a word."""
text = THEME_INSTALLER.read_text()
links = [l for l in text.splitlines()
if l.strip().startswith("link ") and "NexusOS-icons" in l]
assert any(".local/share/icons" in l for l in links), \
"icon theme is not installed to an XDG data dir; Plasma will not see it"
assert any(".icons/NexusOS" in l for l in links), \
"dropping ~/.icons would break the XFCE session and GTK apps"
def test_inherits_check_reads_only_the_primary_parent():
"""The installer compared the whole comma-separated Inherits value against a
directory name, so a valid multi-parent list warned that an installed
fallback was missing."""
text = THEME_INSTALLER.read_text()
inh = [l for l in text.splitlines() if "INH=" in l and "Inherits" in l]
assert inh, "inheritance check not found"
assert any("-f1" in l for l in inh), \
"inheritance check still treats the whole Inherits list as one theme name"
def test_panel_layout_is_portable():
"""The panel script sets the launcher icon by absolute path. Hardcoding
this box's home would give any other clone or user a missing icon, so the
path is a placeholder the installer substitutes."""
js = (KDE / "panel-layout.js").read_text()
assert "/home/" not in js, "panel-layout.js hardcodes a home directory"
assert "__NEXUS_ROOT__" in js, "no placeholder for the repo path"
installer = INSTALLER.read_text()
assert "__NEXUS_ROOT__" in installer, "installer never substitutes the repo path"
# Rewriting the panel wholesale on every restore would wipe later additions.
assert "PANEL_MARKER" in installer, "panel layout is not guarded by a marker"