aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/tests/test_hook_ordering.py
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-hub/tests/test_hook_ordering.py')
-rw-r--r--packages/meshbay-hub/tests/test_hook_ordering.py112
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")