mirror of
https://github.com/saphid/frame-control.git
synced 2026-10-06 02:00:19 +02:00
Don't break startup at Games links, and reload titles in the iPhone app too
The fix review found the Games reload added to showPage() read `link`, which the page declares later: opening Frame Control at #games, #library, #sideloaded or #getgames threw at startup and skipped the rest of the setup. And the iPhone app never watches the connection, so `link.s` stayed null and titles still never reloaded there. The reload now runs only when navigating to Games (the hashchange), where the first load is already done, and doesn't depend on connection state; a failed load shows its error in the list as Refresh did. tests/test_page_startup.py runs the real showPage() at every page and section link with everything declared later still uninitialised, and fails on the old line. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
79dda60bde
commit
8e8c24f370
2 files changed
+59
-3
No files matched your search
@@ -0,0 +1,55 @@
|
||||
"""The page calls showPage() while its first script is still running, before names declared
|
||||
later (in that script or the second one) exist. Touching one of them there throws, and the
|
||||
rest of the page's setup never runs: opening Frame Control at #games did exactly that."""
|
||||
import json
|
||||
import pathlib
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import unittest
|
||||
|
||||
ROOT = pathlib.Path(__file__).resolve().parents[1]
|
||||
STUBS = ['$', 'toggleLive', 'scrollToY', 'loadMacView', 'loadPanels', 'loadPanelSwitcher', 'loadTitles']
|
||||
|
||||
RUN = r'''
|
||||
const vm = require('vm');
|
||||
const {code, hashes} = JSON.parse(require('fs').readFileSync(0, 'utf8'));
|
||||
const failures = [];
|
||||
for (const hash of hashes) {
|
||||
const context = vm.createContext({
|
||||
location: {hash}, document: {querySelectorAll: () => [], title: ''}, live: false,
|
||||
});
|
||||
try { vm.runInContext(code, context); }
|
||||
catch (e) { failures.push(`${hash}: ${e.message}`); }
|
||||
}
|
||||
console.log(JSON.stringify(failures));
|
||||
'''
|
||||
|
||||
|
||||
@unittest.skipUnless(shutil.which('node'), 'Node runs the page code')
|
||||
class PageStartup(unittest.TestCase):
|
||||
def test_opening_any_page_or_section_at_startup_runs(self):
|
||||
page = (ROOT / 'ui/index.html').read_text(encoding='utf-8')
|
||||
first, second = re.findall(r'<script>(.*?)</script>', page, re.S)[:2]
|
||||
tables = first[first.index('const PAGES ='):first.index('let page =')]
|
||||
start = first.index('function showPage(')
|
||||
show = first[start:first.index('\n}\n', start) + 3]
|
||||
# Everything declared after the startup call is still uninitialised when it runs.
|
||||
call = re.search(r'^showPage\(\);', first, re.M).end()
|
||||
later = re.findall(r'^(?:const|let)\s+(\w+)', first[call:] + second, re.M)
|
||||
later = [n for n in dict.fromkeys(later) if n not in STUBS and n not in ('PAGES', 'SECTION_PAGE', 'page')]
|
||||
self.assertIn('link', later) # the name that broke #games
|
||||
stubs = ''.join(f'function {n}() {{ return {{ classList: {{ toggle() {{}} }}, scrollIntoView() {{}} }}; }}\n'
|
||||
for n in STUBS if n != '$')
|
||||
code = ('const $ = id => id === "nowhere" ? null : { classList: { toggle() {} }, scrollIntoView() {} };\n' + stubs + tables + 'let page = "home";\n' + show +
|
||||
'showPage();\n' + ''.join(f'let {n};\n' for n in later))
|
||||
hashes = ['', '#home', '#games', '#android', '#tools', '#settings', '#devices', '#nowhere']
|
||||
hashes += ['#' + k for k in re.findall(r'(\w+): "', tables)]
|
||||
r = subprocess.run(['node', '-e', RUN], input=json.dumps({'code': code, 'hashes': hashes}),
|
||||
capture_output=True, text=True)
|
||||
self.assertEqual(r.returncode, 0, r.stderr)
|
||||
self.assertEqual(json.loads(r.stdout), [])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
+4
-3
@@ -3827,7 +3827,7 @@ const SECTION_PAGE = { view: "home", device: "home", shots: "home", comfort: "ho
|
||||
transfer: "tools", apps: "tools", power: "tools", macview: "tools", media: "tools", panels: "tools",
|
||||
privacy: "settings", artworkSettings: "settings", assistant: "settings" };
|
||||
let page = "home";
|
||||
function showPage() {
|
||||
function showPage(e) {
|
||||
const id = location.hash.slice(1);
|
||||
const next = PAGES.includes(id) ? id : SECTION_PAGE[id] || "home";
|
||||
if (next !== page && page === "home" && live) toggleLive(false); // don't stream video nobody is watching
|
||||
@@ -3840,8 +3840,9 @@ function showPage() {
|
||||
const section = !PAGES.includes(id) && id && $(id);
|
||||
if (section) section.scrollIntoView(); else scrollToY(0);
|
||||
if (page === "tools") { loadMacView(); loadPanels(); }
|
||||
// Another computer may have added or removed a title: phones have no Refresh button.
|
||||
if (page === "games" && prev !== "games" && link.s && link.s.phase === "connected") loadTitles();
|
||||
// Another computer may have added or removed a title: phones have no Refresh button. Only
|
||||
// on navigating here (e: the hashchange); at startup the first load is already on its way.
|
||||
if (e && page === "games" && prev !== "games") loadTitles();
|
||||
}
|
||||
window.addEventListener("hashchange", showPage);
|
||||
// While a text field has focus, phones hide the bottom tab bar (see body.typing in the CSS).
|
||||
|
||||
Reference in new issue
Block a user