aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/tests/test_hook_ordering.py
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-08-17 02:16:28 +0200
committerChristophe Besson <cbesson@gmail.com>2026-08-17 02:16:28 +0200
commit42047dac4041e72e09499e3adf145f1c0f83b284 (patch)
treeb816f20fd8b3b190faa69473b7a2892f3bfc9706 /packages/meshbay-hub/tests/test_hook_ordering.py
parentf5c4c058aa7e91fbdbbdd8cf34042535a05df433 (diff)
downloadmeshbay-42047dac4041e72e09499e3adf145f1c0f83b284.tar.gz
test(hub): a hook that depends on one declared below it never runs
`const a = useCallback(fn, [b])` evaluates `[b]` where it is written, so a `b` further down the component is still in its temporal dead zone. ReferenceError on every render, before anything the component does can run — and 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 caught it. `node --check` passes, the code is well-formed. Worse, the MSE harness extracts the player functions into an order of its own and therefore *reordered* them before running — quietly repairing the one class of defect it was best placed to catch. It sorts by position in the file now, and test_hook_ordering.py checks the property directly across the whole SPA. Both the rule and the harness are checked against the layout that actually shipped. test_video_seek.py covers the rest of seeking, and window_leak.mjs forces the race that made the third seek hang: the whole in-flight window arriving while `reinitAt` is still awaiting. Before, the player is left believing eight segments are in flight and grants nothing; after, the window comes back. A run that happens to work proves nothing about a race, which is the point of forcing the worst case rather than trusting a longer session.
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")