mirror of
https://github.com/saphid/frame-control.git
synced 2026-10-06 14:00:34 +02:00
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>
56 lines
2.7 KiB
Python
56 lines
2.7 KiB
Python
"""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()
|