import json
import re
import shutil
import subprocess
from pathlib import Path
import pytest
TEMPLATE = Path(__file__).resolve().parents[2] / ".claude/skills/preview-verdicts/template.html"
NODE = shutil.which("node")
RUNNER = r"""
const fs = require("node:fs");
const vm = require("node:vm");
const { script, actions } = JSON.parse(fs.readFileSync(0, "utf8"));
const storage = {};
let elements, context, copied;
function reload() {
elements = {};
context = vm.createContext({
document: { getElementById: id => elements[id] ||= {}, title: "Review" },
localStorage: { getItem: key => storage[key], setItem: (key, value) => storage[key] = value },
navigator: { clipboard: { writeText: text => { copied = text; return Promise.resolve(); } } }
});
vm.runInContext(script, context);
}
function snapshot() {
return {
html: elements.app.innerHTML,
counts: elements.counts.innerHTML,
feedback: context.collect(),
rowClass: elements["r-Section__entry"]?.className,
copied
};
}
reload();
const snapshots = [snapshot()];
for (const [action, ...args] of actions) {
if (action === "reload") reload();
else context[action](...args);
snapshots.push(snapshot());
}
process.stdout.write(JSON.stringify(snapshots));
"""
def preview(kind, verdict, actions=(), source=None):
if NODE is None:
pytest.skip("Preview script tests require Node.js")
row = ["entry", "https://github.com/owner/entry", "1,000", kind, verdict, "Review reason"]
if source is not None:
row.append(source)
data = [["Section", "Use case — review note", [row]]]
script = TEMPLATE.read_text().split("", 1)[0].replace("__DATA__", json.dumps(data))
result = subprocess.run([NODE, "-e", RUNNER], input=json.dumps({"script": script, "actions": actions}), capture_output=True, text=True, check=True)
return json.loads(result.stdout)
@pytest.mark.parametrize(
("kind", "accept", "reject", "buttons"),
[
("listed", "keep", "remove", ["Keep", "Remove", "Park"]),
("candidate", "add", "reject", ["Add", "Reject", "Park"]),
("structure", "restructure", "reject", ["Restructure", "Park", "Reject"]),
],
)
def test_parked_rows_keep_their_choices_and_feedback_after_reload(kind, accept, reject, buttons):
initial, accepted, reloaded, copied, rejected, parked = preview(
kind,
"park",
[("setV", "Section::entry", accept), ("reload",), ("copyFeedback",), ("setV", "Section::entry", reject), ("setV", "Section::entry", "park")],
)
for snapshot in (initial, accepted, reloaded, copied, rejected, parked):
assert re.findall(r"", snapshot["html"]) == buttons
assert initial["feedback"] == parked["feedback"] == []
expected = [{"section": "Section", "subcategory": "Use case", "entry": "entry", "kind": kind, "my_verdict": "park", "your_verdict": accept, "reason": ""}]
assert accepted["feedback"] == reloaded["feedback"] == expected
assert json.loads(copied["copied"]) == expected
assert rejected["feedback"][0]["your_verdict"] == reject
assert f'