fix(mcp-server): review fixes for the shell page

- a download button in the header opens the web app's export dialog
  (.drawio, .png, .svg, .drawio.svg), which the classic page had and the
  shell lacked when it became the default
- the shell asks draw.io for the custom library menu (libraries=1), as
  the classic page did; the web app keeps libraries=0
- the newest card no longer shows "Rendering preview" for good: the sync
  takes the thumbnail of a diagram the server recovered from its file
  (saved without pictures) while the canvas kept it, and of a write whose
  picture was skipped because an edit came first, once the canvas shows
  the write again
- e2e: the get_selection test covers a shape in a container the user
  entered; a download test saves a .drawio file
This commit is contained in:
dayuan.jiang
2026-10-11 20:56:07 +09:00
parent 467fd25fd1
commit f257d8ee99
7 changed files with 164 additions and 8 deletions
+14
View File
@@ -77,6 +77,20 @@ describe("shell draw.io URL", () => {
expect(fixed.searchParams.get("ui")).toBe("kennedy")
expect(fixed.searchParams.get("themes")).toBeNull()
})
it("offers draw.io's custom libraries when asked, as the classic page did", () => {
const withLibraries = new URL(
getDrawioSrc(
{ editorUrl: "/drawio/index.html", libraries: true },
false,
),
)
expect(withLibraries.searchParams.get("libraries")).toBe("1")
const without = new URL(
getDrawioSrc({ editorUrl: "/drawio/index.html" }, false),
)
expect(without.searchParams.get("libraries")).toBe("0")
})
})
describe("shell language", () => {
+41
View File
@@ -256,6 +256,23 @@ describe("MCP sync after the server recreated its session", () => {
})
})
it("takes the picture of the recovered diagram, whose History entry came back without one", async () => {
const t = await inStep()
// The process restarted and read the file back: the text the tab
// last saw, so the canvas stays; History is saved without pictures
const poll = t.sync.poll()
t.next("GET").answer(state("S2", 1, "<mxfile>A</mxfile>"))
await poll
expect(t.loads).toHaveLength(1)
expect(t.posts()).toHaveLength(0)
t.nextExport().answer({ data: "<svg>A</svg>" })
await t.settle()
expect(t.next("POST", "/api/history-svg").body).toMatchObject({
stateId: "S2",
version: 1,
})
})
it("shows the server's diagram and keeps the tab's in History", async () => {
const t = await inStep()
const poll = t.sync.poll()
@@ -528,6 +545,30 @@ describe("MCP sync thumbnails", () => {
expect(atob(push.body.svg.split(",")[1])).toBe("<svg>edit</svg>")
})
it("takes the image once the user comes back to the write (an edit came before it)", async () => {
const { t, request } = await loadedB()
t.sync.onAutoSave("<mxfile>B edited</mxfile>")
request.answer({ data: "<svg>thumbnail</svg>" })
await t.settle()
expect(thumbnailPosts(t)).toHaveLength(0)
t.nextExport().answer({ data: "<svg>edit</svg>" })
await t.settle()
t.next("POST").answer({
status: 200,
body: { success: true, version: 4 },
})
await t.settle()
// Ctrl+Z: the canvas shows the write again
t.sync.onAutoSave("<mxfile>B</mxfile>")
t.nextExport().answer({ data: "<svg>B</svg>" })
await t.settle()
expect(thumbnailPosts(t).map((c) => c.body.version)).toEqual([3])
// The undo is pushed as any edit
t.nextExport().answer({ data: "<svg>B</svg>" })
await t.settle()
expect(t.next("POST", "/api/state").body.xml).toBe("<mxfile>B</mxfile>")
})
it("takes the image of a write loaded before draw.io was ready, once it is", async () => {
const t = open()
const poll = t.sync.poll()