current commit
261020.8 · test: make the suite pass consistently on Windows/slow machines
---@diagnostic disable: unused-local, unused-function, unused
local MiniTest = require("mini.test")
local helpers = require("fzf-lua.test.helpers")
local child = helpers.new_child_neovim()
local eq = helpers.expect.equality
local new_set = MiniTest.new_set
local exec_lua = child.lua

--stylua: ignore start
---@format disable-next
local reload = function(config) child.unload(); child.setup(config) end
local type_keys = function(...) return child.type_keys(...) end
local sleep = function(ms) helpers.sleep(ms, child) end
--stylua: ignore end

local T = helpers.new_set_with_child(child, {
  hooks = {
    pre_case = function()
      child.o.shell = "/bin/sh"
      child.o.termguicolors = true
      child.o.background = "dark"
      child.o.statusline = "fzf://"
    end,
  },
})

T["ui_select"] = new_set({ n_retry = not helpers.IS_LINUX() and 5 or nil })

local register = function(opts)
  local literal = vim.inspect(opts or {}):gsub("\n", " ")
  exec_lua(([[FzfLua.register_ui_select(%s)]]):format(literal))
end

-- Wipe any stale /tmp/fzf-lua-ui-select-*.tmp files left by previous
-- (possibly crashed) test runs. The tmpfile name uses `os.time()`
-- which has 1-second resolution, so concurrent runs in the same
-- second would otherwise race on the same path.
local function cleanup_stale_tmpfiles()
  for _, f in ipairs(vim.fn.glob("/tmp/fzf-lua-ui-select-*.tmp", true, true)) do
    pcall(os.remove, f)
  end
  exec_lua(([[
    for _, f in ipairs(vim.fn.glob("/tmp/fzf-lua-ui-select-*.tmp", true, true)) do
      pcall(os.remove, f)
    end
  ]]))
end

-- Open the picker via `vim.ui.select` and wait for it to appear.
-- The buffer is created in the child nvim (buffers don't cross the rpc
-- boundary). `pcall` traps any synchronous crash from the native
-- preview fn (#2743).
local function open_picker(items, preview_lines, ui_opts)
  ---@diagnostic disable-next-line: redefined-local
  exec_lua(function(items, preview_lines, ui_opts)
    _G._ui_select_choice = nil
    _G._ui_select_errors = nil
    local buf = vim.api.nvim_create_buf(false, true)
    vim.api.nvim_buf_set_lines(buf, 0, -1, false, preview_lines)
    local opts = vim.tbl_extend("keep", ui_opts, {
      format_item = function(item) return tostring(item) end,
      preview_item = function() return { buf = buf } end,
    })
    local ok, err = pcall(vim.ui.select, items, opts, function(item, idx)
      _G._ui_select_choice = { item = item, idx = idx }
    end)
    if not ok then
      _G._ui_select_errors = tostring(err); error(err)
    end
  end, { items, preview_lines, ui_opts or {} })
  child.wait_until(function()
    return child.lua_get([[_G._fzf_lua_on_create]]) == true
        or child.lua_get([[_G._ui_select_errors]]) ~= vim.NIL
  end, 5000)
  local errs = child.lua_get([[_G._ui_select_errors]])
  eq(errs, vim.NIL, { fail_reason = "vim.ui.select errored: " .. tostring(errs) })
  eq(child.lua_get([[_G._fzf_lua_on_create]]), true)
end

local function close_picker(key)
  type_keys(key or "<c-c>")
  child.wait_until(function()
    return child.lua_get([[_G._fzf_lua_on_create]]) == vim.NIL
  end, 5000)
  eq(child.lua_get([[_G._fzf_lua_on_create]]), vim.NIL)
end

-- Wait until the picker's previewer reports having rendered the
-- first entry. `last_entry` is the full fzf line (with the `<n>. `
-- prefix produced by ui_select).
local function wait_preview_rendered(expected_entry)
  child.wait_until(function()
    local last = child.lua_get([[
      (FzfLua.utils.fzf_winobj() or {})._previewer
        and FzfLua.utils.fzf_winobj()._previewer.last_entry or nil
    ]])
    return last == expected_entry
  end, 5000)
  sleep(100); if not helpers.IS_LINUX() then sleep(250) end
end

T["ui_select"]["buffer preview (default)"] = function()
  reload({ "default" })
  register()
  open_picker({ "alpha", "beta" }, { "preview-line-1", "preview-line-2" },
    { prompt = "Pick (buffer):" })
  wait_preview_rendered("1. alpha")
  local screen = tostring(child.get_screenshot({ redraw = true }))
  eq(screen:find("alpha", 1, true) ~= nil, true,
    { fail_reason = "alpha not in screen:\n" .. screen })
  eq(screen:find("preview-line-1", 1, true) ~= nil, true,
    { fail_reason = "preview line 1 not in screen:\n" .. screen })
  close_picker()
end

T["ui_select"]["native via fzf-tmux profile #2743"] = function()
  -- The fzf-tmux profile declares `ui_select = { preview_type = "native" }`
  -- so the picker should use the native backend even without an
  -- explicit opt (in real tmux the picker runs in a tmux popup; here
  -- without tmux it degrades to a regular fzf run with the same opts).
  cleanup_stale_tmpfiles()
  -- hermetic environment: when the suite runs inside tmux (developer
  -- machines) `$TMUX` is inherited by the child and the profile's
  -- `--tmux` flag is kept, fzf spawns a real popup which cannot open
  -- in the headless test child (fzf exits immediately, no error)
  exec_lua([[vim.env.TMUX = nil]])
  reload({ "fzf-tmux" })
  register()
  open_picker({ "alpha", "beta" }, { "tmux-line" })
  sleep(300)
  close_picker()
end

T["ui_select"]["async preview_item opens without crash"] = function()
  reload({ "default" })
  register()
  exec_lua(function()
    vim.ui.select({ "alpha", "beta" }, {
      prompt = "Pick (async):",
      preview_item = function(_) return {} end,
      format_item = function(item) return tostring(item) end,
    }, function() end)
  end)
  child.wait_until(function()
    return child.lua_get([[_G._fzf_lua_on_create]]) == true
  end, 5000)
  eq(child.lua_get([[_G._fzf_lua_on_create]]), true)
  close_picker("<esc>")
end

T["ui_select"]["abort via esc"] = function()
  reload({ "default" })
  register()
  open_picker({ "alpha" }, { "esc-preview" })
  close_picker("<esc>")
  eq(child.lua_get([[_G._ui_select_choice]]), vim.NIL)
end

-- #2795: opts sent once from `lsp_code_actions` (`_OPTS_ONCE`) must be
-- merged into the picker opts *before* `config.normalize_opts` runs so
-- that the "hide" profile's `enrich` sees `no_resume`/`no_hide` and does
-- not set `<Esc>` (and all other actions) to hide instead of close.
T["ui_select"]["lsp_code_actions no_resume honored #2795"] = function()
  -- "default" profile includes "hide"
  reload({ "default" })
  register()
  exec_lua(function()
    -- emulate `FzfLua.lsp_code_actions({ no_resume = true })` sending
    -- "once" opts to ui_select (normalized, as done by `normalize_lsp_opts`)
    local ui_select = require("fzf-lua.providers.ui_select")
    local opts_once = require("fzf-lua.config").normalize_opts(
      { no_resume = true, previewer = false }, "lsp.code_actions")
    ui_select.register(nil, true, opts_once)
    -- capture the final opts passed to `fzf_exec`
    local core = require("fzf-lua.core")
    local orig_fzf_exec = core.fzf_exec
    core.fzf_exec = function(contents, opts)
      ---@cast opts table
      _G._fzf_exec_opts = {
        no_resume = opts.no_resume,
        no_hide = opts.no_hide,
        esc_action_type = type(vim.tbl_get(opts, "actions", "esc")),
      }
      return orig_fzf_exec(contents, opts)
    end
    vim.ui.select({ "alpha", "beta" }, {
      prompt = "Code actions:",
      kind = "codeaction",
      format_item = function(item) return tostring(item) end,
    }, function() end)
  end)
  child.wait_until(function()
    return child.lua_get([[_G._fzf_lua_on_create]]) == true
  end, 5000)
  local captured = child.lua_get([[_G._fzf_exec_opts]])
  eq(captured.no_resume, true)
  eq(captured.no_hide, true)
  -- "hide" profile must not have enriched the esc action
  eq(captured.esc_action_type, "nil")
  close_picker("<esc>")
  -- picker must not have been stored as resume data
  eq(child.lua_get(
    [[(require("fzf-lua.config").__resume_data or {}).contents]]), vim.NIL)
  -- explicit `no_hide = false` sent via once-opts must be respected
  -- (not replaced by the implicit code-action `no_hide = true` default)
  exec_lua(function()
    local ui_select = require("fzf-lua.providers.ui_select")
    local opts_once = require("fzf-lua.config").normalize_opts(
      { no_hide = false, previewer = false }, "lsp.code_actions")
    ui_select.register(nil, true, opts_once)
    vim.ui.select({ "alpha", "beta" }, {
      prompt = "Code actions:",
      kind = "codeaction",
      format_item = function(item) return tostring(item) end,
    }, function() end)
  end)
  child.wait_until(function()
    return child.lua_get([[_G._fzf_lua_on_create]]) == true
  end, 5000)
  eq(child.lua_get([[_G._fzf_exec_opts.no_hide]]), false)
  close_picker("<esc>")
end

T["ui_select"]["once opts merged with preview_item #2795"] = function()
  -- "default" profile includes "hide"
  reload({ "default" })
  register()
  exec_lua(function()
    local ui_select = require("fzf-lua.providers.ui_select")
    local opts_once = require("fzf-lua.config").normalize_opts(
      { no_resume = true, previewer = false }, "lsp.code_actions")
    ui_select.register(nil, true, opts_once)
    local core = require("fzf-lua.core")
    local orig_fzf_exec = core.fzf_exec
    core.fzf_exec = function(contents, opts)
      ---@cast opts table
      _G._fzf_exec_opts = {
        no_resume = opts.no_resume,
        no_hide = opts.no_hide,
        esc_action_type = type(vim.tbl_get(opts, "actions", "esc")),
      }
      return orig_fzf_exec(contents, opts)
    end
    -- nvim >= 0.12 code_action sends `preview_item` which used to skip
    -- the `_OPTS_ONCE` merge entirely (`elseif`), dropping `no_resume`
    local buf = vim.api.nvim_create_buf(false, true)
    vim.api.nvim_buf_set_lines(buf, 0, -1, false, { "preview" })
    vim.ui.select({ "alpha", "beta" }, {
      prompt = "Code actions:",
      kind = "codeaction",
      format_item = function(item) return tostring(item) end,
      preview_item = function(_) return { buf = buf } end,
    }, function() end)
  end)
  child.wait_until(function()
    return child.lua_get([[_G._fzf_lua_on_create]]) == true
  end, 5000)
  local captured = child.lua_get([[_G._fzf_exec_opts]])
  eq(captured.no_resume, true)
  eq(captured.no_hide, true)
  eq(captured.esc_action_type, "nil")
  close_picker("<esc>")
  eq(child.lua_get(
    [[(require("fzf-lua.config").__resume_data or {}).contents]]), vim.NIL)
end

T["ui_select"]["registered function opts receive (ui_opts, items) #2770"] = function()
  reload({ "default" })
  -- `config.normalize_opts` calls function opts with no args; the wrapper
  -- in `M.ui_select` must resolve the function with `(ui_opts, items)`
  -- before normalizing, preserving the documented interface (#755).
  exec_lua([[
    FzfLua.register_ui_select(function(fzf_opts, items)
      _G._ui_select_args = {
        kind = fzf_opts.kind,
        prompt = fzf_opts.prompt,
        nitems = #items,
      }
      return {}
    end)
  ]])
  open_picker({ "alpha", "beta" }, { "preview-line-1", "preview-line-2" },
    { prompt = "Pick (fn):", kind = "codeaction" })
  close_picker("<esc>")
  eq(child.lua_get([[_G._ui_select_args]]),
    { kind = "codeaction", prompt = "Pick (fn):", nitems = 2 })
end

return T