Some checks are pending
quality / changes (push) Waiting to run
quality / Lua Format Check (push) Blocked by required conditions
quality / Lua Lint Check (push) Blocked by required conditions
quality / Lua Type Check (push) Blocked by required conditions
quality / Markdown Format Check (push) Blocked by required conditions
test / Test (Neovim nightly) (push) Waiting to run
test / Test (Neovim stable) (push) Waiting to run
Problem: `parse.body()` scanned tokens right-to-left and broke on the first non-metadata token, so metadata only worked at the trailing end of a line. `due:tomorrow Fix the bug` silently failed to parse the due date. Solution: Replace the right-to-left `while` loop with a single left-to-right pass that extracts metadata tokens from any position. Duplicate metadata tokens are dropped with a `log.warn`. Update docs and tests accordingly.
614 lines
21 KiB
Lua
614 lines
21 KiB
Lua
require('spec.helpers')
|
|
|
|
local config = require('pending.config')
|
|
|
|
describe('parse', function()
|
|
before_each(function()
|
|
vim.g.pending = nil
|
|
config.reset()
|
|
end)
|
|
|
|
after_each(function()
|
|
vim.g.pending = nil
|
|
config.reset()
|
|
end)
|
|
|
|
local parse = require('pending.parse')
|
|
|
|
describe('body', function()
|
|
it('returns plain description when no metadata', function()
|
|
local desc, meta = parse.body('Buy groceries')
|
|
assert.are.equal('Buy groceries', desc)
|
|
assert.are.same({}, meta)
|
|
end)
|
|
|
|
it('extracts due date', function()
|
|
local desc, meta = parse.body('Buy groceries due:2026-03-15')
|
|
assert.are.equal('Buy groceries', desc)
|
|
assert.are.equal('2026-03-15', meta.due)
|
|
end)
|
|
|
|
it('extracts category', function()
|
|
local desc, meta = parse.body('Buy groceries cat:Errands')
|
|
assert.are.equal('Buy groceries', desc)
|
|
assert.are.equal('Errands', meta.category)
|
|
end)
|
|
|
|
it('extracts both due and cat', function()
|
|
local desc, meta = parse.body('Buy milk due:2026-03-15 cat:Errands')
|
|
assert.are.equal('Buy milk', desc)
|
|
assert.are.equal('2026-03-15', meta.due)
|
|
assert.are.equal('Errands', meta.category)
|
|
end)
|
|
|
|
it('extracts metadata in any order', function()
|
|
local desc, meta = parse.body('Buy milk cat:Errands due:2026-03-15')
|
|
assert.are.equal('Buy milk', desc)
|
|
assert.are.equal('2026-03-15', meta.due)
|
|
assert.are.equal('Errands', meta.category)
|
|
end)
|
|
|
|
it('first occurrence wins for duplicate keys and warns', function()
|
|
local warnings = {}
|
|
local orig = vim.notify
|
|
vim.notify = function(m, level)
|
|
if level == vim.log.levels.WARN then
|
|
table.insert(warnings, m)
|
|
end
|
|
end
|
|
local desc, meta = parse.body('Buy milk due:2026-03-15 due:2026-04-01')
|
|
vim.notify = orig
|
|
assert.are.equal('Buy milk', desc)
|
|
assert.are.equal('2026-03-15', meta.due)
|
|
assert.are.equal(1, #warnings)
|
|
assert.truthy(warnings[1]:find('duplicate', 1, true))
|
|
end)
|
|
|
|
it('drops identical duplicate metadata tokens and warns', function()
|
|
local warnings = {}
|
|
local orig = vim.notify
|
|
vim.notify = function(m, level)
|
|
if level == vim.log.levels.WARN then
|
|
table.insert(warnings, m)
|
|
end
|
|
end
|
|
local desc, meta = parse.body('Buy milk due:tomorrow due:tomorrow')
|
|
vim.notify = orig
|
|
assert.are.equal('Buy milk', desc)
|
|
assert.are.equal(os.date('%Y-%m-%d', os.time() + 86400), meta.due)
|
|
assert.are.equal(1, #warnings)
|
|
end)
|
|
|
|
it('stops at non-meta token', function()
|
|
local desc, meta = parse.body('Buy milk for breakfast due:2026-03-15')
|
|
assert.are.equal('Buy milk for breakfast', desc)
|
|
assert.are.equal('2026-03-15', meta.due)
|
|
end)
|
|
|
|
it('rejects invalid dates', function()
|
|
local desc, meta = parse.body('Buy milk due:2026-13-15')
|
|
assert.are.equal('Buy milk due:2026-13-15', desc)
|
|
assert.is_nil(meta.due)
|
|
end)
|
|
|
|
it('preserves colons in description', function()
|
|
local desc, meta = parse.body('Meeting at 3:00pm')
|
|
assert.are.equal('Meeting at 3:00pm', desc)
|
|
assert.are.same({}, meta)
|
|
end)
|
|
|
|
it('uses configurable date syntax', function()
|
|
vim.g.pending = { date_syntax = 'by' }
|
|
config.reset()
|
|
local desc, meta = parse.body('Buy milk by:2026-03-15')
|
|
assert.are.equal('Buy milk', desc)
|
|
assert.are.equal('2026-03-15', meta.due)
|
|
end)
|
|
|
|
it('ignores old syntax when date_syntax is changed', function()
|
|
vim.g.pending = { date_syntax = 'by' }
|
|
config.reset()
|
|
local desc, meta = parse.body('Buy milk due:2026-03-15')
|
|
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)
|
|
|
|
it('parses metadata before a forge ref', function()
|
|
local desc, meta = parse.body('Fix bug due:2026-03-15 gh:user/repo#42')
|
|
assert.are.equal('2026-03-15', meta.due)
|
|
assert.truthy(desc:find('gh:user/repo#42', 1, true))
|
|
assert.truthy(desc:find('Fix bug', 1, true))
|
|
end)
|
|
|
|
it('parses metadata after a forge ref', function()
|
|
local desc, meta = parse.body('Fix bug gh:user/repo#42 due:2026-03-15')
|
|
assert.are.equal('2026-03-15', meta.due)
|
|
assert.truthy(desc:find('gh:user/repo#42', 1, true))
|
|
assert.truthy(desc:find('Fix bug', 1, true))
|
|
end)
|
|
|
|
it('parses all metadata around forge ref', function()
|
|
local desc, meta = parse.body('Fix bug due:tomorrow gh:user/repo#42 cat:Work')
|
|
assert.are.equal(os.date('%Y-%m-%d', os.time() + 86400), meta.due)
|
|
assert.are.equal('Work', meta.category)
|
|
assert.truthy(desc:find('gh:user/repo#42', 1, true))
|
|
end)
|
|
|
|
it('parses forge ref between metadata tokens', function()
|
|
local desc, meta = parse.body('Fix bug cat:Work gl:a/b#12 due:2026-03-15')
|
|
assert.are.equal('2026-03-15', meta.due)
|
|
assert.are.equal('Work', meta.category)
|
|
assert.truthy(desc:find('gl:a/b#12', 1, true))
|
|
end)
|
|
|
|
it('extracts leading metadata', function()
|
|
local desc, meta = parse.body('due:2026-03-15 Fix the bug')
|
|
assert.are.equal('Fix the bug', desc)
|
|
assert.are.equal('2026-03-15', meta.due)
|
|
end)
|
|
|
|
it('extracts metadata from the middle', function()
|
|
local desc, meta = parse.body('Fix due:2026-03-15 the bug')
|
|
assert.are.equal('Fix the bug', desc)
|
|
assert.are.equal('2026-03-15', meta.due)
|
|
end)
|
|
|
|
it('extracts multiple metadata from any position', function()
|
|
local desc, meta = parse.body('cat:Work Fix due:2026-03-15 the bug')
|
|
assert.are.equal('Fix the bug', desc)
|
|
assert.are.equal('2026-03-15', meta.due)
|
|
assert.are.equal('Work', meta.category)
|
|
end)
|
|
|
|
it('extracts all metadata types from mixed positions', function()
|
|
local today = os.date('*t') --[[@as osdate]]
|
|
local tomorrow = os.date(
|
|
'%Y-%m-%d',
|
|
os.time({ year = today.year, month = today.month, day = today.day + 1 })
|
|
)
|
|
local desc, meta = parse.body('due:tomorrow cat:Work Fix the bug +!')
|
|
assert.are.equal('Fix the bug', desc)
|
|
assert.are.equal(tomorrow, meta.due)
|
|
assert.are.equal('Work', meta.category)
|
|
assert.are.equal(1, meta.priority)
|
|
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)
|
|
|
|
it("returns yesterday's date for 'yesterday'", function()
|
|
local expected = os.date('%Y-%m-%d', os.time() - 86400)
|
|
local result = parse.resolve_date('yesterday')
|
|
assert.are.equal(expected, result)
|
|
end)
|
|
|
|
it("returns today's date for 'eod'", function()
|
|
local result = parse.resolve_date('eod')
|
|
assert.are.equal(os.date('%Y-%m-%d'), result)
|
|
end)
|
|
|
|
it('returns Monday of current week for sow', function()
|
|
local result = parse.resolve_date('sow')
|
|
assert.is_not_nil(result)
|
|
local y, m, d = result:match('^(%d+)-(%d+)-(%d+)$')
|
|
local t = os.time({ year = tonumber(y), month = tonumber(m), day = tonumber(d) })
|
|
local wday = os.date('*t', t).wday
|
|
assert.are.equal(2, wday)
|
|
end)
|
|
|
|
it('returns Sunday of current week for eow', function()
|
|
local result = parse.resolve_date('eow')
|
|
assert.is_not_nil(result)
|
|
local y, m, d = result:match('^(%d+)-(%d+)-(%d+)$')
|
|
local t = os.time({ year = tonumber(y), month = tonumber(m), day = tonumber(d) })
|
|
local wday = os.date('*t', t).wday
|
|
assert.are.equal(1, wday)
|
|
end)
|
|
|
|
it('returns first day of current month for som', function()
|
|
local today = os.date('*t') --[[@as osdate]]
|
|
local expected = string.format('%04d-%02d-01', today.year, today.month)
|
|
local result = parse.resolve_date('som')
|
|
assert.are.equal(expected, result)
|
|
end)
|
|
|
|
it('returns last day of current month for eom', function()
|
|
local today = os.date('*t') --[[@as osdate]]
|
|
local expected =
|
|
os.date('%Y-%m-%d', os.time({ year = today.year, month = today.month + 1, day = 0 }))
|
|
local result = parse.resolve_date('eom')
|
|
assert.are.equal(expected, result)
|
|
end)
|
|
|
|
it('returns first day of current quarter for soq', function()
|
|
local today = os.date('*t') --[[@as osdate]]
|
|
local q = math.ceil(today.month / 3)
|
|
local first_month = (q - 1) * 3 + 1
|
|
local expected = string.format('%04d-%02d-01', today.year, first_month)
|
|
local result = parse.resolve_date('soq')
|
|
assert.are.equal(expected, result)
|
|
end)
|
|
|
|
it('returns last day of current quarter for eoq', function()
|
|
local today = os.date('*t') --[[@as osdate]]
|
|
local q = math.ceil(today.month / 3)
|
|
local last_month = q * 3
|
|
local expected =
|
|
os.date('%Y-%m-%d', os.time({ year = today.year, month = last_month + 1, day = 0 }))
|
|
local result = parse.resolve_date('eoq')
|
|
assert.are.equal(expected, result)
|
|
end)
|
|
|
|
it('returns Jan 1 of current year for soy', function()
|
|
local today = os.date('*t') --[[@as osdate]]
|
|
local expected = string.format('%04d-01-01', today.year)
|
|
local result = parse.resolve_date('soy')
|
|
assert.are.equal(expected, result)
|
|
end)
|
|
|
|
it('returns Dec 31 of current year for eoy', function()
|
|
local today = os.date('*t') --[[@as osdate]]
|
|
local expected = string.format('%04d-12-31', today.year)
|
|
local result = parse.resolve_date('eoy')
|
|
assert.are.equal(expected, result)
|
|
end)
|
|
|
|
it('resolves +2w to 14 days from today', 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 + 14 })
|
|
)
|
|
local result = parse.resolve_date('+2w')
|
|
assert.are.equal(expected, result)
|
|
end)
|
|
|
|
it('resolves +3m to 3 months from today', function()
|
|
local today = os.date('*t') --[[@as osdate]]
|
|
local expected = os.date(
|
|
'%Y-%m-%d',
|
|
os.time({ year = today.year, month = today.month + 3, day = today.day })
|
|
)
|
|
local result = parse.resolve_date('+3m')
|
|
assert.are.equal(expected, result)
|
|
end)
|
|
|
|
it('resolves -2d to 2 days ago', 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 result = parse.resolve_date('-2d')
|
|
assert.are.equal(expected, result)
|
|
end)
|
|
|
|
it('resolves -1w to 7 days ago', 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 - 7 })
|
|
)
|
|
local result = parse.resolve_date('-1w')
|
|
assert.are.equal(expected, result)
|
|
end)
|
|
|
|
it("resolves 'later' to someday_date", function()
|
|
local result = parse.resolve_date('later')
|
|
assert.are.equal('9999-12-30', result)
|
|
end)
|
|
|
|
it("resolves 'someday' to someday_date", function()
|
|
local result = parse.resolve_date('someday')
|
|
assert.are.equal('9999-12-30', result)
|
|
end)
|
|
|
|
it('resolves 15th to next 15th of month', function()
|
|
local result = parse.resolve_date('15th')
|
|
assert.is_not_nil(result)
|
|
local _, _, d = result:match('^(%d+)-(%d+)-(%d+)$')
|
|
assert.are.equal('15', d)
|
|
end)
|
|
|
|
it('resolves 1st to next 1st of month', function()
|
|
local result = parse.resolve_date('1st')
|
|
assert.is_not_nil(result)
|
|
local _, _, d = result:match('^(%d+)-(%d+)-(%d+)$')
|
|
assert.are.equal('01', d)
|
|
end)
|
|
|
|
it('resolves jan to next January 1st', function()
|
|
local today = os.date('*t') --[[@as osdate]]
|
|
local result = parse.resolve_date('jan')
|
|
assert.is_not_nil(result)
|
|
local y, m, d = result:match('^(%d+)-(%d+)-(%d+)$')
|
|
assert.are.equal('01', m)
|
|
assert.are.equal('01', d)
|
|
if today.month >= 1 then
|
|
assert.are.equal(tostring(today.year + 1), y)
|
|
end
|
|
end)
|
|
|
|
it('resolves dec to next December 1st', function()
|
|
local today = os.date('*t') --[[@as osdate]]
|
|
local result = parse.resolve_date('dec')
|
|
assert.is_not_nil(result)
|
|
local y, m, d = result:match('^(%d+)-(%d+)-(%d+)$')
|
|
assert.are.equal('12', m)
|
|
assert.are.equal('01', d)
|
|
if today.month >= 12 then
|
|
assert.are.equal(tostring(today.year + 1), y)
|
|
else
|
|
assert.are.equal(tostring(today.year), y)
|
|
end
|
|
end)
|
|
end)
|
|
|
|
describe('resolve_date with time suffix', function()
|
|
local today = os.date('*t') --[[@as osdate]]
|
|
local tomorrow_str =
|
|
os.date('%Y-%m-%d', os.time({ year = today.year, month = today.month, day = today.day + 1 })) --[[@as string]]
|
|
|
|
it('resolves bare hour to T09:00', function()
|
|
local result = parse.resolve_date('tomorrow@9')
|
|
assert.are.equal(tomorrow_str .. 'T09:00', result)
|
|
end)
|
|
|
|
it('resolves bare military hour to T14:00', function()
|
|
local result = parse.resolve_date('tomorrow@14')
|
|
assert.are.equal(tomorrow_str .. 'T14:00', result)
|
|
end)
|
|
|
|
it('resolves H:MM to T09:30', function()
|
|
local result = parse.resolve_date('tomorrow@9:30')
|
|
assert.are.equal(tomorrow_str .. 'T09:30', result)
|
|
end)
|
|
|
|
it('resolves HH:MM (existing format) to T09:30', function()
|
|
local result = parse.resolve_date('tomorrow@09:30')
|
|
assert.are.equal(tomorrow_str .. 'T09:30', result)
|
|
end)
|
|
|
|
it('resolves 2pm to T14:00', function()
|
|
local result = parse.resolve_date('tomorrow@2pm')
|
|
assert.are.equal(tomorrow_str .. 'T14:00', result)
|
|
end)
|
|
|
|
it('resolves 9am to T09:00', function()
|
|
local result = parse.resolve_date('tomorrow@9am')
|
|
assert.are.equal(tomorrow_str .. 'T09:00', result)
|
|
end)
|
|
|
|
it('resolves 9:30pm to T21:30', function()
|
|
local result = parse.resolve_date('tomorrow@9:30pm')
|
|
assert.are.equal(tomorrow_str .. 'T21:30', result)
|
|
end)
|
|
|
|
it('resolves 12am to T00:00', function()
|
|
local result = parse.resolve_date('tomorrow@12am')
|
|
assert.are.equal(tomorrow_str .. 'T00:00', result)
|
|
end)
|
|
|
|
it('resolves 12pm to T12:00', function()
|
|
local result = parse.resolve_date('tomorrow@12pm')
|
|
assert.are.equal(tomorrow_str .. 'T12:00', result)
|
|
end)
|
|
|
|
it('rejects hour 24', function()
|
|
assert.is_nil(parse.resolve_date('tomorrow@24'))
|
|
end)
|
|
|
|
it('rejects 13am', function()
|
|
assert.is_nil(parse.resolve_date('tomorrow@13am'))
|
|
end)
|
|
|
|
it('rejects minute 60', function()
|
|
assert.is_nil(parse.resolve_date('tomorrow@9:60'))
|
|
end)
|
|
|
|
it('rejects alphabetic garbage', function()
|
|
assert.is_nil(parse.resolve_date('tomorrow@abc'))
|
|
end)
|
|
end)
|
|
|
|
describe('command_add', function()
|
|
it('parses simple text', function()
|
|
local desc, meta = parse.command_add('Buy milk')
|
|
assert.are.equal('Buy milk', desc)
|
|
assert.are.same({}, meta)
|
|
end)
|
|
|
|
it('detects category prefix', function()
|
|
local desc, meta = parse.command_add('School: Do homework')
|
|
assert.are.equal('Do homework', desc)
|
|
assert.are.equal('School', meta.category)
|
|
end)
|
|
|
|
it('ignores lowercase prefix', function()
|
|
local desc, _ = parse.command_add('hello: world')
|
|
assert.are.equal('hello: world', desc)
|
|
end)
|
|
|
|
it('combines category prefix with inline metadata', function()
|
|
local desc, meta = parse.command_add('School: Do homework due:2026-03-15')
|
|
assert.are.equal('Do homework', desc)
|
|
assert.are.equal('School', meta.category)
|
|
assert.are.equal('2026-03-15', meta.due)
|
|
end)
|
|
end)
|
|
|
|
describe('parse_duration_to_days', function()
|
|
it('parses days suffix', function()
|
|
assert.are.equal(7, parse.parse_duration_to_days('7d'))
|
|
end)
|
|
|
|
it('parses weeks suffix', function()
|
|
assert.are.equal(21, parse.parse_duration_to_days('3w'))
|
|
end)
|
|
|
|
it('parses months suffix (approximated as 30 days)', function()
|
|
assert.are.equal(60, parse.parse_duration_to_days('2m'))
|
|
end)
|
|
|
|
it('parses bare integer as days', function()
|
|
assert.are.equal(30, parse.parse_duration_to_days('30'))
|
|
end)
|
|
|
|
it('returns nil for nil input', function()
|
|
assert.is_nil(parse.parse_duration_to_days(nil))
|
|
end)
|
|
|
|
it('returns nil for empty string', function()
|
|
assert.is_nil(parse.parse_duration_to_days(''))
|
|
end)
|
|
|
|
it('returns nil for unrecognized input', function()
|
|
assert.is_nil(parse.parse_duration_to_days('xyz'))
|
|
end)
|
|
|
|
it('returns nil for negative numbers', function()
|
|
assert.is_nil(parse.parse_duration_to_days('-7d'))
|
|
end)
|
|
|
|
it('handles single digit', function()
|
|
assert.are.equal(1, parse.parse_duration_to_days('1d'))
|
|
end)
|
|
|
|
it('handles large numbers', function()
|
|
assert.are.equal(365, parse.parse_duration_to_days('365d'))
|
|
end)
|
|
end)
|
|
|
|
describe('input_date_formats', function()
|
|
before_each(function()
|
|
config.reset()
|
|
end)
|
|
|
|
after_each(function()
|
|
vim.g.pending = nil
|
|
config.reset()
|
|
end)
|
|
|
|
it('parses MM/DD/YYYY format', function()
|
|
vim.g.pending = { input_date_formats = { '%m/%d/%Y' } }
|
|
config.reset()
|
|
local result = parse.resolve_date('03/15/2026')
|
|
assert.are.equal('2026-03-15', result)
|
|
end)
|
|
|
|
it('parses DD-Mon-YYYY format', function()
|
|
vim.g.pending = { input_date_formats = { '%d-%b-%Y' } }
|
|
config.reset()
|
|
local result = parse.resolve_date('15-Mar-2026')
|
|
assert.are.equal('2026-03-15', result)
|
|
end)
|
|
|
|
it('parses month name case-insensitively', function()
|
|
vim.g.pending = { input_date_formats = { '%d-%b-%Y' } }
|
|
config.reset()
|
|
local result = parse.resolve_date('15-MARCH-2026')
|
|
assert.are.equal('2026-03-15', result)
|
|
end)
|
|
|
|
it('parses two-digit year', function()
|
|
vim.g.pending = { input_date_formats = { '%m/%d/%y' } }
|
|
config.reset()
|
|
local result = parse.resolve_date('03/15/26')
|
|
assert.are.equal('2026-03-15', result)
|
|
end)
|
|
|
|
it('infers year when format has no year field', function()
|
|
vim.g.pending = { input_date_formats = { '%m/%d' } }
|
|
config.reset()
|
|
local result = parse.resolve_date('12/31')
|
|
assert.is_not_nil(result)
|
|
assert.truthy(result:match('^%d%d%d%d%-12%-31$'))
|
|
end)
|
|
|
|
it('returns nil for non-matching input', function()
|
|
vim.g.pending = { input_date_formats = { '%m/%d/%Y' } }
|
|
config.reset()
|
|
local result = parse.resolve_date('not-a-date')
|
|
assert.is_nil(result)
|
|
end)
|
|
|
|
it('tries formats in order, returns first match', function()
|
|
vim.g.pending = { input_date_formats = { '%d/%m/%Y', '%m/%d/%Y' } }
|
|
config.reset()
|
|
local result = parse.resolve_date('01/03/2026')
|
|
assert.are.equal('2026-03-01', result)
|
|
end)
|
|
|
|
it('works with body() for inline due token', function()
|
|
vim.g.pending = { input_date_formats = { '%m/%d/%Y' } }
|
|
config.reset()
|
|
local desc, meta = parse.body('Pay rent due:03/15/2026')
|
|
assert.are.equal('Pay rent', desc)
|
|
assert.are.equal('2026-03-15', meta.due)
|
|
end)
|
|
end)
|
|
end)
|