# -*- coding: utf-8 -*- """Host-side guard for issue #23's `live` kv contract. The issue (owner, 2026-09-07: "啥叫JS活儿?html写不了?" -- the front end is ours, so write it): a kv row that sums a row table -- the Reinforcement page's Strand count, Total IP and Aps -- must follow the table's rows IN THE BROWSER as they are edited, not only on the next page build / Run. The page is plain ES2017 in a static file with no JS test runner, so this suite checks the CONTRACT by reading the source, the same way `test_button_tooltips.py` reads `bundle.yaml` / `script.py`: the three pieces that wire the feature together must all exist and stay connected -- `renderKv` records the spec into `state.liveKv`, `recomputeLiveKv` rewrites the value cells, and `updateValue` (plus the row add/remove handlers) calls it whenever a key of the table changes. And the spec document must still describe the row shape (docs/pa-web-spec.md, "kv"). """ import io import os import re _EXT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) _REPO = os.path.dirname(_EXT) APP_JS = os.path.join(_REPO, 'tools', 'pa_web', 'static', 'app.js') SPEC_MD = os.path.join(_REPO, 'docs', 'pa-web-spec.md') def _read(path): with io.open(path, encoding='utf-8') as f: return f.read() def test_the_files_this_guards_still_exist(): """If the page moves, this suite must fail loudly rather than pass by reading nothing -- the test_button_tooltips.py discipline.""" assert os.path.isfile(APP_JS), 'missing {0}'.format(APP_JS) assert os.path.isfile(SPEC_MD), 'missing {0}'.format(SPEC_MD) def test_app_js_declares_the_live_kv_mechanism(): src = _read(APP_JS) # renderKv records rows into state.liveKv, keyed by the spec's table... assert 'state.liveKv' in src assert 'function recomputeLiveKv' in src # ...the value formatting helper exists, and row discovery is the SAME # helper the editable table itself uses (one idea of "present row"). assert 'function fmtLive' in src assert 'rowIds(live.table)' in src def test_update_value_recomputes_live_kv_rows(): """`updateValue` -- every cell edit's one funnel -- must call `recomputeLiveKv`, not just `recomputeDerived`. Sliced to updateValue's own body so a stray later call elsewhere cannot satisfy the guard.""" src = _read(APP_JS) start = src.index('function updateValue') end = src.find('\n\n // ---', start) assert end != -1, 'could not find the end of updateValue\'s body' body = src[start:end] assert 'recomputeLiveKv(' in body, \ 'updateValue no longer calls recomputeLiveKv after recomputeDerived' def test_spec_documents_live_under_kv(): md = _read(SPEC_MD) start = md.index('### `kv`') nxt = md.find('\n### ', start + 1) assert nxt != -1, 'no section follows kv in pa-web-spec.md' section = md[start:nxt] assert re.search(r'\[label, value, live\]', section) assert '"live"' in section assert '"terms"' in section and '"format"' in section assert '"unknown"' in section # the contract names the trigger: any key of the table, add/remove row # included, and JS-off correctness (server value is the first paint). assert '.present' in section assert 'JS off' in section