test_convert_to_luasnip.py (5829 bytes)
1 """Tests for convert_to_luasnip.""" 2 3 import json 4 from pathlib import Path 5 6 from hypothesis import given, settings 7 from hypothesis import strategies as st 8 9 import convert_to_luasnip as conv 10 11 PLACEHOLDER = "<++>" 12 13 14 def parse_snippet(body: str): 15 """Reference parser for VSCode snippet text. 16 17 Return the source text with placeholders restored, and the list of tab 18 stop numbers in order. This is the inverse of convert_placeholders. 19 """ 20 out, stops, i = [], [], 0 21 while i < len(body): 22 c = body[i] 23 if c == "\\" and i + 1 < len(body) and body[i + 1] in "\\$}": 24 out.append(body[i + 1]) 25 i += 2 26 elif body.startswith("${", i): 27 i += 2 28 inner = [] 29 while body[i] != "}": 30 if body[i] == "\\" and body[i + 1] in "\\$}": 31 inner.append(body[i + 1]) 32 i += 2 33 else: 34 inner.append(body[i]) 35 i += 1 36 i += 1 37 num, _, name = "".join(inner).partition(":") 38 stops.append(num) 39 out.append(f"<+{name}+>" if name else PLACEHOLDER) 40 else: 41 assert c not in "$}", f"unescaped {c!r} at {i} in {body!r}" 42 out.append(c) 43 i += 1 44 return "".join(out), stops 45 46 47 text_without_placeholder = st.text().filter(lambda s: "<+" not in s) 48 49 50 @settings(max_examples=500) 51 @given(text_without_placeholder) 52 def test_escape_round_trip(text): 53 body, count = conv.convert_placeholders(text) 54 assert count == 0 55 assert parse_snippet(body) == (text, []) 56 57 58 @settings(max_examples=500) 59 @given(st.lists(text_without_placeholder, min_size=1, max_size=6)) 60 def test_placeholder_count_matches_tab_stops(chunks): 61 source = PLACEHOLDER.join(chunks) 62 body, count = conv.convert_placeholders(source) 63 assert count == len(chunks) - 1 64 assert parse_snippet(body) == (source, [str(i) for i in range(1, count + 1)]) 65 66 67 def test_shell_variable_is_escaped(): 68 body, _ = conv.convert_placeholders('for i do echo "$i"; done') 69 assert body == 'for i do echo "\\$i"; done' 70 71 72 def test_make_variable_is_escaped(): 73 body, _ = conv.convert_placeholders("$(FOLDER) : | <++>") 74 assert body == "\\$(FOLDER) : | ${1}" 75 76 77 def test_backslash_and_brace_are_escaped(): 78 body, _ = conv.convert_placeholders("printf %s\\\\n; { x; }") 79 assert body == "printf %s\\\\\\\\n; { x; \\}" 80 81 82 def test_snippet_body_ends_with_final_tab_stop(tmp_path): 83 root = tmp_path / "tmpl" 84 (root / "sh").mkdir(parents=True) 85 (root / "sh" / "thing").write_text("echo <++>\n") 86 snippet = conv.process_template_file(root / "sh" / "thing", root) 87 assert snippet["body"] == ["echo ${1}", "$0"] 88 89 90 def test_prefix_joins_path_parts(tmp_path): 91 root = tmp_path / "tmpl" 92 (root / "sh" / "trap").mkdir(parents=True) 93 f = root / "sh" / "trap" / "cleanup-temp" 94 f.write_text("<++>") 95 assert conv.generate_snippet_name(f, root) == "sh-trap-cleanup-temp" 96 (root / "c").mkdir() 97 g = root / "c" / "main.c" 98 g.write_text("<++>") 99 assert conv.generate_snippet_name(g, root) == "c-main" 100 101 102 def test_common_lisp_maps_to_lisp_filetype(): 103 assert conv.LANGUAGE_MAP["cl"] == "lisp" 104 assert conv.LANGUAGE_MAP["sh"] == "sh" 105 106 107 def test_files_without_placeholder_are_skipped(tmp_path): 108 root = tmp_path / "tmpl" 109 (root / "sh").mkdir(parents=True) 110 (root / "sh" / "note").write_text("just prose\n") 111 assert conv.process_template_file(root / "sh" / "note", root) is None 112 113 114 def test_write_creates_package_json(tmp_path): 115 out = tmp_path / "snippets" 116 conv.write_snippet_files({"sh": [{"prefix": "sh-x", "body": ["$1"], "description": "x"}]}, out) 117 package = json.loads((out / "package.json").read_text()) 118 assert package["contributes"]["snippets"] == [{"language": "sh", "path": "./sh.json"}] 119 assert json.loads((out / "sh.json").read_text())["sh-x"]["prefix"] == "sh-x" 120 121 122 def test_named_placeholder_becomes_named_tab_stop(): 123 body, count = conv.convert_placeholders("[remote \"<+name+>\"] <++>") 124 assert body == '[remote "${1:name}"] ${2}' 125 assert count == 2 126 127 128 @settings(max_examples=500) 129 @given(st.text(alphabet=st.characters(blacklist_characters="+>"), min_size=1, max_size=8)) 130 def test_named_placeholder_round_trip(name): 131 body, count = conv.convert_placeholders(f"<+{name}+>") 132 assert count == 1 133 assert parse_snippet(body) == (f"<+{name}+>", ["1"]) 134 135 136 def test_description_comes_from_head_comment(tmp_path): 137 root = tmp_path / "tmpl" 138 (root / "sh").mkdir(parents=True) 139 f = root / "sh" / "thing" 140 f.write_text("#!/bin/sh\n# Print the thing\necho <++>\n") 141 assert conv.get_description(f, root) == "Print the thing" 142 g = root / "sh" / "lisp" 143 g.write_text(";; Lisp style\n(<++>)\n") 144 assert conv.get_description(g, root) == "Lisp style" 145 h = root / "sh" / "page" 146 h.write_text("<!-- Html style -->\n<++>\n") 147 assert conv.get_description(h, root) == "Html style" 148 149 150 def test_description_falls_back_to_path(tmp_path): 151 root = tmp_path / "tmpl" 152 (root / "sh").mkdir(parents=True) 153 f = root / "sh" / "bare" 154 f.write_text("echo <++>\n") 155 assert conv.get_description(f, root) == "sh/bare template" 156 g = root / "sh" / "cfile" 157 g.write_text("#include <stdio.h>\n<++>\n") 158 assert conv.get_description(g, root) == "sh/cfile template" 159 160 161 REPO = Path(__file__).resolve().parent 162 163 164 def test_every_repo_template_has_a_summary(): 165 """Every file with a placeholder starts with a summary comment. JSON has no comments.""" 166 missing = [] 167 for lang, snippets in conv.collect_templates_by_language(REPO).items(): 168 for snippet in snippets: 169 if lang != "json" and snippet["description"].endswith(" template"): 170 missing.append(snippet["prefix"]) 171 assert missing == []