require('spec.helpers') local config = require('pending.config') describe('archive', function() local tmpdir local pending local ui_input_orig before_each(function() tmpdir = vim.fn.tempname() vim.fn.mkdir(tmpdir, 'p') vim.g.pending = { data_path = tmpdir .. '/tasks.json' } config.reset() package.loaded['pending'] = nil pending = require('pending') pending.store():load() ui_input_orig = vim.ui.input end) after_each(function() vim.ui.input = ui_input_orig vim.fn.delete(tmpdir, 'rf') vim.g.pending = nil config.reset() package.loaded['pending'] = nil end) local function auto_confirm_y() vim.ui.input = function(opts, on_confirm) on_confirm('y') end end local function auto_confirm_n() vim.ui.input = function(opts, on_confirm) on_confirm('n') end end it('removes done tasks completed more than 30 days ago', function() auto_confirm_y() local s = pending.store() local t = s:add({ description = 'Old done task' }) s:update(t.id, { status = 'done', ['end'] = '2020-01-01T00:00:00Z' }) pending.archive() assert.are.equal(0, #s:active_tasks()) end) it('keeps done tasks completed fewer than 30 days ago', function() auto_confirm_y() local s = pending.store() local recent_end = os.date('!%Y-%m-%dT%H:%M:%SZ', os.time() - (5 * 86400)) local t = s:add({ description = 'Recent done task' }) s:update(t.id, { status = 'done', ['end'] = recent_end }) pending.archive() local active = s:active_tasks() assert.are.equal(1, #active) assert.are.equal('Recent done task', active[1].description) end) it('respects duration string 7d', function() auto_confirm_y() local s = pending.store() local eight_days_ago = os.date('!%Y-%m-%dT%H:%M:%SZ', os.time() - (8 * 86400)) local t = s:add({ description = 'Old for 7 days' }) s:update(t.id, { status = 'done', ['end'] = eight_days_ago }) pending.archive('7d') assert.are.equal(0, #s:active_tasks()) end) it('respects duration string 2w', function() auto_confirm_y() local s = pending.store() local fifteen_days_ago = os.date('!%Y-%m-%dT%H:%M:%SZ', os.time() - (15 * 86400)) local t = s:add({ description = 'Old for 2 weeks' }) s:update(t.id, { status = 'done', ['end'] = fifteen_days_ago }) pending.archive('2w') assert.are.equal(0, #s:active_tasks()) end) it('respects duration string 2m', function() auto_confirm_y() local s = pending.store() local t = s:add({ description = 'Old for 2 months' }) s:update(t.id, { status = 'done', ['end'] = '2020-01-01T00:00:00Z' }) pending.archive('2m') assert.are.equal(0, #s:active_tasks()) end) it('respects bare integer as days (backwards compat)', function() auto_confirm_y() local s = pending.store() local eight_days_ago = os.date('!%Y-%m-%dT%H:%M:%SZ', os.time() - (8 * 86400)) local t = s:add({ description = 'Old for 7 days' }) s:update(t.id, { status = 'done', ['end'] = eight_days_ago }) pending.archive('7') assert.are.equal(0, #s:active_tasks()) end) it('keeps tasks within the custom duration cutoff', function() auto_confirm_y() local s = pending.store() local five_days_ago = os.date('!%Y-%m-%dT%H:%M:%SZ', os.time() - (5 * 86400)) local t = s:add({ description = 'Recent for 7 days' }) s:update(t.id, { status = 'done', ['end'] = five_days_ago }) pending.archive('7d') local active = s:active_tasks() assert.are.equal(1, #active) end) it('errors on invalid duration input', function() local messages = {} local orig_notify = vim.notify vim.notify = function(msg, ...) table.insert(messages, msg) return orig_notify(msg, ...) end local s = pending.store() local t = s:add({ description = 'Task' }) s:update(t.id, { status = 'done', ['end'] = '2020-01-01T00:00:00Z' }) pending.archive('xyz') vim.notify = orig_notify assert.are.equal(1, #s:tasks()) local found = false for _, msg in ipairs(messages) do if msg:find('Invalid duration') then found = true break end end assert.is_true(found) end) it('never archives pending tasks regardless of age', function() auto_confirm_y() local s = pending.store() s:add({ description = 'Still pending' }) pending.archive() local active = s:active_tasks() assert.are.equal(1, #active) assert.are.equal('pending', active[1].status) end) it('removes deleted tasks past the cutoff', function() auto_confirm_y() local s = pending.store() local t = s:add({ description = 'Old deleted task' }) s:update(t.id, { status = 'deleted', ['end'] = '2020-01-01T00:00:00Z' }) pending.archive() local all = s:tasks() assert.are.equal(0, #all) end) it('keeps deleted tasks within the cutoff', function() auto_confirm_y() local s = pending.store() local recent_end = os.date('!%Y-%m-%dT%H:%M:%SZ', os.time() - (5 * 86400)) local t = s:add({ description = 'Recent deleted' }) s:update(t.id, { status = 'deleted', ['end'] = recent_end }) pending.archive() local all = s:tasks() assert.are.equal(1, #all) end) it('skips confirmation and reports when no tasks match', function() local input_called = false vim.ui.input = function() input_called = true end local messages = {} local orig_notify = vim.notify vim.notify = function(msg, ...) table.insert(messages, msg) return orig_notify(msg, ...) end local s = pending.store() s:add({ description = 'Still pending' }) pending.archive() vim.notify = orig_notify assert.is_false(input_called) local found = false for _, msg in ipairs(messages) do if msg:find('No tasks to archive') then found = true break end end assert.is_true(found) end) it('does not archive when user declines confirmation', function() auto_confirm_n() local s = pending.store() local t = s:add({ description = 'Old done task' }) s:update(t.id, { status = 'done', ['end'] = '2020-01-01T00:00:00Z' }) pending.archive() assert.are.equal(1, #s:tasks()) end) it('does not archive when user cancels confirmation (nil)', function() vim.ui.input = function(opts, on_confirm) on_confirm(nil) end local s = pending.store() local t = s:add({ description = 'Old done task' }) s:update(t.id, { status = 'done', ['end'] = '2020-01-01T00:00:00Z' }) pending.archive() assert.are.equal(1, #s:tasks()) end) it('reports the correct count in vim.notify', function() auto_confirm_y() local s = pending.store() local messages = {} local orig_notify = vim.notify vim.notify = function(msg, ...) table.insert(messages, msg) return orig_notify(msg, ...) end local t1 = s:add({ description = 'Old 1' }) local t2 = s:add({ description = 'Old 2' }) s:add({ description = 'Keep' }) s:update(t1.id, { status = 'done', ['end'] = '2020-01-01T00:00:00Z' }) s:update(t2.id, { status = 'done', ['end'] = '2020-01-01T00:00:00Z' }) pending.archive() vim.notify = orig_notify local found = false for _, msg in ipairs(messages) do if msg:find('Archived 2') then found = true break end end assert.is_true(found) end) it('leaves only kept tasks in store after archive', function() auto_confirm_y() local s = pending.store() local t1 = s:add({ description = 'Old done' }) s:add({ description = 'Keep pending' }) local recent_end = os.date('!%Y-%m-%dT%H:%M:%SZ', os.time() - (5 * 86400)) local t3 = s:add({ description = 'Keep recent done' }) s:update(t1.id, { status = 'done', ['end'] = '2020-01-01T00:00:00Z' }) s:update(t3.id, { status = 'done', ['end'] = recent_end }) pending.archive() local active = s:active_tasks() assert.are.equal(2, #active) local descs = {} for _, task in ipairs(active) do descs[task.description] = true end assert.is_true(descs['Keep pending']) assert.is_true(descs['Keep recent done']) end) it('persists archived tasks to disk after unload/reload', function() auto_confirm_y() local s = pending.store() local t = s:add({ description = 'Archived task' }) s:update(t.id, { status = 'done', ['end'] = '2020-01-01T00:00:00Z' }) pending.archive() s:load() assert.are.equal(0, #s:active_tasks()) end) it('includes the duration in the confirmation prompt', function() local prompt_text vim.ui.input = function(opts, on_confirm) prompt_text = opts.prompt on_confirm('n') end local s = pending.store() local t = s:add({ description = 'Old' }) s:update(t.id, { status = 'done', ['end'] = '2020-01-01T00:00:00Z' }) pending.archive('3w') assert.is_not_nil(prompt_text) assert.truthy(prompt_text:find('21d')) assert.truthy(prompt_text:find('1 task')) end) end)