require('spec.helpers') local config = require('pending.config') local store = require('pending.store') describe('store', function() local tmpdir before_each(function() tmpdir = vim.fn.tempname() vim.fn.mkdir(tmpdir, 'p') vim.g.pending = { data_path = tmpdir .. '/tasks.json' } config.reset() store.unload() end) after_each(function() vim.fn.delete(tmpdir, 'rf') vim.g.pending = nil config.reset() end) describe('load', function() it('returns empty data when no file exists', function() local data = store.load() assert.are.equal(1, data.version) assert.are.equal(1, data.next_id) assert.are.same({}, data.tasks) end) it('loads existing data', function() local path = config.get().data_path local f = io.open(path, 'w') f:write(vim.json.encode({ version = 1, next_id = 3, tasks = { { id = 1, description = 'Pending one', status = 'pending', entry = '2026-01-01T00:00:00Z', modified = '2026-01-01T00:00:00Z', }, { id = 2, description = 'Pending two', status = 'done', entry = '2026-01-01T00:00:00Z', modified = '2026-01-01T00:00:00Z', }, }, })) f:close() local data = store.load() assert.are.equal(3, data.next_id) assert.are.equal(2, #data.tasks) assert.are.equal('Pending one', data.tasks[1].description) assert.are.equal('done', data.tasks[2].status) end) it('preserves unknown fields', function() local path = config.get().data_path local f = io.open(path, 'w') f:write(vim.json.encode({ version = 1, next_id = 2, tasks = { { id = 1, description = 'Pending', status = 'pending', entry = '2026-01-01T00:00:00Z', modified = '2026-01-01T00:00:00Z', custom_field = 'hello', }, }, })) f:close() store.load() local task = store.get(1) assert.is_not_nil(task._extra) assert.are.equal('hello', task._extra.custom_field) end) end) describe('add', function() it('creates a task with incremented id', function() store.load() local t1 = store.add({ description = 'First' }) local t2 = store.add({ description = 'Second' }) assert.are.equal(1, t1.id) assert.are.equal(2, t2.id) assert.are.equal('pending', t1.status) assert.are.equal('Todo', t1.category) end) it('uses provided category', function() store.load() local t = store.add({ description = 'Test', category = 'Work' }) assert.are.equal('Work', t.category) end) end) describe('update', function() it('updates fields and sets modified', function() store.load() local t = store.add({ description = 'Original' }) t.modified = '2025-01-01T00:00:00Z' store.update(t.id, { description = 'Updated' }) local updated = store.get(t.id) assert.are.equal('Updated', updated.description) assert.is_not.equal('2025-01-01T00:00:00Z', updated.modified) end) it('sets end timestamp on completion', function() store.load() local t = store.add({ description = 'Test' }) assert.is_nil(t['end']) store.update(t.id, { status = 'done' }) local updated = store.get(t.id) assert.is_not_nil(updated['end']) end) end) describe('delete', function() it('marks task as deleted', function() store.load() local t = store.add({ description = 'To delete' }) store.delete(t.id) local deleted = store.get(t.id) assert.are.equal('deleted', deleted.status) assert.is_not_nil(deleted['end']) end) end) describe('save and round-trip', function() it('persists and reloads correctly', function() store.load() store.add({ description = 'Persisted', category = 'Work', priority = 1 }) store.save() store.unload() store.load() local tasks = store.active_tasks() assert.are.equal(1, #tasks) assert.are.equal('Persisted', tasks[1].description) assert.are.equal('Work', tasks[1].category) assert.are.equal(1, tasks[1].priority) end) it('round-trips unknown fields', function() local path = config.get().data_path local f = io.open(path, 'w') f:write(vim.json.encode({ version = 1, next_id = 2, tasks = { { id = 1, description = 'Pending', status = 'pending', entry = '2026-01-01T00:00:00Z', modified = '2026-01-01T00:00:00Z', _gcal_event_id = 'abc123', }, }, })) f:close() store.load() store.save() store.unload() store.load() local task = store.get(1) assert.are.equal('abc123', task._extra._gcal_event_id) end) end) describe('active_tasks', function() it('excludes deleted tasks', function() store.load() store.add({ description = 'Active' }) local t2 = store.add({ description = 'To delete' }) store.delete(t2.id) local active = store.active_tasks() assert.are.equal(1, #active) 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)