mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-02-04 07:13:25 +08:00
Merge remote-tracking branch 'origin/develop' into develop
This commit is contained in:
commit
09bc7e0acd
@ -15,31 +15,47 @@
|
|||||||
|
|
||||||
<body>
|
<body>
|
||||||
<pre class="mermaid">
|
<pre class="mermaid">
|
||||||
erDiagram
|
|
||||||
title: This is a title
|
|
||||||
accDescription_ Test a description
|
|
||||||
|
|
||||||
CUSTOMER ||--o{ ORDER : places
|
erDiagram
|
||||||
ORDER ||--|{ LINE-ITEM : contains
|
%% title This is a title
|
||||||
CUSTOMER }|..|{ DELIVERY-ADDRESS : uses
|
%% accDescription Test a description
|
||||||
|
|
||||||
DELIVERY-ADDRESS {
|
"Person . CUSTOMER"||--o{ ORDER : places
|
||||||
int customerId
|
|
||||||
string addressLine1
|
ORDER ||--|{ "€£LINE_ITEM ¥" : contains
|
||||||
string addressLine2
|
|
||||||
string city
|
"Person . CUSTOMER" }|..|{ "Address//StreetAddress::[DELIVERY ADDRESS]" : uses
|
||||||
string county
|
|
||||||
string state
|
"Address//StreetAddress::[DELIVERY ADDRESS]" {
|
||||||
string region
|
int customerID FK
|
||||||
string country
|
string line1 "this is the first address line comment"
|
||||||
string postalCode
|
string line2
|
||||||
}
|
string city
|
||||||
|
string region
|
||||||
|
string state
|
||||||
|
string postal_code
|
||||||
|
string country
|
||||||
|
}
|
||||||
|
|
||||||
|
"a_~`!@#$^&*()-_=+[]{}|/;:'.?¡⁄™€£‹¢›∞fi§‡•°ª·º‚≠±œŒ∑„®†ˇ¥Á¨ˆˆØπ∏“«»åÅßÍ∂΃ϩ˙Ó∆Ô˚¬Ò…ÚæÆΩ¸≈π˛çÇ√◊∫ı˜µÂ≤¯≥˘÷¿" {
|
||||||
|
string name "this is an entity with an absurd name just to show characters that are now acceptable as long as the name is in double quotes"
|
||||||
|
}
|
||||||
|
|
||||||
|
"€£LINE_ITEM ¥" {
|
||||||
|
int orderID FK
|
||||||
|
int currencyId FK
|
||||||
|
number price
|
||||||
|
number quantity
|
||||||
|
number adjustment
|
||||||
|
number final_price
|
||||||
|
}
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import mermaid from '../src/mermaid';
|
import mermaid from '../src/mermaid';
|
||||||
mermaid.initialize({
|
mermaid.initialize({
|
||||||
theme: 'base',
|
theme: 'default',
|
||||||
|
|
||||||
// themeCSS: '.node rect { fill: red; }',
|
// themeCSS: '.node rect { fill: red; }',
|
||||||
logLevel: 3,
|
logLevel: 3,
|
||||||
securityLevel: 'loose',
|
securityLevel: 'loose',
|
||||||
|
@ -65,6 +65,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@braintree/sanitize-url": "^6.0.0",
|
"@braintree/sanitize-url": "^6.0.0",
|
||||||
|
"@types/uuid": "^8.3.4",
|
||||||
"d3": "^7.0.0",
|
"d3": "^7.0.0",
|
||||||
"dagre": "^0.8.5",
|
"dagre": "^0.8.5",
|
||||||
"dagre-d3": "^0.6.4",
|
"dagre-d3": "^0.6.4",
|
||||||
@ -75,7 +76,8 @@
|
|||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"moment-mini": "^2.24.0",
|
"moment-mini": "^2.24.0",
|
||||||
"non-layered-tidy-tree-layout": "^2.0.2",
|
"non-layered-tidy-tree-layout": "^2.0.2",
|
||||||
"stylis": "^4.1.2"
|
"stylis": "^4.1.2",
|
||||||
|
"uuid": "^9.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@applitools/eyes-cypress": "^3.25.7",
|
"@applitools/eyes-cypress": "^3.25.7",
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
import graphlib from 'graphlib';
|
import graphlib from 'graphlib';
|
||||||
import { line, curveBasis, select } from 'd3';
|
import { line, curveBasis, select } from 'd3';
|
||||||
// import erDb from './erDb';
|
|
||||||
// import erParser from './parser/erDiagram';
|
|
||||||
import dagre from 'dagre';
|
import dagre from 'dagre';
|
||||||
import { getConfig } from '../../config';
|
import { getConfig } from '../../config';
|
||||||
import { log } from '../../logger';
|
import { log } from '../../logger';
|
||||||
@ -9,9 +7,17 @@ import erMarkers from './erMarkers';
|
|||||||
import { configureSvgSize } from '../../setupGraphViewbox';
|
import { configureSvgSize } from '../../setupGraphViewbox';
|
||||||
import addSVGAccessibilityFields from '../../accessibility';
|
import addSVGAccessibilityFields from '../../accessibility';
|
||||||
import { parseGenericTypes } from '../common/common';
|
import { parseGenericTypes } from '../common/common';
|
||||||
|
import { v4 as uuid4 } from 'uuid';
|
||||||
|
|
||||||
|
/** Regex used to remove chars from the entity name so the result can be used in an id */
|
||||||
|
const BAD_ID_CHARS_REGEXP = /[^A-Za-z0-9]([\W])*/g;
|
||||||
|
|
||||||
|
// Configuration
|
||||||
let conf = {};
|
let conf = {};
|
||||||
|
|
||||||
|
// Map so we can look up the id of an entity based on the name
|
||||||
|
let entityNameIds = new Map();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allows the top-level API module to inject config specific to this renderer, storing it in the
|
* Allows the top-level API module to inject config specific to this renderer, storing it in the
|
||||||
* local conf object. Note that generic config still needs to be retrieved using getConfig()
|
* local conf object. Note that generic config still needs to be retrieved using getConfig()
|
||||||
@ -31,8 +37,10 @@ export const setConf = function (cnf) {
|
|||||||
*
|
*
|
||||||
* @param groupNode The svg group node for the entity
|
* @param groupNode The svg group node for the entity
|
||||||
* @param entityTextNode The svg node for the entity label text
|
* @param entityTextNode The svg node for the entity label text
|
||||||
* @param attributes An array of attributes defined for the entity (each attribute has a type and a name)
|
* @param attributes An array of attributes defined for the entity (each attribute has a type and a
|
||||||
* @returns {object} The bounding box of the entity, after attributes have been added. The bounding box has a .width and .height
|
* name)
|
||||||
|
* @returns {object} The bounding box of the entity, after attributes have been added. The bounding
|
||||||
|
* box has a .width and .height
|
||||||
*/
|
*/
|
||||||
const drawAttributes = (groupNode, entityTextNode, attributes) => {
|
const drawAttributes = (groupNode, entityTextNode, attributes) => {
|
||||||
const heightPadding = conf.entityPadding / 3; // Padding internal to attribute boxes
|
const heightPadding = conf.entityPadding / 3; // Padding internal to attribute boxes
|
||||||
@ -288,7 +296,7 @@ const drawAttributes = (groupNode, entityTextNode, attributes) => {
|
|||||||
heightOffset += attributeNode.height + heightPadding * 2;
|
heightOffset += attributeNode.height + heightPadding * 2;
|
||||||
|
|
||||||
// Flip the attribute style for row banding
|
// Flip the attribute style for row banding
|
||||||
attribStyle = attribStyle == 'attributeBoxOdd' ? 'attributeBoxEven' : 'attributeBoxOdd';
|
attribStyle = attribStyle === 'attributeBoxOdd' ? 'attributeBoxEven' : 'attributeBoxOdd';
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// Ensure the entity box is a decent size without any attributes
|
// Ensure the entity box is a decent size without any attributes
|
||||||
@ -313,15 +321,18 @@ const drawEntities = function (svgNode, entities, graph) {
|
|||||||
const keys = Object.keys(entities);
|
const keys = Object.keys(entities);
|
||||||
let firstOne;
|
let firstOne;
|
||||||
|
|
||||||
keys.forEach(function (id) {
|
keys.forEach(function (entityName) {
|
||||||
// Create a group for each entity
|
const entityId = generateId(entityName, 'entity');
|
||||||
const groupNode = svgNode.append('g').attr('id', id);
|
entityNameIds.set(entityName, entityId);
|
||||||
|
|
||||||
firstOne = firstOne === undefined ? id : firstOne;
|
// Create a group for each entity
|
||||||
|
const groupNode = svgNode.append('g').attr('id', entityId);
|
||||||
|
|
||||||
|
firstOne = firstOne === undefined ? entityId : firstOne;
|
||||||
|
|
||||||
// Label the entity - this is done first so that we can get the bounding box
|
// Label the entity - this is done first so that we can get the bounding box
|
||||||
// which then determines the size of the rectangle
|
// which then determines the size of the rectangle
|
||||||
const textId = 'entity-' + id;
|
const textId = 'text-' + entityId;
|
||||||
const textNode = groupNode
|
const textNode = groupNode
|
||||||
.append('text')
|
.append('text')
|
||||||
.attr('class', 'er entityLabel')
|
.attr('class', 'er entityLabel')
|
||||||
@ -334,12 +345,12 @@ const drawEntities = function (svgNode, entities, graph) {
|
|||||||
'style',
|
'style',
|
||||||
'font-family: ' + getConfig().fontFamily + '; font-size: ' + conf.fontSize + 'px'
|
'font-family: ' + getConfig().fontFamily + '; font-size: ' + conf.fontSize + 'px'
|
||||||
)
|
)
|
||||||
.text(id);
|
.text(entityName);
|
||||||
|
|
||||||
const { width: entityWidth, height: entityHeight } = drawAttributes(
|
const { width: entityWidth, height: entityHeight } = drawAttributes(
|
||||||
groupNode,
|
groupNode,
|
||||||
textNode,
|
textNode,
|
||||||
entities[id].attributes
|
entities[entityName].attributes
|
||||||
);
|
);
|
||||||
|
|
||||||
// Draw the rectangle - insert it before the text so that the text is not obscured
|
// Draw the rectangle - insert it before the text so that the text is not obscured
|
||||||
@ -356,12 +367,12 @@ const drawEntities = function (svgNode, entities, graph) {
|
|||||||
|
|
||||||
const rectBBox = rectNode.node().getBBox();
|
const rectBBox = rectNode.node().getBBox();
|
||||||
|
|
||||||
// Add the entity to the graph
|
// Add the entity to the graph using the entityId
|
||||||
graph.setNode(id, {
|
graph.setNode(entityId, {
|
||||||
width: rectBBox.width,
|
width: rectBBox.width,
|
||||||
height: rectBBox.height,
|
height: rectBBox.height,
|
||||||
shape: 'rect',
|
shape: 'rect',
|
||||||
id: id,
|
id: entityId,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
return firstOne;
|
return firstOne;
|
||||||
@ -382,9 +393,16 @@ const adjustEntities = function (svgNode, graph) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a name for an edge based on the names of the 2 entities and the role (relationship)
|
||||||
|
* between them. Remove any spaces from it
|
||||||
|
*
|
||||||
|
* @param rel - A (parsed) relationship (e.g. one of the objects in the list returned by
|
||||||
|
* erDb.getRelationships)
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
const getEdgeName = function (rel) {
|
const getEdgeName = function (rel) {
|
||||||
return (rel.entityA + rel.roleA + rel.entityB).replace(/\s/g, '');
|
return (rel.entityA + rel.roleA + rel.entityB).replace(/\s/g, '');
|
||||||
};
|
};
|
||||||
@ -393,12 +411,17 @@ const getEdgeName = function (rel) {
|
|||||||
* Add each relationship to the graph
|
* Add each relationship to the graph
|
||||||
*
|
*
|
||||||
* @param relationships The relationships to be added
|
* @param relationships The relationships to be added
|
||||||
* @param g The graph
|
* @param {Graph} g The graph
|
||||||
* @returns {Array} The array of relationships
|
* @returns {Array} The array of relationships
|
||||||
*/
|
*/
|
||||||
const addRelationships = function (relationships, g) {
|
const addRelationships = function (relationships, g) {
|
||||||
relationships.forEach(function (r) {
|
relationships.forEach(function (r) {
|
||||||
g.setEdge(r.entityA, r.entityB, { relationship: r }, getEdgeName(r));
|
g.setEdge(
|
||||||
|
entityNameIds.get(r.entityA),
|
||||||
|
entityNameIds.get(r.entityB),
|
||||||
|
{ relationship: r },
|
||||||
|
getEdgeName(r)
|
||||||
|
);
|
||||||
});
|
});
|
||||||
return relationships;
|
return relationships;
|
||||||
}; // addRelationships
|
}; // addRelationships
|
||||||
@ -418,7 +441,11 @@ const drawRelationshipFromLayout = function (svg, rel, g, insert, diagObj) {
|
|||||||
relCnt++;
|
relCnt++;
|
||||||
|
|
||||||
// Find the edge relating to this relationship
|
// Find the edge relating to this relationship
|
||||||
const edge = g.edge(rel.entityA, rel.entityB, getEdgeName(rel));
|
const edge = g.edge(
|
||||||
|
entityNameIds.get(rel.entityA),
|
||||||
|
entityNameIds.get(rel.entityB),
|
||||||
|
getEdgeName(rel)
|
||||||
|
);
|
||||||
|
|
||||||
// Get a function that will generate the line path
|
// Get a function that will generate the line path
|
||||||
const lineFunction = line()
|
const lineFunction = line()
|
||||||
@ -535,8 +562,6 @@ const drawRelationshipFromLayout = function (svg, rel, g, insert, diagObj) {
|
|||||||
.attr('height', labelBBox.height)
|
.attr('height', labelBBox.height)
|
||||||
.attr('fill', 'white')
|
.attr('fill', 'white')
|
||||||
.attr('fill-opacity', '85%');
|
.attr('fill-opacity', '85%');
|
||||||
|
|
||||||
return;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -552,7 +577,7 @@ export const draw = function (text, id, _version, diagObj) {
|
|||||||
log.info('Drawing ER diagram');
|
log.info('Drawing ER diagram');
|
||||||
// diag.db.clear();
|
// diag.db.clear();
|
||||||
const securityLevel = getConfig().securityLevel;
|
const securityLevel = getConfig().securityLevel;
|
||||||
// Handle root and Document for when rendering in sanbox mode
|
// Handle root and Document for when rendering in sandbox mode
|
||||||
let sandboxElement;
|
let sandboxElement;
|
||||||
if (securityLevel === 'sandbox') {
|
if (securityLevel === 'sandbox') {
|
||||||
sandboxElement = select('#i' + id);
|
sandboxElement = select('#i' + id);
|
||||||
@ -581,7 +606,7 @@ export const draw = function (text, id, _version, diagObj) {
|
|||||||
// 1. Create all the entities in the svg node at 0,0, but with the correct dimensions (allowing for text content)
|
// 1. Create all the entities in the svg node at 0,0, but with the correct dimensions (allowing for text content)
|
||||||
// 2. Make sure they are all added to the graph
|
// 2. Make sure they are all added to the graph
|
||||||
// 3. Add all the edges (relationships) to the graph as well
|
// 3. Add all the edges (relationships) to the graph as well
|
||||||
// 4. Let dagre do its magic to layout the graph. This assigns:
|
// 4. Let dagre do its magic to lay out the graph. This assigns:
|
||||||
// - the centre co-ordinates for each node, bearing in mind the dimensions and edge relationships
|
// - the centre co-ordinates for each node, bearing in mind the dimensions and edge relationships
|
||||||
// - the path co-ordinates for each edge
|
// - the path co-ordinates for each edge
|
||||||
// But it has no impact on the svg child nodes - the diagram remains with every entity rooted at 0,0
|
// But it has no impact on the svg child nodes - the diagram remains with every entity rooted at 0,0
|
||||||
@ -647,6 +672,35 @@ export const draw = function (text, id, _version, diagObj) {
|
|||||||
addSVGAccessibilityFields(diagObj.db, svg, id);
|
addSVGAccessibilityFields(diagObj.db, svg, id);
|
||||||
}; // draw
|
}; // draw
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a unique id based on the given string. Start with the prefix, then a hyphen, then the
|
||||||
|
* simplified str, then a hyphen, then a unique uuid. (Hyphens are only included if needed.)
|
||||||
|
* Although the official XML standard for ids says that many more characters are valid in the id,
|
||||||
|
* this keeps things simple by accepting only A-Za-z0-9.
|
||||||
|
*
|
||||||
|
* @param {string} [str?=''] Given string to use as the basis for the id. Default is `''`
|
||||||
|
* @param {string} [prefix?=''] String to put at the start, followed by '-'. Default is `''`
|
||||||
|
* @param str
|
||||||
|
* @param prefix
|
||||||
|
* @returns {string}
|
||||||
|
* @see https://www.w3.org/TR/xml/#NT-Name
|
||||||
|
*/
|
||||||
|
export function generateId(str = '', prefix = '') {
|
||||||
|
const simplifiedStr = str.replace(BAD_ID_CHARS_REGEXP, '');
|
||||||
|
return `${strWithHyphen(prefix)}${strWithHyphen(simplifiedStr)}${uuid4()}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Append a hyphen to a string only if the string isn't empty
|
||||||
|
*
|
||||||
|
* @param {string} str
|
||||||
|
* @returns {string}
|
||||||
|
* @todo This could be moved into a string utility file/class.
|
||||||
|
*/
|
||||||
|
function strWithHyphen(str = '') {
|
||||||
|
return str.length > 0 ? `${str}-` : '';
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
setConf,
|
setConf,
|
||||||
draw,
|
draw,
|
||||||
|
@ -24,6 +24,7 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multili
|
|||||||
[\n]+ return 'NEWLINE';
|
[\n]+ return 'NEWLINE';
|
||||||
\s+ /* skip whitespace */
|
\s+ /* skip whitespace */
|
||||||
[\s]+ return 'SPACE';
|
[\s]+ return 'SPACE';
|
||||||
|
\"[^"%\r\n\v\b\\]+\" return 'ENTITY_NAME';
|
||||||
\"[^"]*\" return 'WORD';
|
\"[^"]*\" return 'WORD';
|
||||||
"erDiagram" return 'ER_DIAGRAM';
|
"erDiagram" return 'ER_DIAGRAM';
|
||||||
"{" { this.begin("block"); return 'BLOCK_START'; }
|
"{" { this.begin("block"); return 'BLOCK_START'; }
|
||||||
@ -102,8 +103,8 @@ statement
|
|||||||
;
|
;
|
||||||
|
|
||||||
entityName
|
entityName
|
||||||
: 'ALPHANUM' { $$ = $1; /*console.log('Entity: ' + $1);*/ }
|
: 'ALPHANUM' { $$ = $1; }
|
||||||
| 'ALPHANUM' '.' entityName { $$ = $1 + $2 + $3; }
|
| 'ENTITY_NAME' { $$ = $1.replace(/"/g, ''); }
|
||||||
;
|
;
|
||||||
|
|
||||||
attributes
|
attributes
|
||||||
@ -156,6 +157,7 @@ relType
|
|||||||
|
|
||||||
role
|
role
|
||||||
: 'WORD' { $$ = $1.replace(/"/g, ''); }
|
: 'WORD' { $$ = $1.replace(/"/g, ''); }
|
||||||
|
| 'ENTITY_NAME' { $$ = $1.replace(/"/g, ''); }
|
||||||
| 'ALPHANUM' { $$ = $1; }
|
| 'ALPHANUM' { $$ = $1; }
|
||||||
;
|
;
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { setConfig } from '../../../config';
|
import { setConfig } from '../../../config';
|
||||||
import erDb from '../erDb';
|
import erDb from '../erDb';
|
||||||
import erDiagram from './erDiagram';
|
import erDiagram from './erDiagram'; // jison file
|
||||||
|
|
||||||
setConfig({
|
setConfig({
|
||||||
securityLevel: 'strict',
|
securityLevel: 'strict',
|
||||||
@ -21,14 +21,118 @@ describe('when parsing ER diagram it...', function () {
|
|||||||
expect(erDb.getRelationships().length).toBe(0);
|
expect(erDb.getRelationships().length).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should allow hyphens and underscores in entity names', function () {
|
describe('entity name', () => {
|
||||||
const line1 = 'DUCK-BILLED-PLATYPUS';
|
it('cannot be empty quotes ""', function () {
|
||||||
const line2 = 'CHARACTER_SET';
|
const name = '""';
|
||||||
erDiagram.parser.parse(`erDiagram\n${line1}\n${line2}`);
|
expect(() => {
|
||||||
|
erDiagram.parser.parse(`erDiagram\n ${name}\n`);
|
||||||
|
const entities = erDb.getEntities();
|
||||||
|
expect(entities.hasOwnProperty(name)).toBe(false);
|
||||||
|
}).toThrow();
|
||||||
|
});
|
||||||
|
describe('has non A-Za-z0-9_- chars', function () {
|
||||||
|
// these were entered using the Mac keyboard utility.
|
||||||
|
const chars =
|
||||||
|
"~ ` ! @ # $ ^ & * ( ) - _ = + [ ] { } | / ; : ' . ? ¡ ⁄ ™ € £ ‹ ¢ › ∞ fi § ‡ • ° ª · º ‚ ≠ ± œ Œ ∑ „ ® † ˇ ¥ Á ¨ ˆ ˆ Ø π ∏ “ « » å Å ß Í ∂ Î ƒ Ï © ˙ Ó ∆ Ô ˚ ¬ Ò … Ú æ Æ Ω ¸ ≈ π ˛ ç Ç √ ◊ ∫ ı ˜ µ  ≤ ¯ ≥ ˘ ÷ ¿";
|
||||||
|
const allowed = chars.split(' ');
|
||||||
|
|
||||||
const entities = erDb.getEntities();
|
allowed.forEach((allowedChar) => {
|
||||||
expect(entities.hasOwnProperty('DUCK-BILLED-PLATYPUS')).toBe(true);
|
const singleOccurrence = `Blo${allowedChar}rf`;
|
||||||
expect(entities.hasOwnProperty('CHARACTER_SET')).toBe(true);
|
const repeatedOccurrence = `Blo${allowedChar}${allowedChar}rf`;
|
||||||
|
const cannontStartWith = `${allowedChar}Blorf`;
|
||||||
|
const endsWith = `Blorf${allowedChar}`;
|
||||||
|
|
||||||
|
it(`${singleOccurrence} fails if not surrounded by quotes`, function () {
|
||||||
|
const name = singleOccurrence;
|
||||||
|
expect(() => {
|
||||||
|
erDiagram.parser.parse(`erDiagram\n ${name}\n`);
|
||||||
|
const entities = erDb.getEntities();
|
||||||
|
expect(entities.hasOwnProperty(name)).toBe(false);
|
||||||
|
}).toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`"${singleOccurrence}" single occurrence`, function () {
|
||||||
|
const name = singleOccurrence;
|
||||||
|
erDiagram.parser.parse(`erDiagram\n "${name}"\n`);
|
||||||
|
const entities = erDb.getEntities();
|
||||||
|
expect(entities.hasOwnProperty(name)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`"${repeatedOccurrence}" repeated occurrence`, function () {
|
||||||
|
const name = repeatedOccurrence;
|
||||||
|
erDiagram.parser.parse(`erDiagram\n "${name}"\n`);
|
||||||
|
const entities = erDb.getEntities();
|
||||||
|
expect(entities.hasOwnProperty(name)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`"${singleOccurrence}" ends with`, function () {
|
||||||
|
const name = endsWith;
|
||||||
|
erDiagram.parser.parse(`erDiagram\n "${name}"\n`);
|
||||||
|
const entities = erDb.getEntities();
|
||||||
|
expect(entities.hasOwnProperty(name)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`"${cannontStartWith}" cannot start with the character`, function () {
|
||||||
|
const name = repeatedOccurrence;
|
||||||
|
expect(() => {
|
||||||
|
erDiagram.parser.parse(`erDiagram\n "${name}"\n`);
|
||||||
|
const entities = erDb.getEntities();
|
||||||
|
expect(entities.hasOwnProperty(name)).toBe(false);
|
||||||
|
}).toThrow();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const allCombined = allowed.join('');
|
||||||
|
|
||||||
|
it(`a${allCombined} (all non-alphanumerics) in one, starting with 'a'`, function () {
|
||||||
|
const name = 'a' + allCombined;
|
||||||
|
erDiagram.parser.parse(`erDiagram\n "${name}"\n`);
|
||||||
|
const entities = erDb.getEntities();
|
||||||
|
expect(entities.hasOwnProperty(name)).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('cannot contain % because it interfers with parsing comments', function () {
|
||||||
|
expect(() => {
|
||||||
|
erDiagram.parser.parse(`erDiagram\n "Blo%rf"\n`);
|
||||||
|
const entities = erDb.getEntities();
|
||||||
|
expect(entities.hasOwnProperty(name)).toBe(false);
|
||||||
|
}).toThrow();
|
||||||
|
});
|
||||||
|
it('cannot contain \\ because it could start and escape code', function () {
|
||||||
|
expect(() => {
|
||||||
|
erDiagram.parser.parse(`erDiagram\n "Blo\\rf"\n`);
|
||||||
|
const entities = erDb.getEntities();
|
||||||
|
expect(entities.hasOwnProperty(name)).toBe(false);
|
||||||
|
}).toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('cannot newline, backspace, or vertical characters', function () {
|
||||||
|
const disallowed = ['\n', '\r', '\b', '\v'];
|
||||||
|
disallowed.forEach((badChar) => {
|
||||||
|
const badName = `Blo${badChar}rf`;
|
||||||
|
expect(() => {
|
||||||
|
erDiagram.parser.parse(`erDiagram\n "${badName}"\n`);
|
||||||
|
const entities = erDb.getEntities();
|
||||||
|
expect(entities.hasOwnProperty(badName)).toBe(false);
|
||||||
|
}).toThrow();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// skip this: jison cannot handle non-english letters
|
||||||
|
it.skip('[skipped test] can contain àáâäæãåā', function () {
|
||||||
|
const beyondEnglishName = 'DUCK-àáâäæãåā';
|
||||||
|
erDiagram.parser.parse(`erDiagram\n${beyondEnglishName}\n`);
|
||||||
|
const entities = erDb.getEntities();
|
||||||
|
expect(entities.hasOwnProperty(beyondEnglishName)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can contain - _ without needing ""', function () {
|
||||||
|
const hyphensUnderscore = 'DUCK-BILLED_PLATYPUS';
|
||||||
|
erDiagram.parser.parse(`erDiagram\n${hyphensUnderscore}\n`);
|
||||||
|
const entities = erDb.getEntities();
|
||||||
|
expect(entities.hasOwnProperty(hyphensUnderscore)).toBe(true);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should allow an entity with a single attribute to be defined', function () {
|
it('should allow an entity with a single attribute to be defined', function () {
|
||||||
@ -447,27 +551,23 @@ describe('when parsing ER diagram it...', function () {
|
|||||||
}).toThrowError();
|
}).toThrowError();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should allow an empty quoted label', function () {
|
describe('relationship labels', function () {
|
||||||
erDiagram.parser.parse('erDiagram\nCUSTOMER ||--|{ ORDER : ""');
|
it('should allow an empty quoted label', function () {
|
||||||
const rels = erDb.getRelationships();
|
erDiagram.parser.parse('erDiagram\nCUSTOMER ||--|{ ORDER : ""');
|
||||||
expect(rels[0].roleA).toBe('');
|
const rels = erDb.getRelationships();
|
||||||
});
|
expect(rels[0].roleA).toBe('');
|
||||||
|
});
|
||||||
|
|
||||||
it('should allow an non-empty quoted label', function () {
|
it('should allow an non-empty quoted label', function () {
|
||||||
erDiagram.parser.parse('erDiagram\nCUSTOMER ||--|{ ORDER : "places"');
|
erDiagram.parser.parse('erDiagram\nCUSTOMER ||--|{ ORDER : "places"');
|
||||||
const rels = erDb.getRelationships();
|
const rels = erDb.getRelationships();
|
||||||
expect(rels[0].roleA).toBe('places');
|
expect(rels[0].roleA).toBe('places');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should allow an non-empty unquoted label', function () {
|
it('should allow an non-empty unquoted label', function () {
|
||||||
erDiagram.parser.parse('erDiagram\nCUSTOMER ||--|{ ORDER : places');
|
erDiagram.parser.parse('erDiagram\nCUSTOMER ||--|{ ORDER : places');
|
||||||
const rels = erDb.getRelationships();
|
const rels = erDb.getRelationships();
|
||||||
expect(rels[0].roleA).toBe('places');
|
expect(rels[0].roleA).toBe('places');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should allow an entity name with a dot', function () {
|
|
||||||
erDiagram.parser.parse('erDiagram\nCUSTOMER.PROP ||--|{ ORDER : places');
|
|
||||||
const rels = erDb.getRelationships();
|
|
||||||
expect(rels[0].entityA).toBe('CUSTOMER.PROP');
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -37,7 +37,7 @@
|
|||||||
[0-9]+(?=[ \n]+) return 'NUM';
|
[0-9]+(?=[ \n]+) return 'NUM';
|
||||||
"participant" { this.begin('ID'); return 'participant'; }
|
"participant" { this.begin('ID'); return 'participant'; }
|
||||||
"actor" { this.begin('ID'); return 'participant_actor'; }
|
"actor" { this.begin('ID'); return 'participant_actor'; }
|
||||||
<ID>[^\->:\n,;]+?(?=((?!\n)\s)+"as"(?!\n)\s|[#\n;]|$) { yytext = yytext.trim(); this.begin('ALIAS'); return 'ACTOR'; }
|
<ID>[^\->:\n,;]+?([\-]*[^\->:\n,;]+?)*?(?=((?!\n)\s)+"as"(?!\n)\s|[#\n;]|$) { yytext = yytext.trim(); this.begin('ALIAS'); return 'ACTOR'; }
|
||||||
<ALIAS>"as" { this.popState(); this.popState(); this.begin('LINE'); return 'AS'; }
|
<ALIAS>"as" { this.popState(); this.popState(); this.begin('LINE'); return 'AS'; }
|
||||||
<ALIAS>(?:) { this.popState(); this.popState(); return 'NEWLINE'; }
|
<ALIAS>(?:) { this.popState(); this.popState(); return 'NEWLINE'; }
|
||||||
"loop" { this.begin('LINE'); return 'loop'; }
|
"loop" { this.begin('LINE'); return 'loop'; }
|
||||||
|
@ -249,7 +249,7 @@ Bob-->Alice-in-Wonderland:I am good thanks!`;
|
|||||||
mermaidAPI.parse(str);
|
mermaidAPI.parse(str);
|
||||||
const actors = diagram.db.getActors();
|
const actors = diagram.db.getActors();
|
||||||
expect(actors['Alice-in-Wonderland'].description).toBe('Alice-in-Wonderland');
|
expect(actors['Alice-in-Wonderland'].description).toBe('Alice-in-Wonderland');
|
||||||
actors.Bob.description = 'Bob';
|
expect(actors.Bob.description).toBe('Bob');
|
||||||
|
|
||||||
const messages = diagram.db.getMessages();
|
const messages = diagram.db.getMessages();
|
||||||
|
|
||||||
@ -257,6 +257,28 @@ Bob-->Alice-in-Wonderland:I am good thanks!`;
|
|||||||
expect(messages[0].from).toBe('Alice-in-Wonderland');
|
expect(messages[0].from).toBe('Alice-in-Wonderland');
|
||||||
expect(messages[1].from).toBe('Bob');
|
expect(messages[1].from).toBe('Bob');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should handle dashes in participant names', function () {
|
||||||
|
const str = `
|
||||||
|
sequenceDiagram
|
||||||
|
participant Alice-in-Wonderland
|
||||||
|
participant Bob
|
||||||
|
Alice-in-Wonderland->Bob:Hello Bob, how are - you?
|
||||||
|
Bob-->Alice-in-Wonderland:I am good thanks!`;
|
||||||
|
|
||||||
|
mermaidAPI.parse(str);
|
||||||
|
const actors = diagram.db.getActors();
|
||||||
|
expect(Object.keys(actors)).toEqual(['Alice-in-Wonderland', 'Bob']);
|
||||||
|
expect(actors['Alice-in-Wonderland'].description).toBe('Alice-in-Wonderland');
|
||||||
|
expect(actors.Bob.description).toBe('Bob');
|
||||||
|
|
||||||
|
const messages = diagram.db.getMessages();
|
||||||
|
|
||||||
|
expect(messages.length).toBe(2);
|
||||||
|
expect(messages[0].from).toBe('Alice-in-Wonderland');
|
||||||
|
expect(messages[1].from).toBe('Bob');
|
||||||
|
});
|
||||||
|
|
||||||
it('should alias participants', function () {
|
it('should alias participants', function () {
|
||||||
const str = `
|
const str = `
|
||||||
sequenceDiagram
|
sequenceDiagram
|
||||||
|
10
yarn.lock
10
yarn.lock
@ -2187,6 +2187,11 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.6.tgz#250a7b16c3b91f672a24552ec64678eeb1d3a08d"
|
resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.6.tgz#250a7b16c3b91f672a24552ec64678eeb1d3a08d"
|
||||||
integrity sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==
|
integrity sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==
|
||||||
|
|
||||||
|
"@types/uuid@^8.3.4":
|
||||||
|
version "8.3.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc"
|
||||||
|
integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==
|
||||||
|
|
||||||
"@types/yauzl@^2.9.1":
|
"@types/yauzl@^2.9.1":
|
||||||
version "2.10.0"
|
version "2.10.0"
|
||||||
resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.10.0.tgz#b3248295276cf8c6f153ebe6a9aba0c988cb2599"
|
resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.10.0.tgz#b3248295276cf8c6f153ebe6a9aba0c988cb2599"
|
||||||
@ -10157,6 +10162,11 @@ uuid@^3.3.2:
|
|||||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
|
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
|
||||||
integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
|
integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
|
||||||
|
|
||||||
|
uuid@^9.0.0:
|
||||||
|
version "9.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.0.tgz#592f550650024a38ceb0c562f2f6aa435761efb5"
|
||||||
|
integrity sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==
|
||||||
|
|
||||||
uvu@^0.5.0:
|
uvu@^0.5.0:
|
||||||
version "0.5.6"
|
version "0.5.6"
|
||||||
resolved "https://registry.yarnpkg.com/uvu/-/uvu-0.5.6.tgz#2754ca20bcb0bb59b64e9985e84d2e81058502df"
|
resolved "https://registry.yarnpkg.com/uvu/-/uvu-0.5.6.tgz#2754ca20bcb0bb59b64e9985e84d2e81058502df"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user