From 83f2663f68ef185e3496a772c7a56882aa476d6b Mon Sep 17 00:00:00 2001 From: Knut Sveidqvist Date: Tue, 18 Jun 2024 11:15:59 +0200 Subject: [PATCH] #5237 making styles take preceedence and adding test --- .cspell/code-terms.txt | 1 + .../rendering/stateDiagram-v2.spec.js | 23 +++ cypress/platform/knsv2.html | 142 +++++++++++++++--- .../diagrams/state/parser/state-style.spec.js | 59 ++++---- .../mermaid/src/diagrams/state/stateDb.js | 4 +- .../mermaid/src/rendering-util/createText.ts | 11 +- .../shapes/handdrawnStyles.ts | 27 ++-- 7 files changed, 203 insertions(+), 64 deletions(-) diff --git a/.cspell/code-terms.txt b/.cspell/code-terms.txt index f2b37dcaf..bdde26d14 100644 --- a/.cspell/code-terms.txt +++ b/.cspell/code-terms.txt @@ -110,6 +110,7 @@ strikethrough stringifying struct STYLECLASS +STYLEDEF STYLEOPTS subcomponent subcomponents diff --git a/cypress/integration/rendering/stateDiagram-v2.spec.js b/cypress/integration/rendering/stateDiagram-v2.spec.js index 9a1a27abe..7bc467c83 100644 --- a/cypress/integration/rendering/stateDiagram-v2.spec.js +++ b/cypress/integration/rendering/stateDiagram-v2.spec.js @@ -558,6 +558,29 @@ stateDiagram-v2 { logLevel: 0, fontFamily: 'courier' } ); }); + it(' can have styles applied ', () => { + imgSnapshotTest( + ` +stateDiagram-v2 +AState +style AState fill:#636,border:1px solid red,color:white; + `, + { logLevel: 0, fontFamily: 'courier' } + ); + }); + it(' should let styles take preceedence over classes', () => { + imgSnapshotTest( + ` +stateDiagram-v2 +AState: Should NOT be white +BState +classDef exampleStyleClass fill:#fff,color: blue; +class AState,BState exampleStyleClass +style AState fill:#636,border:1px solid red,color:white; + `, + { logLevel: 0, fontFamily: 'courier' } + ); + }); }); it('1433: should render a simple state diagram with a title', () => { imgSnapshotTest( diff --git a/cypress/platform/knsv2.html b/cypress/platform/knsv2.html index 46c483ee6..268495be3 100644 --- a/cypress/platform/knsv2.html +++ b/cypress/platform/knsv2.html @@ -75,12 +75,127 @@ -
-flowchart LR
+    

Case 1

+
+
+stateDiagram-v2
+AState: Should NOT be white
+BState
+classDef exampleStyleClass fill:#fff,color: blue;
+class AState,BState exampleStyleClass
+style AState fill:#636,border:1px solid red,color:white;
+      
+
+        %%{init: {"look": "classic"} }%%
+stateDiagram-v2
+AState: Should NOT be white
+BState
+classDef exampleStyleClass fill:#fff,color: blue;
+class AState,BState exampleStyleClass
+style AState fill:#636,border:1px solid red,color:white;
+      
+
+stateDiagram-v2
 
-    Apa --AA--> C
-      
+classDef exampleStyleClass background:#bbb,border:1px solid red; +a --> b +class a exampleStyleClass +%% a:::exampleStyleClass +
+
+stateDiagram
+   direction TB
+
+   accTitle: This is the accessible title
+   accDescr: This is an accessible description
+
+   classDef notMoving fill:white
+   classDef movement font-style:italic;
+   classDef badBadEvent fill:#f00,color:white,font-weight:bold,stroke-width:2px,stroke:yellow
+
+   [*] --> Still:::notMoving
+   Still --> [*]
+   Still --> Moving:::movement
+   Moving --> Still
+   Moving --> Crash:::movement
+   Crash:::badBadEvent --> [*]
+      
+
+stateDiagram-v2
+    MyState
+    note left of MyState : I am a leftie
+    note right of MyState : I am a rightie
+
+      
+
+stateDiagram
+%% direction LR
+
+        state C0 {
+          A0 --> B0
+        }
+
+      C0 --> Apa0
+
+      
+
+stateDiagram
+direction LR
+
+
+        state C1 {
+          A1 --> B1
+        }
+
+      C1 --> Apa1
+
+      
+ +

Case 2

+
+
+stateDiagram
+direction LR
+      state Gorilla0 {
+        state Apa0 {
+          A0 --> B0
+        }
+
+      }
+      Apa --> Gorilla0:Label
+      A0 --> C0
+      %% C1: "`This is C`"
+      
+
+stateDiagram
+direction LR
+        state Apa1 {
+          A1
+        }
+
+      Apa11 --> Apa1
+      A1 --> C1
+      %% C1: "`This is C`"
+      
+
+stateDiagram
+    [*] --> Level1
+
+    state Level1 {
+        [*] --> Level2
+
+        state Level2 {
+            [*] --> level2
+            level2 --> Level3
+
+            state Level3 {
+                [*] --> level3
+                level3 --> [*]
+            }
+        }
+    }
+      
+
 flowchart LR
 subgraph Apa["Apa"]
@@ -127,20 +242,7 @@ Apa --> C
 A --> B & C["C"]
       
-
-stateDiagram
-direction LR
-      state Gorilla0 {
-        state Apa0 {
-          A0 --> B0
-        }
 
-      }
-      Apa0 --> C0
-      A0 --> C0
-      C1: "`This is C`"
-      
 flowchart LR
     subgraph Gorilla
@@ -180,7 +282,7 @@ stateDiagram
 
       
-
+    
 flowchart LR
     Apa --Hello--> C
 
@@ -318,7 +420,7 @@ stateDiagram-v2
       mermaid.initialize({
         // theme: 'base',
         // handdrawnSeed: 12,
-        // look: 'handdrawn',
+        look: 'handdrawn',
         // 'elk.nodePlacement.strategy': 'NETWORK_SIMPLEX',
         // layout: 'dagre',
         // layout: 'elk',
diff --git a/packages/mermaid/src/diagrams/state/parser/state-style.spec.js b/packages/mermaid/src/diagrams/state/parser/state-style.spec.js
index 48a8b7e1b..fed63c444 100644
--- a/packages/mermaid/src/diagrams/state/parser/state-style.spec.js
+++ b/packages/mermaid/src/diagrams/state/parser/state-style.spec.js
@@ -221,41 +221,46 @@ describe('ClassDefs and classes when parsing a State diagram', () => {
   describe('style statement for a state (style)', () => {
     describe('defining (style)', () => {
       it('has "style" as a keyword, an id, and can set a css style attribute', function () {
-        stateDiagram.parser.parse(`
-stateDiagram-v2
-  style id1 background:#bbb;`);
-
+        stateDiagram.parser.parse(`stateDiagram-v2
+        id1
+        style id1 background:#bbb`);
         stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
+        const data4Layout = stateDiagram.parser.yy.getData();
 
-        // const styleClasses = stateDb.getClasses();
-        // expect(styleClasses.get('exampleClass').styles.length).toEqual(1);
-        // expect(styleClasses.get('exampleClass').styles[0]).toEqual('background:#bbb');
+        expect(data4Layout.nodes[0].cssStyles).toEqual(['background:#bbb']);
+      });
+      it('has "style" as a keyword, an id, and can set a css style attribute', function () {
+        stateDiagram.parser.parse(`stateDiagram-v2
+        id1
+        id2
+        style id1,id2 background:#bbb`);
+        stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
+        const data4Layout = stateDiagram.parser.yy.getData();
+
+        expect(data4Layout.nodes[0].cssStyles).toEqual(['background:#bbb']);
+        expect(data4Layout.nodes[1].cssStyles).toEqual(['background:#bbb']);
       });
 
-      it('has handles multiple ids', function () {
-        stateDiagram.parser.parse(`
-stateDiagram-v2
-  style id1,id2 background:#bbb;`);
+      it('can define multiple attributes separated by commas', function () {
+        stateDiagram.parser.parse(`stateDiagram-v2
+        id1
+        id2
+        style id1,id2 background:#bbb, font-weight:bold, font-style:italic;`);
 
         stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
+        const data4Layout = stateDiagram.parser.yy.getData();
 
-        // const styleClasses = stateDb.getClasses();
-        // expect(styleClasses.get('exampleClass').styles.length).toEqual(1);
-        // expect(styleClasses.get('exampleClass').styles[0]).toEqual('background:#bbb');
+        expect(data4Layout.nodes[0].cssStyles).toEqual([
+          'background:#bbb',
+          'font-weight:bold',
+          'font-style:italic',
+        ]);
+        expect(data4Layout.nodes[1].cssStyles).toEqual([
+          'background:#bbb',
+          'font-weight:bold',
+          'font-style:italic',
+        ]);
       });
-
-      // it('can define multiple attributes separated by commas', function () {
-      //   stateDiagram.parser.parse(
-      //     'stateDiagram-v2\n classDef exampleClass background:#bbb, font-weight:bold, font-style:italic;'
-      //   );
-      //   stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
-
-      //   const styleClasses = stateDb.getClasses();
-      //   expect(styleClasses.get('exampleClass').styles.length).toEqual(3);
-      //   expect(styleClasses.get('exampleClass').styles[0]).toEqual('background:#bbb');
-      //   expect(styleClasses.get('exampleClass').styles[1]).toEqual('font-weight:bold');
-      //   expect(styleClasses.get('exampleClass').styles[2]).toEqual('font-style:italic');
-      // });
     });
   });
 });
diff --git a/packages/mermaid/src/diagrams/state/stateDb.js b/packages/mermaid/src/diagrams/state/stateDb.js
index 012a64ebd..4ecc1690c 100644
--- a/packages/mermaid/src/diagrams/state/stateDb.js
+++ b/packages/mermaid/src/diagrams/state/stateDb.js
@@ -202,13 +202,13 @@ const extract = (_doc) => {
           ids.forEach((id) => {
             const state = getState(id);
             if (state !== undefined) {
-              state.styles = styles;
+              state.styles = styles.map((s) => s.replace(/;/g, '')?.trim());
             }
           });
         }
         break;
       case STMT_APPLYCLASS:
-        setStyle(item.id.trim(), item.styleClass);
+        setCssClass(item.id.trim(), item.styleClass);
         break;
     }
   });
diff --git a/packages/mermaid/src/rendering-util/createText.ts b/packages/mermaid/src/rendering-util/createText.ts
index e8fb04fb9..a7ccad650 100644
--- a/packages/mermaid/src/rendering-util/createText.ts
+++ b/packages/mermaid/src/rendering-util/createText.ts
@@ -198,7 +198,16 @@ export const createText = (
   } = {},
   config: MermaidConfig
 ) => {
-  log.info('createText', text, style, isTitle, classes, useHtmlLabels, isNode, addSvgBackground);
+  log.info(
+    'IPI createText',
+    text,
+    style,
+    isTitle,
+    classes,
+    useHtmlLabels,
+    isNode,
+    addSvgBackground
+  );
   if (useHtmlLabels) {
     // TODO: addHtmlLabel accepts a labelStyle. Do we possibly have that?
 
diff --git a/packages/mermaid/src/rendering-util/rendering-elements/shapes/handdrawnStyles.ts b/packages/mermaid/src/rendering-util/rendering-elements/shapes/handdrawnStyles.ts
index b14db3fff..5f8162566 100644
--- a/packages/mermaid/src/rendering-util/rendering-elements/shapes/handdrawnStyles.ts
+++ b/packages/mermaid/src/rendering-util/rendering-elements/shapes/handdrawnStyles.ts
@@ -1,6 +1,5 @@
 import { getConfig } from '$root/diagram-api/diagramAPI.js';
 import type { Node } from '$root/rendering-util/types.d.ts';
-import styles from '../../../../dist/diagrams/packet/styles';
 
 // Striped fill like start or fork nodes in state diagrams
 export const solidStateFill = (color: string) => {
@@ -22,31 +21,33 @@ export const compileStyles = (node: Node) => {
   // node.cssStyles is an array of styles directly set on the node
 
   // concat the arrays and remove duplicates such that the values from node.cssStyles are used if there are duplicates
-  return [...(node.cssCompiledStyles || []), ...(node.cssStyles || [])];
+  const stylesMap = styles2Map([...(node.cssCompiledStyles || []), ...(node.cssStyles || [])]);
+  return { stylesMap, stylesArray: [...stylesMap] };
 };
 
 export const styles2Map = (styles: string[]) => {
-  const styleMap = new Map();
+  const styleMap = new Map();
   styles.forEach((style) => {
     const [key, value] = style.split(':');
-    styleMap.set(key.trim(), value.trim());
+    styleMap.set(key.trim(), value?.trim());
   });
   return styleMap;
 };
 
 export const styles2String = (node: Node) => {
-  const styles = compileStyles(node);
+  const { stylesArray } = compileStyles(node);
   const labelStyles: string[] = [];
   const nodeStyles: string[] = [];
 
-  styles.forEach((style) => {
-    const [key, value] = style.split(':');
+  stylesArray.forEach((style) => {
+    const key = style[0];
     if (key === 'color') {
-      labelStyles.push(style);
+      labelStyles.push(style.join(':') + ' !important');
     } else {
-      nodeStyles.push(style);
+      nodeStyles.push(style.join(':') + ' !important');
     }
   });
+
   return { labelStyles: labelStyles.join(';'), nodeStyles: nodeStyles.join(';') };
 };
 
@@ -55,18 +56,16 @@ export const styles2String = (node: Node) => {
 export const userNodeOverrides = (node: Node, options: any) => {
   const { themeVariables, handdrawnSeed } = getConfig();
   const { nodeBorder, mainBkg } = themeVariables;
-  const styles = compileStyles(node);
+  const { stylesArray: styles, stylesMap } = compileStyles(node);
 
   // index the style array to a map object
-  const styleMap = styles2Map(styles);
-
   const result = Object.assign(
     {
       roughness: 0.7,
-      fill: styleMap.get('fill') || mainBkg,
+      fill: stylesMap.get('fill') || mainBkg,
       fillStyle: 'hachure', // solid fill
       fillWeight: 3.5,
-      stroke: styleMap.get('stroke') || nodeBorder,
+      stroke: stylesMap.get('stroke') || nodeBorder,
       seed: handdrawnSeed,
       strokeWidth: 1.3,
     },