diff options
Diffstat (limited to 'packages/meshbay-hub/tests/test_hook_ordering.py')
| -rw-r--r-- | packages/meshbay-hub/tests/test_hook_ordering.py | 112 |
1 files changed, 112 insertions, 0 deletions
diff --git a/packages/meshbay-hub/tests/test_hook_ordering.py b/packages/meshbay-hub/tests/test_hook_ordering.py new file mode 100644 index 0000000..cd9a11e --- /dev/null +++ b/packages/meshbay-hub/tests/test_hook_ordering.py @@ -0,0 +1,112 @@ +""" +A hook cannot depend on one declared below it. + +`const a = useCallback(fn, [b])` evaluates `[b]` where it is written. If `b` is +another `const` further down the component, it is still in its temporal dead +zone and the array throws `ReferenceError: Cannot access 'b' before +initialization` — during render, every render, before anything the component +does can run. + +The symptom is the component simply not appearing. Clicking a video did +nothing at all: no picture, no error on screen, nothing in the node's log +because nothing was ever requested. It reached production. + +Nothing else catches it. `node --check` validates syntax and this is +well-formed. The MSE harness runs the same functions but extracts them into a +list of its own choosing, so it *reorders* them and cannot see an ordering +fault — it is now ordered by position in the file for that reason, and this +test covers the case directly. +""" + +import re +from pathlib import Path + +import pytest + +STATIC = Path(__file__).resolve().parents[1] / "src" / "meshbay_hub" / "static" +APP = STATIC / "app.js" + +pytestmark = pytest.mark.skipif(not APP.exists(), reason="SPA sources unavailable") + +# `const NAME = useCallback(` / `useMemo(` — the declarations that both define a +# binding and take a dependency array. +DECL = re.compile(r"^ const (\w+) = (?:useCallback|useMemo)\(", re.M) +# The closing `}, [a, b]);` of such a declaration. +DEPS = re.compile(r"^ \}, \[([^\]]*)\]\);", re.M) + + +@pytest.fixture(scope="module") +def app(): + return APP.read_text() + + +def _components(app: str): + """Each top-level component, with the offset it starts at.""" + for m in re.finditer(r"^function ([A-Z]\w*)\(", app, re.M): + start = m.start() + nxt = app.find("\nfunction ", start + 1) + yield m.group(1), app[start:nxt if nxt > 0 else len(app)] + + +def test_no_hook_depends_on_something_declared_below_it(app): + """The whole file, not just the player that was broken by it.""" + problems = [] + for name, body in _components(app): + # Where each hook binding becomes usable. + declared_at = {m.group(1): m.start() for m in DECL.finditer(body)} + for deps in DEPS.finditer(body): + for dep in (d.strip() for d in deps.group(1).split(",")): + if not dep or dep not in declared_at: + continue + if declared_at[dep] > deps.start(): + problems.append( + f"{name}: a hook at offset {deps.start()} lists `{dep}` " + f"as a dependency, but `{dep}` is declared below it") + assert not problems, ( + "a dependency array is evaluated where it is written, so this throws " + "on every render and the component never appears:\n " + + "\n ".join(problems)) + + +def test_the_check_would_notice(app): + """A test that cannot fail proves nothing — so make it fail on purpose. + + Swaps two declarations in the real file and confirms the rule fires. If + this stops working the rule above has quietly become decoration. + """ + body = next(b for n, b in _components(app) if n == "VideoPlayer") + decls = list(DECL.finditer(body)) + assert len(decls) >= 2, "VideoPlayer has too few hooks to test the check" + + # Build a body where the first hook depends on the last one, declared after. + first, last = decls[0].group(1), decls[-1].group(1) + broken = body.replace(decls[0].group(0), + decls[0].group(0), 1) + # Inject a dependency on `last` into the first declaration's dep array. + end = broken.index("\n }, [", decls[0].start()) + close = broken.index("]", end) + broken = broken[:close] + (", " if broken[end + 7:close].strip() else "") + last + broken[close:] + + declared_at = {m.group(1): m.start() for m in DECL.finditer(broken)} + caught = False + for deps in DEPS.finditer(broken): + for dep in (d.strip() for d in deps.group(1).split(",")): + if dep in declared_at and declared_at[dep] > deps.start(): + caught = True + assert caught, ( + f"made `{first}` depend on `{last}` which is declared after it, and the " + "rule did not fire — it is not checking what it claims to") + + +def test_the_mse_harness_reads_functions_in_source_order(): + """Otherwise it hides exactly this fault. + + The harness exists to run the shipped code rather than a paraphrase of it. + Extracting into an order of its own quietly repairs an ordering bug before + running it, which is the one class of defect it would otherwise be well + placed to catch. + """ + harness = (Path(__file__).parent / "harness" / "mse_harness.mjs").read_text() + assert "sort" in harness and "indexOf" in harness, ( + "the harness still extracts the player functions in a hardcoded order, " + "so it cannot see one declared before its own dependency") |