@@ -345,6 +345,78 @@ function makeAttributeConnectorCrossingState(): AgentState {
345345 } ;
346346}
347347
348+ function makeEntityEditFollowState ( ) : AgentState {
349+ return {
350+ version : 1 ,
351+ input : "manual" ,
352+ format : "sql" ,
353+ settings : {
354+ colored : true ,
355+ comment : false ,
356+ hideAttrs : false ,
357+ fontScale : 1 ,
358+ attrMode : "moderate" ,
359+ autoAvoid : true ,
360+ } ,
361+ nodes : [
362+ { id : "entity-a" , type : "entity" , label : "a" , nodeType : "entity" , x : 100 , y : 100 } ,
363+ { id : "entity-b" , type : "entity" , label : "b" , nodeType : "entity" , x : 360 , y : 140 } ,
364+ {
365+ id : "attr-a-name" ,
366+ type : "attribute" ,
367+ label : "name" ,
368+ nodeType : "attribute" ,
369+ parentEntity : "entity-a" ,
370+ x : 140 ,
371+ y : 172 ,
372+ } ,
373+ {
374+ id : "attr-b-name" ,
375+ type : "attribute" ,
376+ label : "name" ,
377+ nodeType : "attribute" ,
378+ parentEntity : "entity-b" ,
379+ x : 360 ,
380+ y : 20 ,
381+ } ,
382+ {
383+ id : "rel-a-b" ,
384+ type : "relationship" ,
385+ label : "a_b" ,
386+ nodeType : "relationship" ,
387+ x : 230 ,
388+ y : 120 ,
389+ } ,
390+ ] ,
391+ edges : [
392+ {
393+ id : "edge-a-name" ,
394+ source : "entity-a" ,
395+ target : "attr-a-name" ,
396+ edgeType : "entity-attribute" ,
397+ } ,
398+ {
399+ id : "edge-b-name" ,
400+ source : "entity-b" ,
401+ target : "attr-b-name" ,
402+ edgeType : "entity-attribute" ,
403+ } ,
404+ {
405+ id : "edge-a-rel" ,
406+ source : "entity-a" ,
407+ target : "rel-a-b" ,
408+ edgeType : "entity-relationship" ,
409+ } ,
410+ {
411+ id : "edge-rel-b" ,
412+ source : "rel-a-b" ,
413+ target : "entity-b" ,
414+ edgeType : "relationship-entity" ,
415+ } ,
416+ ] ,
417+ } ;
418+ }
419+
348420function makeAttributeDetailState ( ) : AgentState {
349421 return {
350422 version : 1 ,
@@ -1459,6 +1531,67 @@ describe("sql2er agent CLI entity edits", () => {
14591531 }
14601532 } ) ;
14611533
1534+ it ( "keeps entity attributes attached and relationship diamonds synced without settling default move and nudge edits" , ( ) => {
1535+ const cases = [
1536+ {
1537+ command : [ "move" , "entity-a" , "180" , "210" ] ,
1538+ expectedEntity : { x : 180 , y : 210 } ,
1539+ } ,
1540+ {
1541+ command : [ "nudge" , "entity-a" , "80" , "110" ] ,
1542+ expectedEntity : { x : 180 , y : 210 } ,
1543+ } ,
1544+ ] ;
1545+
1546+ for ( const testCase of cases ) {
1547+ const dir = mkdtempSync ( resolve ( tmpdir ( ) , "sql2er-agent-" ) ) ;
1548+ try {
1549+ const state = resolve ( dir , "er.json" ) ;
1550+ writeFileSync ( state , JSON . stringify ( makeEntityEditFollowState ( ) ) ) ;
1551+
1552+ const edited = runAgent ( [ ...testCase . command , "--state" , state ] ) ;
1553+
1554+ expect ( edited . status ) . toBe ( 0 ) ;
1555+ const next = readState ( state ) ;
1556+ expect ( nodePosition ( next , "entity-a" ) ) . toEqual ( testCase . expectedEntity ) ;
1557+ expect ( nodePosition ( next , "entity-b" ) ) . toEqual ( { x : 360 , y : 140 } ) ;
1558+ expect ( nodePosition ( next , "attr-a-name" ) ) . toEqual ( { x : 220 , y : 282 } ) ;
1559+ expect ( nodePosition ( next , "attr-b-name" ) ) . toEqual ( { x : 360 , y : 20 } ) ;
1560+ expectPointOnSegment (
1561+ nodePosition ( next , "rel-a-b" ) ,
1562+ nodePosition ( next , "entity-a" ) ,
1563+ nodePosition ( next , "entity-b" ) ,
1564+ ) ;
1565+ } finally {
1566+ rmSync ( dir , { recursive : true , force : true } ) ;
1567+ }
1568+ }
1569+ } ) ;
1570+
1571+ it ( "keeps entity attributes attached and relationship diamonds synced without settling default swaps" , ( ) => {
1572+ const dir = mkdtempSync ( resolve ( tmpdir ( ) , "sql2er-agent-" ) ) ;
1573+ try {
1574+ const state = resolve ( dir , "er.json" ) ;
1575+ writeFileSync ( state , JSON . stringify ( makeEntityEditFollowState ( ) ) ) ;
1576+
1577+ const swapped = runAgent ( [ "swap" , "entity-a" , "entity-b" , "--state" , state ] ) ;
1578+
1579+ expect ( swapped . status ) . toBe ( 0 ) ;
1580+ const next = readState ( state ) ;
1581+ expect ( nodePosition ( next , "entity-a" ) ) . toEqual ( { x : 360 , y : 140 } ) ;
1582+ expect ( nodePosition ( next , "entity-b" ) ) . toEqual ( { x : 100 , y : 100 } ) ;
1583+ expect ( nodePosition ( next , "attr-a-name" ) ) . toEqual ( { x : 400 , y : 212 } ) ;
1584+ expect ( nodePosition ( next , "attr-b-name" ) ) . toEqual ( { x : 100 , y : - 20 } ) ;
1585+ expectPointOnSegment (
1586+ nodePosition ( next , "rel-a-b" ) ,
1587+ nodePosition ( next , "entity-a" ) ,
1588+ nodePosition ( next , "entity-b" ) ,
1589+ ) ;
1590+ } finally {
1591+ rmSync ( dir , { recursive : true , force : true } ) ;
1592+ }
1593+ } ) ;
1594+
14621595 it ( "warns and leaves positions unchanged when swapping an attribute id" , ( ) => {
14631596 const dir = mkdtempSync ( resolve ( tmpdir ( ) , "sql2er-agent-" ) ) ;
14641597 try {
0 commit comments