Skip to content

Commit 06a02eb

Browse files
committed
test(vnext): harden integrated completion paths
1 parent 4e613b2 commit 06a02eb

2 files changed

Lines changed: 141 additions & 0 deletions

File tree

src/vnext/__tests__/session.test.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1570,6 +1570,56 @@ describe("namespace completion session integration", () => {
15701570
vi.useRealTimers();
15711571
}
15721572
});
1573+
1574+
it("supports a namespace-only service and preserves partial evidence", async () => {
1575+
const service = createSqlLanguageService<TestContext>({
1576+
dialects: [duckdb],
1577+
namespaces: {
1578+
id: "namespace-only",
1579+
search: async () => ({
1580+
containers: [{
1581+
canonicalPath: [{
1582+
quoted: false,
1583+
role: "schema",
1584+
value: "main",
1585+
}],
1586+
containerEntityId: "schema:main",
1587+
insertText: "main",
1588+
matchQuality: "equivalent",
1589+
}],
1590+
coverage: "partial",
1591+
epoch: { generation: 1, token: "epoch-1" },
1592+
status: "ready",
1593+
}),
1594+
},
1595+
});
1596+
const text = "SELECT * FROM m";
1597+
const session = service.openDocument({
1598+
context: {
1599+
catalog: { scope: "connection:namespace-only" },
1600+
dialect: "duckdb",
1601+
engine: "local",
1602+
},
1603+
text,
1604+
});
1605+
1606+
await expect(session.complete({
1607+
position: text.length,
1608+
trigger: { kind: "invoked" },
1609+
})).resolves.toMatchObject({
1610+
sources: [{
1611+
coverage: "partial",
1612+
feature: "namespace-catalog",
1613+
}],
1614+
status: "ready",
1615+
value: {
1616+
isIncomplete: true,
1617+
issues: [{ reason: "namespace-catalog-partial" }],
1618+
items: [{ kind: "namespace", label: "main" }],
1619+
},
1620+
});
1621+
service.dispose();
1622+
});
15731623
});
15741624

15751625
describe("statement-index session cache", () => {
@@ -3883,6 +3933,18 @@ describe("session coverage hardening", () => {
38833933
});
38843934
});
38853935

3936+
it.each([
3937+
{ columns: { id: "", loadColumns: async () => ({}) } },
3938+
{ namespaces: { id: "", search: async () => ({}) } },
3939+
])("rejects a malformed auxiliary provider %#", (provider) => {
3940+
expectSessionError("invalid-service-options", () => {
3941+
createSqlLanguageService({
3942+
...provider,
3943+
dialects: [duckdb],
3944+
});
3945+
});
3946+
});
3947+
38863948
it.each([
38873949
{
38883950
expectedIssue: "catalog-failed",

src/vnext/codemirror/__tests__/sql-editor.test.ts

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1096,6 +1096,32 @@ describe("sqlEditor", () => {
10961096
expect(harness.sessionDisposals()).toBe(0);
10971097
});
10981098

1099+
it("refuses a completion edit that overlaps an embedded region", async () => {
1100+
const harness = fakeService((revision) =>
1101+
readyResult(revision, [completionItem(14, 16)])
1102+
);
1103+
const support = sqlEditor({
1104+
initialContext: context(),
1105+
initialEmbeddedRegions: [{
1106+
from: 15,
1107+
language: "host",
1108+
to: 16,
1109+
}],
1110+
service: harness.service,
1111+
});
1112+
const view = createView(support.extension);
1113+
expect(startCompletion(view)).toBe(true);
1114+
await waitForActiveCompletion(view);
1115+
const completion = currentCompletions(view.state)[0];
1116+
if (!completion || typeof completion.apply !== "function") {
1117+
throw new Error("Expected completion apply callback");
1118+
}
1119+
1120+
completion.apply(view, completion, 14, 16);
1121+
expect(view.state.doc.toString()).toBe("SELECT * FROM us");
1122+
expect(harness.updates).toEqual([]);
1123+
});
1124+
10991125
it("combines document and final context effects into one current input", async () => {
11001126
const requests: SqlCatalogSearchRequest[] = [];
11011127
const service = createSqlLanguageService<TestContext>({
@@ -1275,6 +1301,59 @@ describe("sqlEditor", () => {
12751301
);
12761302
});
12771303

1304+
it("maps column and namespace completion presentation", async () => {
1305+
const epoch = { generation: 1, token: "epoch-1" };
1306+
const items: readonly SqlCompletionItem[] = [{
1307+
edit: { from: 14, insert: "users_column", to: 16 },
1308+
kind: "column",
1309+
label: "users_column",
1310+
provenance: {
1311+
columnEntityId: "users:name",
1312+
epoch,
1313+
kind: "column-catalog",
1314+
providerId: "columns",
1315+
relationEntityId: "users",
1316+
scope: "connection:fake",
1317+
},
1318+
relationRequestKey: "binding:0",
1319+
}, {
1320+
edit: { from: 14, insert: "users_namespace", to: 16 },
1321+
kind: "namespace",
1322+
label: "users_namespace",
1323+
provenance: {
1324+
containerEntityId: "schema:main",
1325+
epoch,
1326+
kind: "namespace-catalog",
1327+
providerId: "namespaces",
1328+
scope: "connection:fake",
1329+
},
1330+
role: "schema",
1331+
}];
1332+
const harness = fakeService((revision) =>
1333+
readyResult(revision, items)
1334+
);
1335+
const support = sqlEditor({
1336+
initialContext: context(),
1337+
service: harness.service,
1338+
});
1339+
const view = createView(support.extension);
1340+
1341+
expect(startCompletion(view)).toBe(true);
1342+
await waitForActiveCompletion(view);
1343+
expect(currentCompletions(view.state)).toEqual(
1344+
expect.arrayContaining([
1345+
expect.objectContaining({
1346+
label: "users_column",
1347+
type: "property",
1348+
}),
1349+
expect.objectContaining({
1350+
label: "users_namespace",
1351+
type: "namespace",
1352+
}),
1353+
]),
1354+
);
1355+
});
1356+
12781357
it("denies SQL completion at an unmatched template EOF without removing external sources", async () => {
12791358
const harness = fakeService((revision) =>
12801359
readyResult(revision, [completionItem()])

0 commit comments

Comments
 (0)