"""Tests for ``.vue`` SFC extraction. Feeding a whole SFC to the JS grammar produces a top-level ERROR node, dropping imports and symbols. :func:`extract_vue` masks the non-script regions and parses the ``\n" ) masked, lang = _vue_mask_non_script(src) assert lang == "ts" # Same number of lines (newlines preserved) so line numbers are stable. assert masked.count("\n") == src.count("\n") # Template content is gone; the script body survives verbatim. assert "div" not in masked assert "const msg = 'hi'" in masked # The script body sits on the same line it does in the source (line 6). assert masked.splitlines()[5].strip() == "const msg = 'hi'" def test_script_setup_ts_static_imports_resolve(tmp_path): _write(tmp_path / "Child.vue", "\n") _write(tmp_path / "utils/helper.ts", "export function helper(){}\n") comp = _write( tmp_path / "Comp.vue", """ """, ) result = extract_vue(comp) targets = _targets(result, relation="imports_from") assert _make_id(str(tmp_path / "Child.vue")) in targets assert _make_id(str(tmp_path / "utils/helper.ts")) in targets def test_script_setup_extracts_symbols_with_correct_lines(tmp_path): comp = _write( tmp_path / "Widget.vue", """ """, ) result = extract_vue(comp) by_label = {n["label"]: n for n in result["nodes"]} assert "count" in by_label assert "onClick()" in by_label # count is declared on line 8, onClick on line 10 of the SFC. assert by_label["count"]["source_location"] == "L8" assert by_label["onClick()"]["source_location"] == "L10" def test_typed_props_reference_imported_type(tmp_path): _write(tmp_path / "types.ts", "export interface Thing { id: number }\n") comp = _write( tmp_path / "Typed.vue", """ """, ) result = extract_vue(comp) # The imported type is referenced from the script. assert _make_id(str(tmp_path / "types.ts")) in _targets(result, relation="imports_from") def test_two_script_blocks_both_parsed(tmp_path): """Vue allows a classic `` """, ) result = extract_vue(comp) targets = _targets(result, relation="imports_from") assert _make_id(str(tmp_path / "a.ts")) in targets assert _make_id(str(tmp_path / "b.ts")) in targets def test_dynamic_import_recovered(tmp_path): _write(tmp_path / "Lazy.vue", "\n") comp = _write( tmp_path / "Host.vue", """ """, ) result = extract_vue(comp) assert _make_id(str(tmp_path / "Lazy.vue")) in _targets(result, relation="dynamic_import") def test_plain_js_script_block(tmp_path): _write(tmp_path / "dep.js", "export const x = 1\n") comp = _write( tmp_path / "Legacy.vue", """ """, ) result = extract_vue(comp) assert _make_id(str(tmp_path / "dep.js")) in _targets(result, relation="imports_from") def test_template_only_file_does_not_crash(tmp_path): comp = _write(tmp_path / "Static.vue", "\n") result = extract_vue(comp) assert isinstance(result, dict) # Only the file node; no script means no imports/symbols. assert _targets(result, relation="imports_from") == set() def test_whole_file_to_js_grammar_would_extract_nothing(tmp_path): """The SFC must not be parsed as one JS blob. With the bug, a real SFC yields just the file node; after the fix it yields its imports. """ _write(tmp_path / "dep.ts", "export const v = 1\n") comp = _write( tmp_path / "Guard.vue", """ """, ) result = extract_vue(comp) assert _make_id(str(tmp_path / "dep.ts")) in _targets(result, relation="imports_from") def test_vue_joins_cross_file_symbol_resolution(tmp_path): """A ``.vue`` calling an imported function wires to the real symbol across files. The SFC's calls should resolve like any ``.ts`` file's would. """ helper = _write(tmp_path / "helper.ts", "export function helper() {}\n") comp = _write( tmp_path / "Caller.vue", """ """, ) result = extract([comp, helper], cache_root=tmp_path) by_label = {n["label"]: n["id"] for n in result["nodes"]} edges = {(e["source"], e["target"], e["relation"]) for e in result["edges"]} assert (by_label["go()"], by_label["helper()"], "calls") in edges def test_generic_component_open_tag_with_angle_brackets(tmp_path): """A Vue 3.3+ generic= attribute containing '>' (e.g. Record) must not prematurely end the """, ) result = extract_vue(comp) # the import inside the script body is recovered (body wasn't masked away) assert _make_id(str(tmp_path / "utils/helper.ts")) in _targets(result, relation="imports_from") # and no stray '">' leaked from the open tag into a parse error masked, lang = _vue_mask_non_script(comp.read_text(encoding="utf-8")) assert lang == "ts" assert 'generic="T extends Record' not in masked # open tag fully blanked assert "import { helper }" in masked # body preserved