"""Tests for the router.sx spec — client-side route matching. Tests the bootstrapped Python router functions (from sx_ref.py) and the SX page registry serialization (from helpers.py). """ import pytest from shared.sx.ref import sx_ref # --------------------------------------------------------------------------- # split-path-segments # --------------------------------------------------------------------------- class TestSplitPathSegments: def test_simple(self): assert sx_ref.split_path_segments("/docs/hello") == ["docs", "hello"] def test_root(self): assert sx_ref.split_path_segments("/") == [] def test_trailing_slash(self): assert sx_ref.split_path_segments("/docs/") == ["docs"] def test_no_leading_slash(self): assert sx_ref.split_path_segments("docs/hello") == ["docs", "hello"] def test_single_segment(self): assert sx_ref.split_path_segments("/about") == ["about"] def test_deep_path(self): assert sx_ref.split_path_segments("/a/b/c/d") == ["a", "b", "c", "d"] def test_empty(self): assert sx_ref.split_path_segments("") == [] # --------------------------------------------------------------------------- # parse-route-pattern # --------------------------------------------------------------------------- class TestParseRoutePattern: def test_literal_only(self): result = sx_ref.parse_route_pattern("/docs/") assert len(result) == 1 assert result[0]["type"] == "literal" assert result[0]["value"] == "docs" def test_param(self): result = sx_ref.parse_route_pattern("/docs/") assert len(result) == 2 assert result[0] == {"type": "literal", "value": "docs"} assert result[1] == {"type": "param", "value": "slug"} def test_multiple_params(self): result = sx_ref.parse_route_pattern("/users//posts/") assert len(result) == 4 assert result[0]["type"] == "literal" assert result[1] == {"type": "param", "value": "uid"} assert result[2]["type"] == "literal" assert result[3] == {"type": "param", "value": "pid"} def test_root_pattern(self): result = sx_ref.parse_route_pattern("/") assert result == [] # --------------------------------------------------------------------------- # match-route # --------------------------------------------------------------------------- class TestMatchRoute: def test_exact_match(self): params = sx_ref.match_route("/docs/", "/docs/") assert params is not None assert params == {} def test_param_match(self): params = sx_ref.match_route("/docs/components", "/docs/") assert params is not None assert params == {"slug": "components"} def test_no_match_different_length(self): result = sx_ref.match_route("/docs/a/b", "/docs/") assert result is sx_ref.NIL or result is None def test_no_match_literal_mismatch(self): result = sx_ref.match_route("/api/hello", "/docs/") assert result is sx_ref.NIL or result is None def test_root_match(self): params = sx_ref.match_route("/", "/") assert params is not None assert params == {} def test_multiple_params(self): params = sx_ref.match_route("/users/42/posts/7", "/users//posts/") assert params is not None assert params == {"uid": "42", "pid": "7"} # --------------------------------------------------------------------------- # find-matching-route # --------------------------------------------------------------------------- class TestFindMatchingRoute: def _make_routes(self, patterns): """Build route entries like boot.sx does — with parsed patterns.""" routes = [] for name, pattern in patterns: route = { "name": name, "path": pattern, "parsed": sx_ref.parse_route_pattern(pattern), "has-data": False, "content": "(div \"test\")", } routes.append(route) return routes def test_first_match(self): routes = self._make_routes([ ("home", "/"), ("docs-index", "/docs/"), ("docs-page", "/docs/"), ]) match = sx_ref.find_matching_route("/docs/components", routes) assert match is not None assert match["name"] == "docs-page" assert match["params"] == {"slug": "components"} def test_exact_before_param(self): routes = self._make_routes([ ("docs-index", "/docs/"), ("docs-page", "/docs/"), ]) match = sx_ref.find_matching_route("/docs/", routes) assert match is not None assert match["name"] == "docs-index" def test_no_match(self): routes = self._make_routes([ ("home", "/"), ("docs-page", "/docs/"), ]) result = sx_ref.find_matching_route("/unknown/path", routes) assert result is sx_ref.NIL or result is None def test_root_match(self): routes = self._make_routes([ ("home", "/"), ("about", "/about"), ]) match = sx_ref.find_matching_route("/", routes) assert match is not None assert match["name"] == "home" def test_params_not_on_original(self): """find-matching-route should not mutate the original route entry.""" routes = self._make_routes([("page", "/docs/")]) match = sx_ref.find_matching_route("/docs/test", routes) assert match["params"] == {"slug": "test"} # Original should not have params key assert "params" not in routes[0] # --------------------------------------------------------------------------- # Page registry SX serialization # --------------------------------------------------------------------------- class TestBuildPagesSx: """Test the SX page registry format — serialize + parse round-trip.""" def test_round_trip_simple(self): """SX dict literal round-trips through serialize → parse.""" from shared.sx.helpers import _sx_literal from shared.sx.parser import parse_all # Build an SX dict literal like _build_pages_sx does entry = ( "{:name " + _sx_literal("home") + " :path " + _sx_literal("/") + " :auth " + _sx_literal("public") + " :has-data false" + " :content " + _sx_literal("(~home-content)") + " :closure {}}" ) parsed = parse_all(entry) assert len(parsed) == 1 d = parsed[0] assert d["name"] == "home" assert d["path"] == "/" assert d["auth"] == "public" assert d["has-data"] is False assert d["content"] == "(~home-content)" assert d["closure"] == {} def test_round_trip_multiple(self): """Multiple SX dict literals parse as a list.""" from shared.sx.helpers import _sx_literal from shared.sx.parser import parse_all entries = [] for name, path in [("home", "/"), ("docs", "/docs/")]: entry = ( "{:name " + _sx_literal(name) + " :path " + _sx_literal(path) + " :has-data false" + " :content " + _sx_literal("(div)") + " :closure {}}" ) entries.append(entry) text = "\n".join(entries) parsed = parse_all(text) assert len(parsed) == 2 assert parsed[0]["name"] == "home" assert parsed[1]["name"] == "docs" assert parsed[1]["path"] == "/docs/" def test_content_with_quotes(self): """Content expressions with quotes survive serialization.""" from shared.sx.helpers import _sx_literal from shared.sx.parser import parse_all content = '(~docs/page :title "Hello \\"World\\"")' entry = ( "{:name " + _sx_literal("test") + " :content " + _sx_literal(content) + " :closure {}}" ) parsed = parse_all(entry) assert parsed[0]["content"] == content def test_closure_with_values(self): """Closure dict with various value types.""" from shared.sx.helpers import _sx_literal from shared.sx.parser import parse_all entry = '{:name "test" :closure {:label "hello" :count 42 :active true}}' parsed = parse_all(entry) closure = parsed[0]["closure"] assert closure["label"] == "hello" assert closure["count"] == 42 assert closure["active"] is True def test_has_data_true(self): """has-data true marks server-only pages.""" from shared.sx.parser import parse_all entry = '{:name "analyzer" :path "/data" :has-data true :content "" :closure {}}' parsed = parse_all(entry) assert parsed[0]["has-data"] is True # --------------------------------------------------------------------------- # _sx_literal helper # --------------------------------------------------------------------------- class TestSxLiteral: def test_string(self): from shared.sx.helpers import _sx_literal assert _sx_literal("hello") == '"hello"' def test_string_with_quotes(self): from shared.sx.helpers import _sx_literal assert _sx_literal('say "hi"') == '"say \\"hi\\""' def test_string_with_newline(self): from shared.sx.helpers import _sx_literal assert _sx_literal("line1\nline2") == '"line1\\nline2"' def test_string_with_backslash(self): from shared.sx.helpers import _sx_literal assert _sx_literal("a\\b") == '"a\\\\b"' def test_int(self): from shared.sx.helpers import _sx_literal assert _sx_literal(42) == "42" def test_float(self): from shared.sx.helpers import _sx_literal assert _sx_literal(3.14) == "3.14" def test_bool_true(self): from shared.sx.helpers import _sx_literal assert _sx_literal(True) == "true" def test_bool_false(self): from shared.sx.helpers import _sx_literal assert _sx_literal(False) == "false" def test_none(self): from shared.sx.helpers import _sx_literal assert _sx_literal(None) == "nil" def test_empty_string(self): from shared.sx.helpers import _sx_literal assert _sx_literal("") == '""'