Execute prefs.js in a test

prefs.js had never run. It lives in its own process rather than in the
compositor, so enabling the extension in a nested shell does not reach it,
and every other check in this repo was blind to it -- it would first have
executed when someone opened the preferences and found it broken.

It builds under real Adw, and the hook-status line correctly reports all
nine registered events. The shell's ExtensionPreferences base class and its
gettext are stubbed rather than loaded: both resolve an extension by
walking the caller's URL up to a registered UUID, which needs the whole
extension manager for no gain here. The stub keeps the test on this
extension's own code.

The switch count is asserted against the schema's key count, so a setting
added without a row to change it fails the test.
This commit is contained in:
av
2026-08-09 19:32:32 +03:00
parent 5398f32826
commit 97783d43b8
2 changed files with 92 additions and 0 deletions
+87
View File
@@ -0,0 +1,87 @@
#!/usr/bin/gjs -m
// Builds the preferences window for real, under real Adw.
//
// prefs.js runs in its own process, not in the compositor, so a nested-shell
// check cannot reach it -- without this it is the one file in the extension
// that never executes until a user opens it and finds it broken.
//
// The shell's own base class and gettext are stubbed rather than loaded: they
// resolve an extension by walking the caller's URL up to a registered UUID,
// which needs the whole extension manager. Stubbing them keeps the test about
// this extension's code, which is the part that can be wrong.
//
// Run: gjs -m tests/test-prefs.js
import Gio from 'gi://Gio';
import GLib from 'gi://GLib';
import Adw from 'gi://Adw';
Adw.init();
const HERE = GLib.path_get_dirname(
GLib.filename_from_uri(import.meta.url)[0]);
const EXT = GLib.path_get_dirname(HERE);
const SHELL_PREFS = 'resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js';
const tmp = GLib.dir_make_tmp('ccs-prefs-XXXXXX');
const [, source] = GLib.file_get_contents(`${EXT}/prefs.js`);
GLib.file_set_contents(`${tmp}/stub.js`,
'export class ExtensionPreferences {}\nexport const gettext = s => s;\n');
GLib.file_set_contents(`${tmp}/prefs.js`,
new TextDecoder().decode(source).replace(SHELL_PREFS, './stub.js'));
const schemas = Gio.SettingsSchemaSource.new_from_directory(
`${EXT}/schemas`, Gio.SettingsSchemaSource.get_default(), false);
const { default: Prefs } = await import(`file://${tmp}/prefs.js`);
const prefs = new Prefs();
Object.defineProperty(prefs, 'path', { value: EXT }); // a getter upstream
prefs.getSettings = () => new Gio.Settings({
settings_schema: schemas.lookup(
'org.gnome.shell.extensions.claude-code-status', true),
});
const window = new Adw.PreferencesWindow();
prefs.fillPreferencesWindow(window);
const rows = [];
const walk = widget => {
for (let c = widget.get_first_child?.(); c; c = c.get_next_sibling()) {
const type = c.constructor.$gtype.name;
if (type === 'AdwSwitchRow' || type === 'AdwActionRow')
rows.push({ type, title: c.title, subtitle: c.subtitle });
walk(c);
}
};
walk(window);
let failures = 0;
function check(name, condition, detail = '') {
if (!condition)
failures++;
print(`${condition ? 'ok ' : 'FAIL'} ${name}${detail ? ` (${detail})` : ''}`);
}
for (const row of rows)
print(` ${row.type.replace('Adw', '').padEnd(10)} ${row.title}`);
// One switch per settings key, so a key added without a row is caught.
const keys = schemas.lookup('org.gnome.shell.extensions.claude-code-status', true)
.list_keys().length;
const switches = rows.filter(r => r.type === 'AdwSwitchRow').length;
check('a switch for every settings key', switches === keys, `${switches} of ${keys}`);
// The hook status line is the reason this page is worth opening at all: a
// silent panel looks the same whether nothing runs or nothing is installed.
const status = rows.find(r => r.title === 'Status');
check('hook status reported', !!status?.subtitle, status?.subtitle);
check('status names the events it found',
/SessionStart|Not installed/.test(status?.subtitle ?? ''), status?.subtitle);
const dir = rows.find(r => r.title === 'State directory');
check('state directory shown', (dir?.subtitle ?? '').endsWith('claude-code-status'),
dir?.subtitle);
GLib.spawn_command_line_sync(`rm -rf ${tmp}`);
print(failures ? `\n${failures} failure(s)` : '\nall passed');
imports.system.exit(failures ? 1 : 0);