"""Tests for `.astro` extraction (#850). Astro files have a TypeScript frontmatter block (`---...---`) at the top where nearly all imports live, followed by an HTML-with-expressions template and optionally ` """, ) layout = _write(tmp_path / "src/layouts/Layout.astro", "---\n---\n\n") hydrate = _write(tmp_path / "src/client/hydrate.ts", "export function hydrate(){}\n") result = extract_astro(page) targets = _import_targets(result, relation="imports_from") assert _make_id(str(layout)) in targets assert _make_id(str(hydrate)) in targets def test_extract_astro_no_frontmatter_does_not_crash(tmp_path): """Astro permits frontmatter-less files (pure-HTML pages). Must not raise.""" page = _write( tmp_path / "src/pages/plain.astro", "

no frontmatter here

\n", ) result = extract_astro(page) # Empty/no-imports result is acceptable; the extractor must just not crash. assert isinstance(result, dict) assert _import_targets(result, relation="imports_from") == set() def test_extract_astro_handles_tsconfig_path_alias(tmp_path): _write( tmp_path / "tsconfig.json", """{ "compilerOptions": { "baseUrl": ".", "paths": { "@components/*": ["src/components/*"] } } } """, ) page = _write( tmp_path / "src/pages/alias.astro", """--- import Hero from '@components/Hero.astro'; --- """, ) hero = _write(tmp_path / "src/components/Hero.astro", "---\n---\n

h

\n") result = extract_astro(page) targets = _import_targets(result, relation="imports_from") assert _make_id(str(hero)) in targets def _labels(result: dict) -> dict[str, str]: """Label -> source_location for every node except the file node.""" return { str(n.get("label")): str(n.get("source_location")) for n in result.get("nodes", []) if n.get("id") != result.get("nodes", [{}])[0].get("id") } def test_extract_astro_template_is_not_a_parse_error(tmp_path): """The template is not TS; only frontmatter and """, ) result = extract_astro(page) assert result.get("parse_errors") is None labels = _labels(result) assert "rank()" in labels and "toggle()" in labels # Masking keeps offsets, so locations still point at the original lines. assert labels["rank()"] == "L5" assert labels["toggle()"] == "L15" def test_extract_astro_scripts_on_one_line_do_not_merge(tmp_path): """Two script bodies on one line must not parse as a single statement.""" page = _write( tmp_path / "src/pages/inline.astro", "\n", ) result = extract_astro(page) assert result.get("parse_errors") is None assert "b()" in _labels(result) def test_extract_astro_trailing_comment_does_not_hide_next_script(tmp_path): """A comment at the closing tag must end before the next script (#4072).""" page = _write( tmp_path / "inline-comment.astro", "" "\n", ) result = extract_astro(page) assert result.get("parse_errors") is None assert _labels(result)["visible()"] == "L1" def test_astro_mask_preserves_bytes_and_newlines_after_comment(): from graphify.extract import _astro_mask_non_script source = ( "---\nconst title = 'hello';\n---\n" "" "\r\n" ) masked = _astro_mask_non_script(source).encode("utf-8") original = source.encode("utf-8") assert len(masked) == len(original) assert masked.index(b"function visible") == original.index(b"function visible") assert [(i, c) for i, c in enumerate(masked) if c in (10, 13)] == [ (i, c) for i, c in enumerate(original) if c in (10, 13) ]