Files
qbz/scripts/test_qt_kiosk_navigation.mjs
T
vicrodh b1dba198c9 feat(qt): kiosk profile release hardening
Integrate the Kiosk profile hardening round: 12 dedicated Kiosk views plus
22 shared opt-in hosts, shared navigation history with Back/Forward, Albums
as the default library tab, native paged Local Library (artists/tracks),
lightweight MyQBZ grid and detail, Ephemeral lifecycle, bounded lists and
artwork sized to the physical frame, static skeletons, 64px transport bar,
Settings touch reflow and a persistent Immersive exit.

Desktop keeps its prior behavior: every shared component defaults to the
desktop path and only opts into Kiosk behavior through explicit properties
(origin-relative ScrollMemory, viewport-bounded artwork requests).

Regression coverage: kiosk navigation and artwork Node tests added to the
runtime gate, scroll-origin fixtures for fixed and grouped rows, i18n
strings for all eight locales.

Verified by 750 release tests, six QML audits, Kiosk smokes at 800x480 and
1280x800 and a Desktop A/B with seven pixel-identical captures.

cc
2026-09-07 08:23:11 -06:00

45 lines
2.3 KiB
JavaScript

#!/usr/bin/env node
// Regressions in the production QML profile-switch and search restore handlers.
import assert from 'node:assert/strict';
import fs from 'node:fs';
import vm from 'node:vm';
const router = fs.readFileSync(new URL('../crates/qbz-qt/qml/shell/ContentRouter.qml', import.meta.url), 'utf8');
const search = fs.readFileSync(new URL('../crates/qbz-qt/qml/kiosk/KioskSearch.qml', import.meta.url), 'utf8');
function body(source, marker) {
const at = source.indexOf(marker);
assert.ok(at >= 0, marker);
const start = source.indexOf('{', at);
for (let depth = 1, i = start + 1; i < source.length; i++) {
if (source[i] === '{') depth++;
if (source[i] === '}' && --depth === 0) return source.slice(start + 1, i);
}
throw Error('unclosed handler');
}
const reports = [];
const context = vm.createContext({
root: {kiosk: false},
viewLoader: {item: {tab: 2, query: 'new desktop query'}},
QbzShell: {kioskProfile: true, currentView: 'search', reportNavState: (scope, json) => reports.push([scope, JSON.parse(json)])},
});
vm.runInContext(`function switched() {${body(router, 'function onKioskProfileChanged()')}}`, context);
context.switched();
assert.deepEqual(reports.pop(), ['search', {activeTab: '2', query: 'new desktop query'}]);
context.QbzShell.currentView = 'local';
context.viewLoader.item = {activeTab: 'artists', navigationStateJson: JSON.stringify({activeTab: 'artists', selectedGenres: ['Jazz']})};
context.switched();
assert.deepEqual(reports.pop(), ['local', {activeTab: 'artists', selectedGenres: ['Jazz']}]);
context.QbzShell.kioskProfile = false;
context.switched();
assert.equal(reports.length, 0, 'ordinary desktop activity must not invoke the capture seam');
const handler = search.match(/onDocChanged:\s*(.+)/)[1];
Object.assign(context, {restoringQuery: 'A', doc: {query: 'B', loading: false}});
vm.runInContext(handler, context);
assert.equal(context.restoringQuery, 'A');
context.doc = {query: 'A', loading: true};
vm.runInContext(handler, context);
assert.equal(context.restoringQuery, 'A', 'an early query echo still carries stale result rows');
context.doc.loading = false;
vm.runInContext(handler, context);
assert.equal(context.restoringQuery, '', 'completed or failed restores release the loading state');
console.log('Kiosk navigation: current desktop query/tab capture and asynchronous search restoration pass');