telescope.nvim
telescope.nvim copied to clipboard
Add documentation on how to write tests for telescope.nvim
Is your feature request related to a problem? Please describe. I'm writing an extension and, despite checking every extension from the wiki, I couldn't find any that actually runs the their code via telescope calls. The best I saw was a couple plugins test their own internal logic and don't even attempt to call their code the way a user might.
Describe the solution you'd like Be able to call a picker and query its contents in a unittest / integration test.
it("shows up with 1 saved session", function()
-- Some prologue code that could affect `assert.equal` later
local picker = viewer.create()
picker:find()
assert.equal(1, picker.manager:num_results()) -- Some meaningful check
end)
Describe alternatives you've considered n/a
Probably the closest thing to this would be these tests https://github.com/nvim-telescope/telescope.nvim/blob/master/lua/tests/automated/pickers/find_files_spec.lua
But even in our code base, its usage is very limited and hasn't been used for anything but some common operations for the find_files picker.
I tried writing some integration tests for another extension previously but I found it quite tricky (for my usecase). I would like to improve this aspect but I'm constrained for time.
I decided to try this again and found a working solution, at least for my use case. At some point I'll publish the code but for now, this is a taste of it:
A working example
local action_state = require("telescope.actions.state")
local action_utils = require("telescope.actions.utils")
local entry_display = require("telescope.pickers.entry_display")
local finders = require("telescope.finders")
local pickers = require("telescope.pickers")
local telescope_actions = require("telescope.actions")
local telescope_config = require("telescope.config").values
local _ORIGINAL_GET_SELECTION_FUNCTION = nil
local _RESULT = nil
local function _get_selection(buffer)
local books = {}
action_utils.map_selections(buffer, function(selection)
table.insert(books, selection.value)
end)
if not vim.tbl_isempty(books) then
return books
end
local selection = action_state.get_selected_entry()
if selection ~= nil then
return { selection.value }
end
return {}
end
local function _mock_after()
_get_selection = _ORIGINAL_GET_SELECTION_FUNCTION
_RESULT = nil
end
local function _mock_get_selection(caller)
_ORIGINAL_GET_SELECTION_FUNCTION = caller
_get_selection = function(...)
local selection = caller(...)
_RESULT = selection
return selection
end
end
local function _get_picker()
local function _select_book(buffer)
local selection = _get_selection(buffer)
if vim.tbl_isempty(selection) then
vim.notify("No selection could be found", vim.log.levels.ERROR)
telescope_actions.close(buffer)
return
end
print("Selected books:")
for _, book in ipairs(selection) do
print(book)
end
telescope_actions.close(buffer)
end
local displayer = entry_display.create({
separator = " ",
items = {
{ width = 0.8 },
{ remaining = true },
},
})
local books = {
{ "Guns, Germs, and Steel: The Fates of Human Societies", "Jared M. Diamond" },
{ "Herodotus Histories", "Herodotus" },
{ "The Origin of Consciousness in the Breakdown of the Bicameral Mind", "Julian Jaynes" },
{ "What Every Programmer Should Know About Memory", "Ulrich Drepper" },
{ "When: The Scientific Secrets of Perfect Timing", "Daniel H. Pinker" },
}
local options = {}
local picker = pickers
.new(options, {
prompt_title = "Choose A Book",
finder = finders.new_table({
results = books,
entry_maker = function(data)
local name, author = unpack(data)
local value = string.format("%s - %s", name, author)
return {
display = function(entry)
return displayer({
{ entry.name, "PluginTemplateTelescopeEntry" },
{ entry.author, "PluginTemplateTelescopeSecondary" },
})
end,
author = author,
name = name,
value = value,
ordinal = value,
}
end,
}),
previewer = false,
sorter = telescope_config.generic_sorter(options),
attach_mappings = function()
telescope_actions.select_default:replace(_select_book)
return true
end,
})
return picker
end
local function main()
local function _wait_for_picker_to_initialize()
local initialized = false
vim.schedule(function() initialized = true end)
vim.wait(1000, function() return initialized end)
end
local picker = _get_picker()
_mock_get_selection(_get_selection)
picker:find()
_wait_for_picker_to_initialize()
picker:set_selection(picker.max_results - picker.manager:num_results())
picker:toggle_selection(picker:get_selection_row())
picker:move_selection(1)
picker:toggle_selection(picker:get_selection_row())
require("telescope.actions").select_default(vim.api.nvim_get_current_buf())
print("FINAL RESULT")
print(vim.inspect(_RESULT))
_mock_after()
end
main()
The jist is that telescope.nvim uses plenary to schedule tasks and plenary is mostly a wrap around vim.schedule. Since vim.schedule is just a single queue + event loop, So as long as you make your code wait until the end of the queue is exhausted, you can be sure that the telescope floating buffer is ready. If you later do some more stuff and are worried that the Picker floating buffer is out of sync again, just run _wait_for_picker_to_initialize() before trying to access anything. It works for my use cases so far, it seems. I did try some experiments like changing _wait_for_picker_to_initialize() to be a "wait for picker.manager to exist" and other guesses but it seemed not to matter what I did. Just a simple wait was enough.
One curious part though is picker:set_selection(picker.max_results - picker.manager:num_results()). My picker has 5 items in it so I was expecting to be able to call picker:set_selection(1), picker:set_selection(2), picker:set_selection(3), etc but it looks like the row numbers are absolute values and based off of picker.max_results. Is that normal or did I fail to initialize something properly? Don't know!
Anyway as long as the selection is set, the rest has been smooth sailing. One unsolved problem is that I'd really ideally like to call Neo/vim feedkeys, raw, instead of calling telescope's picker API every time I want to interact with the buffer. I tried out things like vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes("<CR>",true,false,true), "m", false) to emulate a Enter key press but it just never worked as expected. If someone works out those details I'd be super happy to hear about it!
I still think this repo could use an example / docs on the expected way to test it but at least we now have a starting point. It has been working for me so far, finger's crossed.