From c17cfcac9d986bbbb893cf76abc5fa219ea7079b Mon Sep 17 00:00:00 2001 From: SAPHID Date: Sun, 25 Oct 2026 09:13:42 +0000 Subject: [PATCH] Test Framey settings persistence across plugin lifecycle --- docs/frame-plugin-author.md | 2 +- docs/framey-loader.md | 6 ++++++ tests/e2e/test_framey.py | 12 ++++++++++++ .../rootfs/usr/local/lib/fakeframe/cef_shim.js | 10 ++++++++-- 4 files changed, 27 insertions(+), 3 deletions(-) diff --git a/docs/frame-plugin-author.md b/docs/frame-plugin-author.md index dac3542..115db92 100644 --- a/docs/frame-plugin-author.md +++ b/docs/frame-plugin-author.md @@ -49,7 +49,7 @@ none of these frontend controls has passed a worn-headset check yet. | `definePlugin` with `content`, `titleView` | Renders a React panel and optional title | Local browser with ProtonDB Badges; host unit test | | `PanelSection`, rows, buttons, toggles, dropdowns | Local touch-sized controls | Local browser and host unit test | | Optional `settings`, `frameyQuickActions` | Separate settings tab; up to eight actions with result text | Host unit test; these fields are Framey additions | -| `serverAPI.call` / `callable` | Calls only that plugin's Decky Python backend via the headset service | Fake Frame e2e for get/set setting; host unit test | +| `serverAPI.call` / `callable` | Calls only that plugin's Decky Python backend via the headset service | Fake Frame e2e for setting read/write across CEF calls, update, and reinstall; host unit test. Real Decky persistence remains unverified. | | `toaster.toast` | Visible toast inside the plugin panel; `dismiss` and optional `onClick` | Host unit test; no Steam system notification | | `routerHook.addRoute` / `removeRoute`, `Navigation.Navigate` / `NavigateBack` | Local routes inside this plugin panel only | Host unit test | | `routerHook.addPatch`, global components | No-op; cannot patch Steam routes | Not applicable | diff --git a/docs/framey-loader.md b/docs/framey-loader.md index c3b9c9e..e0e25ef 100644 --- a/docs/framey-loader.md +++ b/docs/framey-loader.md @@ -54,6 +54,12 @@ has no supported injection target in the Frame Control panel. The [browser and fake Frame evidence](evidence/framey-browser-2026-10-25.md) records exactly what was exercised. +The fake Frame integration test now writes a Decky setting through Framey's +scoped backend endpoint, reads it in a new CEF request, updates the plugin, +then checks it again. It also confirms removal blocks method calls and a +reinstall can read the retained setting. This tests the bridge and fixture; +real Decky settings persistence on a worn Frame is still unverified. + The in-headset **Open official store** view loads the full official catalogue. Search matches names, authors, and descriptions. Filters show non-root plugins or installed plugins, and Previous/Next buttons browse 20 results per page. diff --git a/tests/e2e/test_framey.py b/tests/e2e/test_framey.py index a6b1042..efe0719 100644 --- a/tests/e2e/test_framey.py +++ b/tests/e2e/test_framey.py @@ -109,13 +109,25 @@ u=h/'.config/systemd/user/frame-control-decky.service'; u.parent.mkdir(parents=T {'id':123,'method':'get_setting','args':['theme','dark']})['result'], 'dark') self.assertTrue(ok('POST', '/api/framey/method', {'id':123,'method':'set_setting','args':['theme','light']})['result']) + self.assertEqual(ok('POST', '/api/framey/method', + {'id':123,'method':'get_setting','args':['theme','dark']})['result'], 'light') StoreFixture.release = 1 job = finished(ok('POST', '/api/decky/plugin', {'action':'update','id':123,'acknowledged':True})) self.assertFalse(job.get('error'), job) self.assertEqual(ok('GET', '/api/decky')['plugins'][0]['version'], '1.1.0') + self.assertEqual(ok('POST', '/api/framey/method', + {'id':123,'method':'get_setting','args':['theme','dark']})['result'], 'light') job = finished(ok('POST', '/api/decky/plugin', {'action':'remove','id':123,'acknowledged':True})) self.assertFalse(job.get('error'), job) self.assertEqual(ok('GET', '/api/decky')['plugins'], []) self.assertEqual(ssh('test -e ~/.local/share/frame-control/decky/plugins/sample-panel && echo yes || echo no').strip(), 'no') + status, _, _ = api('POST', '/api/framey/method', + {'id':123,'method':'get_setting','args':['theme','dark']}) + self.assertEqual(status, 400) # No method calls while the plugin is removed. + job = finished(ok('POST', '/api/decky/plugin', + {'action':'install','id':123,'acknowledged':True})) + self.assertFalse(job.get('error'), job) + self.assertEqual(ok('POST', '/api/framey/method', + {'id':123,'method':'get_setting','args':['theme','dark']})['result'], 'light') diff --git a/tests/fakeframe/rootfs/usr/local/lib/fakeframe/cef_shim.js b/tests/fakeframe/rootfs/usr/local/lib/fakeframe/cef_shim.js index a7d03ce..34aa0d5 100644 --- a/tests/fakeframe/rootfs/usr/local/lib/fakeframe/cef_shim.js +++ b/tests/fakeframe/rootfs/usr/local/lib/fakeframe/cef_shim.js @@ -30,8 +30,14 @@ function build(steam) { const deckyBackend = {async call(route, plugin, method, ...args) { if (route !== 'loader/call_plugin_method' || plugin !== 'Sample Panel') throw Error('unexpected Decky plugin route'); - if (method === 'get_setting') return args[1]; - if (method === 'set_setting') return true; + const settings = (steam.decky_settings ||= {}); + const owned = (settings[plugin] ||= {}); + if (method === 'get_setting') + return Object.hasOwn(owned, args[0]) ? owned[args[0]] : args[1]; + if (method === 'set_setting') { + owned[args[0]] = args[1]; + return true; + } throw Error('method unavailable in fake Decky backend'); }}; const findShortcut = id => steam.shortcuts.find(s => s.appid === Number(id));