Files
hell 119ec9edfa test: parse the inline template JavaScript and the translation files
Most of the panel's JavaScript lives inline in templates/*.html, and every UI
string lives in translations/*.json. Neither is covered: a syntax error in a
template only shows up as a silently dead page in the browser, and a broken
translation file makes the panel render raw keys - `load_translations` catches
the JSONDecodeError and leaves the dictionary empty.

The JSON one is the easier trap. Two branches that both append keys at the end
of a translation file merge without a conflict and drop the comma that used to
end the block, which git has no reason to flag. The file stays plausible,
5 files x ~700 keys, and the failure surfaces as English keys in the UI.

test_template_js.py extracts every <script> block that has no src, strips the
Jinja tags (`{{ ... }}` -> a literal, `{% ... %}` -> nothing), and runs
`node --check` over the result; it skips itself when node is missing. The
second test json.loads every translations/*.json.

Ran against the current tree: both pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-08 22:18:19 +07:00

83 lines
3.4 KiB
Python

"""Syntax check for the JavaScript that lives inside the Jinja templates.
The page tests assert that particular handlers and elements are present, which
says nothing about the file still parsing: a merge that lands two blocks at the
same anchor can swallow a closing brace and every one of those assertions still
passes. `node --check` catches exactly that, so it runs here whenever node is
available (it is on the CI image; the test skips without it).
"""
import glob
import json
import os
import re
import shutil
import subprocess
import tempfile
import unittest
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
TEMPLATES = os.path.join(ROOT, 'templates')
INLINE_SCRIPT_RE = re.compile(r'<script(?![^>]*\bsrc=)[^>]*>(.*?)</script>', re.S)
def inline_js(html):
"""Inline script bodies with the Jinja placeholders neutralised: `{{ x }}`
becomes a bare identifier (valid both as a value and inside a string) and
`{% ... %}` tags drop out."""
js = '\n'.join(INLINE_SCRIPT_RE.findall(html))
js = re.sub(r'\{\{.*?\}\}', 'JINJA', js, flags=re.S)
return re.sub(r'\{%.*?%\}', '', js, flags=re.S)
class TranslationFileTests(unittest.TestCase):
"""The same class of silent breakage on the data side: merging two branches
that both appended keys can drop the comma between the blocks, and a
translation file that no longer parses only shows up as raw keys in the UI
(the loader logs the error and serves an empty table)."""
def test_every_translation_file_parses(self):
files = sorted(glob.glob(os.path.join(ROOT, 'translations', '*.json')))
self.assertGreaterEqual(len(files), 5, f'only found {files}')
tables = {}
for path in files:
name = os.path.basename(path)
with open(path, encoding='utf-8') as f:
try:
tables[name] = json.load(f)
except json.JSONDecodeError as err:
self.fail(f'{name} does not parse: {err}')
self.assertGreater(len(tables[name]), 400, f'{name} looks truncated')
# english is the fallback every other language is filled from
for name, table in tables.items():
extra = sorted(set(table) - set(tables['en.json']))
self.assertEqual(extra, [], f'{name} has keys en.json does not: {extra}')
@unittest.skipUnless(shutil.which('node'), 'node is not installed')
class TemplateJsSyntaxTests(unittest.TestCase):
def test_every_template_script_parses(self):
checked = []
for path in sorted(glob.glob(os.path.join(TEMPLATES, '*.html'))):
with open(path, encoding='utf-8') as f:
js = inline_js(f.read())
if not js.strip():
continue
name = os.path.basename(path)
checked.append(name)
with tempfile.NamedTemporaryFile('w', suffix='.js', delete=False, encoding='utf-8') as tmp:
tmp.write(js)
tmp_path = tmp.name
try:
result = subprocess.run(['node', '--check', tmp_path], capture_output=True, text=True)
finally:
os.unlink(tmp_path)
self.assertEqual(result.returncode, 0, f'{name} does not parse:\n{result.stderr}')
# the extraction itself must keep working
self.assertIn('server.html', checked)
self.assertGreaterEqual(len(checked), 5, f'only {checked} carried inline JS')
if __name__ == '__main__':
unittest.main()