generated from eblume/project-template
heph.nvim: RPC client + buffer editing + wiki-links + journal (slice 11a)
Some checks failed
Build / validate (pull_request) Failing after 4s
Some checks failed
Build / validate (pull_request) Failing after 4s
The primary surface begins (tech-spec §8): a Neovim plugin that is a thin
client of the local hephd over its unix-socket JSON-RPC.
- node.resolve {title} → Node|null (heph-core Store + dispatch): exact,
owner-scoped, non-tombstoned alias-then-title match — the same mapping that
materializes wiki links, so follow-link jumps to the node the stored link
points at (never fuzzy search). Unit + rpc_socket integration tests.
- heph.nvim/: vim.uv unix-socket JSON-RPC client (blocking call via vim.wait,
id-demuxed, partial-line buffered, luanil so JSON null → Lua nil; isolated
Sessions for tests). Buffer-backed nodes (heph://node/<id>, acwrite;
BufReadCmd→node.get / BufWriteCmd→node.update, whole-buffer body round-trips
exactly through the CRDT). [[wiki-link]] follow on <CR>. Daily journal.
:Heph command surface + completion.
- Headless e2e (§9): a self-contained busted-style runner (tests/e2e/runner.lua)
— no external plugins, no network, deterministic CI exit codes. Specs: journal
round-trip, follow-link (+ unresolved no-op), link-two-docs/backlink.
`make -C heph.nvim test` builds hephd and runs it.
Docs: heph-nvim reference card, §14 tracker (11a done; 11b/11c/11d queued),
changelog fragment.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
87c76da659
commit
ee865e5635
29 changed files with 1240 additions and 10 deletions
140
heph.nvim/tests/e2e/runner.lua
Normal file
140
heph.nvim/tests/e2e/runner.lua
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
--- A tiny, dependency-free busted-compatible test runner (tech-spec §9 sanctions
|
||||
--- "drive nvim ... from the test runner"). Provides the `describe`/`it`/
|
||||
--- `before_each`/`after_each` globals and a luassert-style `assert` table, then
|
||||
--- runs `*_spec.lua` files in headless Neovim with proper exit codes — no
|
||||
--- external plugins, no network, nothing vendored.
|
||||
|
||||
local M = {}
|
||||
|
||||
-- Registration state (module-local; the installed globals close over these).
|
||||
local cases = {}
|
||||
local scope_stack = {}
|
||||
|
||||
local function full_name(leaf)
|
||||
local parts = {}
|
||||
for _, s in ipairs(scope_stack) do
|
||||
if s.name then
|
||||
parts[#parts + 1] = s.name
|
||||
end
|
||||
end
|
||||
parts[#parts + 1] = leaf
|
||||
return table.concat(parts, " ")
|
||||
end
|
||||
|
||||
--- Install the spec globals. `assert` becomes a callable luassert-ish table:
|
||||
--- `assert(cond, msg)` still works (so harness code using the builtin is fine),
|
||||
--- plus `assert.are.equal`, `assert.is_true/is_false/is_truthy/is_falsy`.
|
||||
function M.install_globals()
|
||||
_G.describe = function(name, fn)
|
||||
table.insert(scope_stack, { name = name, befores = {}, afters = {} })
|
||||
fn()
|
||||
table.remove(scope_stack)
|
||||
end
|
||||
_G.before_each = function(fn)
|
||||
table.insert(scope_stack[#scope_stack].befores, fn)
|
||||
end
|
||||
_G.after_each = function(fn)
|
||||
table.insert(scope_stack[#scope_stack].afters, fn)
|
||||
end
|
||||
_G.it = function(name, fn)
|
||||
-- Snapshot the active before/after chain (outermost-first for befores,
|
||||
-- innermost-first for afters), as busted runs them per-test.
|
||||
local befores, afters = {}, {}
|
||||
for _, s in ipairs(scope_stack) do
|
||||
for _, b in ipairs(s.befores) do
|
||||
befores[#befores + 1] = b
|
||||
end
|
||||
end
|
||||
for i = #scope_stack, 1, -1 do
|
||||
for _, a in ipairs(scope_stack[i].afters) do
|
||||
afters[#afters + 1] = a
|
||||
end
|
||||
end
|
||||
table.insert(cases, { name = full_name(name), fn = fn, befores = befores, afters = afters })
|
||||
end
|
||||
|
||||
local function fail(msg, level)
|
||||
error(msg, (level or 1) + 1)
|
||||
end
|
||||
local A = setmetatable({}, {
|
||||
__call = function(_, cond, msg)
|
||||
if not cond then
|
||||
fail(msg or "assertion failed")
|
||||
end
|
||||
return cond
|
||||
end,
|
||||
})
|
||||
local function eq(a, b, msg)
|
||||
if a ~= b then
|
||||
fail(msg or string.format("expected %s, got %s", vim.inspect(b), vim.inspect(a)))
|
||||
end
|
||||
end
|
||||
A.are = { equal = eq, equals = eq }
|
||||
A.equals = eq
|
||||
A.equal = eq
|
||||
A.is_true = function(x, msg)
|
||||
if x ~= true then
|
||||
fail(msg or ("expected true, got " .. vim.inspect(x)))
|
||||
end
|
||||
end
|
||||
A.is_false = function(x, msg)
|
||||
if x ~= false then
|
||||
fail(msg or ("expected false, got " .. vim.inspect(x)))
|
||||
end
|
||||
end
|
||||
A.is_truthy = function(x, msg)
|
||||
if not x then
|
||||
fail(msg or ("expected truthy, got " .. vim.inspect(x)))
|
||||
end
|
||||
end
|
||||
A.is_falsy = function(x, msg)
|
||||
if x then
|
||||
fail(msg or ("expected falsy, got " .. vim.inspect(x)))
|
||||
end
|
||||
end
|
||||
_G.assert = A
|
||||
end
|
||||
|
||||
--- Run each spec file, returning the number of failed tests. Prints TAP-ish
|
||||
--- lines so failures are obvious in CI logs.
|
||||
function M.run_files(files)
|
||||
local passed, failed = 0, 0
|
||||
for _, file in ipairs(files) do
|
||||
cases = {}
|
||||
scope_stack = {}
|
||||
local loaded, lerr = pcall(dofile, file)
|
||||
if not loaded then
|
||||
failed = failed + 1
|
||||
print("not ok - load " .. file)
|
||||
print(" " .. tostring(lerr):gsub("\n", "\n "))
|
||||
end
|
||||
for _, c in ipairs(cases) do
|
||||
local ok, err = true, nil
|
||||
for _, b in ipairs(c.befores) do
|
||||
local bok, berr = pcall(b)
|
||||
if not bok then
|
||||
ok, err = false, berr
|
||||
break
|
||||
end
|
||||
end
|
||||
if ok then
|
||||
ok, err = pcall(c.fn)
|
||||
end
|
||||
for _, a in ipairs(c.afters) do
|
||||
pcall(a) -- teardown always runs, even after a failure
|
||||
end
|
||||
if ok then
|
||||
passed = passed + 1
|
||||
print("ok - " .. c.name)
|
||||
else
|
||||
failed = failed + 1
|
||||
print("not ok - " .. c.name)
|
||||
print(" " .. tostring(err):gsub("\n", "\n "))
|
||||
end
|
||||
end
|
||||
end
|
||||
print(string.format("\n%d passed, %d failed", passed, failed))
|
||||
return failed
|
||||
end
|
||||
|
||||
return M
|
||||
Loading…
Add table
Add a link
Reference in a new issue