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
45 lines
2.3 KiB
JavaScript
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');
|