Merge pull request #3004 from timmaffett/parse_parseError_fixes

fix mermaidAPI.parse() behavior to match documentation, add tests to ensure behavior matches docs
This commit is contained in:
Knut Sveidqvist
2022-05-10 17:00:22 +02:00
committed by GitHub
4 changed files with 149 additions and 91 deletions

View File

@@ -29,7 +29,7 @@ statement
In the extract of the grammar above, it is defined that a call to the setTitle method in the data object will be done when parsing and the title keyword is encountered. In the extract of the grammar above, it is defined that a call to the setTitle method in the data object will be done when parsing and the title keyword is encountered.
```tip ```tip
Make sure that the `parseError` function for the parser is defined and calling `mermaidPAI.parseError`. This way a common way of detecting parse errors is provided for the end-user. Make sure that the `parseError` function for the parser is defined and calling `mermaid.parseError`. This way a common way of detecting parse errors is provided for the end-user.
``` ```
For more info look in the example diagram type: For more info look in the example diagram type:
@@ -38,7 +38,7 @@ The `yy` object has the following function:
```javascript ```javascript
exports.parseError = function(err, hash){ exports.parseError = function(err, hash){
mermaidAPI.parseError(err, hash) mermaid.parseError(err, hash)
}; };
``` ```

View File

@@ -188,18 +188,38 @@ if (typeof document !== 'undefined') {
); );
} }
/**
* ## setParseErrorHandler Alternativet to directly setting parseError using:
*
* ```js
* mermaid.parseError = function(err,hash){=
* forExampleDisplayErrorInGui(err); // do something with the error
* };
* ```
*
* This is provided for environments where the mermaid object can't directly have a new member added
* to it (eg. dart interop wrapper). (Initially there is no parseError member of mermaid).
*
* @param {function (err, hash)} newParseErrorHandler New parseError() callback.
*/
const setParseErrorHandler = function (newParseErrorHandler) {
mermaid.parseError = newParseErrorHandler;
};
const mermaid = { const mermaid = {
startOnLoad: true, startOnLoad: true,
htmlLabels: true, htmlLabels: true,
mermaidAPI, mermaidAPI,
parse: mermaidAPI.parse, parse: mermaidAPI != undefined ? mermaidAPI.parse : null,
render: mermaidAPI.render, render: mermaidAPI != undefined ? mermaidAPI.render : null,
init, init,
initialize, initialize,
contentLoaded, contentLoaded,
setParseErrorHandler,
}; };
export default mermaid; export default mermaid;

View File

@@ -63,99 +63,120 @@ import getStyles from './styles';
import theme from './themes'; import theme from './themes';
import utils, { directiveSanitizer, assignWithDepth, sanitizeCss } from './utils'; import utils, { directiveSanitizer, assignWithDepth, sanitizeCss } from './utils';
import DOMPurify from 'dompurify'; import DOMPurify from 'dompurify';
import mermaid from './mermaid';
/** /**
* @param text * @param text
* @returns {any} * @returns {any}
*/ */
function parse(text) { function parse(text) {
text = text + '\n'; var parseEncounteredException = false;
const cnf = configApi.getConfig(); try {
const graphInit = utils.detectInit(text, cnf); text = text + '\n';
if (graphInit) { const cnf = configApi.getConfig();
reinitialize(graphInit); const graphInit = utils.detectInit(text, cnf);
log.info('reinit ', graphInit); if (graphInit) {
} reinitialize(graphInit);
const graphType = utils.detectType(text, cnf); log.info('reinit ', graphInit);
let parser; }
const graphType = utils.detectType(text, cnf);
let parser;
log.debug('Type ' + graphType); log.debug('Type ' + graphType);
switch (graphType) { switch (graphType) {
case 'gitGraph': case 'gitGraph':
gitGraphAst.clear(); gitGraphAst.clear();
parser = gitGraphParser; parser = gitGraphParser;
parser.parser.yy = gitGraphAst; parser.parser.yy = gitGraphAst;
break; break;
case 'flowchart': case 'flowchart':
flowDb.clear(); flowDb.clear();
parser = flowParser; parser = flowParser;
parser.parser.yy = flowDb; parser.parser.yy = flowDb;
break; break;
case 'flowchart-v2': case 'flowchart-v2':
flowDb.clear(); flowDb.clear();
parser = flowParser; parser = flowParser;
parser.parser.yy = flowDb; parser.parser.yy = flowDb;
break; break;
case 'sequence': case 'sequence':
sequenceDb.clear(); sequenceDb.clear();
parser = sequenceParser; parser = sequenceParser;
parser.parser.yy = sequenceDb; parser.parser.yy = sequenceDb;
break; break;
case 'gantt': case 'gantt':
parser = ganttParser; parser = ganttParser;
parser.parser.yy = ganttDb; parser.parser.yy = ganttDb;
break; break;
case 'class': case 'class':
parser = classParser; parser = classParser;
parser.parser.yy = classDb; parser.parser.yy = classDb;
break; break;
case 'classDiagram': case 'classDiagram':
parser = classParser; parser = classParser;
parser.parser.yy = classDb; parser.parser.yy = classDb;
break; break;
case 'state': case 'state':
parser = stateParser; parser = stateParser;
parser.parser.yy = stateDb; parser.parser.yy = stateDb;
break; break;
case 'stateDiagram': case 'stateDiagram':
parser = stateParser; parser = stateParser;
parser.parser.yy = stateDb; parser.parser.yy = stateDb;
break; break;
case 'info': case 'info':
log.debug('info info info'); log.debug('info info info');
parser = infoParser; parser = infoParser;
parser.parser.yy = infoDb; parser.parser.yy = infoDb;
break; break;
case 'pie': case 'pie':
log.debug('pie'); log.debug('pie');
parser = pieParser; parser = pieParser;
parser.parser.yy = pieDb; parser.parser.yy = pieDb;
break; break;
case 'er': case 'er':
log.debug('er'); log.debug('er');
parser = erParser; parser = erParser;
parser.parser.yy = erDb; parser.parser.yy = erDb;
break; break;
case 'journey': case 'journey':
log.debug('Journey'); log.debug('Journey');
parser = journeyParser; parser = journeyParser;
parser.parser.yy = journeyDb; parser.parser.yy = journeyDb;
break; break;
case 'requirement': case 'requirement':
case 'requirementDiagram': case 'requirementDiagram':
log.debug('RequirementDiagram'); log.debug('RequirementDiagram');
parser = requirementParser; parser = requirementParser;
parser.parser.yy = requirementDb; parser.parser.yy = requirementDb;
break; break;
} }
parser.parser.yy.graphType = graphType; parser.parser.yy.graphType = graphType;
parser.parser.yy.parseError = (str, hash) => { parser.parser.yy.parseError = (str, hash) => {
const error = { str, hash }; const error = { str, hash };
throw error; throw error;
}; };
parser.parse(text); parser.parse(text);
return parser; } catch (error) {
parseEncounteredException = true;
// Is this the correct way to access mermiad's parseError()
// method ? (or global.mermaid.parseError()) ?
if (mermaid.parseError) {
if (error.str != undefined) {
// handle case where error string and hash were
// wrapped in object like`const error = { str, hash };`
mermaid.parseError(error.str, error.hash);
} else {
// assume it is just error string and pass it on
mermaid.parseError(error);
}
} else {
// No mermaid.parseError() handler defined, so re-throw it
throw error;
}
}
return !parseEncounteredException;
} }
export const encodeEntities = function (text) { export const encodeEntities = function (text) {

View File

@@ -1,3 +1,4 @@
import mermaid from './mermaid';
import mermaidAPI from './mermaidAPI'; import mermaidAPI from './mermaidAPI';
import { assignWithDepth } from './utils'; import { assignWithDepth } from './utils';
@@ -119,12 +120,28 @@ describe('when using mermaidAPI and ', function () {
expect(mermaidAPI.getConfig().dompurifyConfig.ADD_ATTR).toEqual(['onclick']); expect(mermaidAPI.getConfig().dompurifyConfig.ADD_ATTR).toEqual(['onclick']);
}); });
}); });
describe('checking validity of input ', function () { describe('test mermaidApi.parse() for checking validity of input ', function () {
it('it should throw for an invalid definiton', function () { mermaid.parseError = undefined; // ensure it parseError undefined
it('it should throw for an invalid definiton (with no mermaid.parseError() defined)', function () {
expect(mermaid.parseError).toEqual(undefined);
expect(() => mermaidAPI.parse('this is not a mermaid diagram definition')).toThrow(); expect(() => mermaidAPI.parse('this is not a mermaid diagram definition')).toThrow();
}); });
it('it should not throw for a valid definiton', function () { it('it should not throw for a valid definiton', function () {
expect(() => mermaidAPI.parse('graph TD;A--x|text including URL space|B;')).not.toThrow(); expect(() => mermaidAPI.parse('graph TD;A--x|text including URL space|B;')).not.toThrow();
}); });
it('it should return false for invalid definiton WITH a parseError() callback defined', function () {
var parseErrorWasCalled = false;
// also test setParseErrorHandler() call working to set mermaid.parseError
mermaid.setParseErrorHandler(function (error, hash) {
// got here.
parseErrorWasCalled = true;
});
expect(mermaid.parseError).not.toEqual(undefined);
expect(mermaidAPI.parse('this is not a mermaid diagram definition')).toEqual(false);
expect(parseErrorWasCalled).toEqual(true);
});
it('it should return true for valid definiton', function () {
expect(mermaidAPI.parse('graph TD;A--x|text including URL space|B;')).toEqual(true);
});
}); });
}); });