Recently Written · git

tmpl

Insanely simple templates

git clone https://github.com/equwal/tmpl

Log | Files | Refs


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 == []