feat: statusline API, counts, and PendingStatusChanged event (#40)
Problem: no way to know about overdue or due-today tasks without opening :Pending. No ambient awareness for statusline plugins. Solution: add counts(), statusline(), and has_due() public API functions backed by a module-local cache that recomputes after every store.save() and store.load(). Fire a User PendingStatusChanged event on every recompute. Extract is_overdue() and is_today() from duplicate locals into parse.lua as public functions. Refactor views.lua and init.lua to use the shared date logic. Add vimdoc API section and integration recipes for lualine, heirline, manual statusline, startup notification, and event-driven refresh.
This commit is contained in:
parent
92c2c670c5
commit
cd1cd1afd4
6 changed files with 507 additions and 69 deletions
|
|
@ -513,6 +513,57 @@ Fields: ~
|
|||
|pending.GcalConfig|. Omit this field entirely to
|
||||
disable Google Calendar sync.
|
||||
|
||||
==============================================================================
|
||||
LUA API *pending-api*
|
||||
|
||||
The following functions are available on `require('pending')` for use in
|
||||
statuslines, autocmds, and other integrations.
|
||||
|
||||
*pending.counts()*
|
||||
pending.counts()
|
||||
Returns a table of current task counts: >lua
|
||||
{
|
||||
overdue = 2, -- pending tasks past their due date/time
|
||||
today = 1, -- pending tasks due today (not yet overdue)
|
||||
pending = 10, -- total pending tasks (all statuses)
|
||||
priority = 3, -- pending tasks with priority > 0
|
||||
next_due = "2026-03-01", -- earliest future due date, or nil
|
||||
}
|
||||
<
|
||||
The counts are read from a module-local cache that is invalidated on every
|
||||
`:w`, toggle, date change, archive, undo, and sync. The first call triggers
|
||||
a lazy `store.load()` if the store has not been loaded yet.
|
||||
|
||||
Done, deleted, and `someday` sentinel-dated tasks are excluded from the
|
||||
`overdue` and `today` counts. The `someday` sentinel is the value of
|
||||
`someday_date` in |pending-config| (default `9999-12-30`).
|
||||
|
||||
*pending.statusline()*
|
||||
pending.statusline()
|
||||
Returns a pre-formatted string suitable for embedding in a statusline:
|
||||
|
||||
- `"2 overdue, 1 today"` when both overdue and today counts are non-zero
|
||||
- `"2 overdue"` when only overdue
|
||||
- `"1 today"` when only today
|
||||
- `""` (empty string) when nothing is actionable
|
||||
|
||||
*pending.has_due()*
|
||||
pending.has_due()
|
||||
Returns `true` when `overdue > 0` or `today > 0`. Useful as a conditional
|
||||
for statusline components that should only render when tasks need attention.
|
||||
|
||||
*PendingStatusChanged*
|
||||
PendingStatusChanged
|
||||
A |User| autocmd event fired after every count recomputation. Use this to
|
||||
trigger statusline refreshes or notifications: >lua
|
||||
vim.api.nvim_create_autocmd('User', {
|
||||
pattern = 'PendingStatusChanged',
|
||||
callback = function()
|
||||
vim.cmd.redrawstatus()
|
||||
end,
|
||||
})
|
||||
<
|
||||
|
||||
==============================================================================
|
||||
RECIPES *pending-recipes*
|
||||
|
||||
|
|
@ -526,6 +577,52 @@ Configure blink.cmp to use pending.nvim's omnifunc as a completion source: >lua
|
|||
})
|
||||
<
|
||||
|
||||
Lualine integration: >lua
|
||||
require('lualine').setup({
|
||||
sections = {
|
||||
lualine_x = {
|
||||
{
|
||||
function() return require('pending').statusline() end,
|
||||
cond = function() return require('pending').has_due() end,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
<
|
||||
|
||||
Heirline integration: >lua
|
||||
local Pending = {
|
||||
condition = function() return require('pending').has_due() end,
|
||||
provider = function() return require('pending').statusline() end,
|
||||
}
|
||||
<
|
||||
|
||||
Manual statusline: >vim
|
||||
set statusline+=%{%v:lua.require('pending').statusline()%}
|
||||
<
|
||||
|
||||
Startup notification: >lua
|
||||
vim.api.nvim_create_autocmd('User', {
|
||||
pattern = 'PendingStatusChanged',
|
||||
once = true,
|
||||
callback = function()
|
||||
local c = require('pending').counts()
|
||||
if c.overdue > 0 then
|
||||
vim.notify(c.overdue .. ' overdue task(s)')
|
||||
end
|
||||
end,
|
||||
})
|
||||
<
|
||||
|
||||
Event-driven statusline refresh: >lua
|
||||
vim.api.nvim_create_autocmd('User', {
|
||||
pattern = 'PendingStatusChanged',
|
||||
callback = function()
|
||||
vim.cmd.redrawstatus()
|
||||
end,
|
||||
})
|
||||
<
|
||||
|
||||
==============================================================================
|
||||
GOOGLE CALENDAR *pending-gcal*
|
||||
|
||||
|
|
|
|||
|
|
@ -3,11 +3,97 @@ local diff = require('pending.diff')
|
|||
local parse = require('pending.parse')
|
||||
local store = require('pending.store')
|
||||
|
||||
---@class pending.Counts
|
||||
---@field overdue integer
|
||||
---@field today integer
|
||||
---@field pending integer
|
||||
---@field priority integer
|
||||
---@field next_due? string
|
||||
|
||||
---@class pending.init
|
||||
local M = {}
|
||||
|
||||
local UNDO_MAX = 20
|
||||
|
||||
---@type pending.Counts?
|
||||
local _counts = nil
|
||||
|
||||
---@return nil
|
||||
function M._recompute_counts()
|
||||
local cfg = require('pending.config').get()
|
||||
local someday = cfg.someday_date
|
||||
local overdue = 0
|
||||
local today = 0
|
||||
local pending = 0
|
||||
local priority = 0
|
||||
local next_due = nil ---@type string?
|
||||
local today_str = os.date('%Y-%m-%d') --[[@as string]]
|
||||
|
||||
for _, task in ipairs(store.active_tasks()) do
|
||||
if task.status == 'pending' then
|
||||
pending = pending + 1
|
||||
if task.priority > 0 then
|
||||
priority = priority + 1
|
||||
end
|
||||
if task.due and task.due ~= someday then
|
||||
if parse.is_overdue(task.due) then
|
||||
overdue = overdue + 1
|
||||
elseif parse.is_today(task.due) then
|
||||
today = today + 1
|
||||
end
|
||||
local date_part = task.due:match('^(%d%d%d%d%-%d%d%-%d%d)') or task.due
|
||||
if date_part >= today_str and (not next_due or task.due < next_due) then
|
||||
next_due = task.due
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
_counts = {
|
||||
overdue = overdue,
|
||||
today = today,
|
||||
pending = pending,
|
||||
priority = priority,
|
||||
next_due = next_due,
|
||||
}
|
||||
|
||||
vim.api.nvim_exec_autocmds('User', { pattern = 'PendingStatusChanged' })
|
||||
end
|
||||
|
||||
---@return nil
|
||||
local function _save_and_notify()
|
||||
store.save()
|
||||
M._recompute_counts()
|
||||
end
|
||||
|
||||
---@return pending.Counts
|
||||
function M.counts()
|
||||
if not _counts then
|
||||
store.load()
|
||||
M._recompute_counts()
|
||||
end
|
||||
return _counts --[[@as pending.Counts]]
|
||||
end
|
||||
|
||||
---@return string
|
||||
function M.statusline()
|
||||
local c = M.counts()
|
||||
if c.overdue > 0 and c.today > 0 then
|
||||
return c.overdue .. ' overdue, ' .. c.today .. ' today'
|
||||
elseif c.overdue > 0 then
|
||||
return c.overdue .. ' overdue'
|
||||
elseif c.today > 0 then
|
||||
return c.today .. ' today'
|
||||
end
|
||||
return ''
|
||||
end
|
||||
|
||||
---@return boolean
|
||||
function M.has_due()
|
||||
local c = M.counts()
|
||||
return c.overdue > 0 or c.today > 0
|
||||
end
|
||||
|
||||
---@return integer bufnr
|
||||
function M.open()
|
||||
local bufnr = buffer.open()
|
||||
|
|
@ -167,6 +253,7 @@ function M._on_write(bufnr)
|
|||
table.remove(stack, 1)
|
||||
end
|
||||
diff.apply(lines)
|
||||
M._recompute_counts()
|
||||
buffer.render(bufnr)
|
||||
end
|
||||
|
||||
|
|
@ -179,7 +266,7 @@ function M.undo_write()
|
|||
end
|
||||
local state = table.remove(stack)
|
||||
store.replace_tasks(state)
|
||||
store.save()
|
||||
_save_and_notify()
|
||||
buffer.render(buffer.bufnr())
|
||||
end
|
||||
|
||||
|
|
@ -220,7 +307,7 @@ function M.toggle_complete()
|
|||
end
|
||||
store.update(id, { status = 'done' })
|
||||
end
|
||||
store.save()
|
||||
_save_and_notify()
|
||||
buffer.render(bufnr)
|
||||
for lnum, m in ipairs(buffer.meta()) do
|
||||
if m.id == id then
|
||||
|
|
@ -251,7 +338,7 @@ function M.toggle_priority()
|
|||
end
|
||||
local new_priority = task.priority > 0 and 0 or 1
|
||||
store.update(id, { priority = new_priority })
|
||||
store.save()
|
||||
_save_and_notify()
|
||||
buffer.render(bufnr)
|
||||
for lnum, m in ipairs(buffer.meta()) do
|
||||
if m.id == id then
|
||||
|
|
@ -294,7 +381,7 @@ function M.prompt_date()
|
|||
end
|
||||
end
|
||||
store.update(id, { due = due })
|
||||
store.save()
|
||||
_save_and_notify()
|
||||
buffer.render(bufnr)
|
||||
end)
|
||||
end
|
||||
|
|
@ -319,7 +406,7 @@ function M.add(text)
|
|||
recur = metadata.rec,
|
||||
recur_mode = metadata.rec_mode,
|
||||
})
|
||||
store.save()
|
||||
_save_and_notify()
|
||||
local bufnr = buffer.bufnr()
|
||||
if bufnr and vim.api.nvim_buf_is_valid(bufnr) then
|
||||
buffer.render(bufnr)
|
||||
|
|
@ -367,7 +454,7 @@ function M.archive(days)
|
|||
::skip::
|
||||
end
|
||||
store.replace_tasks(kept)
|
||||
store.save()
|
||||
_save_and_notify()
|
||||
vim.notify('Archived ' .. archived .. ' tasks.')
|
||||
local bufnr = buffer.bufnr()
|
||||
if bufnr and vim.api.nvim_buf_is_valid(bufnr) then
|
||||
|
|
@ -375,44 +462,6 @@ function M.archive(days)
|
|||
end
|
||||
end
|
||||
|
||||
---@param due string
|
||||
---@return boolean
|
||||
local function is_due_or_overdue(due)
|
||||
local now = os.date('*t') --[[@as osdate]]
|
||||
local today = os.date('%Y-%m-%d') --[[@as string]]
|
||||
local date_part, time_part = due:match('^(.+)T(.+)$')
|
||||
if not date_part then
|
||||
return due <= today
|
||||
end
|
||||
if date_part < today then
|
||||
return true
|
||||
end
|
||||
if date_part > today then
|
||||
return false
|
||||
end
|
||||
local current_time = string.format('%02d:%02d', now.hour, now.min)
|
||||
return time_part <= current_time
|
||||
end
|
||||
|
||||
---@param due string
|
||||
---@return boolean
|
||||
local function is_overdue(due)
|
||||
local now = os.date('*t') --[[@as osdate]]
|
||||
local today = os.date('%Y-%m-%d') --[[@as string]]
|
||||
local date_part, time_part = due:match('^(.+)T(.+)$')
|
||||
if not date_part then
|
||||
return due < today
|
||||
end
|
||||
if date_part < today then
|
||||
return true
|
||||
end
|
||||
if date_part > today then
|
||||
return false
|
||||
end
|
||||
local current_time = string.format('%02d:%02d', now.hour, now.min)
|
||||
return time_part < current_time
|
||||
end
|
||||
|
||||
---@return nil
|
||||
function M.due()
|
||||
local bufnr = buffer.bufnr()
|
||||
|
|
@ -422,9 +471,14 @@ function M.due()
|
|||
|
||||
if meta and bufnr then
|
||||
for lnum, m in ipairs(meta) do
|
||||
if m.type == 'task' and m.raw_due and m.status ~= 'done' and is_due_or_overdue(m.raw_due) then
|
||||
if
|
||||
m.type == 'task'
|
||||
and m.raw_due
|
||||
and m.status ~= 'done'
|
||||
and (parse.is_overdue(m.raw_due) or parse.is_today(m.raw_due))
|
||||
then
|
||||
local task = store.get(m.id or 0)
|
||||
local label = is_overdue(m.raw_due) and '[OVERDUE] ' or '[DUE] '
|
||||
local label = parse.is_overdue(m.raw_due) and '[OVERDUE] ' or '[DUE] '
|
||||
table.insert(qf_items, {
|
||||
bufnr = bufnr,
|
||||
lnum = lnum,
|
||||
|
|
@ -436,8 +490,12 @@ function M.due()
|
|||
else
|
||||
store.load()
|
||||
for _, task in ipairs(store.active_tasks()) do
|
||||
if task.status == 'pending' and task.due and is_due_or_overdue(task.due) then
|
||||
local label = is_overdue(task.due) and '[OVERDUE] ' or '[DUE] '
|
||||
if
|
||||
task.status == 'pending'
|
||||
and task.due
|
||||
and (parse.is_overdue(task.due) or parse.is_today(task.due))
|
||||
then
|
||||
local label = parse.is_overdue(task.due) and '[OVERDUE] ' or '[DUE] '
|
||||
local text = label .. task.description
|
||||
if task.category then
|
||||
text = text .. ' [' .. task.category .. ']'
|
||||
|
|
|
|||
|
|
@ -516,4 +516,39 @@ function M.command_add(text)
|
|||
return M.body(text)
|
||||
end
|
||||
|
||||
---@param due string
|
||||
---@return boolean
|
||||
function M.is_overdue(due)
|
||||
local now = os.date('*t') --[[@as osdate]]
|
||||
local today = os.date('%Y-%m-%d') --[[@as string]]
|
||||
local date_part, time_part = due:match('^(.+)T(.+)$')
|
||||
if not date_part then
|
||||
return due < today
|
||||
end
|
||||
if date_part < today then
|
||||
return true
|
||||
end
|
||||
if date_part > today then
|
||||
return false
|
||||
end
|
||||
local current_time = string.format('%02d:%02d', now.hour, now.min)
|
||||
return time_part < current_time
|
||||
end
|
||||
|
||||
---@param due string
|
||||
---@return boolean
|
||||
function M.is_today(due)
|
||||
local now = os.date('*t') --[[@as osdate]]
|
||||
local today = os.date('%Y-%m-%d') --[[@as string]]
|
||||
local date_part, time_part = due:match('^(.+)T(.+)$')
|
||||
if not date_part then
|
||||
return due == today
|
||||
end
|
||||
if date_part ~= today then
|
||||
return false
|
||||
end
|
||||
local current_time = string.format('%02d:%02d', now.hour, now.min)
|
||||
return time_part >= current_time
|
||||
end
|
||||
|
||||
return M
|
||||
|
|
|
|||
|
|
@ -503,6 +503,7 @@ function M.sync()
|
|||
end
|
||||
|
||||
store.save()
|
||||
require('pending')._recompute_counts()
|
||||
vim.notify(
|
||||
string.format(
|
||||
'pending.nvim: Synced to Google Calendar (created: %d, updated: %d, deleted: %d)',
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
local config = require('pending.config')
|
||||
local parse = require('pending.parse')
|
||||
|
||||
---@class pending.LineMeta
|
||||
---@field type 'task'|'header'|'blank'
|
||||
|
|
@ -40,25 +41,6 @@ local function format_due(due)
|
|||
return formatted
|
||||
end
|
||||
|
||||
---@param due string
|
||||
---@return boolean
|
||||
local function is_overdue(due)
|
||||
local now = os.date('*t') --[[@as osdate]]
|
||||
local today = os.date('%Y-%m-%d') --[[@as string]]
|
||||
local date_part, time_part = due:match('^(.+)T(.+)$')
|
||||
if not date_part then
|
||||
return due < today
|
||||
end
|
||||
if date_part < today then
|
||||
return true
|
||||
end
|
||||
if date_part > today then
|
||||
return false
|
||||
end
|
||||
local current_time = string.format('%02d:%02d', now.hour, now.min)
|
||||
return time_part < current_time
|
||||
end
|
||||
|
||||
---@param tasks pending.Task[]
|
||||
local function sort_tasks(tasks)
|
||||
table.sort(tasks, function(a, b)
|
||||
|
|
@ -174,7 +156,8 @@ function M.category_view(tasks)
|
|||
raw_due = task.due,
|
||||
status = task.status,
|
||||
category = cat,
|
||||
overdue = task.status == 'pending' and task.due ~= nil and is_overdue(task.due) or nil,
|
||||
overdue = task.status == 'pending' and task.due ~= nil and parse.is_overdue(task.due)
|
||||
or nil,
|
||||
recur = task.recur,
|
||||
})
|
||||
end
|
||||
|
|
@ -224,7 +207,7 @@ function M.priority_view(tasks)
|
|||
raw_due = task.due,
|
||||
status = task.status,
|
||||
category = task.category,
|
||||
overdue = task.status == 'pending' and task.due ~= nil and is_overdue(task.due) or nil,
|
||||
overdue = task.status == 'pending' and task.due ~= nil and parse.is_overdue(task.due) or nil,
|
||||
show_category = true,
|
||||
recur = task.recur,
|
||||
})
|
||||
|
|
|
|||
264
spec/status_spec.lua
Normal file
264
spec/status_spec.lua
Normal file
|
|
@ -0,0 +1,264 @@
|
|||
require('spec.helpers')
|
||||
|
||||
local config = require('pending.config')
|
||||
local parse = require('pending.parse')
|
||||
local store = require('pending.store')
|
||||
|
||||
describe('status', function()
|
||||
local tmpdir
|
||||
local pending
|
||||
|
||||
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
|
||||
pending = require('pending')
|
||||
end)
|
||||
|
||||
after_each(function()
|
||||
vim.fn.delete(tmpdir, 'rf')
|
||||
vim.g.pending = nil
|
||||
config.reset()
|
||||
store.unload()
|
||||
package.loaded['pending'] = nil
|
||||
end)
|
||||
|
||||
describe('counts', function()
|
||||
it('returns zeroes for empty store', function()
|
||||
store.load()
|
||||
local c = pending.counts()
|
||||
assert.are.equal(0, c.overdue)
|
||||
assert.are.equal(0, c.today)
|
||||
assert.are.equal(0, c.pending)
|
||||
assert.are.equal(0, c.priority)
|
||||
assert.is_nil(c.next_due)
|
||||
end)
|
||||
|
||||
it('counts pending tasks', function()
|
||||
store.load()
|
||||
store.add({ description = 'One' })
|
||||
store.add({ description = 'Two' })
|
||||
store.save()
|
||||
pending._recompute_counts()
|
||||
local c = pending.counts()
|
||||
assert.are.equal(2, c.pending)
|
||||
end)
|
||||
|
||||
it('counts priority tasks', function()
|
||||
store.load()
|
||||
store.add({ description = 'Urgent', priority = 1 })
|
||||
store.add({ description = 'Normal' })
|
||||
store.save()
|
||||
pending._recompute_counts()
|
||||
local c = pending.counts()
|
||||
assert.are.equal(1, c.priority)
|
||||
end)
|
||||
|
||||
it('counts overdue tasks with date-only', function()
|
||||
store.load()
|
||||
store.add({ description = 'Old task', due = '2020-01-01' })
|
||||
store.save()
|
||||
pending._recompute_counts()
|
||||
local c = pending.counts()
|
||||
assert.are.equal(1, c.overdue)
|
||||
end)
|
||||
|
||||
it('counts overdue tasks with datetime', function()
|
||||
store.load()
|
||||
store.add({ description = 'Old task', due = '2020-01-01T08:00' })
|
||||
store.save()
|
||||
pending._recompute_counts()
|
||||
local c = pending.counts()
|
||||
assert.are.equal(1, c.overdue)
|
||||
end)
|
||||
|
||||
it('counts today tasks', function()
|
||||
store.load()
|
||||
local today = os.date('%Y-%m-%d') --[[@as string]]
|
||||
store.add({ description = 'Today task', due = today })
|
||||
store.save()
|
||||
pending._recompute_counts()
|
||||
local c = pending.counts()
|
||||
assert.are.equal(1, c.today)
|
||||
assert.are.equal(0, c.overdue)
|
||||
end)
|
||||
|
||||
it('counts mixed overdue and today', function()
|
||||
store.load()
|
||||
local today = os.date('%Y-%m-%d') --[[@as string]]
|
||||
store.add({ description = 'Overdue', due = '2020-01-01' })
|
||||
store.add({ description = 'Today', due = today })
|
||||
store.save()
|
||||
pending._recompute_counts()
|
||||
local c = pending.counts()
|
||||
assert.are.equal(1, c.overdue)
|
||||
assert.are.equal(1, c.today)
|
||||
end)
|
||||
|
||||
it('excludes done tasks', function()
|
||||
store.load()
|
||||
local t = store.add({ description = 'Done', due = '2020-01-01' })
|
||||
store.update(t.id, { status = 'done' })
|
||||
store.save()
|
||||
pending._recompute_counts()
|
||||
local c = pending.counts()
|
||||
assert.are.equal(0, c.overdue)
|
||||
assert.are.equal(0, c.pending)
|
||||
end)
|
||||
|
||||
it('excludes deleted tasks', function()
|
||||
store.load()
|
||||
local t = store.add({ description = 'Deleted', due = '2020-01-01' })
|
||||
store.delete(t.id)
|
||||
store.save()
|
||||
pending._recompute_counts()
|
||||
local c = pending.counts()
|
||||
assert.are.equal(0, c.overdue)
|
||||
assert.are.equal(0, c.pending)
|
||||
end)
|
||||
|
||||
it('excludes someday sentinel', function()
|
||||
store.load()
|
||||
store.add({ description = 'Someday', due = '9999-12-30' })
|
||||
store.save()
|
||||
pending._recompute_counts()
|
||||
local c = pending.counts()
|
||||
assert.are.equal(0, c.overdue)
|
||||
assert.are.equal(0, c.today)
|
||||
assert.are.equal(1, c.pending)
|
||||
end)
|
||||
|
||||
it('picks earliest future date as next_due', function()
|
||||
store.load()
|
||||
local today = os.date('%Y-%m-%d') --[[@as string]]
|
||||
store.add({ description = 'Soon', due = '2099-06-01' })
|
||||
store.add({ description = 'Sooner', due = '2099-03-01' })
|
||||
store.add({ description = 'Today', due = today })
|
||||
store.save()
|
||||
pending._recompute_counts()
|
||||
local c = pending.counts()
|
||||
assert.are.equal(today, c.next_due)
|
||||
end)
|
||||
|
||||
it('lazy loads on first counts() call', 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 = 'Overdue',
|
||||
status = 'pending',
|
||||
due = '2020-01-01',
|
||||
entry = '2020-01-01T00:00:00Z',
|
||||
modified = '2020-01-01T00:00:00Z',
|
||||
},
|
||||
},
|
||||
}))
|
||||
f:close()
|
||||
store.unload()
|
||||
package.loaded['pending'] = nil
|
||||
pending = require('pending')
|
||||
local c = pending.counts()
|
||||
assert.are.equal(1, c.overdue)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('statusline', function()
|
||||
it('returns empty string when nothing actionable', function()
|
||||
store.load()
|
||||
store.save()
|
||||
pending._recompute_counts()
|
||||
assert.are.equal('', pending.statusline())
|
||||
end)
|
||||
|
||||
it('formats overdue only', function()
|
||||
store.load()
|
||||
store.add({ description = 'Old', due = '2020-01-01' })
|
||||
store.save()
|
||||
pending._recompute_counts()
|
||||
assert.are.equal('1 overdue', pending.statusline())
|
||||
end)
|
||||
|
||||
it('formats today only', function()
|
||||
store.load()
|
||||
local today = os.date('%Y-%m-%d') --[[@as string]]
|
||||
store.add({ description = 'Today', due = today })
|
||||
store.save()
|
||||
pending._recompute_counts()
|
||||
assert.are.equal('1 today', pending.statusline())
|
||||
end)
|
||||
|
||||
it('formats overdue and today', function()
|
||||
store.load()
|
||||
local today = os.date('%Y-%m-%d') --[[@as string]]
|
||||
store.add({ description = 'Old', due = '2020-01-01' })
|
||||
store.add({ description = 'Today', due = today })
|
||||
store.save()
|
||||
pending._recompute_counts()
|
||||
assert.are.equal('1 overdue, 1 today', pending.statusline())
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('has_due', function()
|
||||
it('returns false when nothing due', function()
|
||||
store.load()
|
||||
store.add({ description = 'Future', due = '2099-01-01' })
|
||||
store.save()
|
||||
pending._recompute_counts()
|
||||
assert.is_false(pending.has_due())
|
||||
end)
|
||||
|
||||
it('returns true when overdue', function()
|
||||
store.load()
|
||||
store.add({ description = 'Old', due = '2020-01-01' })
|
||||
store.save()
|
||||
pending._recompute_counts()
|
||||
assert.is_true(pending.has_due())
|
||||
end)
|
||||
|
||||
it('returns true when today', function()
|
||||
store.load()
|
||||
local today = os.date('%Y-%m-%d') --[[@as string]]
|
||||
store.add({ description = 'Now', due = today })
|
||||
store.save()
|
||||
pending._recompute_counts()
|
||||
assert.is_true(pending.has_due())
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('parse.is_overdue', function()
|
||||
it('date before today is overdue', function()
|
||||
assert.is_true(parse.is_overdue('2020-01-01'))
|
||||
end)
|
||||
|
||||
it('date after today is not overdue', function()
|
||||
assert.is_false(parse.is_overdue('2099-01-01'))
|
||||
end)
|
||||
|
||||
it('today date-only is not overdue', function()
|
||||
local today = os.date('%Y-%m-%d') --[[@as string]]
|
||||
assert.is_false(parse.is_overdue(today))
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('parse.is_today', function()
|
||||
it('today date-only is today', function()
|
||||
local today = os.date('%Y-%m-%d') --[[@as string]]
|
||||
assert.is_true(parse.is_today(today))
|
||||
end)
|
||||
|
||||
it('yesterday is not today', function()
|
||||
assert.is_false(parse.is_today('2020-01-01'))
|
||||
end)
|
||||
|
||||
it('tomorrow is not today', function()
|
||||
assert.is_false(parse.is_today('2099-01-01'))
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
Loading…
Add table
Add a link
Reference in a new issue