@@ -1405,4 +1405,112 @@ A[Start]-->B[End]`);
14051405 expect ( arrows [ 0 ] . strokeStyle ) . toBe ( "dashed" ) ;
14061406 } ) ;
14071407 } ) ;
1408+
1409+ describe ( "edge cases" , ( ) => {
1410+ it ( "self-loop edge creates valid arrow" , async ( ) => {
1411+ const d = new Diagram ( ) ;
1412+ const a = d . addBox ( "A" , { row : 0 , col : 0 } ) ;
1413+ d . connect ( a , a ) ;
1414+
1415+ const result = await d . render ( { format : "excalidraw" } ) ;
1416+ const arrow = result . json . elements . find ( e => e . type === "arrow" ) ;
1417+ expect ( arrow ) . toBeDefined ( ) ;
1418+ // Self-loop arrow exists — bindings may be null depending on layout engine
1419+ expect ( arrow ! . points ! . length ) . toBeGreaterThanOrEqual ( 2 ) ;
1420+ } ) ;
1421+
1422+ it ( "disconnected nodes included in stats" , async ( ) => {
1423+ const d = new Diagram ( ) ;
1424+ const a = d . addBox ( "A" , { row : 0 , col : 0 } ) ;
1425+ const b = d . addBox ( "B" , { row : 1 , col : 0 } ) ;
1426+ const c = d . addBox ( "C" , { row : 2 , col : 0 } ) ;
1427+ d . connect ( a , b ) ;
1428+
1429+ const result = await d . render ( { format : "excalidraw" } ) ;
1430+ expect ( result . stats ! . nodes ) . toBe ( 3 ) ;
1431+ expect ( result . stats ! . edges ) . toBe ( 1 ) ;
1432+ } ) ;
1433+
1434+ it ( "RL direction renders without error" , async ( ) => {
1435+ const d = new Diagram ( { direction : "RL" } ) ;
1436+ const a = d . addBox ( "A" , { row : 0 , col : 0 } ) ;
1437+ const b = d . addBox ( "B" , { row : 0 , col : 1 } ) ;
1438+ d . connect ( a , b ) ;
1439+
1440+ const result = await d . render ( { format : "excalidraw" } ) ;
1441+ const elA = result . json . elements . find ( e => e . id === a ) ;
1442+ const elB = result . json . elements . find ( e => e . id === b ) ;
1443+ expect ( elA ) . toBeDefined ( ) ;
1444+ expect ( elB ) . toBeDefined ( ) ;
1445+ // RL reversal depends on Graphviz WASM; TS fallback places L→R
1446+ if ( isWasmLoaded ( ) ) {
1447+ expect ( elB ! . x ) . toBeLessThan ( elA ! . x ) ;
1448+ }
1449+ } ) ;
1450+
1451+ it ( "BT direction renders without error" , async ( ) => {
1452+ const d = new Diagram ( { direction : "BT" } ) ;
1453+ const a = d . addBox ( "A" , { row : 0 , col : 0 } ) ;
1454+ const b = d . addBox ( "B" , { row : 1 , col : 0 } ) ;
1455+ d . connect ( a , b ) ;
1456+
1457+ const result = await d . render ( { format : "excalidraw" } ) ;
1458+ const elA = result . json . elements . find ( e => e . id === a ) ;
1459+ const elB = result . json . elements . find ( e => e . id === b ) ;
1460+ expect ( elA ) . toBeDefined ( ) ;
1461+ expect ( elB ) . toBeDefined ( ) ;
1462+ // BT reversal depends on Graphviz WASM; TS fallback places T→B
1463+ if ( isWasmLoaded ( ) ) {
1464+ expect ( elB ! . y ) . toBeLessThan ( elA ! . y ) ;
1465+ }
1466+ } ) ;
1467+
1468+ it ( "empty label produces element" , async ( ) => {
1469+ const d = new Diagram ( ) ;
1470+ const id = d . addBox ( "" ) ;
1471+
1472+ const result = await d . render ( { format : "excalidraw" } ) ;
1473+ const shape = result . json . elements . find ( e => e . id === id ) ;
1474+ expect ( shape ) . toBeDefined ( ) ;
1475+ } ) ;
1476+
1477+ it ( "200-char label produces wider element" , async ( ) => {
1478+ const d = new Diagram ( ) ;
1479+ const id = d . addBox ( "A" . repeat ( 200 ) ) ;
1480+
1481+ const result = await d . render ( { format : "excalidraw" } ) ;
1482+ const shape = result . json . elements . find ( e => e . id === id ) ;
1483+ expect ( shape ) . toBeDefined ( ) ;
1484+ expect ( shape ! . width ) . toBeGreaterThan ( 180 ) ;
1485+ } ) ;
1486+
1487+ it ( "special chars in label don't break JSON" , async ( ) => {
1488+ const label = '<script>alert("xss")</script>&' ;
1489+ const d = new Diagram ( ) ;
1490+ const id = d . addBox ( label ) ;
1491+
1492+ const result = await d . render ( { format : "excalidraw" } ) ;
1493+ expect ( ( ) => JSON . stringify ( result . json ) ) . not . toThrow ( ) ;
1494+ const textEl = result . json . elements . find ( e => e . id === `${ id } -text` ) ;
1495+ expect ( textEl ) . toBeDefined ( ) ;
1496+ expect ( textEl ! . text ) . toBe ( label ) ;
1497+ } ) ;
1498+
1499+ it ( "all arrowhead types wire through" , async ( ) => {
1500+ const arrowheadTypes = [ null , "arrow" , "bar" , "dot" , "triangle" , "diamond" , "diamond_outline" ] as const ;
1501+
1502+ for ( const arrowhead of arrowheadTypes ) {
1503+ const d = new Diagram ( ) ;
1504+ const a = d . addBox ( "A" , { row : 0 , col : 0 } ) ;
1505+ const b = d . addBox ( "B" , { row : 1 , col : 0 } ) ;
1506+ d . connect ( a , b , "label" , { startArrowhead : arrowhead , endArrowhead : arrowhead } ) ;
1507+
1508+ const result = await d . render ( { format : "excalidraw" } ) ;
1509+ const arrow = result . json . elements . find ( e => e . type === "arrow" ) ;
1510+ expect ( arrow ) . toBeDefined ( ) ;
1511+ expect ( arrow ! . startArrowhead ) . toBe ( arrowhead ) ;
1512+ expect ( arrow ! . endArrowhead ) . toBe ( arrowhead ) ;
1513+ }
1514+ } ) ;
1515+ } ) ;
14081516} ) ;
0 commit comments