enhanced_test.go 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402
  1. package parser
  2. import (
  3. "mermaid-go/pkg/renderer"
  4. "testing"
  5. )
  6. // TestEnhancedSequenceDiagramSupport tests the enhanced sequence diagram parsing and rendering
  7. func TestEnhancedSequenceDiagramSupport(t *testing.T) {
  8. tests := []struct {
  9. name string
  10. input string
  11. expected string
  12. }{
  13. {
  14. name: "Basic sequence with different arrow types",
  15. input: `sequenceDiagram
  16. participant A
  17. participant B
  18. A -> B : solid arrow
  19. A --> B : dotted arrow
  20. A ->> B : solid arrow with arrowhead
  21. A -->> B : dotted arrow with arrowhead
  22. A -x B : destroy solid
  23. A --x B : destroy dotted
  24. A -) B : open solid
  25. A --) B : open dotted
  26. A <-> B : bidirectional`,
  27. expected: "sequenceDiagram",
  28. },
  29. {
  30. name: "Sequence with loops and alternatives",
  31. input: `sequenceDiagram
  32. participant U as User
  33. participant S as Server
  34. participant D as Database
  35. loop Authentication
  36. U -> S : login request
  37. S -> D : validate credentials
  38. D --> S : user data
  39. end
  40. alt Valid credentials
  41. S --> U : login success
  42. else Invalid credentials
  43. S --> U : login failed
  44. end
  45. opt Remember me
  46. S -> D : save session
  47. end`,
  48. expected: "sequenceDiagram",
  49. },
  50. {
  51. name: "Sequence with parallel sections",
  52. input: `sequenceDiagram
  53. participant A
  54. participant B
  55. participant C
  56. par Parallel execution
  57. A -> B : message 1
  58. A -> C : message 2
  59. and Another parallel section
  60. B -> C : message 3
  61. end`,
  62. expected: "sequenceDiagram",
  63. },
  64. {
  65. name: "Sequence with boxes and notes",
  66. input: `sequenceDiagram
  67. box "Frontend" lightblue
  68. participant U as User
  69. participant W as WebApp
  70. end
  71. box "Backend" lightgreen
  72. participant S as Server
  73. participant D as Database
  74. end
  75. U -> W : request
  76. W -> S : API call
  77. Note over W,S : Processing request
  78. S -> D : query
  79. D --> S : results
  80. S --> W : response
  81. W --> U : display`,
  82. expected: "sequenceDiagram",
  83. },
  84. {
  85. name: "Sequence with activations",
  86. input: `sequenceDiagram
  87. participant A
  88. participant B
  89. A -> B : start processing
  90. activate B
  91. B -> A : processing...
  92. deactivate B
  93. B --> A : done`,
  94. expected: "sequenceDiagram",
  95. },
  96. }
  97. for _, tt := range tests {
  98. t.Run(tt.name, func(t *testing.T) {
  99. // Parse the input
  100. parser := NewSequenceParser()
  101. diagram, err := parser.Parse(tt.input)
  102. if err != nil {
  103. t.Fatalf("Failed to parse: %v", err)
  104. }
  105. // Verify basic structure
  106. if diagram == nil {
  107. t.Fatal("Diagram is nil")
  108. }
  109. // Render back to mermaid syntax
  110. renderer := renderer.NewSequenceRenderer()
  111. output, err := renderer.Render(diagram)
  112. if err != nil {
  113. t.Fatalf("Failed to render: %v", err)
  114. }
  115. // Verify output contains expected elements
  116. if !containsString(output, tt.expected) {
  117. t.Errorf("Expected output to contain %q, got: %s", tt.expected, output)
  118. }
  119. t.Logf("Successfully parsed and rendered: %s", tt.name)
  120. t.Logf("Output: %s", output)
  121. })
  122. }
  123. }
  124. // TestEnhancedStateDiagramSupport tests the enhanced state diagram parsing and rendering
  125. func TestEnhancedStateDiagramSupport(t *testing.T) {
  126. tests := []struct {
  127. name string
  128. input string
  129. expected string
  130. }{
  131. {
  132. name: "Basic state diagram with transitions",
  133. input: `stateDiagram-v2
  134. [*] --> State1
  135. State1 --> State2 : transition
  136. State2 --> [*]`,
  137. expected: "stateDiagram-v2",
  138. },
  139. {
  140. name: "State diagram with guards and actions",
  141. input: `stateDiagram-v2
  142. [*] --> Idle
  143. Idle --> Processing : start [hasData] / processData
  144. Processing --> Idle : complete [success] / cleanup
  145. Processing --> Error : fail [error] / logError`,
  146. expected: "stateDiagram-v2",
  147. },
  148. {
  149. name: "State diagram with composite states",
  150. input: `stateDiagram-v2
  151. [*] --> Active
  152. state Active {
  153. [*] --> Working
  154. Working --> Waiting
  155. Waiting --> Working
  156. }
  157. Active --> [*]`,
  158. expected: "stateDiagram-v2",
  159. },
  160. {
  161. name: "State diagram with notes",
  162. input: `stateDiagram-v2
  163. [*] --> State1
  164. State1 --> State2
  165. note right of State1 : This is State1
  166. note left of State2 : This is State2`,
  167. expected: "stateDiagram-v2",
  168. },
  169. {
  170. name: "State diagram with special states",
  171. input: `stateDiagram-v2
  172. [*] --> Start
  173. Start --> Choice : <<choice>>
  174. Choice --> Fork : <<fork>>
  175. Fork --> Join : <<join>>
  176. Join --> History : <<history>>
  177. History --> [*]`,
  178. expected: "stateDiagram-v2",
  179. },
  180. }
  181. for _, tt := range tests {
  182. t.Run(tt.name, func(t *testing.T) {
  183. // Parse the input
  184. parser := NewStateParser()
  185. diagram, err := parser.Parse(tt.input)
  186. if err != nil {
  187. t.Fatalf("Failed to parse: %v", err)
  188. }
  189. // Verify basic structure
  190. if diagram == nil {
  191. t.Fatal("Diagram is nil")
  192. }
  193. // Render back to mermaid syntax
  194. renderer := renderer.NewStateRenderer()
  195. output, err := renderer.Render(diagram)
  196. if err != nil {
  197. t.Fatalf("Failed to render: %v", err)
  198. }
  199. // Verify output contains expected elements
  200. if !containsString(output, tt.expected) {
  201. t.Errorf("Expected output to contain %q, got: %s", tt.expected, output)
  202. }
  203. t.Logf("Successfully parsed and rendered: %s", tt.name)
  204. t.Logf("Output: %s", output)
  205. })
  206. }
  207. }
  208. // TestRoundTripConversion tests that parsing and rendering preserves the original structure
  209. func TestRoundTripConversion(t *testing.T) {
  210. // Test sequence diagram round trip
  211. seqInput := `sequenceDiagram
  212. participant A
  213. participant B
  214. A -> B : hello
  215. loop repeat
  216. B --> A : response
  217. end
  218. alt success
  219. A -> B : continue
  220. else error
  221. A -> B : retry
  222. end`
  223. seqParser := NewSequenceParser()
  224. seqDiagram, err := seqParser.Parse(seqInput)
  225. if err != nil {
  226. t.Fatalf("Failed to parse sequence diagram: %v", err)
  227. }
  228. seqRenderer := renderer.NewSequenceRenderer()
  229. seqOutput, err := seqRenderer.Render(seqDiagram)
  230. if err != nil {
  231. t.Fatalf("Failed to render sequence diagram: %v", err)
  232. }
  233. t.Logf("Sequence diagram round trip successful")
  234. t.Logf("Original: %s", seqInput)
  235. t.Logf("Rendered: %s", seqOutput)
  236. // Test state diagram round trip
  237. stateInput := `stateDiagram-v2
  238. [*] --> State1
  239. State1 --> State2 : transition [guard] / action
  240. State2 --> [*]`
  241. stateParser := NewStateParser()
  242. stateDiagram, err := stateParser.Parse(stateInput)
  243. if err != nil {
  244. t.Fatalf("Failed to parse state diagram: %v", err)
  245. }
  246. stateRenderer := renderer.NewStateRenderer()
  247. stateOutput, err := stateRenderer.Render(stateDiagram)
  248. if err != nil {
  249. t.Fatalf("Failed to render state diagram: %v", err)
  250. }
  251. t.Logf("State diagram round trip successful")
  252. t.Logf("Original: %s", stateInput)
  253. t.Logf("Rendered: %s", stateOutput)
  254. }
  255. // TestComplexSequenceDiagram tests a complex sequence diagram with all features
  256. func TestComplexSequenceDiagram(t *testing.T) {
  257. complexInput := `sequenceDiagram
  258. participant U as User
  259. participant W as WebApp
  260. participant A as API
  261. participant D as Database
  262. participant C as Cache
  263. box "Frontend" lightblue
  264. U
  265. W
  266. end
  267. box "Backend" lightgreen
  268. A
  269. D
  270. C
  271. end
  272. U -> W : login request
  273. activate W
  274. W -> A : authenticate
  275. activate A
  276. alt Cache hit
  277. A -> C : get user data
  278. C --> A : cached data
  279. else Cache miss
  280. A -> D : query user
  281. D --> A : user data
  282. A -> C : store in cache
  283. end
  284. A --> W : auth result
  285. deactivate A
  286. opt Remember session
  287. W -> C : store session
  288. end
  289. W --> U : login response
  290. deactivate W
  291. Note over U,C : Authentication complete
  292. par User actions
  293. U -> W : browse content
  294. W -> A : fetch data
  295. and Background tasks
  296. A -> D : cleanup old sessions
  297. end`
  298. parser := NewSequenceParser()
  299. diagram, err := parser.Parse(complexInput)
  300. if err != nil {
  301. t.Fatalf("Failed to parse complex sequence diagram: %v", err)
  302. }
  303. // Verify structure
  304. if len(diagram.Participants) < 5 {
  305. t.Errorf("Expected at least 5 participants, got %d", len(diagram.Participants))
  306. }
  307. if len(diagram.Boxes) < 2 {
  308. t.Errorf("Expected at least 2 boxes, got %d", len(diagram.Boxes))
  309. }
  310. if len(diagram.Alts) < 1 {
  311. t.Errorf("Expected at least 1 alt block, got %d", len(diagram.Alts))
  312. }
  313. if len(diagram.Opts) < 1 {
  314. t.Errorf("Expected at least 1 opt block, got %d", len(diagram.Opts))
  315. }
  316. if len(diagram.Pars) < 1 {
  317. t.Errorf("Expected at least 1 par block, got %d", len(diagram.Pars))
  318. }
  319. if len(diagram.Notes) < 1 {
  320. t.Errorf("Expected at least 1 note, got %d", len(diagram.Notes))
  321. }
  322. // Render and verify
  323. renderer := renderer.NewSequenceRenderer()
  324. output, err := renderer.Render(diagram)
  325. if err != nil {
  326. t.Fatalf("Failed to render complex sequence diagram: %v", err)
  327. }
  328. t.Logf("Complex sequence diagram test passed")
  329. t.Logf("Output: %s", output)
  330. }
  331. // Helper function to check if a string contains a substring
  332. func containsString(s, substr string) bool {
  333. return len(s) >= len(substr) &&
  334. (s == substr ||
  335. (len(s) > len(substr) &&
  336. (s[:len(substr)] == substr ||
  337. s[len(s)-len(substr):] == substr ||
  338. containsSubstring(s, substr))))
  339. }
  340. func containsSubstring(s, substr string) bool {
  341. for i := 0; i <= len(s)-len(substr); i++ {
  342. if s[i:i+len(substr)] == substr {
  343. return true
  344. }
  345. }
  346. return false
  347. }