1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
|
"""Tests for cj-scan.py — org-file cj-annotation scanner.
The script parses an org file and emits JSON describing:
- cj_blocks: every cj annotation found (source-block or legacy-inline form)
- verify_tasks: every VERIFY heading + placement validity (top-level or first-level child only)
- unclosed_blocks: any source-block fence that opened but never closed
"""
import json
import subprocess
from pathlib import Path
import pytest
SCRIPT = Path(__file__).parent.parent / "cj-scan.py"
@pytest.fixture
def run_scan(tmp_path):
"""Write content to a temp org file and run cj-scan; return parsed JSON output."""
def _run(content: str) -> dict:
f = tmp_path / "test.org"
f.write_text(content)
result = subprocess.run(
["python3", str(SCRIPT), str(f)],
capture_output=True,
text=True,
check=True,
)
return json.loads(result.stdout)
return _run
# ----------------------------------------------------------------------
# cj-block detection
# ----------------------------------------------------------------------
class TestCjScanCjBlockDetection:
"""Detection of cj annotations — source-block and legacy-inline forms."""
def test_cj_scan_source_block_single_detected(self, run_scan):
"""Normal: a single source-block cj is detected with correct line range and body."""
content = "* Section\n#+begin_src cj: comment\nplease check this\n#+end_src\n"
result = run_scan(content)
assert len(result["cj_blocks"]) == 1
b = result["cj_blocks"][0]
assert b["form"] == "source-block"
assert b["body"] == "please check this"
assert b["start_line"] == 2
assert b["end_line"] == 4
def test_cj_scan_source_block_multiline_body_preserved(self, run_scan):
"""Normal: multi-line body is preserved with embedded newlines."""
content = "* S\n#+begin_src cj: comment\nline 1\nline 2\nline 3\n#+end_src\n"
result = run_scan(content)
assert result["cj_blocks"][0]["body"] == "line 1\nline 2\nline 3"
def test_cj_scan_multiple_source_blocks_each_detected(self, run_scan):
"""Normal: multiple source-blocks in a file are detected as separate items."""
content = (
"* A\n#+begin_src cj: comment\nfirst\n#+end_src\n"
"* B\n#+begin_src cj: comment\nsecond\n#+end_src\n"
)
result = run_scan(content)
assert len(result["cj_blocks"]) == 2
bodies = [b["body"] for b in result["cj_blocks"]]
assert bodies == ["first", "second"]
def test_cj_scan_legacy_inline_single_line_detected(self, run_scan):
"""Normal: a legacy inline cj line is detected with form=legacy-inline."""
content = "* Section\ncj: please check this\n"
result = run_scan(content)
assert len(result["cj_blocks"]) == 1
b = result["cj_blocks"][0]
assert b["form"] == "legacy-inline"
assert b["body"] == "please check this"
assert b["start_line"] == 2
assert b["end_line"] == 2
def test_cj_scan_mixed_forms_in_same_file(self, run_scan):
"""Normal: source-block + legacy inline coexist; both detected as separate items."""
content = (
"* A\ncj: legacy form\n"
"* B\n#+begin_src cj: comment\nnew form\n#+end_src\n"
)
result = run_scan(content)
assert len(result["cj_blocks"]) == 2
forms = sorted(b["form"] for b in result["cj_blocks"])
assert forms == ["legacy-inline", "source-block"]
def test_cj_scan_empty_file_returns_empty_lists(self, run_scan):
"""Boundary: empty file → empty cj_blocks and verify_tasks lists."""
result = run_scan("")
assert result["cj_blocks"] == []
assert result["verify_tasks"] == []
assert result["unclosed_blocks"] == []
def test_cj_scan_no_cj_content_returns_empty_blocks(self, run_scan):
"""Boundary: org file with no cj content → empty cj_blocks."""
content = "* Section\n** TODO Task\nbody text\n** TODO Another\n"
result = run_scan(content)
assert result["cj_blocks"] == []
def test_cj_scan_block_before_any_heading_empty_chain(self, run_scan):
"""Boundary: cj block at top of file (before any heading) → empty parent chain."""
content = "#+begin_src cj: comment\ntop-level note\n#+end_src\n"
result = run_scan(content)
assert result["cj_blocks"][0]["parent_heading_chain"] == []
assert result["cj_blocks"][0]["parent_depth"] == 0
@pytest.mark.parametrize("fence", [
"#+begin_src cj: comment",
"#+begin_src cj:",
"#+begin_src cj: anything",
"#+BEGIN_SRC cj: comment", # case-insensitive
])
def test_cj_scan_source_block_fence_variants_all_recognized(self, run_scan, fence):
"""Boundary: fence label and case variants are all valid forms."""
content = f"* S\n{fence}\nbody\n#+end_src\n"
result = run_scan(content)
assert len(result["cj_blocks"]) == 1
assert result["cj_blocks"][0]["body"] == "body"
def test_cj_scan_unclosed_source_block_reported(self, run_scan):
"""Error: a source-block that opens but never closes → reported in unclosed_blocks."""
content = "* S\n#+begin_src cj: comment\nbody that never ends\n"
result = run_scan(content)
assert result["cj_blocks"] == []
assert len(result["unclosed_blocks"]) == 1
assert result["unclosed_blocks"][0]["start_line"] == 2
# ----------------------------------------------------------------------
# Parent heading chain reconstruction
# ----------------------------------------------------------------------
class TestCjScanParentChain:
"""Parent heading chain construction — walking the org tree backward."""
def test_cj_scan_nested_parent_chain_three_levels(self, run_scan):
"""Normal: cj block inside three nested headings → chain reflects all three."""
content = (
"* Work\n"
"** DOING [#A] Kostya's contract\n"
"*** VERIFY Question?\n"
"#+begin_src cj: comment\nanswer\n#+end_src\n"
)
result = run_scan(content)
chain = result["cj_blocks"][0]["parent_heading_chain"]
assert len(chain) == 3
assert chain[0] == {"depth": 1, "heading": "Work"}
assert chain[1] == {"depth": 2, "heading": "DOING [#A] Kostya's contract"}
assert chain[2] == {"depth": 3, "heading": "VERIFY Question?"}
assert result["cj_blocks"][0]["parent_depth"] == 3
def test_cj_scan_depth_skip_only_actual_ancestors(self, run_scan):
"""Normal: heading depth skip (e.g., * then ***) → chain captures only present headings."""
content = "* Section\n*** Deep child\n#+begin_src cj: comment\nbody\n#+end_src\n"
result = run_scan(content)
chain = result["cj_blocks"][0]["parent_heading_chain"]
assert [h["depth"] for h in chain] == [1, 3]
def test_cj_scan_shallower_sibling_pops_deeper_frames(self, run_scan):
"""Normal: when a shallower heading appears, deeper frames pop off the stack."""
content = (
"* A\n** A.1\n*** A.1.1\n"
"** B\n"
"#+begin_src cj: comment\nunder B\n#+end_src\n"
)
result = run_scan(content)
chain = result["cj_blocks"][0]["parent_heading_chain"]
assert len(chain) == 2
assert chain[0]["heading"] == "A"
assert chain[1]["heading"] == "B"
# ----------------------------------------------------------------------
# VERIFY task detection + placement audit
# ----------------------------------------------------------------------
class TestCjScanVerifyPlacement:
"""VERIFY task detection and placement audit per the canonical rule."""
def test_cj_scan_verify_at_depth_2_is_valid(self, run_scan):
"""Normal: ** VERIFY (top-level) is valid placement."""
content = "* Work\n** VERIFY [#C] Hayk's Farearth Evaluation :research:hayk:\n"
result = run_scan(content)
assert len(result["verify_tasks"]) == 1
v = result["verify_tasks"][0]
assert v["depth"] == 2
assert v["valid_depth"] is True
assert v["promotion_target"] is None
def test_cj_scan_verify_at_depth_3_is_valid(self, run_scan):
"""Normal: *** VERIFY (first-level child) is valid placement."""
content = "* Work\n** TODO Parent\n*** VERIFY Question?\n"
result = run_scan(content)
v = result["verify_tasks"][0]
assert v["depth"] == 3
assert v["valid_depth"] is True
def test_cj_scan_verify_at_depth_4_invalid_promote_to_3(self, run_scan):
"""Normal: **** VERIFY is buried; suggests promotion to depth 3."""
content = "* W\n** P\n*** Q\n**** VERIFY Buried?\n"
result = run_scan(content)
v = result["verify_tasks"][0]
assert v["depth"] == 4
assert v["valid_depth"] is False
assert v["promotion_target"] == 3
def test_cj_scan_verify_at_depth_6_invalid_promote_to_3(self, run_scan):
"""Normal: ****** VERIFY at any deep level → promotion target is still 3."""
content = "* W\n** P\n*** Q\n**** Q2\n***** Q3\n****** VERIFY Very buried?\n"
result = run_scan(content)
v = result["verify_tasks"][0]
assert v["depth"] == 6
assert v["promotion_target"] == 3
def test_cj_scan_verify_at_depth_1_invalid_promote_to_2(self, run_scan):
"""Boundary: * VERIFY at top-section depth → promotion target is 2 (top-level under section)."""
content = "* VERIFY Should-be-deeper\n"
result = run_scan(content)
v = result["verify_tasks"][0]
assert v["depth"] == 1
assert v["valid_depth"] is False
assert v["promotion_target"] == 2
def test_cj_scan_verify_heading_with_priority_and_tags(self, run_scan):
"""Boundary: VERIFY heading with priority cookie + tags → heading text captured fully."""
content = "* W\n** VERIFY [#C] Hayk's Farearth Evaluation :research:hayk:\n"
result = run_scan(content)
v = result["verify_tasks"][0]
assert "Hayk's Farearth Evaluation" in v["heading"]
assert ":research:" in v["heading"]
def test_cj_scan_no_verify_tasks_empty_list(self, run_scan):
"""Boundary: file with only TODO/DOING headings → empty verify_tasks list."""
content = "* W\n** TODO X\n*** DOING Y\n"
result = run_scan(content)
assert result["verify_tasks"] == []
def test_cj_scan_verify_word_in_body_is_not_a_task(self, run_scan):
"""Error: the word VERIFY appearing in body prose is not detected as a task."""
content = (
"* Work\n"
"** TODO Important task\n"
"Body line mentioning VERIFY in prose.\n"
)
result = run_scan(content)
assert result["verify_tasks"] == []
|