require('spec.helpers') local config = require('pending.config') local diff = require('pending.diff') local store = require('pending.store') describe('filter', function() local tmpdir local pending local buffer before_each(function() tmpdir = vim.fn.tempname() vim.fn.mkdir(tmpdir, 'p') vim.g.pending = { data_path = tmpdir .. '/tasks.json' } config.reset() store.unload() package.loaded['pending'] = nil package.loaded['pending.buffer'] = nil pending = require('pending') buffer = require('pending.buffer') buffer.set_filter({}, {}) end) after_each(function() vim.fn.delete(tmpdir, 'rf') vim.g.pending = nil config.reset() store.unload() package.loaded['pending'] = nil package.loaded['pending.buffer'] = nil end) describe('filter predicates', function() it('cat: hides tasks with non-matching category', function() store.load() store.add({ description = 'Work task', category = 'Work' }) store.add({ description = 'Home task', category = 'Home' }) store.save() pending.filter('cat:Work') local hidden = buffer.hidden_ids() local tasks = store.active_tasks() local work_task = nil local home_task = nil for _, t in ipairs(tasks) do if t.category == 'Work' then work_task = t end if t.category == 'Home' then home_task = t end end assert.is_not_nil(work_task) assert.is_not_nil(home_task) assert.is_nil(hidden[work_task.id]) assert.is_true(hidden[home_task.id]) end) it('cat: hides tasks with no category (default category)', function() store.load() store.add({ description = 'Work task', category = 'Work' }) store.add({ description = 'Inbox task' }) store.save() pending.filter('cat:Work') local hidden = buffer.hidden_ids() local tasks = store.active_tasks() local inbox_task = nil for _, t in ipairs(tasks) do if t.category ~= 'Work' then inbox_task = t end end assert.is_not_nil(inbox_task) assert.is_true(hidden[inbox_task.id]) end) it('overdue hides non-overdue tasks', function() store.load() store.add({ description = 'Old task', due = '2020-01-01' }) store.add({ description = 'Future task', due = '2099-01-01' }) store.add({ description = 'No due task' }) store.save() pending.filter('overdue') local hidden = buffer.hidden_ids() local tasks = store.active_tasks() local overdue_task, future_task, nodue_task for _, t in ipairs(tasks) do if t.due == '2020-01-01' then overdue_task = t end if t.due == '2099-01-01' then future_task = t end if not t.due then nodue_task = t end end assert.is_nil(hidden[overdue_task.id]) assert.is_true(hidden[future_task.id]) assert.is_true(hidden[nodue_task.id]) end) it('today hides non-today tasks', function() store.load() local today = os.date('%Y-%m-%d') --[[@as string]] store.add({ description = 'Today task', due = today }) store.add({ description = 'Old task', due = '2020-01-01' }) store.add({ description = 'Future task', due = '2099-01-01' }) store.save() pending.filter('today') local hidden = buffer.hidden_ids() local tasks = store.active_tasks() local today_task, old_task, future_task for _, t in ipairs(tasks) do if t.due == today then today_task = t end if t.due == '2020-01-01' then old_task = t end if t.due == '2099-01-01' then future_task = t end end assert.is_nil(hidden[today_task.id]) assert.is_true(hidden[old_task.id]) assert.is_true(hidden[future_task.id]) end) it('priority hides non-priority tasks', function() store.load() store.add({ description = 'Important', priority = 1 }) store.add({ description = 'Normal' }) store.save() pending.filter('priority') local hidden = buffer.hidden_ids() local tasks = store.active_tasks() local important_task, normal_task for _, t in ipairs(tasks) do if t.priority and t.priority > 0 then important_task = t end if not t.priority or t.priority == 0 then normal_task = t end end assert.is_nil(hidden[important_task.id]) assert.is_true(hidden[normal_task.id]) end) it('multi-predicate AND: cat:Work + overdue', function() store.load() store.add({ description = 'Work overdue', category = 'Work', due = '2020-01-01' }) store.add({ description = 'Work future', category = 'Work', due = '2099-01-01' }) store.add({ description = 'Home overdue', category = 'Home', due = '2020-01-01' }) store.save() pending.filter('cat:Work overdue') local hidden = buffer.hidden_ids() local tasks = store.active_tasks() local work_overdue, work_future, home_overdue for _, t in ipairs(tasks) do if t.description == 'Work overdue' then work_overdue = t end if t.description == 'Work future' then work_future = t end if t.description == 'Home overdue' then home_overdue = t end end assert.is_nil(hidden[work_overdue.id]) assert.is_true(hidden[work_future.id]) assert.is_true(hidden[home_overdue.id]) end) it('filter clear removes all predicates and hidden ids', function() store.load() store.add({ description = 'Work task', category = 'Work' }) store.add({ description = 'Home task', category = 'Home' }) store.save() pending.filter('cat:Work') assert.are.equal(1, #buffer.filter_predicates()) pending.filter('clear') assert.are.equal(0, #buffer.filter_predicates()) assert.are.same({}, buffer.hidden_ids()) end) it('filter empty string clears filter', function() store.load() store.add({ description = 'Work task', category = 'Work' }) store.save() pending.filter('cat:Work') assert.are.equal(1, #buffer.filter_predicates()) pending.filter('') assert.are.equal(0, #buffer.filter_predicates()) end) it('filter predicates persist across set_filter calls', function() store.load() store.add({ description = 'Work task', category = 'Work' }) store.add({ description = 'Home task', category = 'Home' }) store.save() pending.filter('cat:Work') local preds = buffer.filter_predicates() assert.are.equal(1, #preds) assert.are.equal('cat:Work', preds[1]) local hidden = buffer.hidden_ids() local tasks = store.active_tasks() local home_task for _, t in ipairs(tasks) do if t.category == 'Home' then home_task = t end end assert.is_true(hidden[home_task.id]) end) end) describe('diff.apply with hidden_ids', function() it('does not mark hidden tasks as deleted', function() store.load() store.add({ description = 'Visible task' }) store.add({ description = 'Hidden task' }) store.save() local tasks = store.active_tasks() local hidden_task for _, t in ipairs(tasks) do if t.description == 'Hidden task' then hidden_task = t end end local hidden_ids = { [hidden_task.id] = true } local lines = { '/1/- [ ] Visible task', } diff.apply(lines, hidden_ids) store.unload() store.load() local hidden = store.get(hidden_task.id) assert.are.equal('pending', hidden.status) end) it('marks tasks deleted when not hidden and not in buffer', function() store.load() store.add({ description = 'Keep task' }) store.add({ description = 'Delete task' }) store.save() local tasks = store.active_tasks() local keep_task, delete_task for _, t in ipairs(tasks) do if t.description == 'Keep task' then keep_task = t end if t.description == 'Delete task' then delete_task = t end end local lines = { '/' .. keep_task.id .. '/- [ ] Keep task', } diff.apply(lines, {}) store.unload() store.load() local deleted = store.get(delete_task.id) assert.are.equal('deleted', deleted.status) end) it('strips FILTER: line before parsing', function() store.load() store.add({ description = 'My task' }) store.save() local tasks = store.active_tasks() local task = tasks[1] local lines = { 'FILTER: cat:Work', '/' .. task.id .. '/- [ ] My task', } diff.apply(lines, {}) store.unload() store.load() local t = store.get(task.id) assert.are.equal('pending', t.status) end) it('parse_buffer skips FILTER: header line', function() local lines = { 'FILTER: overdue', '/1/- [ ] A task', } local result = diff.parse_buffer(lines) assert.are.equal(1, #result) assert.are.equal('task', result[1].type) assert.are.equal('A task', result[1].description) end) end) end)