summaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/tests/test_hook_ordering.py
blob: cd9a11e43f30064341763d2d8aef45b2ba2a3ebc (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
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")