From b3c0c57c6d4bb4f6700344bccdf771f593cd1559 Mon Sep 17 00:00:00 2001 From: Alois Klink Date: Fri, 2 Sep 2022 04:28:22 +0100 Subject: [PATCH] fix: fix passing a single Node to mermaid.init() Passing a single Node to mermaid.init() results in an error, as it calls `new NodeList()`, which causes `TypeError: Illegal constructor`. See https://github.com/mermaid-js/mermaid/blob/5597cf45bf3c971fcd1fd35e303e418fe421c5c2/src/mermaid.ts#L73 If we instead use the `ArrayLike` interface, we can just use a simple array, instead of a NodeList. I've also added a basic test case, by mocking the `mermaidAPI.render()` function so it isn't called, as the d3 functions don't work in Node.JS. The mocks are a bit messy, since a) Jest doesn't fully support ESM yet, and b) mermaidAPI is frozen with `Object.freeze()`, but the mermaidAPI mocks work as long as we keep them simple. Fixes: c68ec54fdd980a6b4d1c1b58aec991badba16676 --- src/__mocks__/mermaidAPI.ts | 47 +++++++++++++++++++++++++++++++++++++ src/mermaid.spec.js | 18 ++++++++++++++ src/mermaid.ts | 5 ++-- 3 files changed, 67 insertions(+), 3 deletions(-) create mode 100644 src/__mocks__/mermaidAPI.ts diff --git a/src/__mocks__/mermaidAPI.ts b/src/__mocks__/mermaidAPI.ts new file mode 100644 index 000000000..9531fdc07 --- /dev/null +++ b/src/__mocks__/mermaidAPI.ts @@ -0,0 +1,47 @@ +/** + * Mocks for `./mermaidAPI`. + * + * We can't easily use `jest.spyOn(mermaidAPI, "function")` since the object + * is frozen with `Object.freeze()`. + */ +import * as configApi from '../config'; + +import { addDiagrams } from '../diagram-api/diagram-orchestration'; +import Diagram from '../Diagram'; + +// Normally, we could just do the following to get the original `parse()` +// implementation, however, requireActual isn't currently supported in Jest +// for ESM, see https://github.com/facebook/jest/issues/9430 +// and https://github.com/facebook/jest/pull/10976 +// const {parse} = jest.requireActual("./mermaidAPI"); + +let hasLoadedDiagrams = false; +function parse(text: string, parseError?: Function): boolean { + if (!hasLoadedDiagrams) { + addDiagrams(); + hasLoadedDiagrams = true; + } + const diagram = new Diagram(text, parseError); + return diagram.parse(text, parseError); +} + +// original version cannot be modified since it was frozen with `Object.freeze()` +export const mermaidAPI = { + render: jest.fn(), + parse, + parseDirective: jest.fn(), + initialize: jest.fn(), + getConfig: configApi.getConfig, + setConfig: configApi.setConfig, + getSiteConfig: configApi.getSiteConfig, + updateSiteConfig: configApi.updateSiteConfig, + reset: () => { + configApi.reset(); + }, + globalReset: () => { + configApi.reset(configApi.defaultConfig); + }, + defaultConfig: configApi.defaultConfig, +} + +export default mermaidAPI; diff --git a/src/mermaid.spec.js b/src/mermaid.spec.js index 60b67ad23..c6014dfff 100644 --- a/src/mermaid.spec.js +++ b/src/mermaid.spec.js @@ -1,4 +1,5 @@ import mermaid from './mermaid'; +import { mermaidAPI } from './mermaidAPI'; import flowDb from './diagrams/flowchart/flowDb'; import flowParser from './diagrams/flowchart/parser/flow'; import flowRenderer from './diagrams/flowchart/flowRenderer'; @@ -6,6 +7,13 @@ import Diagram from './Diagram'; const spyOn = jest.spyOn; +// mocks the mermaidAPI.render function (see `./__mocks__/mermaidAPI`) +jest.mock('./mermaidAPI'); + +afterEach(() => { + jest.restoreAllMocks(); +}); + describe('when using mermaid and ', function () { describe('when detecting chart type ', function () { it('should not start rendering with mermaid.startOnLoad set to false', function () { @@ -40,6 +48,16 @@ describe('when using mermaid and ', function () { }); }); + describe('when using #initThrowsErrors', function () { + it('should accept single node', async () => { + const node = document.createElement('div'); + node.appendChild(document.createTextNode('graph TD;\na;')); + + mermaid.initThrowsErrors(undefined, node); + expect(mermaidAPI.render).toHaveBeenCalled(); + }); + }); + describe('when calling addEdges ', function () { beforeEach(function () { flowParser.parser.yy = flowDb; diff --git a/src/mermaid.ts b/src/mermaid.ts index a46103f11..e82597152 100644 --- a/src/mermaid.ts +++ b/src/mermaid.ts @@ -64,14 +64,13 @@ const initThrowsErrors = function ( // if last argument is a function this is the callback function log.debug(`${!callback ? 'No ' : ''}Callback function found`); - let nodesToProcess: NodeListOf; + let nodesToProcess: ArrayLike; if (typeof nodes === 'undefined') { nodesToProcess = document.querySelectorAll('.mermaid'); } else if (typeof nodes === 'string') { nodesToProcess = document.querySelectorAll(nodes); } else if (nodes instanceof HTMLElement) { - nodesToProcess = new NodeList() as NodeListOf; - nodesToProcess[0] = nodes; + nodesToProcess = [nodes]; } else if (nodes instanceof NodeList) { nodesToProcess = nodes; } else {