Files
DankMaterialShell/quickshell/tests
Matt Jiang f27627ead2 powermenu: reboot into a chosen EFI boot entry (#3568)
* powermenu: derive shortcuts and extra actions from one source

The power menu matched keys with a switch that repeated the key hints
getPowerActionData already returns, plus a second key list for release.
Look the pressed key up against the hints of the visible actions instead,
so every action with a hint gets its shortcut and the two cannot drift.

Side effects:
- Ctrl+letter no longer triggers actions. Only Ctrl+P was excluded
  before, so Ctrl+R still rebooted. Ctrl combinations are navigation.
- U now triggers switch user when it is shown. Its hint already said U.
- Releasing a letter with no visible action no longer cancels a hold.

SessionService now owns the custom:N action ids as customPowerActions
and extraPowerActions, which the power menu and the launcher both use.
Actions resolve by index into that list instead of slicing the prefix.

* powermenu: add actions that reboot into a chosen efi boot entry

Settings → Power & Sleep gets a "Reboot to another OS" card that lists
the firmware's active EFI entries through efibootmgr. Each saved entry
becomes a "Reboot to <name>" action in the power menu and the launcher,
bound to 1-9 in the order it was added.

Picking one lists the entries again, and stops with a toast if the id no
longer carries the saved label. Otherwise it sets BootNext through pkexec
and reboots through SessionService, so custom reboot commands still apply.

BootEntryService reports why entries are unavailable: not booted in UEFI
mode, efibootmgr missing, or efibootmgr failing with its stderr. Saved
entries stay listed in settings whatever the status, so a synced config
on another machine can still remove them.

efibootmgr parsing and the picker's filtering live in BootEntries.js,
covered by tests/boot-entries.test.mjs.

* powermenu: ignore special keys in action shortcuts
2026-09-27 11:47:05 -04:00
..
2026-09-25 11:02:51 -04:00
2026-09-25 11:02:51 -04:00
2026-09-25 11:02:51 -04:00

Shell tests

make test-qml runs the shell JavaScript tests, Qt output-cycle tests and every QML fixture in qml/. It needs Python 3, Node.js, make, Qt 6 qmltestrunner, Quickshell, niri, Xvfb, dbus-daemon or dbus-broker, and the dbus_next Python module. The Nix development shell includes these dependencies on Linux.

The qml-tests pre-commit hook runs the same checks when shell code, tests or their configuration changes. CI runs that hook in the QML tests job. The other pre-commit job skips it to avoid running it twice.

prek run qml-tests --all-files
python3 quickshell/tests/run-qml.py logic
python3 quickshell/tests/run-qml.py qt
python3 quickshell/tests/run-qml.py lock
python3 quickshell/tests/run-qml.py widgets
python3 quickshell/tests/run-qml.py media

Set QMLTESTRUNNER to override the Qt 6 executable. Use --jobs 1 to run the suites sequentially.

Every file in qml/ runs. media-*.qml fixtures get a session bus and a fake MPRIS player with artwork, the rest run in the widgets suite. To run some fixtures on their own:

python3 quickshell/tests/run-surface-fixture.py quickshell/tests/qml/bar-content.qml quickshell/tests/qml/instance-routing.qml
python3 quickshell/tests/run-surface-fixture.py --mpris --artwork quickshell/tests/qml/media-lyrics.qml

The runner prints one line per fixture as it finishes and the full log of a failed one. A fixture fails on a nonzero exit, a missing FIXTURE_PASS, a FIXTURE_FAIL line or its 60 second cap. Half the available cores run at a time, up to eight; set DMS_FIXTURE_JOBS to change that.

The fixtures share one X server. Each fixture gets its own niri, Quickshell process, QML tree and config, cache, state and data directories, so nothing one fixture does is visible to another. The host is hidden too: HOME, XDG_DATA_DIRS and XDG_CONFIG_DIRS point into the temporary root, the locale is C.UTF-8, the timezone is UTC, and host DMS_*, QS_* and Qt theme and scale variables are dropped. The only desktop entries and icons a fixture sees are the ones in fixtures/share; add to it when a fixture needs an app or icon. Idle, lock and suspend timeouts are disabled. The runner does not connect to the live shell or session bus.

Writing a fixture

  • Wait on the condition, never on the clock. Poll with a 20 second deadline and a label, then assert. A pass returns as soon as the condition holds, so the budget costs nothing.
  • Start from the real precondition (player on the bus, window active, settings loaded), not a startup timer.
  • Settle layout with isPolishScheduled/waitForPolish on item.Window.window before reading geometry or clicking.
  • Keys only reach a window niri has activated. Wait for Window.active before keyClick.
  • A step Timer retries its checks until they pass or its deadline runs out, then moves on. Put every check before the step's mutations so a retry is safe. Give it an interval above 0: a repeating 0ms Timer never finishes under qs.
  • Assert behavior that would break on a regression. No pixel sampling, no size or locale matrices, no screenshots, no private state unless it is the only deterministic way to order a race.