From aae6989a197f69d94eae018fe8b408833537593f Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Tue, 24 Feb 2026 19:53:50 -0500 Subject: [PATCH] test: add top-priority missing test coverage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- spec/diff_spec.lua | 46 ++++++++++++++++++++++++++++++++++ spec/parse_spec.lua | 61 +++++++++++++++++++++++++++++++++++++++++++++ spec/store_spec.lua | 27 ++++++++++++++++++++ 3 files changed, 134 insertions(+) diff --git a/spec/diff_spec.lua b/spec/diff_spec.lua index 3d3c935..b8fcfd9 100644 --- a/spec/diff_spec.lua +++ b/spec/diff_spec.lua @@ -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) diff --git a/spec/parse_spec.lua b/spec/parse_spec.lua index 92b2239..b4442e9 100644 --- a/spec/parse_spec.lua +++ b/spec/parse_spec.lua @@ -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() diff --git a/spec/store_spec.lua b/spec/store_spec.lua index 8fdf5f4..930fbc0 100644 --- a/spec/store_spec.lua +++ b/spec/store_spec.lua @@ -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)