/* oxlint-disable typescript/no-floating-promises -- expectTypeOf() is a sync type-assertion library that the type-aware linter misidentifies as a promise */ import { strict as assert } from "node:assert"; import path from "node:path"; import { test } from "node:test"; import { expectTypeOf } from "expect-type"; import { type CompletionResult, type CompletionTarget, LspSession, } from "#test-utils/lsp-completion.ts"; const REPO_ROOT = path.resolve(import.meta.dirname, "../../.."); // The `#test-utils/*` self-reference (package.json#imports) is the one route // scattered test files take to reach test helpers; this pins that it resolves // and types without starting a language server // (development/testing.md § Test helpers). test("test-helper home: `#test-utils/…` resolves to the helper and types it", () => { // Arrange const target: CompletionTarget = { file: "src/util/__tests__/lsp-completion.ts", source: "", }; // Assert — no session is constructed: the constructor spawns `tsc --lsp`, // so only the resolved values and their types are checked here. expectTypeOf(LspSession).toBeConstructibleWith("repo-root"); expectTypeOf().toMatchTypeOf<{ labels: readonly string[]; }>(); assert.equal(typeof LspSession, "function"); assert.equal(target.file, "src/util/__tests__/lsp-completion.ts"); }); // The helper drives a language server, so its contract is asserted against the // server. Every document below is probed from memory and carries its // contextual type inline, so the helper is tested without the library's code. const probe = (source: string, marker?: string): Promise => { const session = new LspSession(REPO_ROOT); const target: CompletionTarget = marker === undefined ? { file: "src/__probe.ts", source } : { file: "src/__probe.ts", source, marker }; return session.completionLabelsAt(target).finally(() => session.close()); }; const HANDLERS = [ "type Handlers = { a: () => number; b: () => number; _?: () => number };", "declare const apply: (handlers: Handlers) => Handlers;", ]; test("helper: a fresh object literal completes with its contextual keys", () => { // Arrange const source = [ ...HANDLERS, "const done = apply({", " /*COMPLETE*/", "});", "export { done };", ].join("\n"); // Act const probed = probe(source); // Assert — the position is the marker's, which the helper strips expectTypeOf(probed).toEqualTypeOf>(); return probed.then((result) => { assert.deepEqual(result.position, { line: 3, character: 4 }); assert.deepEqual([...result.labels], ["_?", "a", "b"]); }); }); test("helper: a handled key drops out of the popup", () => { // Arrange const source = [ ...HANDLERS, "const done = apply({", " b: () => 1,", " /*COMPLETE*/", "});", "export { done };", ].join("\n"); // Act const probed = probe(source); // Assert expectTypeOf().toEqualTypeOf< readonly string[] >(); return probed.then((result) => { assert.deepEqual([...result.labels], ["_?", "a"]); }); }); test("helper: a custom marker is located at the line start", () => { // Arrange const source = [ "type Handlers = { a: () => number; _?: () => number };", "declare const apply: (handlers: Handlers) => Handlers;", "const done = apply({", "@@@", "});", "export { done };", ].join("\n"); // Act const probed = probe(source, "@@@"); // Assert expectTypeOf(probed).resolves.toEqualTypeOf(); return probed.then((result) => { assert.deepEqual(result.position, { line: 3, character: 0 }); assert.deepEqual([...result.labels], ["_?", "a"]); }); }); test("helper: labels are read from the server, not from a pattern literal", () => { // Arrange const source = [ 'const text = "x";', "const upper = text./*COMPLETE*/;", "export { upper };", ].join("\n"); // Act const probed = probe(source); // Assert expectTypeOf(probed).resolves.toHaveProperty("labels"); return probed.then((result) => { assert.ok(result.labels.includes("toUpperCase")); }); }); test("helper: a source without the marker rejects", () => { // Arrange const source = "const text = 1;\nexport { text };\n"; // Act const probed = probe(source); // Assert expectTypeOf(probed).resolves.toEqualTypeOf(); return assert.rejects(probed, /marker not found/); });