Files
openidea/e2e/pluginEndpoints.spec.ts
T
Arnaud Ritti 668206ddfd feat(plugins): share project HTTP endpoints with the HTTP client (#1991)
Adds the API 1.3 `endpoints-provider` export and `[[contributes.endpoints]]` (file globs, ^1.3, `project.read`). A plugin's routes join the Spring ones in Search Everywhere, `.http` URL completion and the declaration's Open in HTTP Client gutter action. The UI asks `plugin_endpoints` on demand; the host runs the plugin on a blocking thread, validates the whole list (bounds, project-relative locations, UTF-16 ranges) and caches it per project until a file matching the globs changes, then emits `EndpointsChanged`. A failed or invalid run keeps the last list; disabling, crashing or closing the project withdraws it, and late results can't restore it. Closes #1960.

Co-authored-by: arnaud-ritti <[email protected]>
2026-11-07 22:59:21 +08:00

64 lines
2.8 KiB
TypeScript

import { doubleShift, expect, test } from "./fixtures";
test("plugin routes feed search, HTTP completion and the declaration gutter and disappear on removal", async ({
workbench: page,
}) => {
await page.evaluate(async () => {
const c = "/src/workbench/contributions.ts";
const b = "/src/editor/core/backend.ts";
const { registerEndpointProvider, editorService } = await import(c);
const { docBackend } = await import(b);
docBackend().files.set("/dev/acme-api/routes.txt", "route declaration\n");
docBackend().files.set("/dev/acme-api/routes.http", "GET {{host}}/pe");
(window as unknown as { removeRoutes: () => void }).removeRoutes = registerEndpointProvider({
id: "plugin:routes:test",
endpoints: async (root: string) =>
root === "/dev/acme-api"
? [
{
method: "*",
url: "/people/{id}",
framework: "Rails",
handler: "People#show",
pluginId: "routes",
controller: "Rails",
methodName: "People#show",
path: "/dev/acme-api/routes.txt",
rel: "routes.txt",
line: 0,
col: 0,
},
]
: [],
});
await editorService().openFile("/dev/acme-api/routes.txt");
});
await expect(page.locator('.cm-openidea-gutter-endpoints button[title="Open in HTTP Client"]')).toBeVisible();
await doubleShift(page);
const search = page.getByRole("dialog", { name: "Search Everywhere" });
await search.getByRole("combobox").fill("/people");
await expect(search.getByRole("option", { name: /people/ })).toBeVisible();
await page.keyboard.press("Escape");
await page.evaluate(async () => {
const path = "/src/workbench/contributions.ts";
await (await import(path)).editorService().openFile("/dev/acme-api/routes.http");
});
const content = page.locator(".cm-content").last();
await content.click();
await page.keyboard.press("Meta+End");
await page.keyboard.press("Control+Space");
await expect(page.locator(".cm-tooltip-autocomplete").getByText("/people/{{id}}", { exact: true })).toBeVisible();
await page.keyboard.press("Escape");
await doubleShift(page);
await search.getByRole("combobox").fill("/people");
await expect(search.getByRole("option", { name: /people/ })).toBeVisible();
await page.evaluate(() => (window as unknown as { removeRoutes: () => void }).removeRoutes());
await expect(search.getByRole("option", { name: /people/ })).toBeHidden();
await page.keyboard.press("Escape");
await page.evaluate(async () => {
const path = "/src/workbench/contributions.ts";
await (await import(path)).editorService().openFile("/dev/acme-api/routes.txt");
});
await expect(page.locator('.cm-openidea-gutter-endpoints button[title="Open in HTTP Client"]')).toBeHidden();
});