Files
t8y2--dbx/packages/app-tests/mongoCompletion.test.ts
T
2026-07-13 13:09:14 +08:00

135 lines
6.1 KiB
TypeScript
Vendored

import { strict as assert } from "node:assert";
import { test } from "vitest";
import { buildMongoCompletionItems, getMongoCompletionContext, inferMongoCompletionFields, shouldAutoOpenMongoCompletion } from "../../apps/desktop/src/lib/mongo/mongoCompletion.ts";
const collections = ["users", "user_events", "order-items"];
const fields = [
{ name: "_id", type: "object" },
{ name: "name", type: "string" },
{ name: "profile.email", type: "string" },
{ name: "createdAt", type: "string" },
];
function labels(text: string, input = {}) {
return buildMongoCompletionItems(text, text.length, input).map((item) => item.label);
}
test("suggests MongoDB root snippets and methods", () => {
const items = buildMongoCompletionItems("fi", 2);
assert.ok(items.some((item) => item.type === "function" && item.label === "find" && item.apply === "find({})"));
assert.equal(items.some((item) => item.label === "SELECT"), false);
});
test("suggests collections after db dot", () => {
const items = buildMongoCompletionItems("db.us", "db.us".length, { collections });
assert.deepEqual(
items.filter((item) => item.type === "table" && item.detail === "collection").map((item) => item.label),
["users", "user_events"],
);
});
test("uses getCollection apply text for unsafe collection names", () => {
const item = buildMongoCompletionItems("db.order", "db.order".length, { collections }).find((candidate) => candidate.label === "order-items");
assert.equal(item?.apply, 'getCollection("order-items")');
});
test("suggests collection methods after direct and getCollection references", () => {
assert.ok(labels("db.users.").includes("find"));
assert.ok(labels('db.getCollection("users").ag').includes("aggregate"));
});
test("suggests collection stats methods after a collection reference", () => {
const methodLabels = labels("db.users.");
for (const method of ["stats", "dataSize", "storageSize", "totalIndexSize"]) {
assert.ok(methodLabels.includes(method), `expected completion to include ${method}`);
}
const item = buildMongoCompletionItems("db.users.stat", "db.users.stat".length).find((candidate) => candidate.label === "stats");
assert.equal(item?.apply, "stats()");
});
test("suggests cursor methods after find result chains", () => {
const allItems = buildMongoCompletionItems("db.characters.find({}).", "db.characters.find({}).".length);
const prefixedItems = buildMongoCompletionItems("db.characters.find({}).li", "db.characters.find({}).li".length);
const formattedChainItems = buildMongoCompletionItems("db.characters.find({\n name: 'Ada'\n})\n .", "db.characters.find({\n name: 'Ada'\n})\n .".length);
const formattedPrefixedItems = buildMongoCompletionItems("db.characters.find({\n name: 'Ada'\n})\n .li", "db.characters.find({\n name: 'Ada'\n})\n .li".length);
assert.deepEqual(
allItems.map((item) => item.label),
["limit", "skip", "sort"],
);
assert.deepEqual(
prefixedItems.map((item) => item.label),
["limit"],
);
assert.deepEqual(
formattedChainItems.map((item) => item.label),
["limit", "skip", "sort"],
);
assert.deepEqual(
formattedPrefixedItems.map((item) => item.label),
["limit"],
);
});
test("suggests observed fields inside query objects", () => {
const items = buildMongoCompletionItems('db.users.find({ "pro', 'db.users.find({ "pro'.length, { fields });
const email = items.find((item) => item.label === "profile.email");
assert.equal(email?.detail, "observed field · string");
assert.equal(email?.type, "column");
assert.equal(email?.apply, '"profile.email": ');
});
test("suggests query fields at object starts and after commas", () => {
const objectStart = buildMongoCompletionItems("db.users.find({", "db.users.find({".length, { fields });
const afterComma = buildMongoCompletionItems("db.users.find({ name: 'Ada', ", "db.users.find({ name: 'Ada', ".length, { fields });
assert.ok(objectStart.find((item) => item.label === "name" && item.apply === "name: "));
assert.ok(afterComma.find((item) => item.label === "createdAt" && item.apply === "createdAt: "));
});
test("suggests query operators inside field value objects", () => {
const items = buildMongoCompletionItems("db.users.find({ age: { ", "db.users.find({ age: { ".length, { fields });
assert.ok(items.find((item) => item.label === "$gte"));
assert.equal(items.some((item) => item.label === "name"), false);
});
test("suggests query and update operators", () => {
assert.ok(labels("db.users.find({ age: { $g").includes("$gte"));
assert.ok(labels("db.users.updateOne({}, { $s").includes("$set"));
});
test("suggests aggregation stages inside aggregate pipeline", () => {
const items = buildMongoCompletionItems("db.users.aggregate([{ $m", "db.users.aggregate([{ $m".length);
assert.ok(items.find((item) => item.label === "$match" && item.detail === "aggregation stage"));
});
test("completion context is tolerant of unfinished input", () => {
const context = getMongoCompletionContext('db.getCollection("users").find({ "', 'db.getCollection("users").find({ "'.length);
assert.equal(context.mode, "field");
assert.equal(context.collection, "users");
});
test("auto trigger opens for useful MongoDB characters only", () => {
assert.equal(shouldAutoOpenMongoCompletion("db.", "db.".length), true);
assert.equal(shouldAutoOpenMongoCompletion("db.users.find({ $", "db.users.find({ $".length), true);
assert.equal(shouldAutoOpenMongoCompletion("db.users.find({", "db.users.find({".length), true);
});
test("infers dotted MongoDB fields from sampled documents", () => {
const inferred = inferMongoCompletionFields([
{ _id: "1", profile: { email: "a@example.com" }, tags: ["a"] },
{ _id: "2", profile: { age: 3 }, tags: [{ label: "vip" }] },
]);
assert.ok(inferred.find((field) => field.name === "profile.email" && field.type === "string"));
assert.ok(inferred.find((field) => field.name === "profile.age" && field.type === "number"));
assert.ok(inferred.find((field) => field.name === "tags.label" && field.type === "string"));
});