-
Notifications
You must be signed in to change notification settings - Fork 7
Testing Pages with Rich Text Editors
In the process of integrating tinymce on several Aperta pages, I developed two similar testing libraries, one in Ruby for capybara tests, and one in Javascript for the QUnit tests. This document is a quick introduction to how to use them when writing tests.
Rich-text editors create separate DOM elements in an iframe for their contenteditable storage. So, unlike normal divs/spans, they do not exist in the regular DOM and do not respond in the usual way to DOM events. The libraries wrap the editor-specific calls (currently only tinymce).
Please note that any future tests must use these API's when addressing a rich-text editor. Should you find any tests that are still looking for "textarea" tags and triggering "input" events, these tests will have to be rewritten should those text areas be converted to rich-text editors.
The Ruby method names are in snake_case, while the Javascript function names are camelCase.
The libraries implement these functions:
- **findEditor - **locate the editor on the page (qunit only)
- **get_rich_text / getRichText - **retrieve the text contents from the editor storage
- **set_rich_text / setRichText - **set some new text contents into the editor storage
- **wait_for_editors - **wait for editor instantiation (capybara only)
The Ruby calls use keyword parameters for readability; positional parameters will not work:
- text = get_rich_text(editor: 'some ident')
- set_rich_text(editor: 'some ident', text: 'some text')
The Javascript calls use positional parameters:
- let text = getRichText('ident')
- setRichText('ident', 'text')
The rich-text editors take a while to be created on the page; searching for them before they are completely initialized will fail. The wait_for_editors helper waits for rich-text editors to be instantiated on the page. It will loop until it finds at least one, or it will timeout if none appear in the allotted time.
A typical testing pattern is to wait for the editors, then execute some get and **set **calls to test functionality. Technically, the **wait **call could be made inside the get and set helpers to ensure that the editors have completed instantiation, but it currently is not, because in most cases it will be called many times (once for each get and set), which will slow down the tests for no additional concurrency guarantee. Generally, invoking the **wait **after visiting a page will guarantee that subsequent calls to get and set will find the editor(s) on the page. If invoking wait turns out to be onerous in preventing race conditions, the wait call could be moved inside the get and set methods, at the cost of slowing down tests where the gets and sets outnumber the waits.
Note that none of this synchronization is necessary for the QUnit tests because they run on the page, where the javascript, and thus the editor initialization, is synchronous with the test execution.
tahi/client/tests/helpers/rich-text-editor-helpers.js
tahi/spec/support/feature_helpers/rich_text_editor_helpers.rb
The only complex part of these libraries is locating the editor content in the iframe. The tricky bit is for jQuery to ask the iframe for its contents, which can then be searched for the contenteditable body tag, on which the editor id attribute can be found. In the tinymce runtime, the editors are found under tinymce.editors; each editor's id is a key in this object, e.g., **tinymce.editors.ember1234 **or tinymce.editors['ember1234'].
The algorithm for finding an editor on the page is to find a DOM element with the class ".rich-text-editor" and a "data-editor" attribute, the value of which is derived from the ember "ident" attribute. This class and data-editor attribute are a convention defined in the rich-text-editor component. Once you find the tinymce editor, you can search for its iframe and body, grab the id from the body, and index into the tinymce.editors hash to find the editor object.
The content API for the editor object is **getContent() and setContent(string). **The setContent methods/functions in the helper libraries also call **target.triggerSave() **to trigger the save event on the editor, which is hooked into Aperta's debounced save events.