CVE-2022-36036: Improper Control of Generation of Code ('Code Injection') (CWE-94)
value: mdx-mermaid provides plug and play access to Mermaid in MDX. There is a potential for an arbitrary javascript injection in versions less than 1.3.0 and 2.0.0-rc1. Modify any mermaid code blocks with arbitrary code and it will execute when the component is loaded by MDXjs. This vulnerability was patched in version(s) 1.3.0 and 2.0.0-rc2. There are currently no known workarounds.
CWE description: The product constructs all or part of a code segment using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the syntax or behavior of the intended code segment.
CVSS v3 base score: 7.8
Published: 2022-08-29T18:15Z
Repository: https://github.com/sjwall/mdx-mermaid
Language: TypeScript
Files changed: package.json, src/Mermaid.spec.tsx, src/Mermaid.tsx, src/__snapshots__/Mermaid.spec.tsx.snap, src/mdxast-mermaid.spec.ts, src/mdxast-mermaid.ts, yarn.lock
Commit date: 2022-08-22 18:10:59 +0100
Commit message: Merge pull request from GHSA-rvgm-35jw-q628


Diff: diff --git a/package.json b/package.json
index 7709d91..4805354 100644
--- a/package.json
+++ b/package.json
@@ -51,10 +51,11 @@
     "@babel/preset-react": "^7.18.6",
     "@mdx-js/mdx": "^1.6.22",
     "@rollup/plugin-babel": "^5.3.1",
     "@rollup/plugin-commonjs": "^22.0.2",
     "@rollup/plugin-typescript": "^8.3.4",
+    "@testing-library/react": "^11.1.0",
     "@types/jest": "^27.4.0",
     "@types/mermaid": "^8.2.7",
     "@types/react": "^17.0.38",
     "@types/unist": "^2.0.6",
     "@typescript-eslint/eslint-plugin": "^5.9.0",
@@ -67,11 +68,11 @@
     "eslint-plugin-promise": "^6.0.0",
     "eslint-plugin-react": "^7.28.0",
     "jest": "^27.4.7",
     "mermaid": "^8.0.0",
     "react": "^17.0.1",
-    "react-test-renderer": "^17.0.2",
+    "react-dom": "^17.0.0",
     "rimraf": "^3.0.2",
     "rollup": "^2.78.1",
     "ts-jest": "^27.1.2",
     "typescript": "^4.5.4",
     "unist-util-visit": "^2.0.0"
diff --git a/src/Mermaid.spec.tsx b/src/Mermaid.spec.tsx
index c2adf8b..6582ea2 100644
--- a/src/Mermaid.spec.tsx
+++ b/src/Mermaid.spec.tsx
@@ -5,198 +5,138 @@
  * Copyright (c) Samuel Wall.
  *
  * This source code is licensed under the MIT license found in the
  * license file in the root directory of this source tree.
  */
-import mermaid from 'mermaid'
 import React from 'react'
-import renderer from 'react-test-renderer'
+import { act, render, RenderResult } from '@testing-library/react'
 import { Mermaid } from './Mermaid'
 import {
   DARK_THEME_KEY,
   HTML_THEME_ATTRIBUTE,
   LIGHT_THEME_KEY
 } from './theme.helper'
 import * as ThemeHelper from './theme.helper'
 
-async function waitFor (ms: number) {
-  return new Promise<void>(resolve => {
-    setTimeout(() => resolve(), ms)
-  })
-}
-
 jest.mock('mermaid')
 
+// eslint-disable-next-line import/first
+import mermaid from 'mermaid'
+
+const getThemeSpy = jest.spyOn(ThemeHelper, 'getTheme')
+
+const diagram = `graph TD;
+A-->B;
+A-->C;
+B-->D;
+C-->D;`
+
 afterEach(() => {
   jest.clearAllMocks()
 })
 
+const removeUniqueness = (element: Element) => {
+  element.querySelectorAll('style').forEach((v) => v.remove())
+  element.querySelectorAll('svg').forEach((v) => {
+    v.removeAttribute('id')
+    v.parentElement!.removeAttribute('id')
+  })
+}
+
+const expectMermaidMatch = (result: RenderResult) => {
+  removeUniqueness(result.baseElement)
+  expect(result.baseElement.parentElement).toMatchSnapshot()
+  return result
+}
+
 it('renders without diagram', () => {
-  const component = renderer.create(<Mermaid chart={''} config={{}}  />)
-  expect(mermaid.initialize).toBeCalledTimes(0)
-  expect(mermaid.render).toBeCalledTimes(0)
-  component.update()
-  expect(mermaid.render).toBeCalledTimes(1)
-  expect(mermaid.initialize).toBeCalledTimes(1)
-  component.update()
-  expect(mermaid.render).toBeCalledTimes(1)
-  expect(mermaid.initialize).toBeCalledTimes(1)
+  expectMermaidMatch(render(<Mermaid chart={''} config={{}} />))
 })
 
 it('renders with diagram', () => {
-  const component = renderer.create(<Mermaid chart={`graph TD;
-      A-->B;
-      A-->C;
-      B-->D;
-      C-->D;`} config={{}} />)
-  expect(mermaid.initialize).toBeCalledTimes(0)
-  expect(mermaid.render).toBeCalledTimes(0)
-  component.update()
-  expect(mermaid.render).toBeCalledTimes(1)
-  expect(mermaid.initialize).toBeCalledTimes(1)
-  component.update()
-  expect(mermaid.render).toBeCalledTimes(1)
-  expect(mermaid.initialize).toBeCalledTimes(1)
+  expectMermaidMatch(render(<svg><Mermaid chart={diagram} config={{}} /></svg>))
 })
 
-it('initializes only once', async () => {
-  const component = renderer.create(<>
-        <Mermaid chart={'foo'} config={{}} />
-        <Mermaid chart={'bar'} />
-      </>)
-  expect(mermaid.initialize).toBeCalledTimes(0)
-  expect(mermaid.render).toBeCalledTimes(0)
-  await waitFor(1000)
-  component.update()
-  expect(mermaid.render).toBeCalledTimes(2)
+it('renders with diagram change', () => {
+  const config = {}
+  jest.useFakeTimers()
+  const view = expectMermaidMatch(render(<Mermaid chart={diagram} config={config} />))
+  view.rerender(<Mermaid chart={`graph TD;
+D-->C;
+D-->B;
+C-->A;
+B-->A;`} config={config} />)
+  jest.advanceTimersByTime(1000)
+  expectMermaidMatch(view)
+  jest.useRealTimers()
+  expect(mermaid.contentLoaded).toBeCalledTimes(1)
   expect(mermaid.initialize).toBeCalledTimes(1)
-  component.update()
-  expect(mermaid.render).toBeCalledTimes(2)
+})
+
+it('initializes only once', () => {
+  expectMermaidMatch(render(<>
+    <Mermaid chart={'foo'} config={{}} />
+    <Mermaid chart={'bar'} />
+  </>))
+  expect(mermaid.contentLoaded).toBeCalledTimes(1)
   expect(mermaid.initialize).toBeCalledTimes(1)
 })
 
 it('renders with mermaid config', () => {
-  const component = renderer.create(<Mermaid chart={`graph TD;
-      A-->B;
-      A-->C;
-      B-->D;
-      C-->D;`} config={{ mermaid: { theme: 'dark' } } } />)
-  expect(mermaid.initialize).toBeCalledTimes(0)
-  expect(mermaid.render).toBeCalledTimes(0)
-  component.update()
-  expect(mermaid.render).toHaveBeenCalled()
-  expect(mermaid.initialize).toHaveBeenNthCalledWith(1, { startOnLoad: true, theme: 'dark' })
-  component.update()
-  expect(mermaid.render).toBeCalledTimes(1)
-  expect(mermaid.initialize).toBeCalledTimes(1)
+  expectMermaidMatch(render(<Mermaid chart={diagram} config={{ mermaid: { theme: 'dark' } }} />))
+  expect(mermaid.contentLoaded).toBeCalledTimes(1)
+  expect(mermaid.initialize).toBeCalledWith({ startOnLoad: true, theme: 'dark' })
 })
 
-it('re-renders mermaid theme on html data-theme attribute change', async () => {
-  const component = renderer.create(
-    <html data-theme='light'>
-      <Mermaid chart={`graph TD;
-            A-->B;
-            A-->C;
-            B-->D;
-            C-->D;`} config={{}} />
-    </html>)
-  expect(mermaid.initialize).toBeCalledTimes(0)
-  expect(mermaid.render).toBeCalledTimes(0)
-  component.update()
-  expect(mermaid.render).toBeCalledTimes(1)
-  expect(mermaid.initialize).toBeCalledTimes(1)
-  component.update()
-  expect(mermaid.render).toBeCalledTimes(1)
-  expect(mermaid.initialize).toBeCalledTimes(1)
+it('renders with mermaid config change', () => {
+  const view = expectMermaidMatch(render(<Mermaid chart={diagram} config={{ mermaid: { theme: 'dark' } }} />))
+  view.baseElement.querySelectorAll('div.mermaid').forEach((v) => {
+    v.setAttribute('data-processed', 'true')
+  })
+  expect(mermaid.contentLoaded).toBeCalledTimes(1)
+  expect(mermaid.initialize).toBeCalledWith({ startOnLoad: true, theme: 'dark' })
+  view.rerender(<Mermaid chart={diagram} config={{ mermaid: { theme: 'forest' } }} />)
+  // await waitFor(1000)
+  expectMermaidMatch(view)
+  expect(mermaid.contentLoaded).toBeCalledTimes(2)
+  expect(mermaid.initialize).toHaveBeenNthCalledWith(2, { startOnLoad: true, theme: 'forest' })
+})
 
-  component.update(
-    <html data-theme='dark'>
-      <Mermaid chart={`graph TD;
-            A-->B;
-            A-->C;
-            B-->D;
-            C-->D;`} config={{}} />
-        </html>)
+it('renders with string mermaid config', () => {
+  expectMermaidMatch(render(<Mermaid chart={diagram} config={JSON.stringify({ mermaid: { theme: 'dark' } })} />))
+  expect(mermaid.contentLoaded).toBeCalledTimes(1)
+  expect(mermaid.initialize).toBeCalledWith({ startOnLoad: true, theme: 'dark' })
+})
 
-  // Time for mutation observer to notice change.
-  await waitFor(2000)
+it('re-renders mermaid theme on html data-theme attribute change', () => {
+  const component = render(
+    <Mermaid chart={diagram} config={{}} />)
 
-  expect(mermaid.render).toBeCalledTimes(2)
-  expect(mermaid.initialize).toBeCalledTimes(2)
-})
+  expectMermaidMatch(component)
+  expect(mermaid.contentLoaded).toBeCalledTimes(1)
+  expect(mermaid.initialize).toBeCalledTimes(1)
+  expect(getThemeSpy).toBeCalledTimes(1)
 
-it('renders the output of mermaid into the div', async () => {
-  const expectedOutput = 'mermaid output'
-  mermaid.render = jest.fn((_, __, cb) => {
-    if (cb) cb(expectedOutput, () => 0)
-    return expectedOutput
-  })
+  act(() => document.querySelector('html')!.setAttribute(HTML_THEME_ATTRIBUTE, DARK_THEME_KEY))
 
-  let component: any
-  renderer.act(() => {
-    component = renderer.create(
-      <Mermaid chart={`graph TD;
-            A-->B;
-            A-->C;
-            B-->D;
-            C-->D;`} config={{}} />
-    )
-  })
+  expectMermaidMatch(component)
 
+  expect(mermaid.contentLoaded).toBeCalledTimes(1)
   expect(mermaid.initialize).toBeCalledTimes(1)
-  expect(mermaid.render).toBeCalledTimes(1)
-  expect(component.toJSON()).toMatchSnapshot()
-})
+  expect(getThemeSpy).toBeCalledTimes(1)
 
-describe('changing the theme at runtime', () => {
-  let useRefSpy: jest.SpyInstance
-  let html: HTMLHtmlElement
+  act(() => document.querySelector('html')!.setAttribute(HTML_THEME_ATTRIBUTE, LIGHT_THEME_KEY))
 
-  beforeEach(() => {
-    html = document.createElement('html')
-    html.setAttribute(HTML_THEME_ATTRIBUTE, LIGHT_THEME_KEY)
-    useRefSpy = jest.spyOn(document, 'querySelector').mockReturnValue(html)
-  })
+  expectMermaidMatch(component)
+})
 
-  afterEach(() => {
-    expect(useRefSpy).toHaveBeenCalled()
-  })
+it('does not react to non-theme attribute changes of html', () => {
+  const component = render(<Mermaid chart={diagram} config={{}} />)
 
-  it('reacts to changed theme', async () => {
-    const getThemeSpy = jest.spyOn(ThemeHelper, 'getTheme')
-    renderer.act(() => {
-      renderer.create(
-        <Mermaid chart={`graph TD;
-              A-->B;
-              A-->C;
-              B-->D;
-              C-->D;`} config={{}} />
-      )
-    })
-
-    await renderer.act(async () => {
-      html.setAttribute(HTML_THEME_ATTRIBUTE, DARK_THEME_KEY)
-      await waitFor(1000)
-    })
-
-    expect(getThemeSpy.mock.calls.length).toBeGreaterThan(2)
-  })
+  expectMermaidMatch(component)
+  expect(mermaid.contentLoaded).toBeCalledTimes(1)
+  expect(mermaid.initialize).toBeCalledTimes(1)
 
-  it('does not react to non-theme attribute changes of html', async () => {
-    const getThemeSpy = jest.spyOn(ThemeHelper, 'getTheme')
-    renderer.act(() => {
-      renderer.create(
-        <Mermaid chart={`graph TD;
-              A-->B;
-              A-->C;
-              B-->D;
-              C-->D;`} config={{}} />
-      )
-    })
-
-    await renderer.act(async () => {
-      html.setAttribute('manifest', 'some-value')
-      await waitFor(1000)
-    })
-    expect(getThemeSpy).toHaveBeenCalledTimes(2)
-  })
+  act(() => document.querySelector('html')!.setAttribute('manifest', 'some-value'))
+
+  expectMermaidMatch(component)
 })
diff --git a/src/Mermaid.tsx b/src/Mermaid.tsx
index 99734a5..73a07ff 100644
--- a/src/Mermaid.tsx
+++ b/src/Mermaid.tsx
@@ -3,23 +3,16 @@
  *
  * This source code is licensed under the MIT license found in the
  * license file in the root directory of this source tree.
  */
 
-import React, { useEffect, useState, ReactElement } from 'react'
+import React, { useEffect, useState, ReactElement, useMemo } from 'react'
 import mermaid from 'mermaid'
-import mermaidAPI from 'mermaid/mermaidAPI'
 
 import { Config } from './config.model'
 import { getTheme } from './theme.helper'
 
-/**
- * Assign a unique ID to each mermaid svg as per requirements
- * of `mermaid.render`.
- */
-let id = 0
-
 /**
  * Properties for Mermaid component.
  */
 export type MermaidProps = {
   /**
@@ -28,40 +21,43 @@ export type MermaidProps = {
   chart: string
 
   /**
    * Config to initialize mermaid with.
    */
-  config?: Config
+  config?: Config | string
 }
 
 /**
  * Component to display Mermaid diagrams.
  *
  * @param param0 Diagram to display.
  * @param param1 Config.
  * @returns The component.
  */
-export const Mermaid = ({ chart, config }: MermaidProps): ReactElement<MermaidProps> => {
+export const Mermaid = ({ chart, config: configSrc }: MermaidProps): ReactElement<MermaidProps> => {
   // Due to Docusaurus not correctly parsing client-side from server-side modules, use the provided workaround
   // found in the accompanying issue: https://github.com/facebook/docusaurus/issues/4268#issuecomment-783553084
   /* istanbul ignore next */
   if (typeof window === 'undefined') {
-    return <div></div>
+    return <div className="mermaid" data-mermaid-src={chart}>{chart}</div>
   }
 
+  const config: Config = useMemo(() => typeof configSrc === 'string' ? JSON.parse(configSrc) : configSrc, [configSrc])
+
   const html: HTMLHtmlElement = document.querySelector('html')!
 
-  // Watch for changes in theme in the HTML attribute `data-theme`.
-  const [theme, setTheme] = useState<mermaidAPI.Theme>(getTheme(html, config))
+  const [rerender, setRerender] = useState<boolean>(false)
+
+  const theme = useMemo(() => getTheme(html, config), [config, rerender])
 
   useEffect(() => {
     const observer = new MutationObserver((mutations) => {
       for (const mutation of mutations) {
         if (mutation.type !== 'attributes' || mutation.attributeName !== 'data-theme') {
           continue
         }
-        setTheme(getTheme(mutation.target as HTMLHtmlElement, config))
+        setRerender((cur) => !cur)
         break
       }
     })
 
     observer.observe(html, { attributes: true })
@@ -70,30 +66,28 @@ export const Mermaid = ({ chart, config }: MermaidProps): ReactElement<MermaidPr
         observer.disconnect()
       } catch {
         // Do nothing
       }
     }
-  }, [chart, config, theme])
+  }, [])
 
-  // When theme updates, rerender the SVG.
-  const [svg, setSvg] = useState<string>('')
   useEffect(() => {
-    const render = () => {
-      mermaid.render(`mermaid-svg-${id.toString()}`, chart, (renderedSvg) => setSvg(renderedSvg))
-      id++
-    }
-
     if (config) {
       if (config.mermaid) {
         mermaid.initialize({ startOnLoad: true, ...config.mermaid, theme })
       } else {
         mermaid.initialize({ startOnLoad: true, theme })
       }
-      render()
-    } else {
-      // Is there a better way?
-      setTimeout(render, 0)
+      document.querySelectorAll('div.mermaid[data-processed="true"]').forEach((v) => {
+        v.removeAttribute('data-processed')
+        v.innerHTML = v.getAttribute('data-mermaid-src') as string
+      })
+      mermaid.contentLoaded()
     }
-  }, [theme, chart])
+  }, [config, theme])
+
+  useEffect(() => {
+    setTimeout(() => mermaid.contentLoaded, 0)
+  }, [chart])
 
-  return <div dangerouslySetInnerHTML={{ __html: svg }}></div>
+  return <div className="mermaid" data-mermaid-src={chart}>{chart}</div>
 }
diff --git a/src/__snapshots__/Mermaid.spec.tsx.snap b/src/__snapshots__/Mermaid.spec.tsx.snap
index 2812fe0..77c9f04 100644
--- a/src/__snapshots__/Mermaid.spec.tsx.snap
+++ b/src/__snapshots__/Mermaid.spec.tsx.snap
@@ -1,11 +1,336 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
-exports[`renders the output of mermaid into the div 1`] = `
-<div
-  dangerouslySetInnerHTML={
-    Object {
-      "__html": "mermaid output",
-    }
-  }
-/>
+exports[`does not react to non-theme attribute changes of html 1`] = `
+<html
+  data-theme="light"
+>
+  <head />
+  <body>
+    <div>
+      <div
+        class="mermaid"
+        data-mermaid-src="graph TD;
+A-->B;
+A-->C;
+B-->D;
+C-->D;"
+      >
+        graph TD;
+A--&gt;B;
+A--&gt;C;
+B--&gt;D;
+C--&gt;D;
+      </div>
+    </div>
+  </body>
+</html>
+`;
+
+exports[`does not react to non-theme attribute changes of html 2`] = `
+<html
+  data-theme="light"
+  manifest="some-value"
+>
+  <head />
+  <body>
+    <div>
+      <div
+        class="mermaid"
+        data-mermaid-src="graph TD;
+A-->B;
+A-->C;
+B-->D;
+C-->D;"
+      >
+        graph TD;
+A--&gt;B;
+A--&gt;C;
+B--&gt;D;
+C--&gt;D;
+      </div>
+    </div>
+  </body>
+</html>
+`;
+
+exports[`initializes only once 1`] = `
+<html>
+  <head />
+  <body>
+    <div>
+      <div
+        class="mermaid"
+        data-mermaid-src="foo"
+      >
+        foo
+      </div>
+      <div
+        class="mermaid"
+        data-mermaid-src="bar"
+      >
+        bar
+      </div>
+    </div>
+  </body>
+</html>
+`;
+
+exports[`re-renders mermaid theme on html data-theme attribute change 1`] = `
+<html>
+  <head />
+  <body>
+    <div>
+      <div
+        class="mermaid"
+        data-mermaid-src="graph TD;
+A-->B;
+A-->C;
+B-->D;
+C-->D;"
+      >
+        graph TD;
+A--&gt;B;
+A--&gt;C;
+B--&gt;D;
+C--&gt;D;
+      </div>
+    </div>
+  </body>
+</html>
+`;
+
+exports[`re-renders mermaid theme on html data-theme attribute change 2`] = `
+<html
+  data-theme="dark"
+>
+  <head />
+  <body>
+    <div>
+      <div
+        class="mermaid"
+        data-mermaid-src="graph TD;
+A-->B;
+A-->C;
+B-->D;
+C-->D;"
+      >
+        graph TD;
+A--&gt;B;
+A--&gt;C;
+B--&gt;D;
+C--&gt;D;
+      </div>
+    </div>
+  </body>
+</html>
+`;
+
+exports[`re-renders mermaid theme on html data-theme attribute change 3`] = `
+<html
+  data-theme="light"
+>
+  <head />
+  <body>
+    <div>
+      <div
+        class="mermaid"
+        data-mermaid-src="graph TD;
+A-->B;
+A-->C;
+B-->D;
+C-->D;"
+      >
+        graph TD;
+A--&gt;B;
+A--&gt;C;
+B--&gt;D;
+C--&gt;D;
+      </div>
+    </div>
+  </body>
+</html>
+`;
+
+exports[`renders with diagram 1`] = `
+<html>
+  <head />
+  <body>
+    <div>
+      <svg>
+        <div
+          class="mermaid"
+          data-mermaid-src="graph TD;
+A-->B;
+A-->C;
+B-->D;
+C-->D;"
+        >
+          graph TD;
+A--&gt;B;
+A--&gt;C;
+B--&gt;D;
+C--&gt;D;
+        </div>
+      </svg>
+    </div>
+  </body>
+</html>
+`;
+
+exports[`renders with diagram change 1`] = `
+<html>
+  <head />
+  <body>
+    <div>
+      <div
+        class="mermaid"
+        data-mermaid-src="graph TD;
+A-->B;
+A-->C;
+B-->D;
+C-->D;"
+      >
+        graph TD;
+A--&gt;B;
+A--&gt;C;
+B--&gt;D;
+C--&gt;D;
+      </div>
+    </div>
+  </body>
+</html>
+`;
+
+exports[`renders with diagram change 2`] = `
+<html>
+  <head />
+  <body>
+    <div>
+      <div
+        class="mermaid"
+        data-mermaid-src="graph TD;
+D-->C;
+D-->B;
+C-->A;
+B-->A;"
+      >
+        graph TD;
+D--&gt;C;
+D--&gt;B;
+C--&gt;A;
+B--&gt;A;
+      </div>
+    </div>
+  </body>
+</html>
+`;
+
+exports[`renders with mermaid config 1`] = `
+<html>
+  <head />
+  <body>
+    <div>
+      <div
+        class="mermaid"
+        data-mermaid-src="graph TD;
+A-->B;
+A-->C;
+B-->D;
+C-->D;"
+      >
+        graph TD;
+A--&gt;B;
+A--&gt;C;
+B--&gt;D;
+C--&gt;D;
+      </div>
+    </div>
+  </body>
+</html>
+`;
+
+exports[`renders with mermaid config change 1`] = `
+<html>
+  <head />
+  <body>
+    <div>
+      <div
+        class="mermaid"
+        data-mermaid-src="graph TD;
+A-->B;
+A-->C;
+B-->D;
+C-->D;"
+      >
+        graph TD;
+A--&gt;B;
+A--&gt;C;
+B--&gt;D;
+C--&gt;D;
+      </div>
+    </div>
+  </body>
+</html>
+`;
+
+exports[`renders with mermaid config change 2`] = `
+<html>
+  <head />
+  <body>
+    <div>
+      <div
+        class="mermaid"
+        data-mermaid-src="graph TD;
+A-->B;
+A-->C;
+B-->D;
+C-->D;"
+      >
+        graph TD;
+A--&gt;B;
+A--&gt;C;
+B--&gt;D;
+C--&gt;D;
+      </div>
+    </div>
+  </body>
+</html>
+`;
+
+exports[`renders with string mermaid config 1`] = `
+<html>
+  <head />
+  <body>
+    <div>
+      <div
+        class="mermaid"
+        data-mermaid-src="graph TD;
+A-->B;
+A-->C;
+B-->D;
+C-->D;"
+      >
+        graph TD;
+A--&gt;B;
+A--&gt;C;
+B--&gt;D;
+C--&gt;D;
+      </div>
+    </div>
+  </body>
+</html>
+`;
+
+exports[`renders without diagram 1`] = `
+<html>
+  <head />
+  <body>
+    <div>
+      <div
+        class="mermaid"
+        data-mermaid-src=""
+      />
+    </div>
+  </body>
+</html>
 `;
diff --git a/src/mdxast-mermaid.spec.ts b/src/mdxast-mermaid.spec.ts
index 91b487c..df8b8d9 100644
--- a/src/mdxast-mermaid.spec.ts
+++ b/src/mdxast-mermaid.spec.ts
@@ -22,11 +22,28 @@ function createTestCompiler (config?: Config) {
 }
 
 test('No mermaid', async () => {
   const mdxCompiler = createTestCompiler()
   const result = await mdxCompiler.process('# Heading 1\n\nNo Mermaid diagram :(')
-  expect(result.contents).toEqual('\n\n\nconst layoutProps = {\n  \n};\nconst MDXLayout = "wrapper"\nexport default function MDXContent({\n  components,\n  ...props\n}) {\n  return <MDXLayout {...layoutProps} {...props} components={components} mdxType="MDXLayout">\n    <h1>{`Heading 1`}</h1>\n    <p>{`No Mermaid diagram :(`}</p>\n    </MDXLayout>;\n}\n\n;\nMDXContent.isMDXComponent = true;')
+  expect(result.contents).toEqual(`import { Mermaid } from 'mdx-mermaid/lib/Mermaid';
+
+
+const layoutProps = {\n  \n};
+const MDXLayout = "wrapper"
+export default function MDXContent({
+  components,
+  ...props
+}) {
+  return <MDXLayout {...layoutProps} {...props} components={components} mdxType="MDXLayout">
+
+    <h1>{\`Heading 1\`}</h1>
+    <p>{\`No Mermaid diagram :(\`}</p>
+    </MDXLayout>;
+}
+
+;
+MDXContent.isMDXComponent = true;`)
 })
 
 test('Basic', async () => {
   const mdxCompiler = createTestCompiler()
   const result = await mdxCompiler.process(`# Heading 1\n
@@ -35,11 +52,30 @@ graph TD;
     A-->B;
     A-->C;
     B-->D;
     C-->D;
 \`\`\``)
-  expect(result.contents).toEqual("import { Mermaid } from 'mdx-mermaid/lib/Mermaid';\n\n\nconst layoutProps = {\n  \n};\nconst MDXLayout = \"wrapper\"\nexport default function MDXContent({\n  components,\n  ...props\n}) {\n  return <MDXLayout {...layoutProps} {...props} components={components} mdxType=\"MDXLayout\">\n\n    <h1>{`Heading 1`}</h1>\n    <Mermaid config={{}} chart={`graph TD;\n    A-->B;\n    A-->C;\n    B-->D;\n    C-->D;`} mdxType=\"Mermaid\" />\n    </MDXLayout>;\n}\n\n;\nMDXContent.isMDXComponent = true;")
+  expect(result.contents).toEqual(`import { Mermaid } from 'mdx-mermaid/lib/Mermaid';
+
+
+const layoutProps = {\n  \n};
+const MDXLayout = "wrapper"
+export default function MDXContent({
+  components,
+  ...props
+}) {
+  return <MDXLayout {...layoutProps} {...props} components={components} mdxType="MDXLayout">
+
+    <h1>{\`Heading 1\`}</h1>
+    <Mermaid {...{
+      "chart": "graph TD;\\n    A-->B;\\n    A-->C;\\n    B-->D;\\n    C-->D;"
+    }} mdxType="Mermaid"></Mermaid>
+    </MDXLayout>;
+}
+
+;
+MDXContent.isMDXComponent = true;`)
 })
 
 test('Existing import', async () => {
   const mdxCompiler = createTestCompiler()
   const result = await mdxCompiler.process(`import { Mermaid } from 'mdx-mermaid/lib/Mermaid';\n\n# Heading 1\n
@@ -48,11 +84,30 @@ graph TD;
     A-->B;
     A-->C;
     B-->D;
     C-->D;
 \`\`\``)
-  expect(result.contents).toEqual("import { Mermaid } from 'mdx-mermaid/lib/Mermaid';\n\n\nconst layoutProps = {\n  \n};\nconst MDXLayout = \"wrapper\"\nexport default function MDXContent({\n  components,\n  ...props\n}) {\n  return <MDXLayout {...layoutProps} {...props} components={components} mdxType=\"MDXLayout\">\n\n    <h1>{`Heading 1`}</h1>\n    <Mermaid config={{}} chart={`graph TD;\n    A-->B;\n    A-->C;\n    B-->D;\n    C-->D;`} mdxType=\"Mermaid\" />\n    </MDXLayout>;\n}\n\n;\nMDXContent.isMDXComponent = true;")
+  expect(result.contents).toEqual(`import { Mermaid } from 'mdx-mermaid/lib/Mermaid';
+
+
+const layoutProps = {\n  \n};
+const MDXLayout = "wrapper"
+export default function MDXContent({
+  components,
+  ...props
+}) {
+  return <MDXLayout {...layoutProps} {...props} components={components} mdxType="MDXLayout">
+
+    <h1>{\`Heading 1\`}</h1>
+    <Mermaid {...{
+      "chart": "graph TD;\\n    A-->B;\\n    A-->C;\\n    B-->D;\\n    C-->D;"
+    }} mdxType="Mermaid"></Mermaid>
+    </MDXLayout>;
+}
+
+;
+MDXContent.isMDXComponent = true;`)
 })
 
 test('Existing import from ts exports(without /lib)', async () => {
   const mdxCompiler = createTestCompiler()
   const result = await mdxCompiler.process(`import { Mermaid } from 'mdx-mermaid/Mermaid';\n\n# Heading 1\n
@@ -61,11 +116,30 @@ graph TD;
     A-->B;
     A-->C;
     B-->D;
     C-->D;
 \`\`\``)
-  expect(result.contents).toEqual("import { Mermaid } from 'mdx-mermaid/Mermaid';\n\n\nconst layoutProps = {\n  \n};\nconst MDXLayout = \"wrapper\"\nexport default function MDXContent({\n  components,\n  ...props\n}) {\n  return <MDXLayout {...layoutProps} {...props} components={components} mdxType=\"MDXLayout\">\n\n    <h1>{`Heading 1`}</h1>\n    <Mermaid config={{}} chart={`graph TD;\n    A-->B;\n    A-->C;\n    B-->D;\n    C-->D;`} mdxType=\"Mermaid\" />\n    </MDXLayout>;\n}\n\n;\nMDXContent.isMDXComponent = true;")
+  expect(result.contents).toEqual(`import { Mermaid } from 'mdx-mermaid/Mermaid';
+
+
+const layoutProps = {\n  \n};
+const MDXLayout = "wrapper"
+export default function MDXContent({
+  components,
+  ...props
+}) {
+  return <MDXLayout {...layoutProps} {...props} components={components} mdxType="MDXLayout">
+
+    <h1>{\`Heading 1\`}</h1>
+    <Mermaid {...{
+      "chart": "graph TD;\\n    A-->B;\\n    A-->C;\\n    B-->D;\\n    C-->D;"
+    }} mdxType="Mermaid"></Mermaid>
+    </MDXLayout>;
+}
+
+;
+MDXContent.isMDXComponent = true;`)
 })
 
 test('Other imports', async () => {
   const mdxCompiler = createTestCompiler()
   const result = await mdxCompiler.process(`import { A } from 'a';\n\n# Heading 1\n
@@ -74,11 +148,32 @@ graph TD;
     A-->B;
     A-->C;
     B-->D;
     C-->D;
 \`\`\``)
-  expect(result.contents).toEqual("import { Mermaid } from 'mdx-mermaid/lib/Mermaid';\nimport { A } from 'a';\n\n\nconst layoutProps = {\n  \n};\nconst MDXLayout = \"wrapper\"\nexport default function MDXContent({\n  components,\n  ...props\n}) {\n  return <MDXLayout {...layoutProps} {...props} components={components} mdxType=\"MDXLayout\">\n\n\n    <h1>{`Heading 1`}</h1>\n    <Mermaid config={{}} chart={`graph TD;\n    A-->B;\n    A-->C;\n    B-->D;\n    C-->D;`} mdxType=\"Mermaid\" />\n    </MDXLayout>;\n}\n\n;\nMDXContent.isMDXComponent = true;")
+  expect(result.contents).toEqual(`import { Mermaid } from 'mdx-mermaid/lib/Mermaid';
+import { A } from 'a';
+
+
+const layoutProps = {\n  \n};
+const MDXLayout = "wrapper"
+export default function MDXContent({
+  components,
+  ...props
+}) {
+  return <MDXLayout {...layoutProps} {...props} components={components} mdxType="MDXLayout">
+
+
+    <h1>{\`Heading 1\`}</h1>
+    <Mermaid {...{
+      "chart": "graph TD;\\n    A-->B;\\n    A-->C;\\n    B-->D;\\n    C-->D;"
+    }} mdxType="Mermaid"></Mermaid>
+    </MDXLayout>;
+}
+
+;
+MDXContent.isMDXComponent = true;`)
 })
 
 test('Other imports component', async () => {
   const mdxCompiler = createTestCompiler()
   const result = await mdxCompiler.process(`import { A } from 'a';\n\n# Heading 1\n
@@ -99,11 +194,11 @@ export default function MDXContent({
 }) {
   return <MDXLayout {...layoutProps} {...props} components={components} mdxType="MDXLayout">
 
 
     <h1>{\`Heading 1\`}</h1>
-    <Mermaid config={{}} chart={\`graph TD;
+    <Mermaid chart={\`graph TD;
       A-->B;
       A-->C;
       B-->D;
       C-->D;\`} mdxType="Mermaid" />
     </MDXLayout>;
@@ -142,11 +237,11 @@ export default function MDXContent({
 
 
     <h1>{\`Heading 1\`}</h1>
     <A mdxType="A">Hi</A>
     <h2>{\`Heading 2\`}</h2>
-    <Mermaid config={{}} chart={\`graph TD;
+    <Mermaid chart={\`graph TD;
       A-->B;
       A-->C;
       B-->D;
       C-->D;\`} mdxType="Mermaid" />
     </MDXLayout>;
@@ -176,19 +271,14 @@ export default function MDXContent({
   ...props
 }) {
   return <MDXLayout {...layoutProps} {...props} components={components} mdxType="MDXLayout">
 
     <h1>{\`Heading 1\`}</h1>
-    <Mermaid config={{
-      "mermaid": {
-        "theme": "dark"
-      }
-    }} chart={\`graph TD;
-    A-->B;
-    A-->C;
-    B-->D;
-    C-->D;\`} mdxType="Mermaid" />
+    <Mermaid {...{
+      "config": "{\\"mermaid\\":{\\"theme\\":\\"dark\\"}}",
+      "chart": "graph TD;\\n    A-->B;\\n    A-->C;\\n    B-->D;\\n    C-->D;"
+    }} mdxType="Mermaid"></Mermaid>
     </MDXLayout>;
 }
 
 ;
 MDXContent.isMDXComponent = true;`)
@@ -212,15 +302,11 @@ export default function MDXContent({
   ...props
 }) {
   return <MDXLayout {...layoutProps} {...props} components={components} mdxType="MDXLayout">
 
     <h1>{\`Heading 1\`}</h1>
-    <Mermaid config={{
-      "mermaid": {
-        "theme": "dark"
-      }
-    }} chart={\`graph TD;
+    <Mermaid chart={\`graph TD;
     A-->B;
     A-->C;
     B-->D;
     C-->D;\`} mdxType="Mermaid" />
     </MDXLayout>;
@@ -255,19 +341,14 @@ export default function MDXContent({
   ...props
 }) {
   return <MDXLayout {...layoutProps} {...props} components={components} mdxType="MDXLayout">
 
     <h1>{\`Heading 1\`}</h1>
-    <Mermaid config={{
-      "mermaid": {
-        "theme": "dark"
-      }
-    }} chart={\`graph TD;
-    A-->B;
-    A-->C;
-    B-->D;
-    C-->D;\`} mdxType="Mermaid" />
+    <Mermaid {...{
+      "config": "{\\"mermaid\\":{\\"theme\\":\\"dark\\"}}",
+      "chart": "graph TD;\\n    A-->B;\\n    A-->C;\\n    B-->D;\\n    C-->D;"
+    }} mdxType="Mermaid"></Mermaid>
     <Mermaid chart={\`graph TD;
     E-->F;
     E-->G;
     F-->H;
     G-->H;\`} mdxType="Mermaid" />
@@ -275,5 +356,48 @@ export default function MDXContent({
 }
 
 ;
 MDXContent.isMDXComponent = true;`)
 })
+
+test('Multiple code block', async () => {
+  const mdxCompiler = createTestCompiler({ mermaid: { theme: 'dark' } })
+  const result = await mdxCompiler.process(`# Heading 1\n
+\`\`\`mermaid
+graph TD;
+    A-->B;
+    A-->C;
+    B-->D;
+    C-->D;
+\`\`\`
+\`\`\`mermaid
+graph TD;
+E-->F;
+E-->G;
+F-->H;
+G-->H;
+\`\`\``)
+  expect(result.contents).toEqual(`import { Mermaid } from 'mdx-mermaid/lib/Mermaid';
+
+
+const layoutProps = {\n  \n};
+const MDXLayout = "wrapper"
+export default function MDXContent({
+  components,
+  ...props
+}) {
+  return <MDXLayout {...layoutProps} {...props} components={components} mdxType="MDXLayout">
+
+    <h1>{\`Heading 1\`}</h1>
+    <Mermaid {...{
+      "config": "{\\"mermaid\\":{\\"theme\\":\\"dark\\"}}",
+      "chart": "graph TD;\\n    A-->B;\\n    A-->C;\\n    B-->D;\\n    C-->D;"
+    }} mdxType="Mermaid"></Mermaid>
+    <Mermaid {...{
+      "chart": "graph TD;\\nE-->F;\\nE-->G;\\nF-->H;\\nG-->H;"
+    }} mdxType="Mermaid"></Mermaid>
+    </MDXLayout>;
+}
+
+;
+MDXContent.isMDXComponent = true;`)
+})
diff --git a/src/mdxast-mermaid.ts b/src/mdxast-mermaid.ts
index 4fdfbaf..24822ae 100644
--- a/src/mdxast-mermaid.ts
+++ b/src/mdxast-mermaid.ts
@@ -51,30 +51,22 @@ export default function plugin (config?: Config) {
     visit<CodeMermaid>(ast, { type: 'code', lang: 'mermaid' }, (node, index, parent) => {
       instances.push([node, index, parent as Parent<Node<Data>, Data>])
     })
 
     // Replace each Mermaid code block with the Mermaid component
-    instances.forEach(([node, index, parent]) => {
+    instances.forEach(([node, index, parent], i) => {
       parent.children.splice(index, 1, {
-        type: 'jsx',
-        value: `<Mermaid chart={\`${node.value}\`}/>`,
-        position: node.position
+        type: 'mermaidCodeBlock',
+        data: {
+          hName: 'Mermaid',
+          hProperties: {
+            config: i > 0 ? undefined : JSON.stringify(config),
+            chart: node.value
+          }
+        }
       })
     })
 
-    // Look for any components
-    visit<Literal<string> & { type: 'jsx' }>(ast, { type: 'jsx' }, (node, index, parent) => {
-      if (/.*<Mermaid.*/.test(node.value)) {
-        // If the component doesn't have config
-        if (!/.*config={.*/.test(node.value)) {
-          const index = node.value.indexOf('<Mermaid') + 8
-          node.value = node.value.substring(0, index) +
-          ` config={${JSON.stringify(config || {})}}` +
-            node.value.substring(index)
-        }
-        insertImport(ast)
-        return visit.EXIT
-      }
-    })
+    insertImport(ast)
     return ast
   }
 }
diff --git a/yarn.lock b/yarn.lock
index b030eb5..585cd38 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1499,11 +1499,19 @@
     find-cache-dir "^2.0.0"
     make-dir "^2.1.0"
     pirates "^4.0.0"
     source-map-support "^0.5.16"
 
-"@babel/runtime@^7.8.4":
+"@babel/runtime-corejs3@^7.10.2":
+  version "7.18.9"
+  resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.18.9.tgz#7bacecd1cb2dd694eacd32a91fcf7021c20770ae"
+  integrity sha512-qZEWeccZCrHA2Au4/X05QW5CMdm4VjUDCrGq5gf1ZDcM4hRqreKrtwAn7yci9zfgAS9apvnsFXiGBHBAxZdK9A==
+  dependencies:
+    core-js-pure "^3.20.2"
+    regenerator-runtime "^0.13.4"
+
+"@babel/runtime@^7.10.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.8.4":
   version "7.18.9"
   resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.18.9.tgz#b4fcfce55db3d2e5e080d2490f608a3b9f407f4a"
   integrity sha512-lkqXDcvlFT5rvEjiu6+QYO+1GXrEHRo2LOtS7E4GtX5ESIZOgepqsZBVIj6Pv+a6zqsya9VCgiK1KAK4BvJDAw==
   dependencies:
     regenerator-runtime "^0.13.4"
@@ -1850,10 +1858,21 @@
     pirates "^4.0.4"
     slash "^3.0.0"
     source-map "^0.6.1"
     write-file-atomic "^3.0.0"
 
+"@jest/types@^26.6.2":
+  version "26.6.2"
+  resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.6.2.tgz#bef5a532030e1d88a2f5a6d933f84e97226ed48e"
+  integrity sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==
+  dependencies:
+    "@types/istanbul-lib-coverage" "^2.0.0"
+    "@types/istanbul-reports" "^3.0.0"
+    "@types/node" "*"
+    "@types/yargs" "^15.0.0"
+    chalk "^4.0.0"
+
 "@jest/types@^27.0.6":
   version "27.0.6"
   resolved "https://registry.yarnpkg.com/@jest/types/-/types-27.0.6.tgz#9a992bc517e0c49f035938b8549719c2de40706b"
   integrity sha512-aSquT1qa9Pik26JK5/3rvnYb4bGtm1VFNesHKmNTwmPIgOrixvhL2ghIvFRNEpzy3gU+rUgjIF/KodbkFAl++g==
   dependencies:
@@ -2112,15 +2131,42 @@
   resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-8.1.0.tgz#3fdc2b6cb58935b21bfb8d1625eb1300484316e7"
   integrity sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg==
   dependencies:
     "@sinonjs/commons" "^1.7.0"
 
+"@testing-library/dom@^7.28.1":
+  version "7.31.2"
+  resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-7.31.2.tgz#df361db38f5212b88555068ab8119f5d841a8c4a"
+  integrity sha512-3UqjCpey6HiTZT92vODYLPxTBWlM8ZOOjr3LX5F37/VRipW2M1kX6I/Cm4VXzteZqfGfagg8yXywpcOgQBlNsQ==
+  dependencies:
+    "@babel/code-frame" "^7.10.4"
+    "@babel/runtime" "^7.12.5"
+    "@types/aria-query" "^4.2.0"
+    aria-query "^4.2.2"
+    chalk "^4.1.0"
+    dom-accessibility-api "^0.5.6"
+    lz-string "^1.4.4"
+    pretty-format "^26.6.2"
+
+"@testing-library/react@^11.1.0":
+  version "11.2.7"
+  resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-11.2.7.tgz#b29e2e95c6765c815786c0bc1d5aed9cb2bf7818"
+  integrity sha512-tzRNp7pzd5QmbtXNG/mhdcl7Awfu/Iz1RaVHY75zTdOkmHCuzMhRL83gWHSgOAcjS3CCbyfwUHMZgRJb4kAfpA==
+  dependencies:
+    "@babel/runtime" "^7.12.5"
+    "@testing-library/dom" "^7.28.1"
+
 "@tootallnate/once@1":
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82"
   integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==
 
+"@types/aria-query@^4.2.0":
+  version "4.2.2"
+  resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-4.2.2.tgz#ed4e0ad92306a704f9fb132a0cfcf77486dbe2bc"
+  integrity sha512-HnYpAE1Y6kRyKM/XkEuiRQhTHvkzMBurTHnpFLYLBGPIylZNPs9jJcuOOYWxPLJCSEtmZT0Y8rHDokKN7rRTig==
+
 "@types/babel__core@^7.0.0", "@types/babel__core@^7.1.14":
   version "7.1.15"
   resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.15.tgz#2ccfb1ad55a02c83f8e0ad327cbc332f55eb1024"
   integrity sha512-bxlMKPDbY8x5h6HBwVzEOk2C8fb6SLfYQ5Jw3uBYuYF1lfWk/kbLd81la82vrIkBb0l+JdmrZaDikPrNxpS/Ew==
   dependencies:
@@ -2277,10 +2323,17 @@
 "@types/yargs-parser@*":
   version "20.2.1"
   resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-20.2.1.tgz#3b9ce2489919d9e4fea439b76916abc34b2df129"
   integrity sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==
 
+"@types/yargs@^15.0.0":
+  version "15.0.14"
+  resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.14.tgz#26d821ddb89e70492160b66d10a0eb6df8f6fb06"
+  integrity sha512-yEJzHoxf6SyQGhBhIYGXQDSCkJjB6HohDShto7m8vaKg9Yp0Yn8+71J9eakh2bnPg6BfsH9PRMhiRTZnd4eXGQ==
+  dependencies:
+    "@types/yargs-parser" "*"
+
 "@types/yargs@^16.0.0":
   version "16.0.4"
   resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-16.0.4.tgz#26aad98dd2c2a38e421086ea9ad42b9e51642977"
   integrity sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==
   dependencies:
@@ -2490,10 +2543,18 @@ argparse@^1.0.7:
 argparse@^2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
   integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
 
+aria-query@^4.2.2:
+  version "4.2.2"
+  resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-4.2.2.tgz#0d2ca6c9aceb56b8977e9fed6aed7e15bbd2f83b"
+  integrity sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==
+  dependencies:
+    "@babel/runtime" "^7.10.2"
+    "@babel/runtime-corejs3" "^7.10.2"
+
 arr-diff@^4.0.0:
   version "4.0.0"
   resolved "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz"
   integrity sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=
 
@@ -3112,10 +3173,15 @@ core-js-compat@^3.21.0, core-js-compat@^3.22.1:
   integrity sha512-XhdNAGeRnTpp8xbD+sR/HFDK9CbeeeqXT6TuofXh3urqEevzkWmLRgrVoykodsw8okqo2pu1BOmuCKrHx63zdw==
   dependencies:
     browserslist "^4.21.3"
     semver "7.0.0"
 
+core-js-pure@^3.20.2:
+  version "3.24.1"
+  resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.24.1.tgz#8839dde5da545521bf282feb7dc6d0b425f39fd3"
+  integrity sha512-r1nJk41QLLPyozHUUPmILCEMtMw24NG4oWK6RbsDdjzQgg9ZvrUsPBj1MnG0wXXp1DCDU6j+wUvEmBSrtRbLXg==
+
 cosmiconfig@^7.0.0:
   version "7.0.0"
   resolved "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.0.tgz"
   integrity sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA==
   dependencies:
@@ -3561,10 +3627,15 @@ doctrine@^3.0.0:
   resolved "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz"
   integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==
   dependencies:
     esutils "^2.0.2"
 
+dom-accessibility-api@^0.5.6:
+  version "0.5.14"
+  resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.14.tgz#56082f71b1dc7aac69d83c4285eef39c15d93f56"
+  integrity sha512-NMt+m9zFMPZe0JcY9gN224Qvk6qLIdqex29clBvc/y75ZBX9YA9wNK3frsYvu2DI1xcCIwxwnX+TlsJ2DSOADg==
+
 domexception@^2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/domexception/-/domexception-2.0.1.tgz#fb44aefba793e1574b0af6aed2801d057529f304"
   integrity sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==
   dependencies:
@@ -5720,10 +5791,15 @@ lru-cache@^6.0.0:
   resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz"
   integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==
   dependencies:
     yallist "^4.0.0"
 
+lz-string@^1.4.4:
+  version "1.4.4"
+  resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.4.4.tgz#c0d8eaf36059f705796e1e344811cf4c498d3a26"
+  integrity sha512-0ckx7ZHRPqb0oUm8zNr+90mtf9DQB60H1wMCjBtfi62Kl3a7JbHob6gA2bC+xRvZoOL+1hzUK8jeuEIQE8svEQ==
+
 magic-string@^0.25.7:
   version "0.25.9"
   resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.9.tgz#de7f9faf91ef8a1c91d02c2e5314c8277dbcdd1c"
   integrity sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==
   dependencies:
@@ -6318,10 +6394,20 @@ prelude-ls@^1.2.1:
 prelude-ls@~1.1.2:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
   integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=
 
+pretty-format@^26.6.2:
+  version "26.6.2"
+  resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.6.2.tgz#e35c2705f14cb7fe2fe94fa078345b444120fc93"
+  integrity sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==
+  dependencies:
+    "@jest/types" "^26.6.2"
+    ansi-regex "^5.0.0"
+    ansi-styles "^4.0.0"
+    react-is "^17.0.1"
+
 pretty-format@^27.0.0, pretty-format@^27.4.6:
   version "27.4.6"
   resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.4.6.tgz#1b784d2f53c68db31797b2348fa39b49e31846b7"
   integrity sha512-NblstegA1y/RJW2VyML+3LlpFjzx62cUrtBIKIWDXEDkjNeleA7Od7nrzcs/VLQvAeV4CgSYhrN39DRN88Qi/g==
   dependencies:
@@ -6371,37 +6457,28 @@ punycode@^2.1.0, punycode@^2.1.1:
 queue-microtask@^1.2.2:
   version "1.2.3"
   resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz"
   integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
 
-"react-is@^16.12.0 || ^17.0.0", react-is@^17.0.1, react-is@^17.0.2:
+react-dom@^17.0.0:
   version "17.0.2"
-  resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
-  integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
+  resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23"
+  integrity sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==
+  dependencies:
+    loose-envify "^1.1.0"
+    object-assign "^4.1.1"
+    scheduler "^0.20.2"
 
 react-is@^16.8.1:
   version "16.13.1"
   resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz"
   integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
 
-react-shallow-renderer@^16.13.1:
-  version "16.14.1"
-  resolved "https://registry.yarnpkg.com/react-shallow-renderer/-/react-shallow-renderer-16.14.1.tgz#bf0d02df8a519a558fd9b8215442efa5c840e124"
-  integrity sha512-rkIMcQi01/+kxiTE9D3fdS959U1g7gs+/rborw++42m1O9FAQiNI/UNRZExVUoAOprn4umcXf+pFRou8i4zuBg==
-  dependencies:
-    object-assign "^4.1.1"
-    react-is "^16.12.0 || ^17.0.0"
-
-react-test-renderer@^17.0.2:
+react-is@^17.0.1:
   version "17.0.2"
-  resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-17.0.2.tgz#4cd4ae5ef1ad5670fc0ef776e8cc7e1231d9866c"
-  integrity sha512-yaQ9cB89c17PUb0x6UfWRs7kQCorVdHlutU1boVPEsB8IDZH6n9tHxMacc3y0JoXOJUsZb/t/Mb8FUWMKaM7iQ==
-  dependencies:
-    object-assign "^4.1.1"
-    react-is "^17.0.2"
-    react-shallow-renderer "^16.13.1"
-    scheduler "^0.20.2"
+  resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
+  integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
 
 react@^17.0.1:
   version "17.0.2"
   resolved "https://registry.npmjs.org/react/-/react-17.0.2.tgz"
   integrity sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==

Vulnerable code:     "react-test-renderer": "^17.0.2",
import mermaid from 'mermaid'
import renderer from 'react-test-renderer'
async function waitFor (ms: number) {
  return new Promise<void>(resolve => {
    setTimeout(() => resolve(), ms)
  })
}

  const component = renderer.create(<Mermaid chart={''} config={{}}  />)
  expect(mermaid.initialize).toBeCalledTimes(0)
  expect(mermaid.render).toBeCalledTimes(0)
  component.update()
  expect(mermaid.render).toBeCalledTimes(1)
  expect(mermaid.initialize).toBeCalledTimes(1)
  component.update()
  expect(mermaid.render).toBeCalledTimes(1)
  expect(mermaid.initialize).toBeCalledTimes(1)
  const component = renderer.create(<Mermaid chart={`graph TD;
      A-->B;
      A-->C;
      B-->D;
      C-->D;`} config={{}} />)
  expect(mermaid.initialize).toBeCalledTimes(0)
  expect(mermaid.render).toBeCalledTimes(0)
  component.update()
  expect(mermaid.render).toBeCalledTimes(1)
  expect(mermaid.initialize).toBeCalledTimes(1)
  component.update()
  expect(mermaid.render).toBeCalledTimes(1)
  expect(mermaid.initialize).toBeCalledTimes(1)
it('initializes only once', async () => {
  const component = renderer.create(<>
        <Mermaid chart={'foo'} config={{}} />
        <Mermaid chart={'bar'} />
      </>)
  expect(mermaid.initialize).toBeCalledTimes(0)
  expect(mermaid.render).toBeCalledTimes(0)
  await waitFor(1000)
  component.update()
  expect(mermaid.render).toBeCalledTimes(2)
  component.update()
  expect(mermaid.render).toBeCalledTimes(2)
  const component = renderer.create(<Mermaid chart={`graph TD;
      A-->B;
      A-->C;
      B-->D;
      C-->D;`} config={{ mermaid: { theme: 'dark' } } } />)
  expect(mermaid.initialize).toBeCalledTimes(0)
  expect(mermaid.render).toBeCalledTimes(0)
  component.update()
  expect(mermaid.render).toHaveBeenCalled()
  expect(mermaid.initialize).toHaveBeenNthCalledWith(1, { startOnLoad: true, theme: 'dark' })
  component.update()
  expect(mermaid.render).toBeCalledTimes(1)
  expect(mermaid.initialize).toBeCalledTimes(1)
it('re-renders mermaid theme on html data-theme attribute change', async () => {
  const component = renderer.create(
    <html data-theme='light'>
      <Mermaid chart={`graph TD;
            A-->B;
            A-->C;
            B-->D;
            C-->D;`} config={{}} />
    </html>)
  expect(mermaid.initialize).toBeCalledTimes(0)
  expect(mermaid.render).toBeCalledTimes(0)
  component.update()
  expect(mermaid.render).toBeCalledTimes(1)
  expect(mermaid.initialize).toBeCalledTimes(1)
  component.update()
  expect(mermaid.render).toBeCalledTimes(1)
  expect(mermaid.initialize).toBeCalledTimes(1)
  component.update(
    <html data-theme='dark'>
      <Mermaid chart={`graph TD;
            A-->B;
            A-->C;
            B-->D;
            C-->D;`} config={{}} />
        </html>)
  // Time for mutation observer to notice change.
  await waitFor(2000)
  expect(mermaid.render).toBeCalledTimes(2)
  expect(mermaid.initialize).toBeCalledTimes(2)
})
it('renders the output of mermaid into the div', async () => {
  const expectedOutput = 'mermaid output'
  mermaid.render = jest.fn((_, __, cb) => {
    if (cb) cb(expectedOutput, () => 0)
    return expectedOutput
  })
  let component: any
  renderer.act(() => {
    component = renderer.create(
      <Mermaid chart={`graph TD;
            A-->B;
            A-->C;
            B-->D;
            C-->D;`} config={{}} />
    )
  })
  expect(mermaid.render).toBeCalledTimes(1)
  expect(component.toJSON()).toMatchSnapshot()
})
describe('changing the theme at runtime', () => {
  let useRefSpy: jest.SpyInstance
  let html: HTMLHtmlElement
  beforeEach(() => {
    html = document.createElement('html')
    html.setAttribute(HTML_THEME_ATTRIBUTE, LIGHT_THEME_KEY)
    useRefSpy = jest.spyOn(document, 'querySelector').mockReturnValue(html)
  })
  afterEach(() => {
    expect(useRefSpy).toHaveBeenCalled()
  })
  it('reacts to changed theme', async () => {
    const getThemeSpy = jest.spyOn(ThemeHelper, 'getTheme')
    renderer.act(() => {
      renderer.create(
        <Mermaid chart={`graph TD;
              A-->B;
              A-->C;
              B-->D;
              C-->D;`} config={{}} />
      )
    })

    await renderer.act(async () => {
      html.setAttribute(HTML_THEME_ATTRIBUTE, DARK_THEME_KEY)
      await waitFor(1000)
    })

    expect(getThemeSpy.mock.calls.length).toBeGreaterThan(2)
  })
  it('does not react to non-theme attribute changes of html', async () => {
    const getThemeSpy = jest.spyOn(ThemeHelper, 'getTheme')
    renderer.act(() => {
      renderer.create(
        <Mermaid chart={`graph TD;
              A-->B;
              A-->C;
              B-->D;
              C-->D;`} config={{}} />
      )
    })

    await renderer.act(async () => {
      html.setAttribute('manifest', 'some-value')
      await waitFor(1000)
    })
    expect(getThemeSpy).toHaveBeenCalledTimes(2)
  })
import React, { useEffect, useState, ReactElement } from 'react'
import mermaidAPI from 'mermaid/mermaidAPI'
/**
 * Assign a unique ID to each mermaid svg as per requirements
 * of `mermaid.render`.
 */
let id = 0

  config?: Config
export const Mermaid = ({ chart, config }: MermaidProps): ReactElement<MermaidProps> => {
    return <div></div>
  // Watch for changes in theme in the HTML attribute `data-theme`.
  const [theme, setTheme] = useState<mermaidAPI.Theme>(getTheme(html, config))
        setTheme(getTheme(mutation.target as HTMLHtmlElement, config))
  }, [chart, config, theme])
  // When theme updates, rerender the SVG.
  const [svg, setSvg] = useState<string>('')
    const render = () => {
      mermaid.render(`mermaid-svg-${id.toString()}`, chart, (renderedSvg) => setSvg(renderedSvg))
      id++
    }

      render()
    } else {
      // Is there a better way?
      setTimeout(render, 0)
  }, [theme, chart])
  return <div dangerouslySetInnerHTML={{ __html: svg }}></div>
exports[`renders the output of mermaid into the div 1`] = `
<div
  dangerouslySetInnerHTML={
    Object {
      "__html": "mermaid output",
    }
  }
/>
  expect(result.contents).toEqual('\n\n\nconst layoutProps = {\n  \n};\nconst MDXLayout = "wrapper"\nexport default function MDXContent({\n  components,\n  ...props\n}) {\n  return <MDXLayout {...layoutProps} {...props} components={components} mdxType="MDXLayout">\n    <h1>{`Heading 1`}</h1>\n    <p>{`No Mermaid diagram :(`}</p>\n    </MDXLayout>;\n}\n\n;\nMDXContent.isMDXComponent = true;')
  expect(result.contents).toEqual("import { Mermaid } from 'mdx-mermaid/lib/Mermaid';\n\n\nconst layoutProps = {\n  \n};\nconst MDXLayout = \"wrapper\"\nexport default function MDXContent({\n  components,\n  ...props\n}) {\n  return <MDXLayout {...layoutProps} {...props} components={components} mdxType=\"MDXLayout\">\n\n    <h1>{`Heading 1`}</h1>\n    <Mermaid config={{}} chart={`graph TD;\n    A-->B;\n    A-->C;\n    B-->D;\n    C-->D;`} mdxType=\"Mermaid\" />\n    </MDXLayout>;\n}\n\n;\nMDXContent.isMDXComponent = true;")
  expect(result.contents).toEqual("import { Mermaid } from 'mdx-mermaid/lib/Mermaid';\n\n\nconst layoutProps = {\n  \n};\nconst MDXLayout = \"wrapper\"\nexport default function MDXContent({\n  components,\n  ...props\n}) {\n  return <MDXLayout {...layoutProps} {...props} components={components} mdxType=\"MDXLayout\">\n\n    <h1>{`Heading 1`}</h1>\n    <Mermaid config={{}} chart={`graph TD;\n    A-->B;\n    A-->C;\n    B-->D;\n    C-->D;`} mdxType=\"Mermaid\" />\n    </MDXLayout>;\n}\n\n;\nMDXContent.isMDXComponent = true;")
  expect(result.contents).toEqual("import { Mermaid } from 'mdx-mermaid/Mermaid';\n\n\nconst layoutProps = {\n  \n};\nconst MDXLayout = \"wrapper\"\nexport default function MDXContent({\n  components,\n  ...props\n}) {\n  return <MDXLayout {...layoutProps} {...props} components={components} mdxType=\"MDXLayout\">\n\n    <h1>{`Heading 1`}</h1>\n    <Mermaid config={{}} chart={`graph TD;\n    A-->B;\n    A-->C;\n    B-->D;\n    C-->D;`} mdxType=\"Mermaid\" />\n    </MDXLayout>;\n}\n\n;\nMDXContent.isMDXComponent = true;")
  expect(result.contents).toEqual("import { Mermaid } from 'mdx-mermaid/lib/Mermaid';\nimport { A } from 'a';\n\n\nconst layoutProps = {\n  \n};\nconst MDXLayout = \"wrapper\"\nexport default function MDXContent({\n  components,\n  ...props\n}) {\n  return <MDXLayout {...layoutProps} {...props} components={components} mdxType=\"MDXLayout\">\n\n\n    <h1>{`Heading 1`}</h1>\n    <Mermaid config={{}} chart={`graph TD;\n    A-->B;\n    A-->C;\n    B-->D;\n    C-->D;`} mdxType=\"Mermaid\" />\n    </MDXLayout>;\n}\n\n;\nMDXContent.isMDXComponent = true;")
    <Mermaid config={{}} chart={\`graph TD;
    <Mermaid config={{}} chart={\`graph TD;
    <Mermaid config={{
      "mermaid": {
        "theme": "dark"
      }
    }} chart={\`graph TD;
    A-->B;
    A-->C;
    B-->D;
    C-->D;\`} mdxType="Mermaid" />
    <Mermaid config={{
      "mermaid": {
        "theme": "dark"
      }
    }} chart={\`graph TD;
    <Mermaid config={{
      "mermaid": {
        "theme": "dark"
      }
    }} chart={\`graph TD;
    A-->B;
    A-->C;
    B-->D;
    C-->D;\`} mdxType="Mermaid" />
    instances.forEach(([node, index, parent]) => {
        type: 'jsx',
        value: `<Mermaid chart={\`${node.value}\`}/>`,
        position: node.position
    // Look for any components
    visit<Literal<string> & { type: 'jsx' }>(ast, { type: 'jsx' }, (node, index, parent) => {
      if (/.*<Mermaid.*/.test(node.value)) {
        // If the component doesn't have config
        if (!/.*config={.*/.test(node.value)) {
          const index = node.value.indexOf('<Mermaid') + 8
          node.value = node.value.substring(0, index) +
          ` config={${JSON.stringify(config || {})}}` +
            node.value.substring(index)
        }
        insertImport(ast)
        return visit.EXIT
      }
    })
"@babel/runtime@^7.8.4":
"react-is@^16.12.0 || ^17.0.0", react-is@^17.0.1, react-is@^17.0.2:
  resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
  integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
react-shallow-renderer@^16.13.1:
  version "16.14.1"
  resolved "https://registry.yarnpkg.com/react-shallow-renderer/-/react-shallow-renderer-16.14.1.tgz#bf0d02df8a519a558fd9b8215442efa5c840e124"
  integrity sha512-rkIMcQi01/+kxiTE9D3fdS959U1g7gs+/rborw++42m1O9FAQiNI/UNRZExVUoAOprn4umcXf+pFRou8i4zuBg==
  dependencies:
    object-assign "^4.1.1"
    react-is "^16.12.0 || ^17.0.0"

react-test-renderer@^17.0.2:
  resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-17.0.2.tgz#4cd4ae5ef1ad5670fc0ef776e8cc7e1231d9866c"
  integrity sha512-yaQ9cB89c17PUb0x6UfWRs7kQCorVdHlutU1boVPEsB8IDZH6n9tHxMacc3y0JoXOJUsZb/t/Mb8FUWMKaM7iQ==
  dependencies:
    object-assign "^4.1.1"
    react-is "^17.0.2"
    react-shallow-renderer "^16.13.1"
    scheduler "^0.20.2"
Fixed code:     "@testing-library/react": "^11.1.0",
    "react-dom": "^17.0.0",
import { act, render, RenderResult } from '@testing-library/react'
// eslint-disable-next-line import/first
import mermaid from 'mermaid'

const getThemeSpy = jest.spyOn(ThemeHelper, 'getTheme')

const diagram = `graph TD;
A-->B;
A-->C;
B-->D;
C-->D;`

const removeUniqueness = (element: Element) => {
  element.querySelectorAll('style').forEach((v) => v.remove())
  element.querySelectorAll('svg').forEach((v) => {
    v.removeAttribute('id')
    v.parentElement!.removeAttribute('id')
  })
}

const expectMermaidMatch = (result: RenderResult) => {
  removeUniqueness(result.baseElement)
  expect(result.baseElement.parentElement).toMatchSnapshot()
  return result
}

  expectMermaidMatch(render(<Mermaid chart={''} config={{}} />))
  expectMermaidMatch(render(<svg><Mermaid chart={diagram} config={{}} /></svg>))
it('renders with diagram change', () => {
  const config = {}
  jest.useFakeTimers()
  const view = expectMermaidMatch(render(<Mermaid chart={diagram} config={config} />))
  view.rerender(<Mermaid chart={`graph TD;
D-->C;
D-->B;
C-->A;
B-->A;`} config={config} />)
  jest.advanceTimersByTime(1000)
  expectMermaidMatch(view)
  jest.useRealTimers()
  expect(mermaid.contentLoaded).toBeCalledTimes(1)
})

it('initializes only once', () => {
  expectMermaidMatch(render(<>
    <Mermaid chart={'foo'} config={{}} />
    <Mermaid chart={'bar'} />
  </>))
  expect(mermaid.contentLoaded).toBeCalledTimes(1)
  expectMermaidMatch(render(<Mermaid chart={diagram} config={{ mermaid: { theme: 'dark' } }} />))
  expect(mermaid.contentLoaded).toBeCalledTimes(1)
  expect(mermaid.initialize).toBeCalledWith({ startOnLoad: true, theme: 'dark' })
it('renders with mermaid config change', () => {
  const view = expectMermaidMatch(render(<Mermaid chart={diagram} config={{ mermaid: { theme: 'dark' } }} />))
  view.baseElement.querySelectorAll('div.mermaid').forEach((v) => {
    v.setAttribute('data-processed', 'true')
  })
  expect(mermaid.contentLoaded).toBeCalledTimes(1)
  expect(mermaid.initialize).toBeCalledWith({ startOnLoad: true, theme: 'dark' })
  view.rerender(<Mermaid chart={diagram} config={{ mermaid: { theme: 'forest' } }} />)
  // await waitFor(1000)
  expectMermaidMatch(view)
  expect(mermaid.contentLoaded).toBeCalledTimes(2)
  expect(mermaid.initialize).toHaveBeenNthCalledWith(2, { startOnLoad: true, theme: 'forest' })
})
it('renders with string mermaid config', () => {
  expectMermaidMatch(render(<Mermaid chart={diagram} config={JSON.stringify({ mermaid: { theme: 'dark' } })} />))
  expect(mermaid.contentLoaded).toBeCalledTimes(1)
  expect(mermaid.initialize).toBeCalledWith({ startOnLoad: true, theme: 'dark' })
})
it('re-renders mermaid theme on html data-theme attribute change', () => {
  const component = render(
    <Mermaid chart={diagram} config={{}} />)
  expectMermaidMatch(component)
  expect(mermaid.contentLoaded).toBeCalledTimes(1)
  expect(mermaid.initialize).toBeCalledTimes(1)
  expect(getThemeSpy).toBeCalledTimes(1)
  act(() => document.querySelector('html')!.setAttribute(HTML_THEME_ATTRIBUTE, DARK_THEME_KEY))
  expectMermaidMatch(component)
  expect(mermaid.contentLoaded).toBeCalledTimes(1)
  expect(getThemeSpy).toBeCalledTimes(1)
  act(() => document.querySelector('html')!.setAttribute(HTML_THEME_ATTRIBUTE, LIGHT_THEME_KEY))
  expectMermaidMatch(component)
})
it('does not react to non-theme attribute changes of html', () => {
  const component = render(<Mermaid chart={diagram} config={{}} />)
  expectMermaidMatch(component)
  expect(mermaid.contentLoaded).toBeCalledTimes(1)
  expect(mermaid.initialize).toBeCalledTimes(1)
  act(() => document.querySelector('html')!.setAttribute('manifest', 'some-value'))

  expectMermaidMatch(component)
import React, { useEffect, useState, ReactElement, useMemo } from 'react'
  config?: Config | string
export const Mermaid = ({ chart, config: configSrc }: MermaidProps): ReactElement<MermaidProps> => {
    return <div className="mermaid" data-mermaid-src={chart}>{chart}</div>
  const config: Config = useMemo(() => typeof configSrc === 'string' ? JSON.parse(configSrc) : configSrc, [configSrc])

  const [rerender, setRerender] = useState<boolean>(false)

  const theme = useMemo(() => getTheme(html, config), [config, rerender])
        setRerender((cur) => !cur)
  }, [])
      document.querySelectorAll('div.mermaid[data-processed="true"]').forEach((v) => {
        v.removeAttribute('data-processed')
        v.innerHTML = v.getAttribute('data-mermaid-src') as string
      })
      mermaid.contentLoaded()
  }, [config, theme])

  useEffect(() => {
    setTimeout(() => mermaid.contentLoaded, 0)
  }, [chart])
  return <div className="mermaid" data-mermaid-src={chart}>{chart}</div>
exports[`does not react to non-theme attribute changes of html 1`] = `
<html
  data-theme="light"
>
  <head />
  <body>
    <div>
      <div
        class="mermaid"
        data-mermaid-src="graph TD;
A-->B;
A-->C;
B-->D;
C-->D;"
      >
        graph TD;
A--&gt;B;
A--&gt;C;
B--&gt;D;
C--&gt;D;
      </div>
    </div>
  </body>
</html>
`;

exports[`does not react to non-theme attribute changes of html 2`] = `
<html
  data-theme="light"
  manifest="some-value"
>
  <head />
  <body>
    <div>
      <div
        class="mermaid"
        data-mermaid-src="graph TD;
A-->B;
A-->C;
B-->D;
C-->D;"
      >
        graph TD;
A--&gt;B;
A--&gt;C;
B--&gt;D;
C--&gt;D;
      </div>
    </div>
  </body>
</html>
`;

exports[`initializes only once 1`] = `
<html>
  <head />
  <body>
    <div>
      <div
        class="mermaid"
        data-mermaid-src="foo"
      >
        foo
      </div>
      <div
        class="mermaid"
        data-mermaid-src="bar"
      >
        bar
      </div>
    </div>
  </body>
</html>
`;

exports[`re-renders mermaid theme on html data-theme attribute change 1`] = `
<html>
  <head />
  <body>
    <div>
      <div
        class="mermaid"
        data-mermaid-src="graph TD;
A-->B;
A-->C;
B-->D;
C-->D;"
      >
        graph TD;
A--&gt;B;
A--&gt;C;
B--&gt;D;
C--&gt;D;
      </div>
    </div>
  </body>
</html>
`;

exports[`re-renders mermaid theme on html data-theme attribute change 2`] = `
<html
  data-theme="dark"
>
  <head />
  <body>
    <div>
      <div
        class="mermaid"
        data-mermaid-src="graph TD;
A-->B;
A-->C;
B-->D;
C-->D;"
      >
        graph TD;
A--&gt;B;
A--&gt;C;
B--&gt;D;
C--&gt;D;
      </div>
    </div>
  </body>
</html>
`;

exports[`re-renders mermaid theme on html data-theme attribute change 3`] = `
<html
  data-theme="light"
>
  <head />
  <body>
    <div>
      <div
        class="mermaid"
        data-mermaid-src="graph TD;
A-->B;
A-->C;
B-->D;
C-->D;"
      >
        graph TD;
A--&gt;B;
A--&gt;C;
B--&gt;D;
C--&gt;D;
      </div>
    </div>
  </body>
</html>
`;

exports[`renders with diagram 1`] = `
<html>
  <head />
  <body>
    <div>
      <svg>
        <div
          class="mermaid"
          data-mermaid-src="graph TD;
A-->B;
A-->C;
B-->D;
C-->D;"
        >
          graph TD;
A--&gt;B;
A--&gt;C;
B--&gt;D;
C--&gt;D;
        </div>
      </svg>
    </div>
  </body>
</html>
`;

exports[`renders with diagram change 1`] = `
<html>
  <head />
  <body>
    <div>
      <div
        class="mermaid"
        data-mermaid-src="graph TD;
A-->B;
A-->C;
B-->D;
C-->D;"
      >
        graph TD;
A--&gt;B;
A--&gt;C;
B--&gt;D;
C--&gt;D;
      </div>
    </div>
  </body>
</html>
`;

exports[`renders with diagram change 2`] = `
<html>
  <head />
  <body>
    <div>
      <div
        class="mermaid"
        data-mermaid-src="graph TD;
D-->C;
D-->B;
C-->A;
B-->A;"
      >
        graph TD;
D--&gt;C;
D--&gt;B;
C--&gt;A;
B--&gt;A;
      </div>
    </div>
  </body>
</html>
`;

exports[`renders with mermaid config 1`] = `
<html>
  <head />
  <body>
    <div>
      <div
        class="mermaid"
        data-mermaid-src="graph TD;
A-->B;
A-->C;
B-->D;
C-->D;"
      >
        graph TD;
A--&gt;B;
A--&gt;C;
B--&gt;D;
C--&gt;D;
      </div>
    </div>
  </body>
</html>
`;

exports[`renders with mermaid config change 1`] = `
<html>
  <head />
  <body>
    <div>
      <div
        class="mermaid"
        data-mermaid-src="graph TD;
A-->B;
A-->C;
B-->D;
C-->D;"
      >
        graph TD;
A--&gt;B;
A--&gt;C;
B--&gt;D;
C--&gt;D;
      </div>
    </div>
  </body>
</html>
`;

exports[`renders with mermaid config change 2`] = `
<html>
  <head />
  <body>
    <div>
      <div
        class="mermaid"
        data-mermaid-src="graph TD;
A-->B;
A-->C;
B-->D;
C-->D;"
      >
        graph TD;
A--&gt;B;
A--&gt;C;
B--&gt;D;
C--&gt;D;
      </div>
    </div>
  </body>
</html>
`;

exports[`renders with string mermaid config 1`] = `
<html>
  <head />
  <body>
    <div>
      <div
        class="mermaid"
        data-mermaid-src="graph TD;
A-->B;
A-->C;
B-->D;
C-->D;"
      >
        graph TD;
A--&gt;B;
A--&gt;C;
B--&gt;D;
C--&gt;D;
      </div>
    </div>
  </body>
</html>
`;

exports[`renders without diagram 1`] = `
<html>
  <head />
  <body>
    <div>
      <div
        class="mermaid"
        data-mermaid-src=""
      />
    </div>
  </body>
</html>
  expect(result.contents).toEqual(`import { Mermaid } from 'mdx-mermaid/lib/Mermaid';


const layoutProps = {\n  \n};
const MDXLayout = "wrapper"
export default function MDXContent({
  components,
  ...props
}) {
  return <MDXLayout {...layoutProps} {...props} components={components} mdxType="MDXLayout">

    <h1>{\`Heading 1\`}</h1>
    <p>{\`No Mermaid diagram :(\`}</p>
    </MDXLayout>;
}

;
MDXContent.isMDXComponent = true;`)
  expect(result.contents).toEqual(`import { Mermaid } from 'mdx-mermaid/lib/Mermaid';


const layoutProps = {\n  \n};
const MDXLayout = "wrapper"
export default function MDXContent({
  components,
  ...props
}) {
  return <MDXLayout {...layoutProps} {...props} components={components} mdxType="MDXLayout">

    <h1>{\`Heading 1\`}</h1>
    <Mermaid {...{
      "chart": "graph TD;\\n    A-->B;\\n    A-->C;\\n    B-->D;\\n    C-->D;"
    }} mdxType="Mermaid"></Mermaid>
    </MDXLayout>;
}

;
MDXContent.isMDXComponent = true;`)
  expect(result.contents).toEqual(`import { Mermaid } from 'mdx-mermaid/lib/Mermaid';


const layoutProps = {\n  \n};
const MDXLayout = "wrapper"
export default function MDXContent({
  components,
  ...props
}) {
  return <MDXLayout {...layoutProps} {...props} components={components} mdxType="MDXLayout">

    <h1>{\`Heading 1\`}</h1>
    <Mermaid {...{
      "chart": "graph TD;\\n    A-->B;\\n    A-->C;\\n    B-->D;\\n    C-->D;"
    }} mdxType="Mermaid"></Mermaid>
    </MDXLayout>;
}

;
MDXContent.isMDXComponent = true;`)
  expect(result.contents).toEqual(`import { Mermaid } from 'mdx-mermaid/Mermaid';


const layoutProps = {\n  \n};
const MDXLayout = "wrapper"
export default function MDXContent({
  components,
  ...props
}) {
  return <MDXLayout {...layoutProps} {...props} components={components} mdxType="MDXLayout">

    <h1>{\`Heading 1\`}</h1>
    <Mermaid {...{
      "chart": "graph TD;\\n    A-->B;\\n    A-->C;\\n    B-->D;\\n    C-->D;"
    }} mdxType="Mermaid"></Mermaid>
    </MDXLayout>;
}

;
MDXContent.isMDXComponent = true;`)
  expect(result.contents).toEqual(`import { Mermaid } from 'mdx-mermaid/lib/Mermaid';
import { A } from 'a';


const layoutProps = {\n  \n};
const MDXLayout = "wrapper"
export default function MDXContent({
  components,
  ...props
}) {
  return <MDXLayout {...layoutProps} {...props} components={components} mdxType="MDXLayout">


    <h1>{\`Heading 1\`}</h1>
    <Mermaid {...{
      "chart": "graph TD;\\n    A-->B;\\n    A-->C;\\n    B-->D;\\n    C-->D;"
    }} mdxType="Mermaid"></Mermaid>
    </MDXLayout>;
}

;
MDXContent.isMDXComponent = true;`)
    <Mermaid chart={\`graph TD;
    <Mermaid chart={\`graph TD;
    <Mermaid {...{
      "config": "{\\"mermaid\\":{\\"theme\\":\\"dark\\"}}",
      "chart": "graph TD;\\n    A-->B;\\n    A-->C;\\n    B-->D;\\n    C-->D;"
    }} mdxType="Mermaid"></Mermaid>
    <Mermaid chart={\`graph TD;
    <Mermaid {...{
      "config": "{\\"mermaid\\":{\\"theme\\":\\"dark\\"}}",
      "chart": "graph TD;\\n    A-->B;\\n    A-->C;\\n    B-->D;\\n    C-->D;"
    }} mdxType="Mermaid"></Mermaid>

test('Multiple code block', async () => {
  const mdxCompiler = createTestCompiler({ mermaid: { theme: 'dark' } })
  const result = await mdxCompiler.process(`# Heading 1\n
\`\`\`mermaid
graph TD;
    A-->B;
    A-->C;
    B-->D;
    C-->D;
\`\`\`
\`\`\`mermaid
graph TD;
E-->F;
E-->G;
F-->H;
G-->H;
\`\`\``)
  expect(result.contents).toEqual(`import { Mermaid } from 'mdx-mermaid/lib/Mermaid';


const layoutProps = {\n  \n};
const MDXLayout = "wrapper"
export default function MDXContent({
  components,
  ...props
}) {
  return <MDXLayout {...layoutProps} {...props} components={components} mdxType="MDXLayout">

    <h1>{\`Heading 1\`}</h1>
    <Mermaid {...{
      "config": "{\\"mermaid\\":{\\"theme\\":\\"dark\\"}}",
      "chart": "graph TD;\\n    A-->B;\\n    A-->C;\\n    B-->D;\\n    C-->D;"
    }} mdxType="Mermaid"></Mermaid>
    <Mermaid {...{
      "chart": "graph TD;\\nE-->F;\\nE-->G;\\nF-->H;\\nG-->H;"
    }} mdxType="Mermaid"></Mermaid>
    </MDXLayout>;
}

;
MDXContent.isMDXComponent = true;`)
})
    instances.forEach(([node, index, parent], i) => {
        type: 'mermaidCodeBlock',
        data: {
          hName: 'Mermaid',
          hProperties: {
            config: i > 0 ? undefined : JSON.stringify(config),
            chart: node.value
          }
        }
    insertImport(ast)
"@babel/runtime-corejs3@^7.10.2":
  version "7.18.9"
  resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.18.9.tgz#7bacecd1cb2dd694eacd32a91fcf7021c20770ae"
  integrity sha512-qZEWeccZCrHA2Au4/X05QW5CMdm4VjUDCrGq5gf1ZDcM4hRqreKrtwAn7yci9zfgAS9apvnsFXiGBHBAxZdK9A==
  dependencies:
    core-js-pure "^3.20.2"
    regenerator-runtime "^0.13.4"

"@babel/runtime@^7.10.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.8.4":
"@jest/types@^26.6.2":
  version "26.6.2"
  resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.6.2.tgz#bef5a532030e1d88a2f5a6d933f84e97226ed48e"
  integrity sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==
  dependencies:
    "@types/istanbul-lib-coverage" "^2.0.0"
    "@types/istanbul-reports" "^3.0.0"
    "@types/node" "*"
    "@types/yargs" "^15.0.0"
    chalk "^4.0.0"

"@testing-library/dom@^7.28.1":
  version "7.31.2"
  resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-7.31.2.tgz#df361db38f5212b88555068ab8119f5d841a8c4a"
  integrity sha512-3UqjCpey6HiTZT92vODYLPxTBWlM8ZOOjr3LX5F37/VRipW2M1kX6I/Cm4VXzteZqfGfagg8yXywpcOgQBlNsQ==
  dependencies:
    "@babel/code-frame" "^7.10.4"
    "@babel/runtime" "^7.12.5"
    "@types/aria-query" "^4.2.0"
    aria-query "^4.2.2"
    chalk "^4.1.0"
    dom-accessibility-api "^0.5.6"
    lz-string "^1.4.4"
    pretty-format "^26.6.2"

"@testing-library/react@^11.1.0":
  version "11.2.7"
  resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-11.2.7.tgz#b29e2e95c6765c815786c0bc1d5aed9cb2bf7818"
  integrity sha512-tzRNp7pzd5QmbtXNG/mhdcl7Awfu/Iz1RaVHY75zTdOkmHCuzMhRL83gWHSgOAcjS3CCbyfwUHMZgRJb4kAfpA==
  dependencies:
    "@babel/runtime" "^7.12.5"
    "@testing-library/dom" "^7.28.1"

"@types/aria-query@^4.2.0":
  version "4.2.2"
  resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-4.2.2.tgz#ed4e0ad92306a704f9fb132a0cfcf77486dbe2bc"
  integrity sha512-HnYpAE1Y6kRyKM/XkEuiRQhTHvkzMBurTHnpFLYLBGPIylZNPs9jJcuOOYWxPLJCSEtmZT0Y8rHDokKN7rRTig==

"@types/yargs@^15.0.0":
  version "15.0.14"
  resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.14.tgz#26d821ddb89e70492160b66d10a0eb6df8f6fb06"
  integrity sha512-yEJzHoxf6SyQGhBhIYGXQDSCkJjB6HohDShto7m8vaKg9Yp0Yn8+71J9eakh2bnPg6BfsH9PRMhiRTZnd4eXGQ==
  dependencies:
    "@types/yargs-parser" "*"

aria-query@^4.2.2:
  version "4.2.2"
  resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-4.2.2.tgz#0d2ca6c9aceb56b8977e9fed6aed7e15bbd2f83b"
  integrity sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==
  dependencies:
    "@babel/runtime" "^7.10.2"
    "@babel/runtime-corejs3" "^7.10.2"

core-js-pure@^3.20.2:
  version "3.24.1"
  resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.24.1.tgz#8839dde5da545521bf282feb7dc6d0b425f39fd3"
  integrity sha512-r1nJk41QLLPyozHUUPmILCEMtMw24NG4oWK6RbsDdjzQgg9ZvrUsPBj1MnG0wXXp1DCDU6j+wUvEmBSrtRbLXg==

dom-accessibility-api@^0.5.6:
  version "0.5.14"
  resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.14.tgz#56082f71b1dc7aac69d83c4285eef39c15d93f56"
  integrity sha512-NMt+m9zFMPZe0JcY9gN224Qvk6qLIdqex29clBvc/y75ZBX9YA9wNK3frsYvu2DI1xcCIwxwnX+TlsJ2DSOADg==

lz-string@^1.4.4:
  version "1.4.4"
  resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.4.4.tgz#c0d8eaf36059f705796e1e344811cf4c498d3a26"
  integrity sha512-0ckx7ZHRPqb0oUm8zNr+90mtf9DQB60H1wMCjBtfi62Kl3a7JbHob6gA2bC+xRvZoOL+1hzUK8jeuEIQE8svEQ==

pretty-format@^26.6.2:
  version "26.6.2"
  resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.6.2.tgz#e35c2705f14cb7fe2fe94fa078345b444120fc93"
  integrity sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==
  dependencies:
    "@jest/types" "^26.6.2"
    ansi-regex "^5.0.0"
    ansi-styles "^4.0.0"
    react-is "^17.0.1"

react-dom@^17.0.0:
  resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23"
  integrity sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==
  dependencies:
    loose-envify "^1.1.0"
    object-assign "^4.1.1"
    scheduler "^0.20.2"
react-is@^17.0.1:
  resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
  integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==