mirror of
https://github.com/DayuanJiang/next-ai-draw-io.git
synced 2026-10-12 04:29:51 +08:00
fix(mcp-server): a hidden tab's delayed selection answer checks the canvas again first
This commit is contained in:
@@ -769,7 +769,21 @@ export function createMcpSync(options: SyncOptions): McpSync {
|
|||||||
) {
|
) {
|
||||||
const id = s.selectionId
|
const id = s.selectionId
|
||||||
answeredSelectionId = id
|
answeredSelectionId = id
|
||||||
const answer = () =>
|
const answer = () => {
|
||||||
|
// A hidden tab answers later: the canvas may have moved
|
||||||
|
// on since the poll (a projection, a full load, draw.io
|
||||||
|
// starting over). The same conditions as above; when
|
||||||
|
// they no longer hold, the next poll decides again
|
||||||
|
if (
|
||||||
|
!isReady ||
|
||||||
|
projectionExportActive ||
|
||||||
|
canvas.loadPending()
|
||||||
|
) {
|
||||||
|
if (answeredSelectionId === id) {
|
||||||
|
answeredSelectionId = null
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
postJson("/state", {
|
postJson("/state", {
|
||||||
sessionId,
|
sessionId,
|
||||||
selectionId: id,
|
selectionId: id,
|
||||||
@@ -784,6 +798,7 @@ export function createMcpSync(options: SyncOptions): McpSync {
|
|||||||
answeredSelectionId = null
|
answeredSelectionId = null
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
}
|
||||||
// A hidden tab lets a tab in front (the same session open
|
// A hidden tab lets a tab in front (the same session open
|
||||||
// twice) answer first; the server takes the first answer
|
// twice) answer first; the server takes the first answer
|
||||||
// and ignores the later one
|
// and ignores the later one
|
||||||
|
|||||||
@@ -1078,6 +1078,84 @@ describe("MCP sync selection requests (get_selection)", () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("holds a hidden tab's answer when a projection started meanwhile, and answers once it ended", async () => {
|
||||||
|
const t = await inStep({ isHidden: () => true })
|
||||||
|
t.canvas.selection = picked
|
||||||
|
let poll = t.sync.poll()
|
||||||
|
t.next("GET").answer(selectionState())
|
||||||
|
await poll
|
||||||
|
// The next poll, 2 s later, brings an export of another page: the
|
||||||
|
// projection replaces the user's page before the answer is due
|
||||||
|
await vi.advanceTimersByTimeAsync(2000)
|
||||||
|
poll = t.sync.poll()
|
||||||
|
t.next("GET").answer(
|
||||||
|
selectionState({
|
||||||
|
exportFormat: "svg",
|
||||||
|
exportId: "e1",
|
||||||
|
exportXml: "<mxfile>page 2 only</mxfile>",
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
await poll
|
||||||
|
expect(t.sync.read().projectionActive).toBe(true)
|
||||||
|
t.canvas.selection = { pageId: "p2", pageName: "Page-2", cells: [] }
|
||||||
|
await vi.advanceTimersByTimeAsync(500)
|
||||||
|
expect(t.posts()).toHaveLength(0)
|
||||||
|
// The export answers and the document comes back
|
||||||
|
await vi.advanceTimersByTimeAsync(100)
|
||||||
|
t.nextExport().answer({ data: "<svg>page 2</svg>" })
|
||||||
|
await t.settle()
|
||||||
|
t.next("POST").answer({ status: 200, body: { success: true } })
|
||||||
|
await t.settle()
|
||||||
|
await t.settle()
|
||||||
|
t.next("GET").answer(selectionState())
|
||||||
|
await t.settle()
|
||||||
|
await t.settle()
|
||||||
|
t.sync.onDrawioLoad()
|
||||||
|
t.canvas.selection = picked
|
||||||
|
// The request is still pending: this poll schedules it again
|
||||||
|
poll = t.sync.poll()
|
||||||
|
t.next("GET").answer(selectionState())
|
||||||
|
await poll
|
||||||
|
expect(t.posts()).toHaveLength(0)
|
||||||
|
await vi.advanceTimersByTimeAsync(2500)
|
||||||
|
expect(t.next("POST").body).toMatchObject({
|
||||||
|
selectionId: "sel-1",
|
||||||
|
selection: picked,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("holds a hidden tab's answer while a full load has yet to reach the editor", async () => {
|
||||||
|
const t = await inStep({ isHidden: () => true })
|
||||||
|
t.canvas.selection = picked
|
||||||
|
let poll = t.sync.poll()
|
||||||
|
t.next("GET").answer(selectionState())
|
||||||
|
await poll
|
||||||
|
// A new version arrives before the answer is due: loaded in full,
|
||||||
|
// the editor still shows the old document
|
||||||
|
await vi.advanceTimersByTimeAsync(2000)
|
||||||
|
t.canvas.loadPending = true
|
||||||
|
poll = t.sync.poll()
|
||||||
|
t.next("GET").answer(
|
||||||
|
state("S1", 3, "<mxfile>B</mxfile>", { selectionId: "sel-1" }),
|
||||||
|
)
|
||||||
|
await poll
|
||||||
|
expect(t.loads.at(-1)?.xml).toBe("<mxfile>B</mxfile>")
|
||||||
|
await vi.advanceTimersByTimeAsync(500)
|
||||||
|
expect(t.posts()).toHaveLength(0)
|
||||||
|
// draw.io reports the load: the next poll answers
|
||||||
|
t.canvas.loadPending = false
|
||||||
|
poll = t.sync.poll()
|
||||||
|
t.next("GET").answer(
|
||||||
|
state("S1", 3, "<mxfile>B</mxfile>", { selectionId: "sel-1" }),
|
||||||
|
)
|
||||||
|
await poll
|
||||||
|
await vi.advanceTimersByTimeAsync(2500)
|
||||||
|
expect(t.next("POST").body).toMatchObject({
|
||||||
|
selectionId: "sel-1",
|
||||||
|
selection: picked,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
it("never answers while a projection is on screen", async () => {
|
it("never answers while a projection is on screen", async () => {
|
||||||
const t = await inStep()
|
const t = await inStep()
|
||||||
t.canvas.selection = picked
|
t.canvas.selection = picked
|
||||||
|
|||||||
Reference in New Issue
Block a user