"""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, \ 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 , 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"