* feat(gtasks): add Google Tasks bidirectional sync
Problem: pending.nvim only supported one-way push to Google Calendar.
Users who use Google Tasks had no way to sync tasks bidirectionally.
Solution: add `lua/pending/sync/gtasks.lua` backend with OAuth PKCE
auth, push/pull/sync actions, and field mapping between pending tasks
and Google Tasks (category↔tasklist, `priority`/`recur` via notes).
* refactor(cli): promote sync backends to top-level subcommands
Problem: `:Pending sync gtasks auth` required an extra `sync` keyword
that added no value and made the command unnecessarily verbose.
Solution: route `gtasks` and `gcal` as top-level `:Pending` subcommands
via `SYNC_BACKEND_SET` lookup. Tab completion introspects backend
modules for available actions instead of hardcoding `{ 'auth', 'sync' }`.
* docs(gtasks): document Google Tasks backend and CLI changes
Problem: vimdoc had no coverage for the gtasks backend and still
referenced the old `:Pending sync <backend>` command form.
Solution: add `:Pending-gtasks` and `:Pending-gcal` command sections
with per-action docs, update sync backend interface, and add gtasks
config example.
* ci: format
120 lines
3.1 KiB
Lua
120 lines
3.1 KiB
Lua
require('spec.helpers')
|
|
|
|
local config = require('pending.config')
|
|
|
|
describe('sync', 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()
|
|
package.loaded['pending'] = nil
|
|
pending = require('pending')
|
|
end)
|
|
|
|
after_each(function()
|
|
vim.fn.delete(tmpdir, 'rf')
|
|
vim.g.pending = nil
|
|
config.reset()
|
|
package.loaded['pending'] = nil
|
|
end)
|
|
|
|
describe('dispatch', function()
|
|
it('errors on unknown subcommand', function()
|
|
local msg
|
|
local orig = vim.notify
|
|
vim.notify = function(m, level)
|
|
if level == vim.log.levels.ERROR then
|
|
msg = m
|
|
end
|
|
end
|
|
pending.command('notreal')
|
|
vim.notify = orig
|
|
assert.are.equal('Unknown Pending subcommand: notreal', msg)
|
|
end)
|
|
|
|
it('errors on unknown action for valid backend', function()
|
|
local msg
|
|
local orig = vim.notify
|
|
vim.notify = function(m, level)
|
|
if level == vim.log.levels.ERROR then
|
|
msg = m
|
|
end
|
|
end
|
|
pending.command('gcal notreal')
|
|
vim.notify = orig
|
|
assert.are.equal("gcal backend has no 'notreal' action", msg)
|
|
end)
|
|
|
|
it('defaults to sync action when action is omitted', function()
|
|
local called = false
|
|
local gcal = require('pending.sync.gcal')
|
|
local orig_sync = gcal.sync
|
|
gcal.sync = function()
|
|
called = true
|
|
end
|
|
pending.command('gcal')
|
|
gcal.sync = orig_sync
|
|
assert.is_true(called)
|
|
end)
|
|
|
|
it('routes explicit sync action', function()
|
|
local called = false
|
|
local gcal = require('pending.sync.gcal')
|
|
local orig_sync = gcal.sync
|
|
gcal.sync = function()
|
|
called = true
|
|
end
|
|
pending.command('gcal sync')
|
|
gcal.sync = orig_sync
|
|
assert.is_true(called)
|
|
end)
|
|
|
|
it('routes auth action', function()
|
|
local called = false
|
|
local gcal = require('pending.sync.gcal')
|
|
local orig_auth = gcal.auth
|
|
gcal.auth = function()
|
|
called = true
|
|
end
|
|
pending.command('gcal auth')
|
|
gcal.auth = orig_auth
|
|
assert.is_true(called)
|
|
end)
|
|
end)
|
|
|
|
it('works with sync.gcal config', function()
|
|
config.reset()
|
|
vim.g.pending = {
|
|
data_path = tmpdir .. '/tasks.json',
|
|
sync = { gcal = { calendar = 'NewStyle' } },
|
|
}
|
|
local cfg = config.get()
|
|
assert.are.equal('NewStyle', cfg.sync.gcal.calendar)
|
|
end)
|
|
|
|
describe('gcal module', function()
|
|
it('has name field', function()
|
|
local gcal = require('pending.sync.gcal')
|
|
assert.are.equal('gcal', gcal.name)
|
|
end)
|
|
|
|
it('has auth function', function()
|
|
local gcal = require('pending.sync.gcal')
|
|
assert.are.equal('function', type(gcal.auth))
|
|
end)
|
|
|
|
it('has sync function', function()
|
|
local gcal = require('pending.sync.gcal')
|
|
assert.are.equal('function', type(gcal.sync))
|
|
end)
|
|
|
|
it('has health function', function()
|
|
local gcal = require('pending.sync.gcal')
|
|
assert.are.equal('function', type(gcal.health))
|
|
end)
|
|
end)
|
|
end)
|