ember-cli-tinymce icon indicating copy to clipboard operation
ember-cli-tinymce copied to clipboard

Acceptance testing the editor

Open jplindstrom opened this issue 7 years ago • 4 comments

In an Ember acceptance test I'd like to change the text in the tinymce editor, but I can't seem to even find() any element inside the iframe element.

Are there any examples of changing the editor in an acceptance test?

jplindstrom avatar Nov 22 '18 09:11 jplindstrom

@jplindstrom Here are some things we did to use the editor in an acceptance test:

  • At the top of the acceptance test file, before imports: /* global tinymce: true */

  • If you only have one editor on the page, you can just use .getContent() and .setContent('something') on the active editor like so: await tinymce.activeEditor.setContent('something');

  • If you have multiple editors, you'll need to find editors by id. This add on doesn't make that easy, but this worked for me: const firstTinyMceInput = $('.my-classname iframe').contents().find('body').attr('data-id'); then I could do tinymce.get(firstTinyMceInput).setContent('something');

Depending on how your form works, you may need to use tinymce.triggerSave() or, instead of using .setContent you can try tinymce.activeEditor.execCommand('mceInsertContent', false, 'something');

bap22 avatar Oct 15 '19 18:10 bap22

Thanks!

jplindstrom avatar Jan 20 '20 17:01 jplindstrom

@bap22 I tried your solution but it doesn't seem to be working. I have pulled out a helper method to abstract things out so it looks like so:

/* global tinyMCE: true */
import { find } from "@ember/test-helpers";
import { TinymceEditor } from "tinymce"; /* My own custom type declaration since I can't find any type declarations for tinymce. The TinymceEditor is the editor type that has things like setContent(value) and such on it */

/**
 * Enters the value into the rich text field contained in the 
 */
export async function fillinRichText(richTextInput: HTMLDivElement | string, value: string): Promise<void> {
  const editor: TinymceEditor = await getTinymceEditor(richTextInput);
  // await editor.setContent(value);
  // editor.save();
  tinyMCE.setActive(editor);
  tinyMCE.activeEditor.execCommand('mceInsertContent', false, value);
}



async function getTinymceEditor(richTextInput: HTMLDivElement | string): Promise<TinymceEditor> {
  let inputContainer: HTMLDivElement;
  if (typeof richTextInput == 'string')
    inputContainer = await find(richTextInput) as HTMLDivElement;
  else
    inputContainer = richTextInput;

    const iframe = inputContainer.getElementsByTagName("iframe")[0] as HTMLIFrameElement;
    const tinymceEditor = iframe.contentWindow!.document.body.getAttribute("data-id")!;
    const tinymceInstance = tinyMCE.get(tinymceEditor) as TinymceEditor;
    return tinymceInstance;
}

When I inspect the ui, it seems to show up but upon further inspection, it doesn't fire the onValueChanged action for the component so my component never actually sees the change.

I've tried both methods you proposed. You can see the other method commented out in the fillinRichText() method

ikirkpat avatar Apr 02 '20 16:04 ikirkpat

Disclaimer: I don't use this addon. I use TinyMCE directly but I got here when looking for a solution on how to interact with it in tests.... It's been a year since the last post but maybe this will help someone 👇

@ikirkpat Your code looks good but I think the issue is connected to TinyMCE's initialization. Ember doesn't know about it.

So I created these helpers that work fine for us:

// file: tests/helpers/tinymce.js

/* global tinymce: true */

import { waitUntil } from '@ember/test-helpers';

/**
 * Writes content into TinyMCE editor
 * @param {string} content
 * @param {object} [options]
 * @returns {Promise<*>}
 */
export async function fillInRichText(content, options = {}) {
  await waitForTinyMce(options);
  tinymce.activeEditor.setContent(content);
  tinymce.activeEditor.save();
}

/**
 * Gets content of TinyMCE editor
 * @param {object} [options]
 * @returns {Promise<*>}
 */
export async function getRichTextContent(options = {}) {
  await waitForTinyMce(options);
  return tinymce.activeEditor.getContent();
}

/**
 * Waits for TinyMCE instance to load
 * @param {object} [options]
 * @param {number} [options.timeout=5000]
 * @returns {Promise<*>}
 */
export async function waitForTinyMce({ timeout = 5000 } = {}) {
  return await waitUntil(
    () => {
      return tinymce.activeEditor.__isLoaded;
    },
    { timeout }
  );
}

waitForTinyMce is crucial here. In the component where I have TinyMCE I wait for initialization, like this:

tinymce.init({
  // all the TinyMCE's options
  // ...
  init_instance_callback: editor => {
    editor.__isLoaded = true;
  },
});

and add a flag (__isLoaded) to the editor's instance. Then waitUntil waits until that property will be true. This way I am sure TinyMCE is initialized and we can interact with it.

Later in the tests I just use fillInRichText, it waits for TinyMCE to initialize:

await fillInRichText('Lorem ipsum dolor');

That fills the content properly and fires save action. Please note: The helper needs an extension to work with multiple TinyMCE instances on a page. But I think @ikirkpat has it covered in their getTinymceEditor function.

tniezurawski avatar Apr 16 '21 13:04 tniezurawski