test: add top-priority missing test coverage

Problem: several critical code paths had zero test coverage —
parse.resolve_date (relative date resolution), store.snapshot
(foundation of the undo stack), and the diff.apply invariant that
unchanged tasks do not get their modified timestamp bumped. The
diff.apply due/priority clearing paths were also untested.

Solution: add six targeted test blocks across parse_spec, store_spec,
and diff_spec: resolve_date happy/failure paths, parse.body with
relative due tokens, snapshot copy-semantics and deleted-task
exclusion, diff unchanged-modified invariant, due cleared on removal,
priority cleared on ! removal.
This commit is contained in:
Barrett Ruth 2026-02-24 19:53:50 -05:00 committed by Barrett Ruth
parent 20b8cb760f
commit 39c98012d0
3 changed files with 134 additions and 0 deletions

View file

@ -135,5 +135,51 @@ describe('diff', function()
local task = store.get(1)
assert.are.equal('Work', task.category)
end)
it('does not update modified when task is unchanged', function()
store.add({ description = 'Stable task', category = 'Inbox' })
store.save()
local lines = {
'Inbox',
'/1/ Stable task',
}
diff.apply(lines)
store.unload()
store.load()
local modified_after_first = store.get(1).modified
diff.apply(lines)
store.unload()
store.load()
local task = store.get(1)
assert.are.equal(modified_after_first, task.modified)
end)
it('clears due when removed from buffer line', function()
store.add({ description = 'Pay bill', due = '2026-03-15' })
store.save()
local lines = {
'Inbox',
'/1/ Pay bill',
}
diff.apply(lines)
store.unload()
store.load()
local task = store.get(1)
assert.is_nil(task.due)
end)
it('clears priority when ! is removed from buffer line', function()
store.add({ description = 'Task name', priority = 1 })
store.save()
local lines = {
'Inbox',
'/1/ Task name',
}
diff.apply(lines)
store.unload()
store.load()
local task = store.get(1)
assert.are.equal(0, task.priority)
end)
end)
end)

View file

@ -87,6 +87,67 @@ describe('parse', function()
assert.are.equal('Buy milk due:2026-03-15', desc)
assert.is_nil(meta.due)
end)
it('resolves due:today to today date', function()
local desc, meta = parse.body('Buy milk due:today')
assert.are.equal('Buy milk', desc)
assert.are.equal(os.date('%Y-%m-%d'), meta.due)
end)
it('resolves due:+2d to today plus 2 days', function()
local today = os.date('*t') --[[@as osdate]]
local expected = os.date('%Y-%m-%d', os.time({ year = today.year, month = today.month, day = today.day + 2 }))
local desc, meta = parse.body('Task due:+2d')
assert.are.equal('Task', desc)
assert.are.equal(expected, meta.due)
end)
it('leaves unresolvable due token in description', function()
local desc, meta = parse.body('Task due:garbage')
assert.is_nil(meta.due)
assert.truthy(desc:find('due:garbage', 1, true))
end)
end)
describe('parse.resolve_date', function()
it("returns today's date for 'today'", function()
local result = parse.resolve_date('today')
assert.are.equal(os.date('%Y-%m-%d'), result)
end)
it("returns tomorrow's date for 'tomorrow'", function()
local expected = os.date('%Y-%m-%d', os.time() + 86400)
local result = parse.resolve_date('tomorrow')
assert.are.equal(expected, result)
end)
it("returns today + 3 days for '+3d'", function()
local today = os.date('*t') --[[@as osdate]]
local expected = os.date('%Y-%m-%d', os.time({ year = today.year, month = today.month, day = today.day + 3 }))
local result = parse.resolve_date('+3d')
assert.are.equal(expected, result)
end)
it("returns today for '+0d'", function()
local result = parse.resolve_date('+0d')
assert.are.equal(os.date('%Y-%m-%d'), result)
end)
it("returns a future Monday (or today) for 'mon'", function()
local result = parse.resolve_date('mon')
assert.is_not_nil(result)
assert.truthy(result:match('^%d%d%d%d%-%d%d%-%d%d$'))
end)
it("returns nil for garbage input", function()
local result = parse.resolve_date('notadate')
assert.is_nil(result)
end)
it("returns nil for empty string", function()
local result = parse.resolve_date('')
assert.is_nil(result)
end)
end)
describe('command_add', function()

View file

@ -186,4 +186,31 @@ describe('store', function()
assert.are.equal('Active', active[1].description)
end)
end)
describe('snapshot', function()
it('returns a table of tasks', function()
store.load()
store.add({ description = 'Snap one' })
store.add({ description = 'Snap two' })
local snap = store.snapshot()
assert.are.equal(2, #snap)
end)
it('returns a copy that does not affect the store', function()
store.load()
local t = store.add({ description = 'Original' })
local snap = store.snapshot()
snap[1].description = 'Mutated'
local live = store.get(t.id)
assert.are.equal('Original', live.description)
end)
it('excludes deleted tasks', function()
store.load()
local t = store.add({ description = 'Will be deleted' })
store.delete(t.id)
local snap = store.snapshot()
assert.are.equal(0, #snap)
end)
end)
end)