mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-09-12 20:09:46 +02:00
Merge pull request #1519 from cmmoran/develop
Utils - memoize, calculateTextDimensions (and friends), assignWithDepth, etc
This commit is contained in:
334
src/utils.js
334
src/utils.js
@@ -9,10 +9,13 @@ import {
|
|||||||
curveNatural,
|
curveNatural,
|
||||||
curveStep,
|
curveStep,
|
||||||
curveStepAfter,
|
curveStepAfter,
|
||||||
curveStepBefore
|
curveStepBefore,
|
||||||
|
select
|
||||||
} from 'd3';
|
} from 'd3';
|
||||||
import { logger } from './logger';
|
import { logger } from './logger';
|
||||||
import { sanitizeUrl } from '@braintree/sanitize-url';
|
import { sanitizeUrl } from '@braintree/sanitize-url';
|
||||||
|
import common from './diagrams/common/common';
|
||||||
|
import cryptoRandomString from 'crypto-random-string';
|
||||||
|
|
||||||
// Effectively an enum of the supported curve types, accessible by name
|
// Effectively an enum of the supported curve types, accessible by name
|
||||||
const d3CurveTypes = {
|
const d3CurveTypes = {
|
||||||
@@ -60,17 +63,29 @@ const anyComment = /\s*%%.*\n/gm;
|
|||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* @param {string} text The text defining the graph
|
* @param {string} text The text defining the graph
|
||||||
* @returns {object} the json object representing the init to pass to mermaid.initialize()
|
* @returns {object} the json object representing the init passed to mermaid.initialize()
|
||||||
*/
|
*/
|
||||||
export const detectInit = function(text) {
|
export const detectInit = function(text) {
|
||||||
let inits = detectDirective(text, /(?:init\b)|(?:initialize\b)/);
|
let inits = detectDirective(text, /(?:init\b)|(?:initialize\b)/);
|
||||||
let results = {};
|
let results = {};
|
||||||
if (Array.isArray(inits)) {
|
if (Array.isArray(inits)) {
|
||||||
let args = inits.map(init => init.args);
|
let args = inits.map(init => init.args);
|
||||||
results = Object.assign(results, ...args);
|
results = assignWithDepth(results, [...args]);
|
||||||
} else {
|
} else {
|
||||||
results = inits.args;
|
results = inits.args;
|
||||||
}
|
}
|
||||||
|
if (results) {
|
||||||
|
let type = detectType(text);
|
||||||
|
['config'].forEach(prop => {
|
||||||
|
if (typeof results[prop] !== 'undefined') {
|
||||||
|
if (type === 'flowchart-v2') {
|
||||||
|
type = 'flowchart';
|
||||||
|
}
|
||||||
|
results[type] = results[prop];
|
||||||
|
delete results[prop];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
return results;
|
return results;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -91,8 +106,8 @@ export const detectInit = function(text) {
|
|||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* @param {string} text The text defining the graph
|
* @param {string} text The text defining the graph
|
||||||
* @param {string|RegExp} type The directive to return (default: null
|
* @param {string|RegExp} type The directive to return (default: null)
|
||||||
* @returns {object | Array} An object or Array representing the directive(s): { type: string, args: object|null } matchd by the input type
|
* @returns {object | Array} An object or Array representing the directive(s): { type: string, args: object|null } matched by the input type
|
||||||
* if a single directive was found, that directive object will be returned.
|
* if a single directive was found, that directive object will be returned.
|
||||||
*/
|
*/
|
||||||
export const detectDirective = function(text, type = null) {
|
export const detectDirective = function(text, type = null) {
|
||||||
@@ -206,6 +221,20 @@ export const detectType = function(text) {
|
|||||||
return 'flowchart';
|
return 'flowchart';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const memoize = (fn, resolver) => {
|
||||||
|
let cache = {};
|
||||||
|
return (...args) => {
|
||||||
|
let n = resolver ? resolver.apply(this, args) : args[0];
|
||||||
|
if (n in cache) {
|
||||||
|
return cache[n];
|
||||||
|
} else {
|
||||||
|
let result = fn(...args);
|
||||||
|
cache[n] = result;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @function isSubstringInArray
|
* @function isSubstringInArray
|
||||||
* Detects whether a substring in present in a given array
|
* Detects whether a substring in present in a given array
|
||||||
@@ -241,13 +270,13 @@ export const formatUrl = (linkStr, config) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const runFunc = (functionName, ...params) => {
|
export const runFunc = (functionName, ...params) => {
|
||||||
var arrPaths = functionName.split('.');
|
const arrPaths = functionName.split('.');
|
||||||
|
|
||||||
var len = arrPaths.length - 1;
|
const len = arrPaths.length - 1;
|
||||||
var fnName = arrPaths[len];
|
const fnName = arrPaths[len];
|
||||||
|
|
||||||
var obj = window;
|
let obj = window;
|
||||||
for (var i = 0; i < len; i++) {
|
for (let i = 0; i < len; i++) {
|
||||||
obj = obj[arrPaths[i]];
|
obj = obj[arrPaths[i]];
|
||||||
if (!obj) return;
|
if (!obj) return;
|
||||||
}
|
}
|
||||||
@@ -268,10 +297,8 @@ const traverseEdge = points => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Traverse half of total distance along points
|
// Traverse half of total distance along points
|
||||||
const distanceToLabel = totalDistance / 2;
|
let remainingDistance = totalDistance / 2;
|
||||||
|
let center = undefined;
|
||||||
let remainingDistance = distanceToLabel;
|
|
||||||
let center;
|
|
||||||
prevPoint = undefined;
|
prevPoint = undefined;
|
||||||
points.forEach(point => {
|
points.forEach(point => {
|
||||||
if (prevPoint && !center) {
|
if (prevPoint && !center) {
|
||||||
@@ -298,8 +325,7 @@ const traverseEdge = points => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const calcLabelPosition = points => {
|
const calcLabelPosition = points => {
|
||||||
const p = traverseEdge(points);
|
return traverseEdge(points);
|
||||||
return p;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const calcCardinalityPosition = (isRelationTypePresent, points, initialPosition) => {
|
const calcCardinalityPosition = (isRelationTypePresent, points, initialPosition) => {
|
||||||
@@ -317,7 +343,7 @@ const calcCardinalityPosition = (isRelationTypePresent, points, initialPosition)
|
|||||||
const distanceToCardinalityPoint = 25;
|
const distanceToCardinalityPoint = 25;
|
||||||
|
|
||||||
let remainingDistance = distanceToCardinalityPoint;
|
let remainingDistance = distanceToCardinalityPoint;
|
||||||
let center;
|
let center = { x: 0, y: 0 };
|
||||||
prevPoint = undefined;
|
prevPoint = undefined;
|
||||||
points.forEach(point => {
|
points.forEach(point => {
|
||||||
if (prevPoint && !center) {
|
if (prevPoint && !center) {
|
||||||
@@ -382,7 +408,279 @@ export const generateId = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const random = options => {
|
||||||
|
return cryptoRandomString(options);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function assignWithDepth
|
||||||
|
* Extends the functionality of {@link ObjectConstructor.assign} with the ability to merge arbitrary-depth objects
|
||||||
|
* For each key in src with path `k` (recursively) performs an Object.assign(dst[`k`], src[`k`]) with
|
||||||
|
* a slight change from the typical handling of undefined for dst[`k`]: instead of raising an error,
|
||||||
|
* dst[`k`] is auto-initialized to {} and effectively merged with src[`k`]
|
||||||
|
* <p>
|
||||||
|
* Additionally, dissimilar types will not clobber unless the config.clobber parameter === true. Example:
|
||||||
|
* ```
|
||||||
|
* let config_0 = { foo: { bar: 'bar' }, bar: 'foo' };
|
||||||
|
* let config_1 = { foo: 'foo', bar: 'bar' };
|
||||||
|
* let result = assignWithDepth(config_0, config_1);
|
||||||
|
* console.log(result);
|
||||||
|
* //-> result: { foo: { bar: 'bar' }, bar: 'bar' }
|
||||||
|
* ```
|
||||||
|
* <p>
|
||||||
|
* Traditional Object.assign would have clobbered foo in config_0 with foo in config_1.
|
||||||
|
* <p>
|
||||||
|
* If src is a destructured array of objects and dst is not an array, assignWithDepth will apply each element of src to dst
|
||||||
|
* in order.
|
||||||
|
* @param dst:any - the destination of the merge
|
||||||
|
* @param src:any - the source object(s) to merge into destination
|
||||||
|
* @param config:{ depth: number, clobber: boolean } - depth: depth to traverse within src and dst for merging -
|
||||||
|
* clobber: should dissimilar types clobber (default: { depth: 2, clobber: false })
|
||||||
|
* @returns {*}
|
||||||
|
*/
|
||||||
|
export const assignWithDepth = function(dst, src, config) {
|
||||||
|
const { depth, clobber } = Object.assign({ depth: 2, clobber: false }, config);
|
||||||
|
if (Array.isArray(src) && !Array.isArray(dst)) {
|
||||||
|
src.forEach(s => assignWithDepth(dst, s, config));
|
||||||
|
return dst;
|
||||||
|
}
|
||||||
|
if (typeof dst === 'undefined' || depth <= 0) {
|
||||||
|
if (dst !== undefined && dst !== null && typeof dst === 'object' && typeof src === 'object') {
|
||||||
|
return Object.assign(dst, src);
|
||||||
|
} else {
|
||||||
|
return src;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (typeof src !== 'undefined' && typeof dst === 'object' && typeof src === 'object') {
|
||||||
|
Object.keys(src).forEach(key => {
|
||||||
|
if (
|
||||||
|
typeof src[key] === 'object' &&
|
||||||
|
(dst[key] === undefined || typeof dst[key] === 'object')
|
||||||
|
) {
|
||||||
|
if (dst[key] === undefined) {
|
||||||
|
dst[key] = Array.isArray(src[key]) ? [] : {};
|
||||||
|
}
|
||||||
|
dst[key] = assignWithDepth(dst[key], src[key], { depth: depth - 1, clobber });
|
||||||
|
} else if (clobber || (typeof dst[key] !== 'object' && typeof src[key] !== 'object')) {
|
||||||
|
dst[key] = src[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return dst;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getTextObj = function() {
|
||||||
|
return {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
fill: undefined,
|
||||||
|
anchor: 'start',
|
||||||
|
style: '#666',
|
||||||
|
width: 100,
|
||||||
|
height: 100,
|
||||||
|
textMargin: 0,
|
||||||
|
rx: 0,
|
||||||
|
ry: 0,
|
||||||
|
valign: undefined
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const drawSimpleText = function(elem, textData) {
|
||||||
|
// Remove and ignore br:s
|
||||||
|
const nText = textData.text.replace(common.lineBreakRegex, ' ');
|
||||||
|
|
||||||
|
const textElem = elem.append('text');
|
||||||
|
textElem.attr('x', textData.x);
|
||||||
|
textElem.attr('y', textData.y);
|
||||||
|
textElem.style('text-anchor', textData.anchor);
|
||||||
|
textElem.style('font-family', textData.fontFamily);
|
||||||
|
textElem.style('font-size', textData.fontSize);
|
||||||
|
textElem.style('font-weight', textData.fontWeight);
|
||||||
|
textElem.attr('fill', textData.fill);
|
||||||
|
if (typeof textData.class !== 'undefined') {
|
||||||
|
textElem.attr('class', textData.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
const span = textElem.append('tspan');
|
||||||
|
span.attr('x', textData.x + textData.textMargin * 2);
|
||||||
|
span.attr('fill', textData.fill);
|
||||||
|
span.text(nText);
|
||||||
|
|
||||||
|
return textElem;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const wrapLabel = memoize(
|
||||||
|
(label, maxWidth, config) => {
|
||||||
|
if (!label) {
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
config = Object.assign(
|
||||||
|
{ fontSize: 12, fontWeight: 400, fontFamily: 'Arial', joinWith: '<br/>' },
|
||||||
|
config
|
||||||
|
);
|
||||||
|
if (common.lineBreakRegex.test(label)) {
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
const words = label.split(' ');
|
||||||
|
const completedLines = [];
|
||||||
|
let nextLine = '';
|
||||||
|
words.forEach((word, index) => {
|
||||||
|
const wordLength = calculateTextWidth(`${word} `, config);
|
||||||
|
const nextLineLength = calculateTextWidth(nextLine, config);
|
||||||
|
if (wordLength > maxWidth) {
|
||||||
|
const { hyphenatedStrings, remainingWord } = breakString(word, maxWidth, '-', config);
|
||||||
|
completedLines.push(nextLine, ...hyphenatedStrings);
|
||||||
|
nextLine = remainingWord;
|
||||||
|
} else if (nextLineLength + wordLength >= maxWidth) {
|
||||||
|
completedLines.push(nextLine);
|
||||||
|
nextLine = word;
|
||||||
|
} else {
|
||||||
|
nextLine = [nextLine, word].filter(Boolean).join(' ');
|
||||||
|
}
|
||||||
|
const currentWord = index + 1;
|
||||||
|
const isLastWord = currentWord === words.length;
|
||||||
|
if (isLastWord) {
|
||||||
|
completedLines.push(nextLine);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return completedLines.filter(line => line !== '').join(config.joinWith);
|
||||||
|
},
|
||||||
|
(label, maxWidth, config) =>
|
||||||
|
`${label}-${maxWidth}-${config.fontSize}-${config.fontWeight}-${config.fontFamily}-${config.joinWith}`
|
||||||
|
);
|
||||||
|
|
||||||
|
const breakString = memoize(
|
||||||
|
(word, maxWidth, hyphenCharacter = '-', config) => {
|
||||||
|
config = Object.assign(
|
||||||
|
{ fontSize: 12, fontWeight: 400, fontFamily: 'Arial', margin: 0 },
|
||||||
|
config
|
||||||
|
);
|
||||||
|
const characters = word.split('');
|
||||||
|
const lines = [];
|
||||||
|
let currentLine = '';
|
||||||
|
characters.forEach((character, index) => {
|
||||||
|
const nextLine = `${currentLine}${character}`;
|
||||||
|
const lineWidth = calculateTextWidth(nextLine, config);
|
||||||
|
if (lineWidth >= maxWidth) {
|
||||||
|
const currentCharacter = index + 1;
|
||||||
|
const isLastLine = characters.length === currentCharacter;
|
||||||
|
const hyphenatedNextLine = `${nextLine}${hyphenCharacter}`;
|
||||||
|
lines.push(isLastLine ? nextLine : hyphenatedNextLine);
|
||||||
|
currentLine = '';
|
||||||
|
} else {
|
||||||
|
currentLine = nextLine;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return { hyphenatedStrings: lines, remainingWord: currentLine };
|
||||||
|
},
|
||||||
|
(word, maxWidth, hyphenCharacter = '-', config) =>
|
||||||
|
`${word}-${maxWidth}-${hyphenCharacter}-${config.fontSize}-${config.fontWeight}-${config.fontFamily}`
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This calculates the text's height, taking into account the wrap breaks and
|
||||||
|
* both the statically configured height, width, and the length of the text (in pixels).
|
||||||
|
*
|
||||||
|
* If the wrapped text text has greater height, we extend the height, so it's
|
||||||
|
* value won't overflow.
|
||||||
|
*
|
||||||
|
* @return - The height for the given text
|
||||||
|
* @param text the text to measure
|
||||||
|
* @param config - the config for fontSize, fontFamily, and fontWeight all impacting the resulting size
|
||||||
|
*/
|
||||||
|
export const calculateTextHeight = function(text, config) {
|
||||||
|
config = Object.assign(
|
||||||
|
{ fontSize: 12, fontWeight: 400, fontFamily: 'Arial', margin: 15 },
|
||||||
|
config
|
||||||
|
);
|
||||||
|
return calculateTextDimensions(text, config).height;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This calculates the width of the given text, font size and family.
|
||||||
|
*
|
||||||
|
* @return - The width for the given text
|
||||||
|
* @param text - The text to calculate the width of
|
||||||
|
* @param config - the config for fontSize, fontFamily, and fontWeight all impacting the resulting size
|
||||||
|
*/
|
||||||
|
export const calculateTextWidth = function(text, config) {
|
||||||
|
config = Object.assign({ fontSize: 12, fontWeight: 400, fontFamily: 'Arial' }, config);
|
||||||
|
return calculateTextDimensions(text, config).width;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This calculates the dimensions of the given text, font size, font family, font weight, and margins.
|
||||||
|
*
|
||||||
|
* @return - The width for the given text
|
||||||
|
* @param text - The text to calculate the width of
|
||||||
|
* @param config - the config for fontSize, fontFamily, fontWeight, and margin all impacting the resulting size
|
||||||
|
*/
|
||||||
|
export const calculateTextDimensions = memoize(
|
||||||
|
function(text, config) {
|
||||||
|
config = Object.assign({ fontSize: 12, fontWeight: 400, fontFamily: 'Arial' }, config);
|
||||||
|
const { fontSize, fontFamily, fontWeight } = config;
|
||||||
|
if (!text) {
|
||||||
|
return { width: 0, height: 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
// We can't really know if the user supplied font family will render on the user agent;
|
||||||
|
// thus, we'll take the max width between the user supplied font family, and a default
|
||||||
|
// of sans-serif.
|
||||||
|
const fontFamilies = ['sans-serif', fontFamily];
|
||||||
|
const lines = text.split(common.lineBreakRegex);
|
||||||
|
let dims = [];
|
||||||
|
|
||||||
|
const body = select('body');
|
||||||
|
// We don't want to leak DOM elements - if a removal operation isn't available
|
||||||
|
// for any reason, do not continue.
|
||||||
|
if (!body.remove) {
|
||||||
|
return { width: 0, height: 0, lineHeight: 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
const g = body.append('svg');
|
||||||
|
|
||||||
|
for (let fontFamily of fontFamilies) {
|
||||||
|
let cheight = 0;
|
||||||
|
let dim = { width: 0, height: 0, lineHeight: 0 };
|
||||||
|
for (let line of lines) {
|
||||||
|
const textObj = getTextObj();
|
||||||
|
textObj.text = line;
|
||||||
|
const textElem = drawSimpleText(g, textObj)
|
||||||
|
.style('font-size', fontSize)
|
||||||
|
.style('font-weight', fontWeight)
|
||||||
|
.style('font-family', fontFamily);
|
||||||
|
|
||||||
|
let bBox = (textElem._groups || textElem)[0][0].getBBox();
|
||||||
|
dim.width = Math.round(Math.max(dim.width, bBox.width));
|
||||||
|
cheight = Math.round(bBox.height);
|
||||||
|
dim.height += cheight;
|
||||||
|
dim.lineHeight = Math.round(Math.max(dim.lineHeight, cheight));
|
||||||
|
}
|
||||||
|
dims.push(dim);
|
||||||
|
}
|
||||||
|
|
||||||
|
g.remove();
|
||||||
|
|
||||||
|
let index =
|
||||||
|
isNaN(dims[1].height) ||
|
||||||
|
isNaN(dims[1].width) ||
|
||||||
|
isNaN(dims[1].lineHeight) ||
|
||||||
|
(dims[0].height > dims[1].height &&
|
||||||
|
dims[0].width > dims[1].width &&
|
||||||
|
dims[0].lineHeight > dims[1].lineHeight)
|
||||||
|
? 0
|
||||||
|
: 1;
|
||||||
|
return dims[index];
|
||||||
|
},
|
||||||
|
(text, config) => `${text}-${config.fontSize}-${config.fontWeight}-${config.fontFamily}`
|
||||||
|
);
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
assignWithDepth,
|
||||||
|
wrapLabel,
|
||||||
|
calculateTextHeight,
|
||||||
|
calculateTextWidth,
|
||||||
|
calculateTextDimensions,
|
||||||
detectInit,
|
detectInit,
|
||||||
detectDirective,
|
detectDirective,
|
||||||
detectType,
|
detectType,
|
||||||
@@ -393,5 +691,7 @@ export default {
|
|||||||
formatUrl,
|
formatUrl,
|
||||||
getStylesFromArray,
|
getStylesFromArray,
|
||||||
generateId,
|
generateId,
|
||||||
|
random,
|
||||||
|
memoize,
|
||||||
runFunc
|
runFunc
|
||||||
};
|
};
|
||||||
|
@@ -1,6 +1,93 @@
|
|||||||
/* eslint-env jasmine */
|
/* eslint-env jasmine */
|
||||||
import utils from './utils';
|
import utils from './utils';
|
||||||
|
|
||||||
|
describe('when assignWithDepth: should merge objects within objects', function() {
|
||||||
|
it('should handle simple, depth:1 types (identity)', function() {
|
||||||
|
let config_0 = { foo: 'bar', bar: 0 };
|
||||||
|
let config_1 = { foo: 'bar', bar: 0 };
|
||||||
|
let result = utils.assignWithDepth(config_0, config_1);
|
||||||
|
expect(result).toEqual(config_1);
|
||||||
|
});
|
||||||
|
it('should handle simple, depth:1 types (dst: undefined)', function() {
|
||||||
|
let config_0 = undefined;
|
||||||
|
let config_1 = { foo: 'bar', bar: 0 };
|
||||||
|
let result = utils.assignWithDepth(config_0, config_1);
|
||||||
|
expect(result).toEqual(config_1);
|
||||||
|
});
|
||||||
|
it('should handle simple, depth:1 types (src: undefined)', function() {
|
||||||
|
let config_0 = { foo: 'bar', bar: 0 };
|
||||||
|
let config_1 = undefined;
|
||||||
|
let result = utils.assignWithDepth(config_0, config_1);
|
||||||
|
expect(result).toEqual(config_0);
|
||||||
|
});
|
||||||
|
it('should handle simple, depth:1 types (merge)', function() {
|
||||||
|
let config_0 = { foo: 'bar', bar: 0 };
|
||||||
|
let config_1 = { foo: 'foo' };
|
||||||
|
let result = utils.assignWithDepth(config_0, config_1);
|
||||||
|
expect(result).toEqual({ foo: 'foo', bar: 0});
|
||||||
|
});
|
||||||
|
it('should handle depth:2 types (dst: orphan)', function() {
|
||||||
|
let config_0 = { foo: 'bar', bar: { foo: 'bar' } };
|
||||||
|
let config_1 = { foo: 'bar' };
|
||||||
|
let result = utils.assignWithDepth(config_0, config_1);
|
||||||
|
expect(result).toEqual(config_0);
|
||||||
|
});
|
||||||
|
it('should handle depth:2 types (dst: object, src: simple type)', function() {
|
||||||
|
let config_0 = { foo: 'bar', bar: { foo: 'bar' } };
|
||||||
|
let config_1 = { foo: 'foo', bar: 'should NOT clobber'};
|
||||||
|
let result = utils.assignWithDepth(config_0, config_1);
|
||||||
|
expect(result).toEqual({ foo: 'foo', bar: { foo: 'bar' } } );
|
||||||
|
});
|
||||||
|
it('should handle depth:2 types (src: orphan)', function() {
|
||||||
|
let config_0 = { foo: 'bar' };
|
||||||
|
let config_1 = { foo: 'bar', bar: { foo: 'bar' } };
|
||||||
|
let result = utils.assignWithDepth(config_0, config_1);
|
||||||
|
expect(result).toEqual(config_1);
|
||||||
|
});
|
||||||
|
it('should handle depth:2 types (merge)', function() {
|
||||||
|
let config_0 = { foo: 'bar', bar: { foo: 'bar' }, boofar: 1 };
|
||||||
|
let config_1 = { foo: 'foo', bar: { bar: 0 }, foobar: 'foobar' };
|
||||||
|
let result = utils.assignWithDepth(config_0, config_1);
|
||||||
|
expect(result).toEqual({ foo: "foo", bar: { foo: "bar", bar: 0 }, foobar: "foobar", boofar: 1 });
|
||||||
|
});
|
||||||
|
it('should handle depth:3 types (merge with clobber because assignWithDepth::depth == 2)', function() {
|
||||||
|
let config_0 = { foo: 'bar', bar: { foo: 'bar', bar: { foo: { message: 'this', willbe: 'clobbered' } } }, boofar: 1 };
|
||||||
|
let config_1 = { foo: 'foo', bar: { foo: 'foo', bar: { foo: { message: 'clobbered other foo' } } }, foobar: 'foobar' };
|
||||||
|
let result = utils.assignWithDepth(config_0, config_1);
|
||||||
|
expect(result).toEqual({ foo: "foo", bar: { foo: 'foo', bar: { foo: { message: 'clobbered other foo' } } }, foobar: "foobar", boofar: 1 });
|
||||||
|
});
|
||||||
|
it('should handle depth:3 types (merge with clobber because assignWithDepth::depth == 1)', function() {
|
||||||
|
let config_0 = { foo: 'bar', bar: { foo: 'bar', bar: { foo: { message: '', willNotbe: 'present' }, bar: 'shouldNotBePresent' } }, boofar: 1 };
|
||||||
|
let config_1 = { foo: 'foo', bar: { foo: 'foo', bar: { foo: { message: 'this' } } }, foobar: 'foobar' };
|
||||||
|
let result = utils.assignWithDepth(config_0, config_1, { depth: 1 });
|
||||||
|
expect(result).toEqual({ foo: "foo", bar: { foo: 'foo', bar: { foo: { message: 'this' } } }, foobar: "foobar", boofar: 1 });
|
||||||
|
});
|
||||||
|
it('should handle depth:3 types (merge with no clobber because assignWithDepth::depth == 3)', function() {
|
||||||
|
let config_0 = { foo: 'bar', bar: { foo: 'bar', bar: { foo: { message: '', willbe: 'present' } } }, boofar: 1 };
|
||||||
|
let config_1 = { foo: 'foo', bar: { foo: 'foo', bar: { foo: { message: 'this' } } }, foobar: 'foobar' };
|
||||||
|
let result = utils.assignWithDepth(config_0, config_1, { depth: 3 });
|
||||||
|
expect(result).toEqual({ foo: "foo", bar: { foo: 'foo', bar: { foo: { message: 'this', willbe: 'present' } } }, foobar: "foobar", boofar: 1 });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('when memoizing', function() {
|
||||||
|
it('should return the same value', function() {
|
||||||
|
const fib = utils.memoize(function(n, canary) {
|
||||||
|
canary.flag = true;
|
||||||
|
if (n < 2){
|
||||||
|
return 1;
|
||||||
|
}else{
|
||||||
|
//We'll console.log a loader every time we have to recurse
|
||||||
|
return fib(n-2, canary) + fib(n-1, canary);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let canary = {flag: false};
|
||||||
|
fib(10, canary);
|
||||||
|
expect(canary.flag).toBe(true);
|
||||||
|
canary = {flag: false};
|
||||||
|
fib(10, canary);
|
||||||
|
expect(canary.flag).toBe(false);
|
||||||
|
});
|
||||||
|
})
|
||||||
describe('when detecting chart type ', function() {
|
describe('when detecting chart type ', function() {
|
||||||
it('should handle a graph definition', function() {
|
it('should handle a graph definition', function() {
|
||||||
const str = 'graph TB\nbfs1:queue';
|
const str = 'graph TB\nbfs1:queue';
|
||||||
@@ -27,6 +114,16 @@ Alice->Bob: hi`;
|
|||||||
expect(type).toBe('sequence');
|
expect(type).toBe('sequence');
|
||||||
expect(init).toEqual({logLevel:0,theme:"dark"});
|
expect(init).toEqual({logLevel:0,theme:"dark"});
|
||||||
});
|
});
|
||||||
|
it('should handle an init definition with config converted to the proper diagram configuration', function() {
|
||||||
|
const str = `
|
||||||
|
%%{init: { 'logLevel': 0, 'theme': 'dark', 'config': {'wrapEnabled': true} } }%%
|
||||||
|
sequenceDiagram
|
||||||
|
Alice->Bob: hi`;
|
||||||
|
const type = utils.detectType(str);
|
||||||
|
const init = utils.detectInit(str);
|
||||||
|
expect(type).toBe('sequence');
|
||||||
|
expect(init).toEqual({logLevel:0, theme:"dark", sequence: { wrapEnabled: true }});
|
||||||
|
});
|
||||||
it('should handle a multiline init definition', function() {
|
it('should handle a multiline init definition', function() {
|
||||||
const str = `
|
const str = `
|
||||||
%%{
|
%%{
|
||||||
|
Reference in New Issue
Block a user