Files
wehub-resource-sync 1b279d4d10
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
Build & Publish / Build C FFI aarch64-apple-darwin (push) Has been cancelled
Build & Publish / Build C FFI aarch64-pc-windows-msvc (push) Has been cancelled
Build & Publish / Build C FFI x86_64-apple-darwin (push) Has been cancelled
Build & Publish / Build C FFI x86_64-pc-windows-msvc (push) Has been cancelled
e2e Tests / e2e (macos-latest) (push) Has been cancelled
e2e Tests / e2e (windows-latest) (push) Has been cancelled
Nix CI / check (push) Has been cancelled
Python CI / Python bindings (macos-latest) (push) Has been cancelled
Python CI / Python bindings (windows-latest) (push) Has been cancelled
Build & Publish / Build Neovim aarch64-apple-darwin (push) Has been cancelled
Build & Publish / Build Neovim aarch64-pc-windows-msvc (push) Has been cancelled
Build & Publish / Build Neovim x86_64-apple-darwin (push) Has been cancelled
Build & Publish / Build Neovim x86_64-pc-windows-msvc (push) Has been cancelled
Build & Publish / Build MCP aarch64-apple-darwin (push) Has been cancelled
Build & Publish / Build MCP aarch64-pc-windows-msvc (push) Has been cancelled
Build & Publish / Build MCP x86_64-apple-darwin (push) Has been cancelled
Build & Publish / Build MCP x86_64-pc-windows-msvc (push) Has been cancelled
Build & Publish / Build Python wheels aarch64 (macos-latest) (push) Has been cancelled
Build & Publish / Build Python wheels x86_64 (macos-latest) (push) Has been cancelled
Build & Publish / Build Python wheels x86_64 (windows-latest) (push) Has been cancelled
Build & Publish / Release (push) Has been cancelled
Build & Publish / Publish Python wheels to PyPI (push) Has been cancelled
Build & Publish / Publish Rust crates (push) Has been cancelled
Build & Publish / Publish npm packages (push) Has been cancelled
Rust CI / Test (macos-latest) (push) Has been cancelled
Rust CI / Test (windows-latest) (push) Has been cancelled
Rust CI / Fuzz Tests (macos-latest) (push) Has been cancelled
Rust CI / Fuzz Tests (windows-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:24:42 +08:00

354 lines
12 KiB
JavaScript

import { after, before, describe, it } from "node:test";
import { strict as assert } from "node:assert";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { FileFinder } from "../dist/src/index.js";
const __dirname = dirname(fileURLToPath(import.meta.url));
const REPO_ROOT = resolve(__dirname, "..", "..", "..");
const normalizePath = (p) => p.replace(/\\/g, "/");
/** @type {import("../dist/src/finder.js").FileFinder | null} */
let finder = null;
describe("fff-node", { concurrency: 1 }, () => {
before(async () => {
const result = FileFinder.create({ basePath: REPO_ROOT });
assert.ok(result.ok, `create failed: ${!result.ok ? result.error : ""}`);
finder = result.value;
const wait = await finder.waitForScan(5_000);
if (!wait.ok) {
assert.ok(wait.ok, `waitForScan failed: ${wait.error}`);
}
assert.equal(wait.value, true, "scan should finish within 5s");
});
after(() => {
if (finder && !finder.isDestroyed) {
finder.destroy();
}
});
it("isAvailable returns true when the native library is loadable", () => {
assert.equal(FileFinder.isAvailable(), true);
});
it("getScanProgress reports >100 indexed files", () => {
const r = finder.getScanProgress();
assert.ok(r.ok, "getScanProgress failed");
assert.equal(typeof r.value.scannedFilesCount, "number");
assert.ok(
r.value.scannedFilesCount > 100,
`expected >100, got ${r.value.scannedFilesCount}`,
);
assert.equal(r.value.isScanning, false);
});
describe("search", { concurrency: 1 }, () => {
it("finds the root Cargo.toml", () => {
const r = finder.fileSearch("Cargo.toml", { pageSize: 10 });
assert.ok(r.ok, `search failed: ${!r.ok ? r.error : ""}`);
const paths = r.value.items.map((i) => i.relativePath);
assert.ok(
paths.includes("Cargo.toml"),
`expected Cargo.toml in results, got: ${paths}`,
);
});
it("finds crates/fff-c/src/lib.rs", () => {
const r = finder.fileSearch("lib.rs", { pageSize: 20 });
assert.ok(r.ok);
const paths = r.value.items.map((i) => i.relativePath);
assert.ok(
paths.some((p) => p.includes("fff-c") && p.endsWith("lib.rs")),
`expected fff-c/src/lib.rs, got: ${paths}`,
);
});
it("respects pageSize", () => {
const r = finder.fileSearch("lua", { pageSize: 3 });
assert.ok(r.ok);
assert.ok(
r.value.items.length <= 3,
`expected <=3, got ${r.value.items.length}`,
);
});
it("items contain all required fields", () => {
const r = finder.fileSearch("Cargo.toml", { pageSize: 1 });
assert.ok(r.ok);
const item = r.value.items[0];
for (const f of ["relativePath", "fileName", "gitStatus"]) {
assert.equal(typeof item[f], "string", `${f} should be a string`);
}
for (const f of ["size", "modified"]) {
assert.equal(typeof item[f], "number", `${f} should be a number`);
}
});
it("scores contain all required fields", () => {
const r = finder.fileSearch("Cargo.toml", { pageSize: 1 });
assert.ok(r.ok);
assert.ok(r.value.scores.length > 0);
const s = r.value.scores[0];
for (const f of ["total", "baseScore"]) {
assert.equal(typeof s[f], "number", `${f} should be a number`);
}
assert.equal(typeof s.matchType, "string");
});
it("totalMatched <= totalFiles", () => {
const r = finder.fileSearch("rs", { pageSize: 1 });
assert.ok(r.ok);
assert.ok(r.value.totalMatched > 0);
assert.ok(r.value.totalFiles > 100);
assert.ok(r.value.totalFiles >= r.value.totalMatched);
});
it("empty query returns files", () => {
const r = finder.fileSearch("", { pageSize: 5 });
assert.ok(r.ok, `empty query failed: ${!r.ok ? r.error : ""}`);
assert.equal(typeof r.value.totalFiles, "number");
});
});
describe("glob", { concurrency: 1 }, () => {
it("filters by extension via raw glob pattern", () => {
const r = finder.glob("**/*.rs", { pageSize: 50 });
assert.ok(r.ok, `glob failed: ${!r.ok ? r.error : ""}`);
assert.ok(r.value.items.length > 0, "expected at least one .rs file");
for (const item of r.value.items) {
assert.ok(
item.relativePath.endsWith(".rs"),
`unexpected file: ${item.relativePath}`,
);
}
});
it("returns empty result for non-matching pattern", () => {
const r = finder.glob("**/this-extension-does-not-exist-anywhere.zzz");
assert.ok(r.ok);
assert.equal(r.value.items.length, 0);
});
it("rejects empty pattern", () => {
const r = finder.glob("");
assert.equal(r.ok, false);
});
it("respects pageSize", () => {
const r = finder.glob("**/*.rs", { pageSize: 3 });
assert.ok(r.ok);
assert.ok(r.value.items.length <= 3);
});
});
describe("grep", { concurrency: 1 }, () => {
it("finds FffResult in Rust sources", () => {
// Constrain to .rs files so the assertion doesn't depend on result ordering
// or content-indexing timing for other file types.
const rustResults = finder.grep("*.rs FffResult", { mode: "plain" });
assert.ok(
rustResults.ok,
`grep failed: ${!rustResults.ok ? rustResults.error : ""}`,
);
assert.ok(
rustResults.value.items.length > 0,
"expected at least one .rs match",
);
assert.ok(
rustResults.value.items.some((m) => m.relativePath.endsWith(".rs")),
);
const cResults = finder.grep("!**/*.{js,ts,rs} FffResult", {
mode: "plain",
});
assert.ok(
cResults.ok,
`grep failed: ${!cResults.ok ? cResults.error : ""}`,
);
assert.ok(
cResults.value.items.length > 0,
"expected at least one non-js/ts/rs match",
);
assert.ok(
cResults.value.items.some((m) => m.relativePath.endsWith(".h")),
);
});
it("match items contain all required fields", () => {
const r = finder.grep("FffResult", { mode: "plain" });
assert.ok(r.ok);
const m = r.value.items[0];
assert.equal(typeof m.relativePath, "string");
assert.equal(typeof m.lineNumber, "number");
assert.ok(m.lineNumber > 0, "lineNumber is 1-based");
assert.equal(typeof m.lineContent, "string");
assert.ok(m.lineContent.includes("FffResult"));
assert.equal(typeof m.col, "number");
assert.equal(typeof m.byteOffset, "number");
assert.ok(Array.isArray(m.matchRanges));
console.log(m.matchRanges);
});
it("pagination returns a second page", () => {
const r = finder.grep("fn", { mode: "plain" });
assert.ok(r.ok);
if (r.value.nextCursor) {
const r2 = finder.grep("fn", { cursor: r.value.nextCursor });
assert.ok(r2.ok, `page 2 failed: ${!r2.ok ? r2.error : ""}`);
assert.equal(typeof r2.value.totalMatched, "number");
}
});
it("respects pageSize", () => {
// Cap to one match per file so pageSize bounds the total deterministically.
const unbounded = finder.grep("fn", {
mode: "plain",
maxMatchesPerFile: 1,
});
assert.ok(
unbounded.ok,
`grep failed: ${!unbounded.ok ? unbounded.error : ""}`,
);
assert.ok(unbounded.value.items.length > 2);
const limited = finder.grep("fn", {
mode: "plain",
maxMatchesPerFile: 1,
pageSize: 2,
});
assert.ok(limited.ok, `grep failed: ${!limited.ok ? limited.error : ""}`);
assert.ok(
limited.value.items.length <= 2,
`expected <=2 items, got ${limited.value.items.length}`,
);
assert.ok(
limited.value.items.length < unbounded.value.items.length,
"limited page should yield fewer matches than the default",
);
assert.ok(limited.value.nextCursor !== null, "nextCursor should be set");
});
it("regex mode matches pub fn declarations", () => {
const r = finder.grep("pub fn \\w+", { mode: "regex" });
assert.ok(r.ok, `regex grep failed: ${!r.ok ? r.error : ""}`);
assert.ok(r.value.items.length > 0);
});
it("decodes before/after context lines", () => {
const r = finder.grep(
"LOLLOWOIEJIWOIUOIWUIWUIOUWE", // the random text visible here
{
mode: "plain",
beforeContext: 1,
afterContext: 1,
maxMatchesPerFile: 5,
},
);
assert.ok(r.ok, `grep with context failed: ${!r.ok ? r.error : ""}`);
const match = r.value.items.find(
(m) =>
normalizePath(m.relativePath) === "packages/fff-node/test/e2e.mjs",
);
assert.ok(
match,
`expected a single match in the codebase, got: ${r.value.items
.map((m) => normalizePath(m.relativePath))
.join(", ")}`,
);
assert.deepEqual(match.contextBefore, [" const r = finder.grep("]);
assert.deepEqual(match.contextAfter, [" {"]);
});
});
describe("multiGrep", { concurrency: 1 }, () => {
it("finds lines matching any of the C FFI function names", () => {
const r = finder.multiGrep({
patterns: ["fff_create_instance", "fff_destroy", "fff_search"],
});
assert.ok(r.ok, `multiGrep failed: ${!r.ok ? r.error : ""}`);
assert.ok(r.value.items.length > 0);
});
it("rejects empty patterns array", () => {
const r = finder.multiGrep({ patterns: [] });
assert.ok(!r.ok);
});
});
it("refreshGitStatus returns a positive count", () => {
const r = finder.refreshGitStatus();
assert.ok(r.ok, `refreshGitStatus failed: ${!r.ok ? r.error : ""}`);
assert.equal(typeof r.value, "number");
assert.ok(r.value > 0);
});
it("isScanning returns a boolean", () => {
assert.equal(typeof finder.isScanning(), "boolean");
});
describe("healthCheck", { concurrency: 1 }, () => {
it("reports initialized state with instance", () => {
const r = finder.healthCheck(REPO_ROOT);
assert.ok(r.ok, `healthCheck failed: ${!r.ok ? r.error : ""}`);
assert.equal(typeof r.value.version, "string");
assert.ok(r.value.version.length > 0);
assert.equal(r.value.git.available, true);
assert.equal(r.value.git.repositoryFound, true);
assert.equal(r.value.filePicker.initialized, true);
assert.ok(r.value.filePicker.indexedFiles > 100);
});
it("static check works without instance", () => {
const r = FileFinder.healthCheckStatic(REPO_ROOT);
assert.ok(r.ok, `static healthCheck failed: ${!r.ok ? r.error : ""}`);
assert.equal(typeof r.value.version, "string");
assert.equal(r.value.filePicker.initialized, false);
assert.equal(r.value.git.repositoryFound, true);
});
});
it("create rejects a non-existent path", () => {
const r = FileFinder.create({ basePath: "/nonexistent/fff-test-path" });
assert.ok(!r.ok);
assert.ok(
r.error.toLowerCase().includes("invalid path") ||
r.error.toLowerCase().includes("not exist"),
`unexpected error: ${r.error}`,
);
});
// This must be the last describe block since it destroys the shared instance.
describe("after destroy", { concurrency: 1 }, () => {
before(() => {
finder.destroy();
});
it("isDestroyed is true", () => {
assert.equal(finder.isDestroyed, true);
});
it("search returns an error", () => {
const r = finder.fileSearch("test");
assert.ok(!r.ok);
assert.ok(r.error.includes("destroyed"));
});
it("grep returns an error", () => {
const r = finder.grep("test");
assert.ok(!r.ok);
assert.ok(r.error.includes("destroyed"));
});
it("double destroy is a safe no-op", () => {
finder.destroy();
assert.equal(finder.isDestroyed, true);
});
});
});