<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Erick Martinez's Blog]]></title><description><![CDATA[Erick Martinez's Blog]]></description><link>https://blog.rey-ss.com</link><generator>RSS for Node</generator><lastBuildDate>Wed, 29 Apr 2026 07:44:40 GMT</lastBuildDate><atom:link href="https://blog.rey-ss.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Smoke Testing a TinyMCE React Component]]></title><description><![CDATA[Preface
Let me start off by saying, thank you for reading this post. I’ve finally rounded up the courage to join the learn in public movement and I plan to write about specific situations that I've found to have a dearth of coverage online.
If you fi...]]></description><link>https://blog.rey-ss.com/smoke-testing-a-tinymce-react-component</link><guid isPermaLink="true">https://blog.rey-ss.com/smoke-testing-a-tinymce-react-component</guid><category><![CDATA[React]]></category><category><![CDATA[Jest]]></category><dc:creator><![CDATA[Erick Martinez]]></dc:creator><pubDate>Mon, 27 Sep 2021 23:57:56 GMT</pubDate><content:encoded><![CDATA[<h1 id="preface">Preface</h1>
<p>Let me start off by saying, thank you for reading this post. I’ve finally rounded up the courage to join the learn in public movement and I plan to write about specific situations that I've found to have a dearth of coverage online.</p>
<p>If you find place for improvement in the solutions laid out below, I encourage you to share your feedback so that I and anyone else that reads this post may benefit from it.</p>
<h1 id="context">Context</h1>
<p>This post will cover how to use Jest and React Testing Library to write a simple smoke test for a <a target="_blank" href="https://www.tiny.cloud/docs/integrations/react/">TinyMCE React component</a> being used as controlled component in a Formik form within a NextJS project.</p>
<p>Prior to incorporating a rich text editor in my project, my form and corresponding smoke test looked similar to:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// NewQuizForm.js</span>
<span class="hljs-keyword">import</span> { Formik, Field, Form, ErrorMessage, FieldArray } <span class="hljs-keyword">from</span> <span class="hljs-string">"formik"</span>;
<span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> yup <span class="hljs-keyword">from</span> <span class="hljs-string">"yup"</span>;

<span class="hljs-keyword">const</span> validationSchema = yup.object().shape({
  <span class="hljs-attr">query</span>: yup.string().required(<span class="hljs-string">"Query is required."</span>),
});

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">NewQuizForm</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Formik</span>
      <span class="hljs-attr">initialValues</span>=<span class="hljs-string">{{</span>
        <span class="hljs-attr">query:</span> "",
      }}
      <span class="hljs-attr">validationSchema</span>=<span class="hljs-string">{validationSchema}</span>
      <span class="hljs-attr">onSubmit</span>=<span class="hljs-string">{(values,</span> { <span class="hljs-attr">setSubmitting</span> }) =&gt;</span> {
        setSubmitting(false);
        alert(JSON.stringify(values, null, 2));
      }}
    &gt;
      {({ values }) =&gt; {
        return (
          <span class="hljs-tag">&lt;<span class="hljs-name">Form</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Questions<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>

            <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">htmlFor</span>=<span class="hljs-string">"query"</span>&gt;</span>Query<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">Field</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"query"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"query"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span> /&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">ErrorMessage</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"query"</span> /&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">Form</span>&gt;</span>
        );
      }}
    <span class="hljs-tag">&lt;/<span class="hljs-name">Formik</span>&gt;</span></span>
  );
}
</code></pre>
<pre><code class="lang-javascript"><span class="hljs-comment">// NewQuizForm.spec.js</span>
<span class="hljs-keyword">import</span> { screen, render } <span class="hljs-keyword">from</span> <span class="hljs-string">"@testing-library/react"</span>;
<span class="hljs-keyword">import</span> NewQuizForm <span class="hljs-keyword">from</span> <span class="hljs-string">"../pages/new-quiz"</span>;

it(<span class="hljs-string">"renders expected input fields and no error messages"</span>, <span class="hljs-function">() =&gt;</span> {
  render(<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">NewQuizForm</span> /&gt;</span></span>);

  <span class="hljs-keyword">const</span> queryInput = screen.getByRole(<span class="hljs-string">"textbox"</span>, { <span class="hljs-attr">name</span>: <span class="hljs-regexp">/query/i</span> });

  <span class="hljs-keyword">const</span> queryError = screen.queryByText(<span class="hljs-regexp">/query is required/i</span>);

  expect(queryInput).toBeInTheDocument();

  expect(queryError).toBe(<span class="hljs-literal">null</span>);
});
</code></pre>
<h1 id="initial-failed-test">Initial Failed Test</h1>
<p>When I incorporated TinyMCE's rich text editor component into the form, the component and corresponding test output now looked something like so:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// NewQuizForm.js</span>

<span class="hljs-comment">//...</span>
&lt;label htmlFor=<span class="hljs-string">"query"</span>&gt;Query&lt;/label&gt;
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Field</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"query"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"query"</span>&gt;</span>
    {(props) =&gt; {
      return (
        <span class="hljs-tag">&lt;<span class="hljs-name">RichTextEditor</span>
          {/* <span class="hljs-attr">...</span> */}
        /&gt;</span>
      );
    }}
  <span class="hljs-tag">&lt;/<span class="hljs-name">Field</span>&gt;</span></span>
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">ErrorMessage</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"query"</span> /&gt;</span></span>
</code></pre>
<pre><code class="lang-shell"># output after running `yarn test __tests__/NewQuizForm.spec.js`
TestingLibraryElementError: Unable to find an accessible element with the role "textbox" and name `/query/i`

# RTL's log of elements rendered to the DOM
&lt;h1&gt;
  Questions
&lt;/h1&gt;
&lt;div&gt;
  &lt;label
    for="query"
  &gt;
    Query
  &lt;/label&gt;
  &lt;textarea
    id="query"
    style="visibility: hidden;"
  /&gt;
</code></pre>
<p>Note that in the output, we see a hidden <code>&lt;textarea&gt;</code> element. This is because when the TinyMCE editor is in classic (iframe) mode it inserts a textarea element that then gets replaced with an <code>iframe</code> and other UI elements (required for the toolbar, menu bar, etc..) when the rich text editor component is loaded.</p>
<p>The challenge at this point became how to load the rich text editor in my testing environment.</p>
<h1 id="loading-tinymce-in-testing-environment">Loading TinyMCE in Testing Environment</h1>
<p>Thanks to <a target="_blank" href="https://stackoverflow.com/questions/53274713/how-to-test-content-of-iframe-using-jest">a post on stackoverflow</a>, I realized that because I had chosen the TinyMCE self-hosted option using <code>tinymceScriptScr</code> (attribute added to the rich text editor component which specifies the path to the TinyMCE script, which was located in my project's <code>public</code> folder) I needed to configure my <code>jsdom</code> testing environment to load sub-resources.</p>
<p>As the stackoverflow post suggested, I started by modifying the <code>testEnvironmentOptions</code> in my Jest config file:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// jest.config.js</span>
<span class="hljs-built_in">module</span>.exports = {
  <span class="hljs-comment">// ...</span>
  <span class="hljs-attr">testEnvironment</span>: <span class="hljs-string">"jsdom"</span>,
  <span class="hljs-attr">testEnvironmentOptions</span>: { <span class="hljs-attr">resources</span>: <span class="hljs-string">"usable"</span> },
};
</code></pre>
<p>However, that still didn't do the trick, as the rich text editor was still not being loaded. Luckily, I found <a target="_blank" href="https://stackoverflow.com/questions/53069500/jsdom-is-not-loading-javascript-included-with-script-tag">another article on stackoverflow</a> that referenced an adiitional option needed to specifically load subresources that are scripts. I went on to verify the information by examining <a target="_blank" href="https://github.com/jsdom/jsdom#loading-subresources">jsdom's loading subresources section</a>, and discovered that there was one last hitch. I would be unable to load the rich text editor component because in my project I was using a relative URL for it's script path value. For a third time, users of stackoverflow came to the rescue by posting about <a target="_blank" href="https://stackoverflow.com/questions/34927863/how-can-i-configure-the-jsdom-instance-used-by-jest">how to configure a testURL in jest</a>.</p>
<p>The final configuration that got everything to work was:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// jest.config.js</span>
<span class="hljs-built_in">module</span>.exports = {
  <span class="hljs-comment">// ...</span>

  <span class="hljs-comment">// note that 'runscripts' property was added to testEnvironmentOptions' object</span>
  <span class="hljs-attr">testEnvironmentOptions</span>: { <span class="hljs-attr">resources</span>: <span class="hljs-string">"usable"</span>, <span class="hljs-attr">runScripts</span>: <span class="hljs-string">"dangerously"</span> },
  <span class="hljs-comment">// AND a absolute testURL was provided</span>
  <span class="hljs-attr">testURL</span>: <span class="hljs-string">"http://localhost:3000"</span>,
};
</code></pre>
<p>Note that because I was loading the TinyMCE script from my project's <code>public</code> folder, the testURL matched the address of my local development server (which <strong>has to be running</strong> while conducting the tests for the resource to be fetched and loaded properly during testing).</p>
<h1 id="updating-the-test-case">Updating the Test Case</h1>
<p>Next, I had to update the test case to reflect that I was querying for something that would not be available right away:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// NewQuizForm.spec.js</span>
<span class="hljs-comment">// ...</span>

it(<span class="hljs-string">"renders expected input fields and no error messages"</span>, <span class="hljs-keyword">async</span> () =&gt; {
  render(<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">NewQuizForm</span> /&gt;</span></span>);

  <span class="hljs-comment">// notice we not only use async/await but we switch our query from `getByRole` to `findByRole`</span>
  <span class="hljs-keyword">const</span> queryInput = <span class="hljs-keyword">await</span> screen.findByRole(<span class="hljs-string">"textbox"</span>, { <span class="hljs-attr">name</span>: <span class="hljs-regexp">/query/i</span> });

  <span class="hljs-comment">// ...</span>
});
</code></pre>
<p>If you're interested in learning more about why <code>findByRole</code> was needed, checkout this great article on <a target="_blank" href="https://kentcdodds.com/blog/common-mistakes-with-react-testing-library">React Testing Library best practices</a>.</p>
<p>Running <code>yarn test __tests__NewQuizForm.spec.js</code> at this point would unfornately yield another error:</p>
<pre><code class="lang-shell"># ...
Error: Uncaught [TypeError: window.matchMedia is not a function]
# ...
</code></pre>
<p>Fortunately for us, Jest's docs had a <a target="_blank" href="https://jestjs.io/docs/manual-mocks#mocking-methods-which-are-not-implemented-in-jsdom">ready made solution</a> for this issue:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// NewQuizForm.spec.js</span>
<span class="hljs-comment">// ...</span>

<span class="hljs-comment">// obtained from Jest docs on mocking methods which are not implemented in JSDOM</span>
<span class="hljs-built_in">Object</span>.defineProperty(<span class="hljs-built_in">window</span>, <span class="hljs-string">"matchMedia"</span>, {
  <span class="hljs-attr">writable</span>: <span class="hljs-literal">true</span>,
  <span class="hljs-attr">value</span>: jest.fn().mockImplementation(<span class="hljs-function">(<span class="hljs-params">query</span>) =&gt;</span> ({
    <span class="hljs-attr">matches</span>: <span class="hljs-literal">false</span>,
    <span class="hljs-attr">media</span>: query,
    <span class="hljs-attr">onchange</span>: <span class="hljs-literal">null</span>,
    <span class="hljs-attr">addListener</span>: jest.fn(), <span class="hljs-comment">// deprecated</span>
    <span class="hljs-attr">removeListener</span>: jest.fn(), <span class="hljs-comment">// deprecated</span>
    <span class="hljs-attr">addEventListener</span>: jest.fn(),
    <span class="hljs-attr">removeEventListener</span>: jest.fn(),
    <span class="hljs-attr">dispatchEvent</span>: jest.fn(),
  })),
});

it(<span class="hljs-string">"should initially render expected number of tinyMCE instances and no error messages"</span>, <span class="hljs-keyword">async</span> () =&gt; {
  <span class="hljs-comment">// ...</span>
});
</code></pre>
<p>Running the test after adding the above manual mock resulted in the following failed result:</p>
<pre><code class="lang-shell"># FAIL status output..

renders expected input fields and no error messages

# RTL's log of elements rendered to the DOM
&lt;label
  for="query"
&gt;
  Query
&lt;/label&gt;
&lt;textarea
  aria-hidden="true"
  id="query"
  style="display: none;"
/&gt;
&lt;div
  aria-disabled="false"
  class="tox tox-tinymce"
  role="application"
  style="visibility: hidden; height: 200px;"
&gt;
# the total rendered output of the rich text editor is omitted for the sake of brevity
</code></pre>
<p>Although the test failed, we finally arrived at the point were the TinyMCE rich text editor component is being loaded! My final step was to adjust my query to reflect the appropriate role we are now querying for:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// NewQuizForm.spec.js</span>
<span class="hljs-comment">// ...</span>
<span class="hljs-keyword">const</span> queryInput = <span class="hljs-keyword">await</span> screen.findByRole(<span class="hljs-string">"application"</span>, {
  <span class="hljs-attr">hidden</span>: <span class="hljs-literal">true</span>,
});

<span class="hljs-comment">// ...</span>
</code></pre>
<p>That's all I have for today, I hope this helped!</p>
]]></content:encoded></item></channel></rss>