| 1 | import { imgSnapshotTest, renderGraph } from '../../helpers/util.ts'; |
| 2 | |
| 3 | describe('Entity Relationship Diagram', () => { |
| 4 | it('should render a simple ER diagram', () => { |
| 5 | imgSnapshotTest( |
| 6 | ` |
| 7 | erDiagram |
| 8 | CUSTOMER ||--o{ ORDER : places |
| 9 | ORDER ||--|{ LINE-ITEM : contains |
| 10 | `, |
| 11 | { logLevel: 1 } |
| 12 | ); |
| 13 | }); |
| 14 | |
| 15 | it('should render an ER diagram with a recursive relationship', () => { |
| 16 | imgSnapshotTest( |
| 17 | ` |
| 18 | erDiagram |
| 19 | CUSTOMER ||..o{ CUSTOMER : refers |
| 20 | CUSTOMER ||--o{ ORDER : places |
| 21 | ORDER ||--|{ LINE-ITEM : contains |
| 22 | `, |
| 23 | { logLevel: 1 } |
| 24 | ); |
| 25 | }); |
| 26 | |
| 27 | it('should render an ER diagram with multiple relationships between the same two entities', () => { |
| 28 | imgSnapshotTest( |
| 29 | ` |
| 30 | erDiagram |
| 31 | CUSTOMER ||--|{ ADDRESS : "invoiced at" |
| 32 | CUSTOMER ||--|{ ADDRESS : "receives goods at" |
| 33 | `, |
| 34 | { logLevel: 1 } |
| 35 | ); |
| 36 | }); |
| 37 | |
| 38 | it('should render a cyclical ER diagram', () => { |
| 39 | imgSnapshotTest( |
| 40 | ` |
| 41 | erDiagram |
| 42 | A ||--|{ B : likes |
| 43 | B ||--|{ C : likes |
| 44 | C ||--|{ A : likes |
| 45 | `, |
| 46 | { logLevel: 1 } |
| 47 | ); |
| 48 | }); |
| 49 | |
| 50 | it('should render a not-so-simple ER diagram', () => { |
| 51 | imgSnapshotTest( |
| 52 | ` |
| 53 | erDiagram |
| 54 | CUSTOMER }|..|{ DELIVERY-ADDRESS : has |
| 55 | CUSTOMER ||--o{ ORDER : places |
| 56 | CUSTOMER ||--o{ INVOICE : "liable for" |
| 57 | DELIVERY-ADDRESS ||--o{ ORDER : receives |
| 58 | INVOICE ||--|{ ORDER : covers |
| 59 | ORDER ||--|{ ORDER-ITEM : includes |
| 60 | PRODUCT-CATEGORY ||--|{ PRODUCT : contains |
| 61 | PRODUCT ||--o{ ORDER-ITEM : "ordered in" |
| 62 | `, |
| 63 | { logLevel: 1 } |
| 64 | ); |
| 65 | }); |
| 66 | |
| 67 | it('should render multiple ER diagrams', () => { |
| 68 | imgSnapshotTest( |
| 69 | [ |
| 70 | ` |
| 71 | erDiagram |
| 72 | CUSTOMER ||--o{ ORDER : places |
| 73 | ORDER ||--|{ LINE-ITEM : contains |
| 74 | `, |
| 75 | ` |
| 76 | erDiagram |
| 77 | CUSTOMER ||--o{ ORDER : places |
| 78 | ORDER ||--|{ LINE-ITEM : contains |
| 79 | `, |
| 80 | ], |
| 81 | { logLevel: 1 } |
| 82 | ); |
| 83 | }); |
| 84 | |
| 85 | it('should render an ER diagram with blank or empty labels', () => { |
| 86 | imgSnapshotTest( |
| 87 | ` |
| 88 | erDiagram |
| 89 | BOOK }|..|{ AUTHOR : "" |
| 90 | BOOK }|..|{ GENRE : " " |
| 91 | AUTHOR }|..|{ GENRE : " " |
| 92 | `, |
| 93 | { logLevel: 1 } |
| 94 | ); |
| 95 | }); |
| 96 | |
| 97 | it('should render an ER diagrams when useMaxWidth is true (default)', () => { |
| 98 | renderGraph( |
| 99 | ` |
| 100 | erDiagram |
| 101 | CUSTOMER ||--o{ ORDER : places |
| 102 | ORDER ||--|{ LINE-ITEM : contains |
| 103 | `, |
| 104 | { er: { useMaxWidth: true } } |
| 105 | ); |
| 106 | cy.get('svg').should((svg) => { |
| 107 | expect(svg).to.have.attr('width', '100%'); |
| 108 | // expect(svg).to.have.attr('height', '465'); |
| 109 | const style = svg.attr('style'); |
| 110 | expect(style).to.match(/^max-width: [\d.]+px;$/); |
| 111 | const maxWidthValue = parseFloat(style.match(/[\d.]+/g).join('')); |
| 112 | // use within because the absolute value can be slightly different depending on the environment ±6% |
| 113 | expect(maxWidthValue).to.be.within(140 * 0.96, 140 * 1.06); |
| 114 | }); |
| 115 | }); |
| 116 | |
| 117 | it('should render an ER when useMaxWidth is false', () => { |
| 118 | renderGraph( |
| 119 | ` |
| 120 | erDiagram |
| 121 | CUSTOMER ||--o{ ORDER : places |
| 122 | ORDER ||--|{ LINE-ITEM : contains |
| 123 | `, |
| 124 | { er: { useMaxWidth: false } } |
| 125 | ); |
| 126 | cy.get('svg').should((svg) => { |
| 127 | const width = parseFloat(svg.attr('width')); |
| 128 | // use within because the absolute value can be slightly different depending on the environment ±6% |
| 129 | expect(width).to.be.within(140 * 0.96, 140 * 1.06); |
| 130 | // expect(svg).to.have.attr('height', '465'); |
| 131 | expect(svg).to.not.have.attr('style'); |
| 132 | }); |
| 133 | }); |
| 134 | |
| 135 | it('should render entities that have no relationships', () => { |
| 136 | renderGraph( |
| 137 | ` |
| 138 | erDiagram |
| 139 | DEAD_PARROT |
| 140 | HERMIT |
| 141 | RECLUSE |
| 142 | SOCIALITE }o--o{ SOCIALITE : "interacts with" |
| 143 | RECLUSE }o--o{ SOCIALITE : avoids |
| 144 | `, |
| 145 | { er: { useMaxWidth: false } } |
| 146 | ); |
| 147 | }); |
| 148 | |
| 149 | it('should render entities with and without attributes', () => { |
| 150 | renderGraph( |
| 151 | ` |
| 152 | erDiagram |
| 153 | BOOK { string title } |
| 154 | AUTHOR }|..|{ BOOK : writes |
| 155 | BOOK { float price } |
| 156 | `, |
| 157 | { logLevel: 1 } |
| 158 | ); |
| 159 | }); |
| 160 | |
| 161 | it('should render entities with generic and array attributes', () => { |
| 162 | renderGraph( |
| 163 | ` |
| 164 | erDiagram |
| 165 | BOOK { |
| 166 | string title |
| 167 | string[] authors |
| 168 | type~T~ type |
| 169 | } |
| 170 | `, |
| 171 | { logLevel: 1 } |
| 172 | ); |
| 173 | }); |
| 174 | |
| 175 | it('should render entities with length in attributes type', () => { |
| 176 | renderGraph( |
| 177 | ` |
| 178 | erDiagram |
| 179 | CLUSTER { |
| 180 | varchar(99) name |
| 181 | string(255) description |
| 182 | } |
| 183 | `, |
| 184 | { logLevel: 1 } |
| 185 | ); |
| 186 | }); |
| 187 | |
| 188 | it('should render entities and attributes with big and small entity names', () => { |
| 189 | renderGraph( |
| 190 | ` |
| 191 | erDiagram |
| 192 | PRIVATE_FINANCIAL_INSTITUTION { |
| 193 | string name |
| 194 | int turnover |
| 195 | } |
| 196 | PRIVATE_FINANCIAL_INSTITUTION ||..|{ EMPLOYEE : employs |
| 197 | EMPLOYEE { bool officer_of_firm } |
| 198 | `, |
| 199 | { logLevel: 1 } |
| 200 | ); |
| 201 | }); |
| 202 | |
| 203 | it('should render entities with attributes that begin with asterisk', () => { |
| 204 | imgSnapshotTest( |
| 205 | ` |
| 206 | erDiagram |
| 207 | BOOK { |
| 208 | int *id |
| 209 | string name |
| 210 | varchar(99) summary |
| 211 | } |
| 212 | BOOK }o..o{ STORE : soldBy |
| 213 | STORE { |
| 214 | int *id |
| 215 | string name |
| 216 | varchar(50) address |
| 217 | } |
| 218 | `, |
| 219 | { loglevel: 1 } |
| 220 | ); |
| 221 | }); |
| 222 | |
| 223 | it('should render entities with keys', () => { |
| 224 | renderGraph( |
| 225 | ` |
| 226 | erDiagram |
| 227 | AUTHOR_WITH_LONG_ENTITY_NAME { |
| 228 | string name PK |
| 229 | } |
| 230 | AUTHOR_WITH_LONG_ENTITY_NAME }|..|{ BOOK : writes |
| 231 | BOOK { |
| 232 | float price |
| 233 | string author FK |
| 234 | string title PK |
| 235 | } |
| 236 | `, |
| 237 | { logLevel: 1 } |
| 238 | ); |
| 239 | }); |
| 240 | |
| 241 | it('should render entities with comments', () => { |
| 242 | renderGraph( |
| 243 | ` |
| 244 | erDiagram |
| 245 | AUTHOR_WITH_LONG_ENTITY_NAME { |
| 246 | string name "comment" |
| 247 | } |
| 248 | AUTHOR_WITH_LONG_ENTITY_NAME }|..|{ BOOK : writes |
| 249 | BOOK { |
| 250 | string author |
| 251 | string title "author comment" |
| 252 | float price "price comment" |
| 253 | } |
| 254 | `, |
| 255 | { logLevel: 1 } |
| 256 | ); |
| 257 | }); |
| 258 | |
| 259 | it('should render entities with keys and comments', () => { |
| 260 | renderGraph( |
| 261 | ` |
| 262 | erDiagram |
| 263 | AUTHOR_WITH_LONG_ENTITY_NAME { |
| 264 | string name PK "comment" |
| 265 | } |
| 266 | AUTHOR_WITH_LONG_ENTITY_NAME }|..|{ BOOK : writes |
| 267 | BOOK { |
| 268 | string description |
| 269 | float price "price comment" |
| 270 | string title PK "title comment" |
| 271 | string author FK |
| 272 | } |
| 273 | `, |
| 274 | { logLevel: 1 } |
| 275 | ); |
| 276 | }); |
| 277 | |
| 278 | it('should render entities with aliases', () => { |
| 279 | renderGraph( |
| 280 | ` |
| 281 | erDiagram |
| 282 | T1 one or zero to one or more T2 : test |
| 283 | T2 one or many optionally to zero or one T3 : test |
| 284 | T3 zero or more to zero or many T4 : test |
| 285 | T4 many(0) to many(1) T5 : test |
| 286 | T5 many optionally to one T6 : test |
| 287 | T6 only one optionally to only one T1 : test |
| 288 | T4 0+ to 1+ T6 : test |
| 289 | T1 1 to 1 T3 : test |
| 290 | `, |
| 291 | { logLevel: 1 } |
| 292 | ); |
| 293 | }); |
| 294 | |
| 295 | it('1433: should render a simple ER diagram with a title', () => { |
| 296 | imgSnapshotTest( |
| 297 | `--- |
| 298 | title: simple ER diagram |
| 299 | --- |
| 300 | erDiagram |
| 301 | CUSTOMER ||--o{ ORDER : places |
| 302 | ORDER ||--|{ LINE-ITEM : contains |
| 303 | `, |
| 304 | {} |
| 305 | ); |
| 306 | }); |
| 307 | |
| 308 | it('should render entities with entity name aliases', () => { |
| 309 | imgSnapshotTest( |
| 310 | ` |
| 311 | erDiagram |
| 312 | p[Person] { |
| 313 | varchar(64) firstName |
| 314 | varchar(64) lastName |
| 315 | } |
| 316 | c["Customer Account"] { |
| 317 | varchar(128) email |
| 318 | } |
| 319 | p ||--o| c : has |
| 320 | `, |
| 321 | { logLevel: 1 } |
| 322 | ); |
| 323 | }); |
| 324 | |
| 325 | it('should render relationship labels with line breaks', () => { |
| 326 | imgSnapshotTest( |
| 327 | ` |
| 328 | erDiagram |
| 329 | p[Person] { |
| 330 | string firstName |
| 331 | string lastName |
| 332 | } |
| 333 | a["Customer Account"] { |
| 334 | string email |
| 335 | } |
| 336 | |
| 337 | b["Customer Account Secondary"] { |
| 338 | string email |
| 339 | } |
| 340 | |
| 341 | c["Customer Account Tertiary"] { |
| 342 | string email |
| 343 | } |
| 344 | |
| 345 | d["Customer Account Nth"] { |
| 346 | string email |
| 347 | } |
| 348 | |
| 349 | p ||--o| a : "has<br />one" |
| 350 | p ||--o| b : "has<br />one<br />two" |
| 351 | p ||--o| c : "has<br />one<br/>two<br />three" |
| 352 | p ||--o| d : "has<br />one<br />two<br/>three<br />...<br/>Nth" |
| 353 | `, |
| 354 | { logLevel: 1 } |
| 355 | ); |
| 356 | }); |
| 357 | |
| 358 | describe('Include char sequence "graph" in text (#6795)', () => { |
| 359 | it('has a label with char sequence "graph"', () => { |
| 360 | imgSnapshotTest( |
| 361 | ` |
| 362 | erDiagram |
| 363 | p[Photograph] { |
| 364 | varchar(12) jobId |
| 365 | date dateCreated |
| 366 | } |
| 367 | `, |
| 368 | { flowchart: { defaultRenderer: 'elk' } } |
| 369 | ); |
| 370 | }); |
| 371 | }); |
| 372 | |
| 373 | describe('Special characters and numbers syntax', () => { |
| 374 | it('should render ER diagram with numeric entity names', () => { |
| 375 | imgSnapshotTest( |
| 376 | ` |
| 377 | erDiagram |
| 378 | 1 ||--|| ORDER : places |
| 379 | ORDER ||--|{ 2 : contains |
| 380 | 2 ||--o{ 3.5 : references |
| 381 | `, |
| 382 | { logLevel: 1 } |
| 383 | ); |
| 384 | }); |
| 385 | |
| 386 | it('should render ER diagram with "u" character in entity names and cardinality', () => { |
| 387 | imgSnapshotTest( |
| 388 | ` |
| 389 | erDiagram |
| 390 | CUSTOMER ||--|| u : has |
| 391 | u ||--|| ORDER : places |
| 392 | PROJECT u--o{ TEAM_MEMBER : "parent" |
| 393 | `, |
| 394 | { logLevel: 1 } |
| 395 | ); |
| 396 | }); |
| 397 | |
| 398 | it('should render ER diagram with decimal numbers in relationships', () => { |
| 399 | imgSnapshotTest( |
| 400 | ` |
| 401 | erDiagram |
| 402 | 2.5 ||--|| 1.5 : has |
| 403 | CUSTOMER ||--o{ 3.14 : references |
| 404 | 1.0 ||--|{ ORDER : contains |
| 405 | `, |
| 406 | { logLevel: 1 } |
| 407 | ); |
| 408 | }); |
| 409 | |
| 410 | it('should render ER diagram with numeric entity names and attributes', () => { |
| 411 | imgSnapshotTest( |
| 412 | ` |
| 413 | erDiagram |
| 414 | 1 { |
| 415 | string name |
| 416 | int value |
| 417 | } |
| 418 | 1 ||--|| ORDER : places |
| 419 | ORDER { |
| 420 | float price |
| 421 | string description |
| 422 | } |
| 423 | `, |
| 424 | { logLevel: 1 } |
| 425 | ); |
| 426 | }); |
| 427 | |
| 428 | it('should render complex ER diagram with mixed special entity names', () => { |
| 429 | imgSnapshotTest( |
| 430 | ` |
| 431 | erDiagram |
| 432 | CUSTOMER ||--o{ 1 : places |
| 433 | 1 ||--|{ u : contains |
| 434 | 1.5 |
| 435 | u ||--|| 2.5 : processes |
| 436 | 2.5 { |
| 437 | string id |
| 438 | float value |
| 439 | } |
| 440 | u { |
| 441 | varchar(50) name |
| 442 | int count |
| 443 | } |
| 444 | `, |
| 445 | { logLevel: 1 } |
| 446 | ); |
| 447 | }); |
| 448 | it('should render ER diagram with standalone numeric entities', () => { |
| 449 | imgSnapshotTest( |
| 450 | `erDiagram |
| 451 | PRODUCT ||--o{ ORDER-ITEM : has |
| 452 | 1.5 |
| 453 | u |
| 454 | 1 |
| 455 | `, |
| 456 | { logLevel: 1 } |
| 457 | ); |
| 458 | }); |
| 459 | }); |
| 460 | |
| 461 | it('should render edge labels correctly when flowchart htmlLabels is false', () => { |
| 462 | imgSnapshotTest( |
| 463 | ` |
| 464 | erDiagram |
| 465 | CUSTOMER ||--o{ ORDER : places |
| 466 | ORDER ||--|{ LINE-ITEM : contains |
| 467 | CUSTOMER ||--|{ ADDRESS : "invoiced at" |
| 468 | CUSTOMER ||--|{ ADDRESS : "receives goods at" |
| 469 | ORDER ||--o{ INVOICE : "liable for" |
| 470 | `, |
| 471 | { logLevel: 1, flowchart: { htmlLabels: false } } |
| 472 | ); |
| 473 | }); |
| 474 | |
| 475 | it('should render ER diagram with "1" cardinality alias before relationship operators', () => { |
| 476 | imgSnapshotTest( |
| 477 | ` |
| 478 | erDiagram |
| 479 | CUSTOMER 1--1 ORDER : "exactly one" |
| 480 | ORDER 1--o{ LINE-ITEM : "one to many" |
| 481 | PRODUCT 1--|{ CATEGORY : "one or more" |
| 482 | USER 1..1 PROFILE : "exactly one optional" |
| 483 | `, |
| 484 | { logLevel: 1 } |
| 485 | ); |
| 486 | }); |
| 487 | |
| 488 | it('should render ER diagram with "1" cardinality using all 4 relationship operator styles', () => { |
| 489 | imgSnapshotTest( |
| 490 | ` |
| 491 | erDiagram |
| 492 | A 1--1 B : "solid-solid" |
| 493 | C 1..1 D : "dotted-dotted" |
| 494 | E 1.-1 F : "dotted-solid" |
| 495 | G 1-.1 H : "solid-dotted" |
| 496 | `, |
| 497 | { logLevel: 1 } |
| 498 | ); |
| 499 | }); |
| 500 | }); |
| 501 | |