fix: rename edit_diagram type field to operation for better model compatibility (#402)

Fixes #374 - Models were confused by the `type` field name and sent
`operation` instead. This change:

- Renames DiagramOperation.type to DiagramOperation.operation across
  all files (MCP server, web app, hooks, components, system prompts)
- Adds JSON examples in tool descriptions to show correct format
- Updates all test data to use the new field name

Affected files:
- lib/utils.ts
- app/api/chat/route.ts
- hooks/use-diagram-tool-handlers.ts
- components/chat-message-display.tsx
- lib/system-prompts.ts
- packages/mcp-server/src/diagram-operations.ts
- packages/mcp-server/src/index.ts
- scripts/test-diagram-operations.mjs

MCP server version bumped to 0.1.6
This commit is contained in:
Dayuan Jiang
2025-12-25 13:19:04 +09:00
committed by GitHub
parent ed069afdea
commit 3047d19238
9 changed files with 78 additions and 62 deletions

View File

@@ -609,14 +609,22 @@ Operations:
For update/add, new_xml must be a complete mxCell element including mxGeometry. For update/add, new_xml must be a complete mxCell element including mxGeometry.
⚠️ JSON ESCAPING: Every " inside new_xml MUST be escaped as \\". Example: id=\\"5\\" value=\\"Label\\"`, ⚠️ JSON ESCAPING: Every " inside new_xml MUST be escaped as \\". Example: id=\\"5\\" value=\\"Label\\"
Example - Add a rectangle:
{"operations": [{"operation": "add", "cell_id": "rect-1", "new_xml": "<mxCell id=\\"rect-1\\" value=\\"Hello\\" style=\\"rounded=0;\\" vertex=\\"1\\" parent=\\"1\\"><mxGeometry x=\\"100\\" y=\\"100\\" width=\\"120\\" height=\\"60\\" as=\\"geometry\\"/></mxCell>"}]}
Example - Delete a cell:
{"operations": [{"operation": "delete", "cell_id": "rect-1"}]}`,
inputSchema: z.object({ inputSchema: z.object({
operations: z operations: z
.array( .array(
z.object({ z.object({
type: z operation: z
.enum(["update", "add", "delete"]) .enum(["update", "add", "delete"])
.describe("Operation type"), .describe(
"Operation to perform: add, update, or delete",
),
cell_id: z cell_id: z
.string() .string()
.describe( .describe(

View File

@@ -40,7 +40,7 @@ import ExamplePanel from "./chat-example-panel"
import { CodeBlock } from "./code-block" import { CodeBlock } from "./code-block"
interface DiagramOperation { interface DiagramOperation {
type: "update" | "add" | "delete" operation: "update" | "add" | "delete"
cell_id: string cell_id: string
new_xml?: string new_xml?: string
} }
@@ -53,12 +53,12 @@ function getCompleteOperations(
return operations.filter( return operations.filter(
(op) => (op) =>
op && op &&
typeof op.type === "string" && typeof op.operation === "string" &&
["update", "add", "delete"].includes(op.type) && ["update", "add", "delete"].includes(op.operation) &&
typeof op.cell_id === "string" && typeof op.cell_id === "string" &&
op.cell_id.length > 0 && op.cell_id.length > 0 &&
// delete doesn't need new_xml, update/add do // delete doesn't need new_xml, update/add do
(op.type === "delete" || typeof op.new_xml === "string"), (op.operation === "delete" || typeof op.new_xml === "string"),
) )
} }
@@ -79,20 +79,20 @@ function OperationsDisplay({ operations }: { operations: DiagramOperation[] }) {
<div className="space-y-3"> <div className="space-y-3">
{operations.map((op, index) => ( {operations.map((op, index) => (
<div <div
key={`${op.type}-${op.cell_id}-${index}`} key={`${op.operation}-${op.cell_id}-${index}`}
className="rounded-lg border border-border/50 overflow-hidden bg-background/50" className="rounded-lg border border-border/50 overflow-hidden bg-background/50"
> >
<div className="px-3 py-1.5 bg-muted/40 border-b border-border/30 flex items-center gap-2"> <div className="px-3 py-1.5 bg-muted/40 border-b border-border/30 flex items-center gap-2">
<span <span
className={`text-[10px] font-medium uppercase tracking-wide ${ className={`text-[10px] font-medium uppercase tracking-wide ${
op.type === "delete" op.operation === "delete"
? "text-red-600" ? "text-red-600"
: op.type === "add" : op.operation === "add"
? "text-green-600" ? "text-green-600"
: "text-blue-600" : "text-blue-600"
}`} }`}
> >
{op.type} {op.operation}
</span> </span>
<span className="text-xs text-muted-foreground"> <span className="text-xs text-muted-foreground">
cell_id: {op.cell_id} cell_id: {op.cell_id}

View File

@@ -30,7 +30,7 @@ type AddToolOutputParams = AddToolOutputSuccess | AddToolOutputError
type AddToolOutputFn = (params: AddToolOutputParams) => void type AddToolOutputFn = (params: AddToolOutputParams) => void
interface DiagramOperation { interface DiagramOperation {
type: "update" | "add" | "delete" operation: "update" | "add" | "delete"
cell_id: string cell_id: string
new_xml?: string new_xml?: string
} }

View File

@@ -99,9 +99,9 @@ When using edit_diagram tool:
- For update/add: provide cell_id and complete new_xml (full mxCell element including mxGeometry) - For update/add: provide cell_id and complete new_xml (full mxCell element including mxGeometry)
- For delete: only cell_id is needed - For delete: only cell_id is needed
- Find the cell_id from "Current diagram XML" in system context - Find the cell_id from "Current diagram XML" in system context
- Example update: {"operations": [{"type": "update", "cell_id": "3", "new_xml": "<mxCell id=\\"3\\" value=\\"New Label\\" style=\\"rounded=1;\\" vertex=\\"1\\" parent=\\"1\\">\\n <mxGeometry x=\\"100\\" y=\\"100\\" width=\\"120\\" height=\\"60\\" as=\\"geometry\\"/>\\n</mxCell>"}]} - Example update: {"operations": [{"operation": "update", "cell_id": "3", "new_xml": "<mxCell id=\\"3\\" value=\\"New Label\\" style=\\"rounded=1;\\" vertex=\\"1\\" parent=\\"1\\">\\n <mxGeometry x=\\"100\\" y=\\"100\\" width=\\"120\\" height=\\"60\\" as=\\"geometry\\"/>\\n</mxCell>"}]}
- Example delete: {"operations": [{"type": "delete", "cell_id": "5"}]} - Example delete: {"operations": [{"operation": "delete", "cell_id": "5"}]}
- Example add: {"operations": [{"type": "add", "cell_id": "new1", "new_xml": "<mxCell id=\\"new1\\" value=\\"New Box\\" style=\\"rounded=1;\\" vertex=\\"1\\" parent=\\"1\\">\\n <mxGeometry x=\\"400\\" y=\\"200\\" width=\\"120\\" height=\\"60\\" as=\\"geometry\\"/>\\n</mxCell>"}]} - Example add: {"operations": [{"operation": "add", "cell_id": "new1", "new_xml": "<mxCell id=\\"new1\\" value=\\"New Box\\" style=\\"rounded=1;\\" vertex=\\"1\\" parent=\\"1\\">\\n <mxGeometry x=\\"400\\" y=\\"200\\" width=\\"120\\" height=\\"60\\" as=\\"geometry\\"/>\\n</mxCell>"}]}
⚠️ JSON ESCAPING: Every " inside new_xml MUST be escaped as \\". Example: id=\\"5\\" value=\\"Label\\" ⚠️ JSON ESCAPING: Every " inside new_xml MUST be escaped as \\". Example: id=\\"5\\" value=\\"Label\\"
@@ -282,9 +282,9 @@ edit_diagram uses ID-based operations to modify cells directly by their id attri
\`\`\`json \`\`\`json
{ {
"operations": [ "operations": [
{"type": "update", "cell_id": "3", "new_xml": "<mxCell ...complete element...>"}, {"operation": "update", "cell_id": "3", "new_xml": "<mxCell ...complete element...>"},
{"type": "add", "cell_id": "new1", "new_xml": "<mxCell ...new element...>"}, {"operation": "add", "cell_id": "new1", "new_xml": "<mxCell ...new element...>"},
{"type": "delete", "cell_id": "5"} {"operation": "delete", "cell_id": "5"}
] ]
} }
\`\`\` \`\`\`
@@ -293,17 +293,17 @@ edit_diagram uses ID-based operations to modify cells directly by their id attri
Change label: Change label:
\`\`\`json \`\`\`json
{"operations": [{"type": "update", "cell_id": "3", "new_xml": "<mxCell id=\\"3\\" value=\\"New Label\\" style=\\"rounded=1;\\" vertex=\\"1\\" parent=\\"1\\">\\n <mxGeometry x=\\"100\\" y=\\"100\\" width=\\"120\\" height=\\"60\\" as=\\"geometry\\"/>\\n</mxCell>"}]} {"operations": [{"operation": "update", "cell_id": "3", "new_xml": "<mxCell id=\\"3\\" value=\\"New Label\\" style=\\"rounded=1;\\" vertex=\\"1\\" parent=\\"1\\">\\n <mxGeometry x=\\"100\\" y=\\"100\\" width=\\"120\\" height=\\"60\\" as=\\"geometry\\"/>\\n</mxCell>"}]}
\`\`\` \`\`\`
Add new shape: Add new shape:
\`\`\`json \`\`\`json
{"operations": [{"type": "add", "cell_id": "new1", "new_xml": "<mxCell id=\\"new1\\" value=\\"New Box\\" style=\\"rounded=1;fillColor=#dae8fc;\\" vertex=\\"1\\" parent=\\"1\\">\\n <mxGeometry x=\\"400\\" y=\\"200\\" width=\\"120\\" height=\\"60\\" as=\\"geometry\\"/>\\n</mxCell>"}]} {"operations": [{"operation": "add", "cell_id": "new1", "new_xml": "<mxCell id=\\"new1\\" value=\\"New Box\\" style=\\"rounded=1;fillColor=#dae8fc;\\" vertex=\\"1\\" parent=\\"1\\">\\n <mxGeometry x=\\"400\\" y=\\"200\\" width=\\"120\\" height=\\"60\\" as=\\"geometry\\"/>\\n</mxCell>"}]}
\`\`\` \`\`\`
Delete cell: Delete cell:
\`\`\`json \`\`\`json
{"operations": [{"type": "delete", "cell_id": "5"}]} {"operations": [{"operation": "delete", "cell_id": "5"}]}
\`\`\` \`\`\`
**Error Recovery:** **Error Recovery:**

View File

@@ -455,7 +455,7 @@ export function replaceNodes(currentXML: string, nodes: string): string {
// ============================================================================ // ============================================================================
export interface DiagramOperation { export interface DiagramOperation {
type: "update" | "add" | "delete" operation: "update" | "add" | "delete"
cell_id: string cell_id: string
new_xml?: string new_xml?: string
} }
@@ -528,7 +528,7 @@ export function applyDiagramOperations(
// Process each operation // Process each operation
for (const op of operations) { for (const op of operations) {
if (op.type === "update") { if (op.operation === "update") {
const existingCell = cellMap.get(op.cell_id) const existingCell = cellMap.get(op.cell_id)
if (!existingCell) { if (!existingCell) {
errors.push({ errors.push({
@@ -580,7 +580,7 @@ export function applyDiagramOperations(
// Update the map with the new element // Update the map with the new element
cellMap.set(op.cell_id, importedNode) cellMap.set(op.cell_id, importedNode)
} else if (op.type === "add") { } else if (op.operation === "add") {
// Check if ID already exists // Check if ID already exists
if (cellMap.has(op.cell_id)) { if (cellMap.has(op.cell_id)) {
errors.push({ errors.push({
@@ -632,7 +632,7 @@ export function applyDiagramOperations(
// Add to map // Add to map
cellMap.set(op.cell_id, importedNode) cellMap.set(op.cell_id, importedNode)
} else if (op.type === "delete") { } else if (op.operation === "delete") {
const existingCell = cellMap.get(op.cell_id) const existingCell = cellMap.get(op.cell_id)
if (!existingCell) { if (!existingCell) {
errors.push({ errors.push({

View File

@@ -1,6 +1,6 @@
{ {
"name": "@next-ai-drawio/mcp-server", "name": "@next-ai-drawio/mcp-server",
"version": "0.1.5", "version": "0.1.6",
"description": "MCP server for Next AI Draw.io - AI-powered diagram generation with real-time browser preview", "description": "MCP server for Next AI Draw.io - AI-powered diagram generation with real-time browser preview",
"type": "module", "type": "module",
"main": "dist/index.js", "main": "dist/index.js",

View File

@@ -4,7 +4,7 @@
*/ */
export interface DiagramOperation { export interface DiagramOperation {
type: "update" | "add" | "delete" operation: "update" | "add" | "delete"
cell_id: string cell_id: string
new_xml?: string new_xml?: string
} }
@@ -77,7 +77,7 @@ export function applyDiagramOperations(
// Process each operation // Process each operation
for (const op of operations) { for (const op of operations) {
if (op.type === "update") { if (op.operation === "update") {
const existingCell = cellMap.get(op.cell_id) const existingCell = cellMap.get(op.cell_id)
if (!existingCell) { if (!existingCell) {
errors.push({ errors.push({
@@ -129,7 +129,7 @@ export function applyDiagramOperations(
// Update the map with the new element // Update the map with the new element
cellMap.set(op.cell_id, importedNode) cellMap.set(op.cell_id, importedNode)
} else if (op.type === "add") { } else if (op.operation === "add") {
// Check if ID already exists // Check if ID already exists
if (cellMap.has(op.cell_id)) { if (cellMap.has(op.cell_id)) {
errors.push({ errors.push({
@@ -181,7 +181,7 @@ export function applyDiagramOperations(
// Add to map // Add to map
cellMap.set(op.cell_id, importedNode) cellMap.set(op.cell_id, importedNode)
} else if (op.type === "delete") { } else if (op.operation === "delete") {
const existingCell = cellMap.get(op.cell_id) const existingCell = cellMap.get(op.cell_id)
if (!existingCell) { if (!existingCell) {
errors.push({ errors.push({

View File

@@ -265,14 +265,22 @@ server.registerTool(
"- add: Add a new cell. Provide cell_id (new unique id) and new_xml.\n" + "- add: Add a new cell. Provide cell_id (new unique id) and new_xml.\n" +
"- update: Replace an existing cell by its id. Provide cell_id and complete new_xml.\n" + "- update: Replace an existing cell by its id. Provide cell_id and complete new_xml.\n" +
"- delete: Remove a cell by its id. Only cell_id is needed.\n\n" + "- delete: Remove a cell by its id. Only cell_id is needed.\n\n" +
"For add/update, new_xml must be a complete mxCell element including mxGeometry.", "For add/update, new_xml must be a complete mxCell element including mxGeometry.\n\n" +
"Example - Add a rectangle:\n" +
'{"operations": [{"operation": "add", "cell_id": "rect-1", "new_xml": "<mxCell id=\\"rect-1\\" value=\\"Hello\\" style=\\"rounded=0;\\" vertex=\\"1\\" parent=\\"1\\"><mxGeometry x=\\"100\\" y=\\"100\\" width=\\"120\\" height=\\"60\\" as=\\"geometry\\"/></mxCell>"}]}\n\n' +
"Example - Update a cell:\n" +
'{"operations": [{"operation": "update", "cell_id": "3", "new_xml": "<mxCell id=\\"3\\" value=\\"New Label\\" style=\\"rounded=1;\\" vertex=\\"1\\" parent=\\"1\\"><mxGeometry x=\\"100\\" y=\\"100\\" width=\\"120\\" height=\\"60\\" as=\\"geometry\\"/></mxCell>"}]}\n\n' +
"Example - Delete a cell:\n" +
'{"operations": [{"operation": "delete", "cell_id": "rect-1"}]}',
inputSchema: { inputSchema: {
operations: z operations: z
.array( .array(
z.object({ z.object({
type: z operation: z
.enum(["update", "add", "delete"]) .enum(["update", "add", "delete"])
.describe("Operation type"), .describe(
"Operation to perform: add, update, or delete",
),
cell_id: z.string().describe("The id of the mxCell"), cell_id: z.string().describe("The id of the mxCell"),
new_xml: z new_xml: z
.string() .string()
@@ -356,13 +364,13 @@ server.registerTool(
) )
if (fixed) { if (fixed) {
log.info( log.info(
`Operation ${op.type} ${op.cell_id}: XML auto-fixed: ${fixes.join(", ")}`, `Operation ${op.operation} ${op.cell_id}: XML auto-fixed: ${fixes.join(", ")}`,
) )
return { ...op, new_xml: fixed } return { ...op, new_xml: fixed }
} }
if (!valid && error) { if (!valid && error) {
log.warn( log.warn(
`Operation ${op.type} ${op.cell_id}: XML validation failed: ${error}`, `Operation ${op.operation} ${op.cell_id}: XML validation failed: ${error}`,
) )
} }
} }

View File

@@ -22,7 +22,7 @@ function applyDiagramOperations(xmlContent, operations) {
result: xmlContent, result: xmlContent,
errors: [ errors: [
{ {
type: "update", operation: "update",
cellId: "", cellId: "",
message: `XML parse error: ${parseError.textContent}`, message: `XML parse error: ${parseError.textContent}`,
}, },
@@ -36,7 +36,7 @@ function applyDiagramOperations(xmlContent, operations) {
result: xmlContent, result: xmlContent,
errors: [ errors: [
{ {
type: "update", operation: "update",
cellId: "", cellId: "",
message: "Could not find <root> element in XML", message: "Could not find <root> element in XML",
}, },
@@ -51,11 +51,11 @@ function applyDiagramOperations(xmlContent, operations) {
}) })
for (const op of operations) { for (const op of operations) {
if (op.type === "update") { if (op.operation === "update") {
const existingCell = cellMap.get(op.cell_id) const existingCell = cellMap.get(op.cell_id)
if (!existingCell) { if (!existingCell) {
errors.push({ errors.push({
type: "update", operation: "update",
cellId: op.cell_id, cellId: op.cell_id,
message: `Cell with id="${op.cell_id}" not found`, message: `Cell with id="${op.cell_id}" not found`,
}) })
@@ -63,7 +63,7 @@ function applyDiagramOperations(xmlContent, operations) {
} }
if (!op.new_xml) { if (!op.new_xml) {
errors.push({ errors.push({
type: "update", operation: "update",
cellId: op.cell_id, cellId: op.cell_id,
message: "new_xml is required for update operation", message: "new_xml is required for update operation",
}) })
@@ -76,7 +76,7 @@ function applyDiagramOperations(xmlContent, operations) {
const newCell = newDoc.querySelector("mxCell") const newCell = newDoc.querySelector("mxCell")
if (!newCell) { if (!newCell) {
errors.push({ errors.push({
type: "update", operation: "update",
cellId: op.cell_id, cellId: op.cell_id,
message: "new_xml must contain an mxCell element", message: "new_xml must contain an mxCell element",
}) })
@@ -85,7 +85,7 @@ function applyDiagramOperations(xmlContent, operations) {
const newCellId = newCell.getAttribute("id") const newCellId = newCell.getAttribute("id")
if (newCellId !== op.cell_id) { if (newCellId !== op.cell_id) {
errors.push({ errors.push({
type: "update", operation: "update",
cellId: op.cell_id, cellId: op.cell_id,
message: `ID mismatch: cell_id is "${op.cell_id}" but new_xml has id="${newCellId}"`, message: `ID mismatch: cell_id is "${op.cell_id}" but new_xml has id="${newCellId}"`,
}) })
@@ -94,10 +94,10 @@ function applyDiagramOperations(xmlContent, operations) {
const importedNode = doc.importNode(newCell, true) const importedNode = doc.importNode(newCell, true)
existingCell.parentNode?.replaceChild(importedNode, existingCell) existingCell.parentNode?.replaceChild(importedNode, existingCell)
cellMap.set(op.cell_id, importedNode) cellMap.set(op.cell_id, importedNode)
} else if (op.type === "add") { } else if (op.operation === "add") {
if (cellMap.has(op.cell_id)) { if (cellMap.has(op.cell_id)) {
errors.push({ errors.push({
type: "add", operation: "add",
cellId: op.cell_id, cellId: op.cell_id,
message: `Cell with id="${op.cell_id}" already exists`, message: `Cell with id="${op.cell_id}" already exists`,
}) })
@@ -105,7 +105,7 @@ function applyDiagramOperations(xmlContent, operations) {
} }
if (!op.new_xml) { if (!op.new_xml) {
errors.push({ errors.push({
type: "add", operation: "add",
cellId: op.cell_id, cellId: op.cell_id,
message: "new_xml is required for add operation", message: "new_xml is required for add operation",
}) })
@@ -118,7 +118,7 @@ function applyDiagramOperations(xmlContent, operations) {
const newCell = newDoc.querySelector("mxCell") const newCell = newDoc.querySelector("mxCell")
if (!newCell) { if (!newCell) {
errors.push({ errors.push({
type: "add", operation: "add",
cellId: op.cell_id, cellId: op.cell_id,
message: "new_xml must contain an mxCell element", message: "new_xml must contain an mxCell element",
}) })
@@ -127,7 +127,7 @@ function applyDiagramOperations(xmlContent, operations) {
const newCellId = newCell.getAttribute("id") const newCellId = newCell.getAttribute("id")
if (newCellId !== op.cell_id) { if (newCellId !== op.cell_id) {
errors.push({ errors.push({
type: "add", operation: "add",
cellId: op.cell_id, cellId: op.cell_id,
message: `ID mismatch: cell_id is "${op.cell_id}" but new_xml has id="${newCellId}"`, message: `ID mismatch: cell_id is "${op.cell_id}" but new_xml has id="${newCellId}"`,
}) })
@@ -136,11 +136,11 @@ function applyDiagramOperations(xmlContent, operations) {
const importedNode = doc.importNode(newCell, true) const importedNode = doc.importNode(newCell, true)
root.appendChild(importedNode) root.appendChild(importedNode)
cellMap.set(op.cell_id, importedNode) cellMap.set(op.cell_id, importedNode)
} else if (op.type === "delete") { } else if (op.operation === "delete") {
const existingCell = cellMap.get(op.cell_id) const existingCell = cellMap.get(op.cell_id)
if (!existingCell) { if (!existingCell) {
errors.push({ errors.push({
type: "delete", operation: "delete",
cellId: op.cell_id, cellId: op.cell_id,
message: `Cell with id="${op.cell_id}" not found`, message: `Cell with id="${op.cell_id}" not found`,
}) })
@@ -201,7 +201,7 @@ function assert(condition, message) {
test("Update operation changes cell value", () => { test("Update operation changes cell value", () => {
const { result, errors } = applyDiagramOperations(sampleXml, [ const { result, errors } = applyDiagramOperations(sampleXml, [
{ {
type: "update", operation: "update",
cell_id: "2", cell_id: "2",
new_xml: new_xml:
'<mxCell id="2" value="Updated Box A" style="rounded=1;" vertex="1" parent="1"><mxGeometry x="100" y="100" width="120" height="60" as="geometry"/></mxCell>', '<mxCell id="2" value="Updated Box A" style="rounded=1;" vertex="1" parent="1"><mxGeometry x="100" y="100" width="120" height="60" as="geometry"/></mxCell>',
@@ -224,7 +224,7 @@ test("Update operation changes cell value", () => {
test("Update operation fails for non-existent cell", () => { test("Update operation fails for non-existent cell", () => {
const { errors } = applyDiagramOperations(sampleXml, [ const { errors } = applyDiagramOperations(sampleXml, [
{ {
type: "update", operation: "update",
cell_id: "999", cell_id: "999",
new_xml: '<mxCell id="999" value="Test"/>', new_xml: '<mxCell id="999" value="Test"/>',
}, },
@@ -239,7 +239,7 @@ test("Update operation fails for non-existent cell", () => {
test("Update operation fails on ID mismatch", () => { test("Update operation fails on ID mismatch", () => {
const { errors } = applyDiagramOperations(sampleXml, [ const { errors } = applyDiagramOperations(sampleXml, [
{ {
type: "update", operation: "update",
cell_id: "2", cell_id: "2",
new_xml: '<mxCell id="WRONG" value="Test"/>', new_xml: '<mxCell id="WRONG" value="Test"/>',
}, },
@@ -254,7 +254,7 @@ test("Update operation fails on ID mismatch", () => {
test("Add operation creates new cell", () => { test("Add operation creates new cell", () => {
const { result, errors } = applyDiagramOperations(sampleXml, [ const { result, errors } = applyDiagramOperations(sampleXml, [
{ {
type: "add", operation: "add",
cell_id: "new1", cell_id: "new1",
new_xml: new_xml:
'<mxCell id="new1" value="New Box" style="rounded=1;" vertex="1" parent="1"><mxGeometry x="500" y="100" width="120" height="60" as="geometry"/></mxCell>', '<mxCell id="new1" value="New Box" style="rounded=1;" vertex="1" parent="1"><mxGeometry x="500" y="100" width="120" height="60" as="geometry"/></mxCell>',
@@ -274,7 +274,7 @@ test("Add operation creates new cell", () => {
test("Add operation fails for duplicate ID", () => { test("Add operation fails for duplicate ID", () => {
const { errors } = applyDiagramOperations(sampleXml, [ const { errors } = applyDiagramOperations(sampleXml, [
{ {
type: "add", operation: "add",
cell_id: "2", cell_id: "2",
new_xml: '<mxCell id="2" value="Duplicate"/>', new_xml: '<mxCell id="2" value="Duplicate"/>',
}, },
@@ -289,7 +289,7 @@ test("Add operation fails for duplicate ID", () => {
test("Add operation fails on ID mismatch", () => { test("Add operation fails on ID mismatch", () => {
const { errors } = applyDiagramOperations(sampleXml, [ const { errors } = applyDiagramOperations(sampleXml, [
{ {
type: "add", operation: "add",
cell_id: "new1", cell_id: "new1",
new_xml: '<mxCell id="WRONG" value="Test"/>', new_xml: '<mxCell id="WRONG" value="Test"/>',
}, },
@@ -303,7 +303,7 @@ test("Add operation fails on ID mismatch", () => {
test("Delete operation removes cell", () => { test("Delete operation removes cell", () => {
const { result, errors } = applyDiagramOperations(sampleXml, [ const { result, errors } = applyDiagramOperations(sampleXml, [
{ type: "delete", cell_id: "3" }, { operation: "delete", cell_id: "3" },
]) ])
assert( assert(
errors.length === 0, errors.length === 0,
@@ -315,7 +315,7 @@ test("Delete operation removes cell", () => {
test("Delete operation fails for non-existent cell", () => { test("Delete operation fails for non-existent cell", () => {
const { errors } = applyDiagramOperations(sampleXml, [ const { errors } = applyDiagramOperations(sampleXml, [
{ type: "delete", cell_id: "999" }, { operation: "delete", cell_id: "999" },
]) ])
assert(errors.length === 1, "Should have one error") assert(errors.length === 1, "Should have one error")
assert( assert(
@@ -327,18 +327,18 @@ test("Delete operation fails for non-existent cell", () => {
test("Multiple operations in sequence", () => { test("Multiple operations in sequence", () => {
const { result, errors } = applyDiagramOperations(sampleXml, [ const { result, errors } = applyDiagramOperations(sampleXml, [
{ {
type: "update", operation: "update",
cell_id: "2", cell_id: "2",
new_xml: new_xml:
'<mxCell id="2" value="Updated" style="rounded=1;" vertex="1" parent="1"><mxGeometry x="100" y="100" width="120" height="60" as="geometry"/></mxCell>', '<mxCell id="2" value="Updated" style="rounded=1;" vertex="1" parent="1"><mxGeometry x="100" y="100" width="120" height="60" as="geometry"/></mxCell>',
}, },
{ {
type: "add", operation: "add",
cell_id: "new1", cell_id: "new1",
new_xml: new_xml:
'<mxCell id="new1" value="Added" style="rounded=1;" vertex="1" parent="1"><mxGeometry x="500" y="100" width="120" height="60" as="geometry"/></mxCell>', '<mxCell id="new1" value="Added" style="rounded=1;" vertex="1" parent="1"><mxGeometry x="500" y="100" width="120" height="60" as="geometry"/></mxCell>',
}, },
{ type: "delete", cell_id: "3" }, { operation: "delete", cell_id: "3" },
]) ])
assert( assert(
errors.length === 0, errors.length === 0,
@@ -354,14 +354,14 @@ test("Multiple operations in sequence", () => {
test("Invalid XML returns parse error", () => { test("Invalid XML returns parse error", () => {
const { errors } = applyDiagramOperations("<not valid xml", [ const { errors } = applyDiagramOperations("<not valid xml", [
{ type: "delete", cell_id: "1" }, { operation: "delete", cell_id: "1" },
]) ])
assert(errors.length === 1, "Should have one error") assert(errors.length === 1, "Should have one error")
}) })
test("Missing root element returns error", () => { test("Missing root element returns error", () => {
const { errors } = applyDiagramOperations("<mxfile></mxfile>", [ const { errors } = applyDiagramOperations("<mxfile></mxfile>", [
{ type: "delete", cell_id: "1" }, { operation: "delete", cell_id: "1" },
]) ])
assert(errors.length === 1, "Should have one error") assert(errors.length === 1, "Should have one error")
assert( assert(