Files
dmtrkovalenko--fff.nvim/tests/picker_dir_resolution_spec.lua
wehub-resource-sync 1b279d4d10
e2e Tests / e2e (macos-latest) (push) Waiting to run
e2e Tests / e2e (windows-latest) (push) Waiting to run
Nix CI / check (push) Waiting to run
Python CI / Python bindings (macos-latest) (push) Waiting to run
Python CI / Python bindings (windows-latest) (push) Waiting to run
Build & Publish / Build Neovim aarch64-apple-darwin (push) Waiting to run
Build & Publish / Build Neovim aarch64-pc-windows-msvc (push) Waiting to run
Build & Publish / Build Neovim x86_64-apple-darwin (push) Waiting to run
Build & Publish / Build Neovim x86_64-pc-windows-msvc (push) Waiting to run
Build & Publish / Build C FFI aarch64-apple-darwin (push) Waiting to run
Build & Publish / Build C FFI aarch64-pc-windows-msvc (push) Waiting to run
Build & Publish / Build C FFI x86_64-apple-darwin (push) Waiting to run
Build & Publish / Build C FFI x86_64-pc-windows-msvc (push) Waiting to run
Build & Publish / Build MCP x86_64-apple-darwin (push) Waiting to run
Build & Publish / Build MCP x86_64-pc-windows-msvc (push) Waiting to run
Build & Publish / Build Python wheels aarch64 (macos-latest) (push) Waiting to run
Build & Publish / Build Python wheels x86_64 (macos-latest) (push) Waiting to run
Build & Publish / Build Python wheels x86_64 (windows-latest) (push) Waiting to run
Build & Publish / Release (push) Blocked by required conditions
Build & Publish / Publish Python wheels to PyPI (push) Blocked by required conditions
Build & Publish / Publish Rust crates (push) Blocked by required conditions
Build & Publish / Publish npm packages (push) Blocked by required conditions
Rust CI / Test (macos-latest) (push) Waiting to run
Rust CI / Test (windows-latest) (push) Waiting to run
Rust CI / Fuzz Tests (macos-latest) (push) Waiting to run
Rust CI / Fuzz Tests (windows-latest) (push) Waiting to run
Build & Publish / Build MCP aarch64-apple-darwin (push) Waiting to run
Build & Publish / Build MCP aarch64-pc-windows-msvc (push) Waiting to run
Lua CI / lua-language-server type check (push) Failing after 1s
Lua CI / luacheck lint (push) Failing after 1s
Python CI / Python bindings (ubuntu-latest) (push) Failing after 1s
Build & Publish / Build Neovim aarch64-linux-android (push) Failing after 3s
Build & Publish / Build Neovim aarch64-unknown-linux-gnu (push) Failing after 0s
Build & Publish / Build Neovim aarch64-unknown-linux-musl (push) Failing after 1s
Build & Publish / Build Neovim x86_64-unknown-linux-gnu (push) Failing after 1s
Build & Publish / Build Neovim x86_64-unknown-linux-musl (push) Failing after 1s
Build & Publish / Build C FFI aarch64-linux-android (push) Failing after 1s
Build & Publish / Build C FFI aarch64-unknown-linux-gnu (push) Failing after 1s
Build & Publish / Build C FFI aarch64-unknown-linux-musl (push) Failing after 0s
Build & Publish / Build C FFI x86_64-unknown-linux-gnu (push) Failing after 1s
Build & Publish / Build C FFI x86_64-unknown-linux-musl (push) Failing after 3s
Build & Publish / Build MCP aarch64-unknown-linux-gnu (push) Failing after 1s
Build & Publish / Build MCP aarch64-unknown-linux-musl (push) Failing after 0s
Build & Publish / Build MCP x86_64-unknown-linux-gnu (push) Failing after 2s
Rust CI / Fuzz Tests (ubuntu-latest) (push) Failing after 1s
Rust CI / Build i686-unknown-linux-gnu (push) Failing after 1s
Rust CI / cargo fmt (push) Failing after 1s
Build & Publish / Build MCP x86_64-unknown-linux-musl (push) Failing after 4s
Build & Publish / Build Python wheels aarch64 (ubuntu-latest) (push) Failing after 1s
Build & Publish / Build Python wheels x86_64 (ubuntu-latest) (push) Failing after 1s
Build & Publish / Build Python sdist (push) Failing after 0s
Rust CI / Test (ubuntu-latest) (push) Failing after 1s
Rust CI / cargo clippy (push) Failing after 1s
Spelling / Spell Check with Typos (push) Failing after 1s
Stylua / Check lua files using Stylua (push) Failing after 1s
e2e Tests / e2e (ubuntu-latest) (push) Failing after 4s
e2e Tests / e2e (alpine-musl) (push) Successful in 58m54s
chore: import upstream snapshot with attribution
2026-07-13 12:24:42 +08:00

198 lines
8.2 KiB
Lua

---@diagnostic disable: undefined-field, missing-fields
-- Regression test for https://github.com/dmtrKovalenko/fff.nvim/issues/389
-- When find_files_in_dir(dir) runs with dir != neovim's cwd, the Rust indexer
-- reports paths relative to `dir`, but the Lua side used to resolve them
-- against neovim's cwd when calling :edit / preview / quickfix — so files
-- opened as phantom buffers and previews showed "No preview available".
local fff_rust = require('fff.rust')
local picker_ui = require('fff.picker_ui.picker_ui')
local file_picker = require('fff.file_picker')
--- Normalise a path so that comparisons work on every OS.
--- Windows complicates things: Rust may return forward-slash paths while
--- vim.fn.resolve uses backslashes, temp paths may contain 8.3 short names
--- (RUNNER~1), and the filesystem is case-insensitive.
--- vim.uv.fs_realpath expands 8.3 names on Windows (unlike vim.fn.resolve).
--- @param p string
--- @return string
local function norm(p)
-- fs_realpath is the closest Lua equivalent of Rust's std::fs::canonicalize
-- and expands 8.3 short names on Windows.
local rp = vim.uv.fs_realpath(p) or vim.fn.fnamemodify(vim.fn.resolve(p), ':p')
local n = vim.fs.normalize(rp)
-- Strip trailing slash for consistent comparison
n = n:gsub('/$', '')
-- Case-fold on Windows (drive letters, 8.3 short names, etc.)
if vim.fn.has('win32') == 1 then n = n:lower() end
return n
end
--- `change_indexing_directory` swaps the picker on a background thread, so the
--- `FILE_PICKER` global may still point at the *old* picker for a moment —
--- `wait_for_initial_scan` on the old picker returns immediately and the
--- search then runs against the new, still-empty index. Poll `health_check`
--- until `base_path` matches the expected dir before waiting on the scan.
local function wait_for_reindex(expected_dir, timeout_ms)
local expected = norm(expected_dir)
local deadline = vim.uv.hrtime() + timeout_ms * 1e6
while vim.uv.hrtime() < deadline do
local ok, health = pcall(fff_rust.health_check, expected)
if ok and health and health.file_picker and health.file_picker.base_path then
if norm(health.file_picker.base_path) == expected then return true end
end
vim.wait(20, function() return false end)
end
return false
end
local function wait_for_scan(expected_dir, timeout_ms)
assert.is_true(wait_for_reindex(expected_dir, timeout_ms), 'reindex to ' .. expected_dir .. ' did not complete')
fff_rust.wait_for_initial_scan(timeout_ms)
end
describe('picker find_files_in_dir path resolution (issue #389)', function()
local sandbox_root, target_dir, other_cwd, target_filename, second_filename
before_each(function()
sandbox_root = vim.fn.tempname()
target_dir = sandbox_root .. '/target-dir'
other_cwd = sandbox_root .. '/other-cwd'
vim.fn.mkdir(target_dir, 'p')
vim.fn.mkdir(other_cwd, 'p')
target_filename = 'issue389_target.lua'
local fd = assert(io.open(target_dir .. '/' .. target_filename, 'w'))
fd:write('-- issue #389 regression fixture\nreturn true\n')
fd:close()
second_filename = 'issue389_second.lua'
fd = assert(io.open(target_dir .. '/' .. second_filename, 'w'))
fd:write('-- issue #389 second fixture\nreturn true\n')
fd:close()
-- Clear the DirChanged autocmd that a previous test run (e.g. fff_core_spec)
-- may have installed. Without this, the :cd below triggers a scheduled
-- change_indexing_directory(other_cwd) that races with our explicit
-- change_indexing_directory(target_dir) and overwrites the FILE_PICKER.
pcall(vim.api.nvim_del_augroup_by_name, 'fff_file_tracking')
vim.cmd('cd ' .. vim.fn.fnameescape(other_cwd))
-- Equivalent to require('fff').setup({}) — just seeds vim.g.fff — but
-- avoids the top-level fff module lookup which plenary's sandboxed
-- require can miss depending on package.path.
vim.g.fff = {}
file_picker.setup()
end)
after_each(function()
pcall(picker_ui.close)
pcall(fff_rust.stop_background_monitor)
pcall(fff_rust.cleanup_file_picker)
if sandbox_root then vim.fn.delete(sandbox_root, 'rf') end
end)
it(':edit opens the file inside base_path even when neovim cwd differs', function()
assert.are_not.equal(norm(target_dir), norm(vim.fn.getcwd()))
assert.is_true(require('fff.core').change_indexing_directory(target_dir))
wait_for_scan(target_dir, 10000)
local items = file_picker.search_files('', nil, nil, nil, nil)
assert.is_true(#items > 0, 'indexer returned no items for target_dir (norm=' .. norm(target_dir) .. ')')
local target_item
for _, item in ipairs(items) do
if item.name == target_filename then
target_item = item
break
end
end
assert.is_not_nil(target_item, 'target fixture file missing from results')
-- On Windows Rust may use backslash separators; compare just the filename.
local rel = target_item.relative_path
assert.are.equal(target_filename, rel:match('[^/\\]+$') or rel)
assert.is_nil(
vim.uv.fs_stat(target_item.relative_path),
'relative_path should not resolve against cwd — if it does, the test fixture is wrong'
)
-- Drive the same code path that user hits when pressing <CR>: populate
-- UI state as open_ui_with_state would and invoke select('edit').
picker_ui.state.active = true
picker_ui.state.filtered_items = items
picker_ui.state.cursor = (function()
for i, item in ipairs(items) do
if item.name == target_filename then return i end
end
return 1
end)()
picker_ui.state.query = ''
picker_ui.state.mode = nil
picker_ui.state.location = nil
picker_ui.state.suggestion_source = nil
picker_ui.state.selected_files = {}
picker_ui.state.selected_file_order = {}
picker_ui.state.selected_items = {}
picker_ui.select('edit')
-- select('edit') defers the actual :edit via vim.schedule (see picker_ui.lua)
-- to let picker float teardown finish before opening the file. Flush here.
vim.wait(2000, function() return vim.api.nvim_buf_get_name(0) ~= '' end)
local bufname = vim.api.nvim_buf_get_name(0)
assert.is_true(bufname ~= '', 'expected :edit to open a buffer with a non-empty name')
local stat = vim.uv.fs_stat(bufname)
assert.is_not_nil(stat, 'opened buffer points at a non-existent file: ' .. bufname)
-- The opened file must be the fixture inside target_dir, not a phantom
-- file under cwd.
local expected = norm(target_dir .. '/' .. target_filename)
local actual = norm(bufname)
assert.are.equal(expected, actual)
end)
it(':edit loads all selected files as listed buffers', function()
assert.is_true(require('fff.core').change_indexing_directory(target_dir))
wait_for_scan(target_dir, 10000)
local items = file_picker.search_files('', nil, nil, nil, nil)
local selected = {}
for _, item in ipairs(items) do
if item.name == target_filename or item.name == second_filename then table.insert(selected, item) end
end
assert.are.equal(2, #selected, 'expected both fixture files in picker results')
picker_ui.state.active = true
picker_ui.state.filtered_items = items
picker_ui.state.cursor = 1
picker_ui.state.query = ''
picker_ui.state.mode = nil
picker_ui.state.location = nil
picker_ui.state.suggestion_source = nil
picker_ui.state.selected_files = {}
picker_ui.state.selected_file_order = {}
picker_ui.state.selected_items = {}
for _, item in ipairs(selected) do
picker_ui.state.selected_files[item.relative_path] = true
table.insert(picker_ui.state.selected_file_order, item.relative_path)
end
picker_ui.select('edit')
local expected_first = norm(target_dir .. '/' .. selected[1].name)
vim.wait(2000, function() return norm(vim.api.nvim_buf_get_name(0)) == expected_first end)
assert.are.equal(expected_first, norm(vim.api.nvim_buf_get_name(0)))
for _, item in ipairs(selected) do
local path = norm(target_dir .. '/' .. item.name)
local bufnr = vim.fn.bufnr(path)
assert.is_true(bufnr > 0, 'expected selected file to have a buffer: ' .. path)
assert.are.equal(1, vim.fn.buflisted(bufnr), 'expected selected file to be listed: ' .. path)
end
end)
end)