drawio-mcp-server 2.0.4 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -3
- package/build/assets/downloader.js +16 -17
- package/build/config.js +253 -5
- package/build/config.test.js +449 -1
- package/build/emitter_bus.js +3 -0
- package/build/emitter_bus.test.js +7 -0
- package/build/index.capabilities.test.js +45 -0
- package/build/index.js +416 -82
- package/build/install-desktop-plugin.js +56 -0
- package/build/install-desktop-plugin.test.js +66 -0
- package/build/multi-transport.test.js +117 -0
- package/build/plugin/mcp-plugin.js +2180 -1105
- package/build/prefetch-assets.js +6 -5
- package/build/real-environment/document-targeting.test.js +124 -0
- package/build/real-environment/export-diagram.test.js +1 -1
- package/build/real-environment/harness.js +118 -31
- package/build/real-environment/import-mermaid.test.js +81 -0
- package/build/real-environment/pages-and-concurrency.test.js +448 -0
- package/build/real-environment/shapes.test.js +58 -0
- package/build/real-environment/tools.js +51 -6
- package/build/register-tool.js +31 -0
- package/build/request_queue.js +29 -0
- package/build/request_queue.test.js +98 -0
- package/build/stdio-transport-purity.test.js +88 -0
- package/build/strip-schema.js +61 -0
- package/build/tls/expiry.js +14 -0
- package/build/tls/expiry.test.js +54 -0
- package/build/tls/generate.js +123 -0
- package/build/tls/generate.test.js +115 -0
- package/build/tls/index.js +80 -0
- package/build/tls/index.test.js +141 -0
- package/build/tls/install-hint.js +45 -0
- package/build/tls/install-hint.test.js +32 -0
- package/build/tls/load.js +18 -0
- package/build/tls/load.test.js +40 -0
- package/build/tls/paths.js +30 -0
- package/build/tls/paths.test.js +53 -0
- package/build/tls/san.js +27 -0
- package/build/tls/san.test.js +72 -0
- package/build/tool-registry.test.js +823 -0
- package/build/tool.js +74 -15
- package/build/tool.test.js +250 -7
- package/build/tools/add-cell-of-shape.js +4 -2
- package/build/tools/add-edge.js +3 -1
- package/build/tools/add-rectangle.js +4 -2
- package/build/tools/copy-page.js +13 -0
- package/build/tools/create-layer.js +4 -2
- package/build/tools/create-page.js +8 -0
- package/build/tools/delete-cell-by-id.js +4 -2
- package/build/tools/edit-cell.js +3 -1
- package/build/tools/edit-edge.js +3 -1
- package/build/tools/export-diagram.js +7 -3
- package/build/tools/get-active-layer.js +4 -1
- package/build/tools/get-current-page.js +5 -0
- package/build/tools/get-selected-cell.js +4 -1
- package/build/tools/import-diagram.js +12 -2
- package/build/tools/import-mermaid.js +31 -0
- package/build/tools/index.js +14 -0
- package/build/tools/list-documents.js +17 -0
- package/build/tools/list-layers.js +4 -1
- package/build/tools/list-paged-model.js +4 -3
- package/build/tools/list-pages.js +5 -0
- package/build/tools/move-cell-to-layer.js +3 -1
- package/build/tools/rename-page.js +10 -0
- package/build/tools/set-active-layer.js +4 -2
- package/build/tools/set-cell-data.js +3 -1
- package/build/tools/set-cell-parent.js +3 -1
- package/build/tools/set-cell-shape.js +3 -1
- package/build/tools/shared.js +35 -0
- package/build/tools/shared.test.js +29 -0
- package/package.json +12 -4
package/build/prefetch-assets.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { ensureAssets } from "./assets/index.js";
|
|
2
|
+
import { create_logger } from "./mcp_console_logger.js";
|
|
2
3
|
async function main() {
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
});
|
|
6
|
-
console.log(`Assets ready at ${assetRoot}`);
|
|
4
|
+
const log = create_logger();
|
|
5
|
+
const { assetRoot } = await ensureAssets({}, log);
|
|
6
|
+
log.log("info", `Assets ready at ${assetRoot}`);
|
|
7
7
|
}
|
|
8
8
|
main().catch((error) => {
|
|
9
|
-
|
|
9
|
+
const log = create_logger();
|
|
10
|
+
log.log("error", "Failed to prefetch draw.io assets", error);
|
|
10
11
|
process.exit(1);
|
|
11
12
|
});
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, it } from "@jest/globals";
|
|
2
|
+
import { createRealEnvironmentContext, disposeRealEnvironmentContext, openConnectedDrawioPage, resetDiagram, } from "./harness.js";
|
|
3
|
+
import { expectNoBrowserErrors, expectNoServerErrors } from "./assertions.js";
|
|
4
|
+
import { callToolJson } from "./tools.js";
|
|
5
|
+
import { expectToolSuccess, unwrapToolPayload } from "./test-helpers.js";
|
|
6
|
+
function firstText(result) {
|
|
7
|
+
return result.content.find((item) => item.type === "text")?.text;
|
|
8
|
+
}
|
|
9
|
+
describe("real environment/document targeting", () => {
|
|
10
|
+
let context;
|
|
11
|
+
beforeAll(async () => {
|
|
12
|
+
context = await createRealEnvironmentContext();
|
|
13
|
+
}, 180000);
|
|
14
|
+
afterAll(async () => {
|
|
15
|
+
await disposeRealEnvironmentContext(context);
|
|
16
|
+
});
|
|
17
|
+
it("lists connected documents and requires target_document when multiple tabs are connected", async () => {
|
|
18
|
+
await resetDiagram(context);
|
|
19
|
+
context.browserMessages.length = 0;
|
|
20
|
+
const logCountBefore = context.logger.entries.length;
|
|
21
|
+
const { payload: renamedDocOnePayload } = await callToolJson(context, "rename-page", {
|
|
22
|
+
page: { index: 0 },
|
|
23
|
+
name: "Doc One Page A",
|
|
24
|
+
});
|
|
25
|
+
expectToolSuccess(renamedDocOnePayload);
|
|
26
|
+
const docOneCurrentPage = unwrapToolPayload(renamedDocOnePayload);
|
|
27
|
+
expect(docOneCurrentPage.name).toBe("Doc One Page A");
|
|
28
|
+
const { payload: createdDocOnePagePayload } = await callToolJson(context, "create-page", {
|
|
29
|
+
name: "Doc One Page B",
|
|
30
|
+
});
|
|
31
|
+
expectToolSuccess(createdDocOnePagePayload);
|
|
32
|
+
const docOneSecondPage = unwrapToolPayload(createdDocOnePagePayload);
|
|
33
|
+
expect(docOneSecondPage.name).toBe("Doc One Page B");
|
|
34
|
+
const { payload: singleDocumentPayload } = await callToolJson(context, "list-documents", {});
|
|
35
|
+
expectToolSuccess(singleDocumentPayload);
|
|
36
|
+
const [docOne] = unwrapToolPayload(singleDocumentPayload);
|
|
37
|
+
expect(docOne).toMatchObject({
|
|
38
|
+
page_count: 2,
|
|
39
|
+
current_page: {
|
|
40
|
+
id: docOneCurrentPage.id,
|
|
41
|
+
name: "Doc One Page A",
|
|
42
|
+
is_current: true,
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
const secondPage = await openConnectedDrawioPage({
|
|
46
|
+
browser: context.browser,
|
|
47
|
+
httpPort: context.httpPort,
|
|
48
|
+
wsPort: context.wsPort,
|
|
49
|
+
browserMessages: context.browserMessages,
|
|
50
|
+
});
|
|
51
|
+
try {
|
|
52
|
+
const { payload: twoDocumentsPayload } = await callToolJson(context, "list-documents", {});
|
|
53
|
+
expectToolSuccess(twoDocumentsPayload);
|
|
54
|
+
const documents = unwrapToolPayload(twoDocumentsPayload);
|
|
55
|
+
expect(documents).toHaveLength(2);
|
|
56
|
+
const refreshedDocOne = documents.find((document) => document.id === docOne.id);
|
|
57
|
+
const docTwo = documents.find((document) => document.id !== docOne.id);
|
|
58
|
+
expect(refreshedDocOne?.page_count).toBe(2);
|
|
59
|
+
expect(docTwo).toBeDefined();
|
|
60
|
+
const { payload: renamedDocTwoPayload } = await callToolJson(context, "rename-page", {
|
|
61
|
+
target_document: { id: docTwo.id },
|
|
62
|
+
page: { index: 0 },
|
|
63
|
+
name: "Doc Two Page Only",
|
|
64
|
+
});
|
|
65
|
+
expectToolSuccess(renamedDocTwoPayload);
|
|
66
|
+
const docTwoCurrentPage = unwrapToolPayload(renamedDocTwoPayload);
|
|
67
|
+
expect(docTwoCurrentPage.name).toBe("Doc Two Page Only");
|
|
68
|
+
const ambiguousListPages = (await context.client.callTool({
|
|
69
|
+
name: "list-pages",
|
|
70
|
+
arguments: {},
|
|
71
|
+
}));
|
|
72
|
+
expect(ambiguousListPages.isError).toBe(true);
|
|
73
|
+
expect(firstText(ambiguousListPages)).toContain("Multiple Draw.io documents are connected");
|
|
74
|
+
const { payload: docOnePagesPayload } = await callToolJson(context, "list-pages", {
|
|
75
|
+
target_document: { id: docOne.id },
|
|
76
|
+
});
|
|
77
|
+
expectToolSuccess(docOnePagesPayload);
|
|
78
|
+
const docOnePages = unwrapToolPayload(docOnePagesPayload);
|
|
79
|
+
expect(docOnePages.map((page) => page.name)).toEqual([
|
|
80
|
+
"Doc One Page A",
|
|
81
|
+
"Doc One Page B",
|
|
82
|
+
]);
|
|
83
|
+
const { payload: docTwoPagesPayload } = await callToolJson(context, "list-pages", {
|
|
84
|
+
target_document: { id: docTwo.id },
|
|
85
|
+
});
|
|
86
|
+
expectToolSuccess(docTwoPagesPayload);
|
|
87
|
+
const docTwoPages = unwrapToolPayload(docTwoPagesPayload);
|
|
88
|
+
expect(docTwoPages).toEqual([
|
|
89
|
+
{
|
|
90
|
+
index: 0,
|
|
91
|
+
id: docTwoCurrentPage.id,
|
|
92
|
+
name: "Doc Two Page Only",
|
|
93
|
+
is_current: true,
|
|
94
|
+
},
|
|
95
|
+
]);
|
|
96
|
+
const { payload: finalDocumentsPayload } = await callToolJson(context, "list-documents", {});
|
|
97
|
+
expectToolSuccess(finalDocumentsPayload);
|
|
98
|
+
const finalDocuments = unwrapToolPayload(finalDocumentsPayload);
|
|
99
|
+
const finalDocOne = finalDocuments.find((document) => document.id === docOne.id);
|
|
100
|
+
const finalDocTwo = finalDocuments.find((document) => document.id === docTwo.id);
|
|
101
|
+
expect(finalDocOne).toMatchObject({
|
|
102
|
+
page_count: 2,
|
|
103
|
+
current_page: {
|
|
104
|
+
id: docOneCurrentPage.id,
|
|
105
|
+
name: "Doc One Page A",
|
|
106
|
+
is_current: true,
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
expect(finalDocTwo).toMatchObject({
|
|
110
|
+
page_count: 1,
|
|
111
|
+
current_page: {
|
|
112
|
+
id: docTwoCurrentPage.id,
|
|
113
|
+
name: "Doc Two Page Only",
|
|
114
|
+
is_current: true,
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
finally {
|
|
119
|
+
await secondPage.close();
|
|
120
|
+
}
|
|
121
|
+
await expectNoBrowserErrors(context, "document-targeting");
|
|
122
|
+
await expectNoServerErrors(context, "document-targeting", logCountBefore);
|
|
123
|
+
}, 180000);
|
|
124
|
+
});
|
|
@@ -334,7 +334,7 @@ describe("real environment/export-diagram", () => {
|
|
|
334
334
|
const plainSvg = findText(plainContent, "<svg");
|
|
335
335
|
expect(plainSvg).toBeDefined();
|
|
336
336
|
expect(plainSvg.text).not.toContain("mxfile");
|
|
337
|
-
expect(embeddedData
|
|
337
|
+
expect(embeddedData).not.toBe(plainSvg.text);
|
|
338
338
|
const metaContent = findText(embeddedContent, "Exported svg");
|
|
339
339
|
expect(metaContent).toBeDefined();
|
|
340
340
|
expect(metaContent.text).toContain("image/svg+xml");
|
|
@@ -1,27 +1,81 @@
|
|
|
1
1
|
import { chromium, } from "@playwright/test";
|
|
2
2
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
3
3
|
import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
|
|
4
|
+
import { spawnCaddy } from "drawio-mcp-dev-proxy";
|
|
5
|
+
import { createServer } from "node:net";
|
|
4
6
|
import { ensureAssets } from "../assets/index.js";
|
|
5
|
-
import { getHttpFeatureConfig } from "../config.js";
|
|
7
|
+
import { defaultConfig, getHttpFeatureConfig, } from "../config.js";
|
|
6
8
|
import { createDrawioMcpApp } from "../index.js";
|
|
7
9
|
import { MemoryLogger } from "./logger.js";
|
|
8
10
|
import { createArtifactRunDir } from "./screenshot.js";
|
|
11
|
+
async function getFreePort() {
|
|
12
|
+
return new Promise((resolve, reject) => {
|
|
13
|
+
const srv = createServer();
|
|
14
|
+
srv.once("error", reject);
|
|
15
|
+
srv.listen(0, "127.0.0.1", () => {
|
|
16
|
+
const addr = srv.address();
|
|
17
|
+
if (addr && typeof addr === "object") {
|
|
18
|
+
const port = addr.port;
|
|
19
|
+
srv.close(() => resolve(port));
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
srv.close(() => reject(new Error("could not resolve free port")));
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
}
|
|
9
27
|
export async function createRealEnvironmentContext() {
|
|
10
28
|
const logger = new MemoryLogger();
|
|
11
29
|
const browserMessages = [];
|
|
12
30
|
const artifactRunDir = await createArtifactRunDir();
|
|
13
|
-
await ensureAssets({},
|
|
14
|
-
const app = createDrawioMcpApp({ log: logger });
|
|
31
|
+
await ensureAssets({}, logger);
|
|
32
|
+
const app = createDrawioMcpApp({ config: defaultConfig(), log: logger });
|
|
15
33
|
const wsServer = await app.startWebSocketServer(0);
|
|
16
34
|
const wsPort = Number(wsServer.address().port);
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
35
|
+
const useHttps = process.env.HARNESS_HTTPS === "1";
|
|
36
|
+
let caddy = null;
|
|
37
|
+
let proxyPort = null;
|
|
38
|
+
let webSocketUrl;
|
|
39
|
+
let upstreamHttpPort;
|
|
40
|
+
if (useHttps) {
|
|
41
|
+
proxyPort = await getFreePort();
|
|
42
|
+
const httpUpstream = await getFreePort();
|
|
43
|
+
caddy = await spawnCaddy({
|
|
44
|
+
proxyPort,
|
|
45
|
+
httpUpstream,
|
|
46
|
+
wsUpstream: wsPort,
|
|
47
|
+
});
|
|
48
|
+
webSocketUrl = `wss://localhost:${proxyPort}/ws`;
|
|
49
|
+
const config = {
|
|
50
|
+
extensionPort: wsPort,
|
|
51
|
+
httpPort: httpUpstream,
|
|
52
|
+
transports: ["http"],
|
|
53
|
+
editorEnabled: true,
|
|
54
|
+
webSocketUrl,
|
|
55
|
+
logger: "console",
|
|
56
|
+
tlsEnabled: false,
|
|
57
|
+
tlsAuto: false,
|
|
58
|
+
};
|
|
59
|
+
await app.startHttpServer(httpUpstream, config, getHttpFeatureConfig(config));
|
|
60
|
+
upstreamHttpPort = httpUpstream;
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
const config = {
|
|
64
|
+
extensionPort: wsPort,
|
|
65
|
+
httpPort: 0,
|
|
66
|
+
transports: ["http"],
|
|
67
|
+
editorEnabled: true,
|
|
68
|
+
logger: "console",
|
|
69
|
+
tlsEnabled: false,
|
|
70
|
+
tlsAuto: false,
|
|
71
|
+
};
|
|
72
|
+
const startedHttp = await app.startHttpServer(0, config, getHttpFeatureConfig(config));
|
|
73
|
+
upstreamHttpPort = startedHttp.port;
|
|
74
|
+
}
|
|
75
|
+
const httpPort = useHttps ? proxyPort : upstreamHttpPort;
|
|
76
|
+
const baseUrl = useHttps
|
|
77
|
+
? `https://localhost:${proxyPort}/`
|
|
78
|
+
: `http://localhost:${upstreamHttpPort}/`;
|
|
25
79
|
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
|
|
26
80
|
const client = new Client({
|
|
27
81
|
name: "real-environment-test",
|
|
@@ -33,27 +87,23 @@ export async function createRealEnvironmentContext() {
|
|
|
33
87
|
client.connect(clientTransport),
|
|
34
88
|
]);
|
|
35
89
|
const browser = await chromium.launch({ headless: true });
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
browserMessages.push({
|
|
39
|
-
type: message.type(),
|
|
40
|
-
text: message.text(),
|
|
41
|
-
});
|
|
90
|
+
const browserContext = await browser.newContext({
|
|
91
|
+
ignoreHTTPSErrors: useHttps,
|
|
42
92
|
});
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
}, wsPort);
|
|
51
|
-
await page.goto(`http://localhost:${httpPort}/`, {
|
|
52
|
-
waitUntil: "domcontentloaded",
|
|
93
|
+
const page = await openConnectedDrawioPage({
|
|
94
|
+
browserContext,
|
|
95
|
+
baseUrl,
|
|
96
|
+
webSocketUrl,
|
|
97
|
+
httpPort,
|
|
98
|
+
wsPort,
|
|
99
|
+
browserMessages,
|
|
53
100
|
});
|
|
54
|
-
|
|
101
|
+
const proxy = caddy
|
|
102
|
+
? { stop: () => caddy.stop(), proxyPort: caddy.proxyPort }
|
|
103
|
+
: null;
|
|
55
104
|
return {
|
|
56
105
|
browser,
|
|
106
|
+
browserContext,
|
|
57
107
|
page,
|
|
58
108
|
client,
|
|
59
109
|
app,
|
|
@@ -62,12 +112,48 @@ export async function createRealEnvironmentContext() {
|
|
|
62
112
|
artifactRunDir,
|
|
63
113
|
httpPort,
|
|
64
114
|
wsPort,
|
|
115
|
+
baseUrl,
|
|
116
|
+
proxy,
|
|
65
117
|
};
|
|
66
118
|
}
|
|
119
|
+
export async function openConnectedDrawioPage({ browser, browserContext, baseUrl, webSocketUrl, httpPort, wsPort, browserMessages, }) {
|
|
120
|
+
const page = browserContext
|
|
121
|
+
? await browserContext.newPage()
|
|
122
|
+
: await browser.newPage();
|
|
123
|
+
const editorUrl = baseUrl ?? `http://localhost:${httpPort}/`;
|
|
124
|
+
if (browserMessages) {
|
|
125
|
+
page.on("console", (message) => {
|
|
126
|
+
browserMessages.push({
|
|
127
|
+
type: message.type(),
|
|
128
|
+
text: message.text(),
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
await page.addInitScript((args) => {
|
|
133
|
+
const store = {
|
|
134
|
+
websocketPort: args.port,
|
|
135
|
+
};
|
|
136
|
+
if (args.websocketUrl) {
|
|
137
|
+
store.websocketUrl = args.websocketUrl;
|
|
138
|
+
}
|
|
139
|
+
window.__DRAWIO_MCP_TEST_HOOKS__ = true;
|
|
140
|
+
window.localStorage.setItem("drawio-mcp-plugin-config", JSON.stringify(store));
|
|
141
|
+
window.localStorage.setItem("drawio-mcp-config", JSON.stringify(store));
|
|
142
|
+
}, { port: wsPort, websocketUrl: webSocketUrl });
|
|
143
|
+
await page.goto(editorUrl, { waitUntil: "domcontentloaded" });
|
|
144
|
+
await waitForPluginReady(page);
|
|
145
|
+
return page;
|
|
146
|
+
}
|
|
67
147
|
export async function disposeRealEnvironmentContext(context) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
await context.
|
|
148
|
+
if (!context)
|
|
149
|
+
return;
|
|
150
|
+
await context.client?.close();
|
|
151
|
+
await context.browserContext?.close();
|
|
152
|
+
await context.browser?.close();
|
|
153
|
+
await context.app?.close();
|
|
154
|
+
if (context.proxy) {
|
|
155
|
+
await context.proxy.stop();
|
|
156
|
+
}
|
|
71
157
|
}
|
|
72
158
|
export async function waitForPluginReady(page) {
|
|
73
159
|
await page.waitForFunction(() => {
|
|
@@ -160,6 +246,7 @@ export async function resetDiagram(context) {
|
|
|
160
246
|
await context.client.callTool({
|
|
161
247
|
name: "import-diagram",
|
|
162
248
|
arguments: {
|
|
249
|
+
target_page: { index: 0 },
|
|
163
250
|
data: emptyDiagram,
|
|
164
251
|
format: "xml",
|
|
165
252
|
mode: "replace",
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, it } from "@jest/globals";
|
|
2
|
+
import { createRealEnvironmentContext, disposeRealEnvironmentContext, resetDiagram, } from "./harness.js";
|
|
3
|
+
import { expectNoBrowserErrors, expectNoServerErrors } from "./assertions.js";
|
|
4
|
+
import { callToolJson } from "./tools.js";
|
|
5
|
+
const FLOWCHART = "graph TD\nA[Start] --> B[Stop]";
|
|
6
|
+
describe("real environment/import-mermaid", () => {
|
|
7
|
+
let context;
|
|
8
|
+
beforeAll(async () => {
|
|
9
|
+
context = await createRealEnvironmentContext();
|
|
10
|
+
}, 180000);
|
|
11
|
+
afterAll(async () => {
|
|
12
|
+
await disposeRealEnvironmentContext(context);
|
|
13
|
+
});
|
|
14
|
+
it("native mode converts a flowchart to native cells on the target page", async () => {
|
|
15
|
+
await resetDiagram(context);
|
|
16
|
+
context.browserMessages.length = 0;
|
|
17
|
+
const logCountBefore = context.logger.entries.length;
|
|
18
|
+
const { payload } = await callToolJson(context, "import-mermaid", {
|
|
19
|
+
target_page: { index: 0 },
|
|
20
|
+
mermaid_source: FLOWCHART,
|
|
21
|
+
mode: "native",
|
|
22
|
+
insert_mode: "add",
|
|
23
|
+
});
|
|
24
|
+
expect(payload.success).toBe(true);
|
|
25
|
+
expect(payload.result?.mode).toBe("native");
|
|
26
|
+
expect(typeof payload.result?.xml).toBe("string");
|
|
27
|
+
expect(payload.result?.xml ?? "").toContain("mxGraphModel");
|
|
28
|
+
await context.page.waitForFunction(() => {
|
|
29
|
+
const ui = window.ui;
|
|
30
|
+
const model = ui?.editor?.graph?.getModel?.();
|
|
31
|
+
const cells = Object.values(model?.cells ?? {});
|
|
32
|
+
return cells.some((cell) => cell?.vertex === true);
|
|
33
|
+
});
|
|
34
|
+
await expectNoBrowserErrors(context, "import-mermaid:native");
|
|
35
|
+
await expectNoServerErrors(context, "import-mermaid:native", logCountBefore);
|
|
36
|
+
}, 180000);
|
|
37
|
+
it("embed mode produces XML carrying the mermaidData attribute", async () => {
|
|
38
|
+
await resetDiagram(context);
|
|
39
|
+
const { payload } = await callToolJson(context, "import-mermaid", {
|
|
40
|
+
target_page: { index: 0 },
|
|
41
|
+
mermaid_source: FLOWCHART,
|
|
42
|
+
mode: "embed",
|
|
43
|
+
insert_mode: "replace",
|
|
44
|
+
});
|
|
45
|
+
expect(payload.success).toBe(true);
|
|
46
|
+
expect(payload.result?.mode).toBe("embed");
|
|
47
|
+
expect(payload.result?.xml ?? "").toContain("mermaidData");
|
|
48
|
+
expect(payload.result?.xml ?? "").toContain("shape=image");
|
|
49
|
+
}, 180000);
|
|
50
|
+
it("requires target_page for replace and add modes", async () => {
|
|
51
|
+
await resetDiagram(context);
|
|
52
|
+
const result = await context.client.callTool({
|
|
53
|
+
name: "import-mermaid",
|
|
54
|
+
arguments: {
|
|
55
|
+
mermaid_source: FLOWCHART,
|
|
56
|
+
mode: "native",
|
|
57
|
+
insert_mode: "add",
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
expect(result.isError).toBe(true);
|
|
61
|
+
const content = result.content;
|
|
62
|
+
expect(String(content[0]?.text ?? "")).toContain("`target_page` is required");
|
|
63
|
+
}, 180000);
|
|
64
|
+
it("allows new-page imports without target_page", async () => {
|
|
65
|
+
await resetDiagram(context);
|
|
66
|
+
const { payload: beforePagesPayload } = await callToolJson(context, "list-pages", {});
|
|
67
|
+
expect(beforePagesPayload.success).toBe(true);
|
|
68
|
+
const { payload } = await callToolJson(context, "import-mermaid", {
|
|
69
|
+
mermaid_source: FLOWCHART,
|
|
70
|
+
mode: "native",
|
|
71
|
+
insert_mode: "new-page",
|
|
72
|
+
});
|
|
73
|
+
expect(payload.success).toBe(true);
|
|
74
|
+
expect(payload.result?.mode).toBe("native");
|
|
75
|
+
expect(payload.result?.xml ?? "").toContain("mxGraphModel");
|
|
76
|
+
const { payload: afterPagesPayload } = await callToolJson(context, "list-pages", {});
|
|
77
|
+
expect(afterPagesPayload.success).toBe(true);
|
|
78
|
+
expect(afterPagesPayload.result).toHaveLength(beforePagesPayload.result.length + 1);
|
|
79
|
+
expect(afterPagesPayload.result.at(-1)?.is_current).toBe(true);
|
|
80
|
+
}, 180000);
|
|
81
|
+
});
|