示例#1
0
 const sCheckP2 = function (situation) {
   return GeneralSteps.sequence([
     sSetP2,
     sCheckInNoList(situation)
   ]);
 };
UnitTest.asynctest('browser.tinymce.core.dom.ParentsTest', function () {
  const success = arguments[arguments.length - 2];
  const failure = arguments[arguments.length - 1];

  const cCreateStructure = function (html) {
    return Chain.mapper(function (_) {
      return Element.fromHtml(html);
    });
  };

  const cParentsUntil = function (startPath, rootPath, predicate) {
    return Chain.mapper(function (structure) {
      const startNode = Hierarchy.follow(structure, startPath).getOrDie();
      const rootNode = Hierarchy.follow(structure, rootPath).getOrDie();
      return Parents.parentsUntil(startNode, rootNode, predicate);
    });
  };

  const cParents = function (startPath, rootPath) {
    return Chain.mapper(function (structure) {
      const startNode = Hierarchy.follow(structure, startPath).getOrDie();
      const rootNode = Hierarchy.follow(structure, rootPath).getOrDie();
      return Parents.parents(startNode, rootNode);
    });
  };

  const cParentsAndSelf = function (startPath, rootPath) {
    return Chain.mapper(function (structure) {
      const startNode = Hierarchy.follow(structure, startPath).getOrDie();
      const rootNode = Hierarchy.follow(structure, rootPath).getOrDie();
      return Parents.parentsAndSelf(startNode, rootNode);
    });
  };

  const cAssertElementNames = function (expectedNames) {
    return Chain.mapper(function (parents) {
      const names = Arr.map(parents, Node.name);
      Assertions.assertEq('Should be expected names', expectedNames, names);
      return {};
    });
  };

  const hasName = function (name) {
    return function (elm) {
      return Node.name(elm) === name;
    };
  };

  Pipeline.async({}, [
    Logger.t('parentsUntil', GeneralSteps.sequence([
      Logger.t('parentsUntil root', Chain.asStep({}, [
        cCreateStructure('<p><b>a</b></p>'),
        cParentsUntil([0, 0], [], hasName('p')),
        cAssertElementNames(['b'])
      ])),

      Logger.t('parentsUntil root on elm', Chain.asStep({}, [
        cCreateStructure('<p><b><i></i></b></p>'),
        cParentsUntil([0, 0], [], hasName('p')),
        cAssertElementNames(['b'])
      ])),

      Logger.t('parentsUntil root deeper', Chain.asStep({}, [
        cCreateStructure('<p><b><i><u>a</u></i></b></p>'),
        cParentsUntil([0, 0, 0, 0], [], hasName('p')),
        cAssertElementNames(['u', 'i', 'b'])
      ])),

      Logger.t('parentsUntil end at b', Chain.asStep({}, [
        cCreateStructure('<p><b><i><u>a</u></i></b></p>'),
        cParentsUntil([0, 0, 0, 0], [], hasName('b')),
        cAssertElementNames(['u', 'i'])
      ])),

      Logger.t('parentsUntil end at b', Chain.asStep({}, [
        cCreateStructure('<p><b>a</b></p>'),
        cParentsUntil([0, 0], [], hasName('b')),
        cAssertElementNames([])
      ])),

      Logger.t('parentsUntil root scope', Chain.asStep({}, [
        cCreateStructure('<p><b><i><u>a</u></i></b></p>'),
        cParentsUntil([0, 0, 0, 0], [0], hasName('p')),
        cAssertElementNames(['u', 'i'])
      ]))
    ])),

    Logger.t('parents', GeneralSteps.sequence([
      Logger.t('parents', Chain.asStep({}, [
        cCreateStructure('<p><b><i><u>a</u></i></b></p>'),
        cParents([0, 0, 0, 0], []),
        cAssertElementNames(['u', 'i', 'b'])
      ])),

      Logger.t('parents scoped', Chain.asStep({}, [
        cCreateStructure('<p><b><i><u>a</u></i></b></p>'),
        cParents([0, 0, 0, 0], [0]),
        cAssertElementNames(['u', 'i'])
      ]))
    ])),

    Logger.t('parentsAndSelf', GeneralSteps.sequence([
      Logger.t('parentsAndSelf', Chain.asStep({}, [
        cCreateStructure('<p><b><i><u>a</u></i></b></p>'),
        cParentsAndSelf([0, 0, 0, 0], []),
        cAssertElementNames(['#text', 'u', 'i', 'b'])
      ])),

      Logger.t('parentsAndSelf scoped', Chain.asStep({}, [
        cCreateStructure('<p><b><i><u>a</u></i></b></p>'),
        cParentsAndSelf([0, 0, 0, 0], [0]),
        cAssertElementNames(['#text', 'u', 'i'])
      ]))
    ]))
  ], function () {
    success();
  }, failure);
});
const sTypeContentAtSelection = function (doc, text) {
  return GeneralSteps.sequence(Arr.map(text.split(''), Fun.curry(sTypeChar, doc)));
};
示例#4
0
  TinyLoader.setup(function (editor, onSuccess, onFailure) {
    const tinyApis = TinyApis(editor);

    Pipeline.async({}, [
      Logger.t('getContent html', GeneralSteps.sequence([
        tinyApis.sSetContent('<p>html</p>'),
        Step.sync(() => {
          const html = EditorContent.getContent(editor);
          Assertions.assertHtml('Should be expected html', '<p>html</p>', html);
        })
      ])),
      Logger.t('getContent tree', GeneralSteps.sequence([
        tinyApis.sSetContent('<p>tree</p>'),
        Step.sync(() => {
          const tree = EditorContent.getContent(editor, { format: 'tree' }) as Node;
          Assertions.assertHtml('Should be expected tree html', '<p>tree</p>', toHtml(tree));
        })
      ])),
      Logger.t('getContent tree filtered', GeneralSteps.sequence([
        Step.sync(() => {
          EditorContent.setContent(editor, '<p><font size="7">x</font></p>', { format: 'raw' });
          const tree = EditorContent.getContent(editor, { format: 'tree' }) as Node;
          Assertions.assertHtml('Should be expected tree filtered html', '<p><span style="font-size: 300%;">x</span></p>', toHtml(tree));
        })
      ])),
      Logger.t('getContent tree using public api', GeneralSteps.sequence([
        tinyApis.sSetContent('<p>html</p>'),
        Step.sync(() => {
          const tree = editor.getContent({ format: 'tree'}) as Node;
          Assertions.assertHtml('Should be expected filtered html', '<p>html</p>', toHtml(tree));
        })
      ])),
      Logger.t('setContent html', GeneralSteps.sequence([
        tinyApis.sSetContent('<p>html</p>'),
        Step.sync(function () {
          EditorContent.setContent(editor, '<p>new html</p>');
        }),
        tinyApis.sAssertContent('<p>new html</p>')
      ])),
      Logger.t('setContent tree', GeneralSteps.sequence([
        tinyApis.sSetContent('<p>tree</p>'),
        Step.sync(() => {
          const tree = EditorContent.getContent(editor, { format: 'tree' }) as Node;
          Assertions.assertHtml('Should be expected tree html', '<p>tree</p>', toHtml(tree));

          EditorContent.setContent(editor, '<p>new html</p>');
          Assertions.assertHtml('Should be expected html', '<p>new html</p>', EditorContent.getContent(editor));

          EditorContent.setContent(editor, tree);
          Assertions.assertHtml('Should be expected tree html', '<p>tree</p>', EditorContent.getContent(editor));
        })
      ])),
      Logger.t('setContent tree filtered', GeneralSteps.sequence([
        tinyApis.sSetContent('<p>tree</p>'),
        Step.sync(() => {
          EditorContent.setContent(editor, getFontTree());
          Assertions.assertHtml('Should be expected filtered html', '<span style="font-size: 300%;">x</span>', EditorContent.getContent(editor));
        })
      ])),
      Logger.t('setContent tree using public api', GeneralSteps.sequence([
        tinyApis.sSetContent('<p>tree</p>'),
        Step.sync(() => {
          editor.setContent(getFontTree());
          Assertions.assertHtml('Should be expected filtered html', '<span style="font-size: 300%;">x</span>', EditorContent.getContent(editor));
        })
      ]))
    ], onSuccess, onFailure);
  }, {
    TinyLoader.setup(function (editor, onSuccess, onFailure) {
      const tinyApis = TinyApis(editor);
      const tinyActions = TinyActions(editor);

      Pipeline.async({}, [
        tinyApis.sFocus,
        Logger.t('Arrow keys anchor with text', GeneralSteps.sequence([
          Logger.t('From start to end inside anchor over text', GeneralSteps.sequence([
            tinyApis.sSetRawContent('<p><a href="#">x</a></p>'),
            tinyApis.sSetCursor([0, 0, 0], 0),
            tinyApis.sNodeChanged,
            tinyActions.sContentKeystroke(Keys.right(), { }),
            tinyApis.sAssertSelection([0, 0, 0], 1, [0, 0, 0], 1),
            sAssertCaretAtZwsp(editor)
          ])),
          Logger.t('From start to before anchor with text', GeneralSteps.sequence([
            tinyApis.sSetRawContent('<p><a href="#">x</a></p>'),
            tinyApis.sSetCursor([0, 0, 0], 0),
            tinyApis.sNodeChanged,
            tinyActions.sContentKeystroke(Keys.left(), { }),
            tinyApis.sAssertSelection([0, 0], 0, [0, 0], 0),
            sAssertCaretAtZwsp(editor)
          ])),
          Logger.t('From end to after anchor with text', GeneralSteps.sequence([
            tinyApis.sSetRawContent('<p><a href="#">x</a></p>'),
            tinyApis.sSetCursor([0, 0, 0], 1),
            tinyApis.sNodeChanged,
            tinyActions.sContentKeystroke(Keys.right(), { }),
            tinyApis.sAssertSelection([0, 1], 1, [0, 1], 1),
            sAssertCaretAfterZwsp(editor)
          ])),
          Logger.t('From end to start inside anchor over text', GeneralSteps.sequence([
            tinyApis.sSetRawContent('<p><a href="#">x</a></p>'),
            tinyApis.sSetCursor([0, 0, 0], 1),
            tinyApis.sNodeChanged,
            tinyActions.sContentKeystroke(Keys.left(), { }),
            tinyApis.sAssertSelection([0, 0, 0], 1, [0, 0, 0], 1),
            sAssertCaretAfterZwsp(editor)
          ]))
        ])),

        Logger.t('Arrow keys anchor with image', GeneralSteps.sequence([
          Logger.t('From start to end inside anchor over img', GeneralSteps.sequence([
            tinyApis.sSetRawContent('<p><a href="#"><img src="#"></a></p>'),
            tinyApis.sSetCursor([0, 0], 0),
            tinyApis.sNodeChanged,
            tinyActions.sContentKeystroke(Keys.right(), { }),
            tinyApis.sAssertSelection([0, 0, 1], 0, [0, 0, 1], 0),
            sAssertCaretAtZwsp(editor)
          ])),
          Logger.t('From start to before on anchor with img', GeneralSteps.sequence([
            tinyApis.sSetRawContent('<p><a href="#"><img src="#"></a></p>'),
            tinyApis.sSetCursor([0, 0], 0),
            tinyApis.sNodeChanged,
            tinyActions.sContentKeystroke(Keys.left(), { }),
            tinyApis.sAssertSelection([0, 0], 0, [0, 0], 0),
            sAssertCaretAtZwsp(editor)
          ])),
          Logger.t('From end to after on anchor with img', GeneralSteps.sequence([
            tinyApis.sSetRawContent('<p><a href="#"><img src="#"></a></p>'),
            tinyApis.sSetCursor([0, 0], 1),
            tinyApis.sNodeChanged,
            tinyActions.sContentKeystroke(Keys.right(), { }),
            tinyApis.sAssertSelection([0, 1], 1, [0, 1], 1),
            sAssertCaretAfterZwsp(editor)
          ])),
          Logger.t('From end to start inside anchor over img', GeneralSteps.sequence([
            tinyApis.sSetRawContent('<p><a href="#"><img src="#"></a></p>'),
            tinyApis.sSetCursor([0, 0], 1),
            tinyApis.sNodeChanged,
            tinyActions.sContentKeystroke(Keys.left(), { }),
            tinyApis.sAssertSelection([0, 0, 0], 1, [0, 0, 0], 1),
            sAssertCaretAfterZwsp(editor)
          ]))
        ])),

        Logger.t('Arrow keys between blocks', GeneralSteps.sequence([
          Logger.t('From end of anchor text to after anchor to start of anchor in next paragraph', GeneralSteps.sequence([
            tinyApis.sSetRawContent('<p><a href="#">a</a></p><p><a href="#">b</a></p>'),
            tinyApis.sSetCursor([0, 0, 0], 1),
            tinyApis.sNodeChanged,
            tinyActions.sContentKeystroke(Keys.right(), { }),
            tinyApis.sSetCursor([0, 1], 1),
            sAssertCaretAfterZwsp(editor),
            tinyActions.sContentKeystroke(Keys.right(), { }),
            tinyApis.sAssertSelection([1, 0, 0], 1, [1, 0, 0], 1),
            sAssertCaretAfterZwsp(editor)
          ])),
          Logger.t('From start of anchor text to before anchor to end of anchor in previous paragraph', GeneralSteps.sequence([
            tinyApis.sSetRawContent('<p><a href="#">a</a></p><p><a href="#">b</a></p>'),
            tinyApis.sSetCursor([1, 0, 0], 0),
            tinyApis.sNodeChanged,
            tinyActions.sContentKeystroke(Keys.left(), { }),
            tinyApis.sSetCursor([1, 0], 0),
            sAssertCaretAtZwsp(editor),
            tinyActions.sContentKeystroke(Keys.left(), { }),
            tinyApis.sAssertSelection([0, 0, 0], 1, [0, 0, 0], 1),
            sAssertCaretAtZwsp(editor)
          ])),
          Logger.t('From end of anchor text to after anchor to but not to next paragraph', GeneralSteps.sequence([
            tinyApis.sSetRawContent('<p><a href="#">a</a></p><p>b<a href="#">c</a></p>'),
            tinyApis.sSetCursor([0, 0, 0], 1),
            tinyApis.sNodeChanged,
            tinyActions.sContentKeystroke(Keys.right(), { }),
            tinyApis.sSetCursor([0, 1], 1),
            sAssertCaretAfterZwsp(editor),
            tinyActions.sContentKeystroke(Keys.right(), { }),
            tinyApis.sAssertSelection([0, 1], 1, [0, 1], 1),
            sAssertCaretAfterZwsp(editor)
          ])),
          Logger.t('From start of anchor text to before anchor to end of anchor but not to previous paragraph', GeneralSteps.sequence([
            tinyApis.sSetRawContent('<p><a href="#">a</a>b</p><p><a href="#">c</a></p>'),
            tinyApis.sSetCursor([1, 0, 0], 0),
            tinyApis.sNodeChanged,
            tinyActions.sContentKeystroke(Keys.left(), { }),
            tinyApis.sSetCursor([1, 0], 0),
            sAssertCaretAtZwsp(editor),
            tinyActions.sContentKeystroke(Keys.left(), { }),
            tinyApis.sAssertSelection([1, 0], 0, [1, 0], 0),
            sAssertCaretAtZwsp(editor)
          ]))
        ])),

        Logger.t('Arrow keys between lists', GeneralSteps.sequence([
          Logger.t('From end of anchor text to after anchor to start of anchor in next list item', GeneralSteps.sequence([
            tinyApis.sSetRawContent('<ul><li><a href="#">a</a></li><li><a href="#">b</a></li></ul>'),
            tinyApis.sSetCursor([0, 0, 0, 0], 1),
            tinyApis.sNodeChanged,
            tinyActions.sContentKeystroke(Keys.right(), { }),
            tinyApis.sSetCursor([0, 0, 1], 1),
            sAssertCaretAfterZwsp(editor),
            tinyActions.sContentKeystroke(Keys.right(), { }),
            tinyApis.sAssertSelection([0, 1, 0, 0], 1, [0, 1, 0, 0], 1),
            sAssertCaretAfterZwsp(editor)
          ])),
          Logger.t('From start of anchor text to before anchor to end of anchor in previous list item', GeneralSteps.sequence([
            tinyApis.sSetRawContent('<ul><li><a href="#">a</a></li><li><a href="#">b</a></li></ul>'),
            tinyApis.sSetCursor([0, 1, 0, 0], 0),
            tinyApis.sNodeChanged,
            tinyActions.sContentKeystroke(Keys.left(), { }),
            tinyApis.sSetCursor([0, 1, 0], 0),
            sAssertCaretAtZwsp(editor),
            tinyActions.sContentKeystroke(Keys.left(), { }),
            tinyApis.sAssertSelection([0, 0, 0, 0], 1, [0, 0, 0, 0], 1),
            sAssertCaretAtZwsp(editor)
          ])),
          Logger.t('From end of anchor text to after anchor to but not to next list item', GeneralSteps.sequence([
            tinyApis.sSetRawContent('<ul><li><a href="#">a</a></li><li>b<a href="#">c</a></li></ul>'),
            tinyApis.sSetCursor([0, 0, 0, 0], 1),
            tinyApis.sNodeChanged,
            tinyActions.sContentKeystroke(Keys.right(), { }),
            tinyApis.sSetCursor([0, 0, 1], 1),
            sAssertCaretAfterZwsp(editor),
            tinyActions.sContentKeystroke(Keys.right(), { }),
            tinyApis.sAssertSelection([0, 0, 1], 1, [0, 0, 1], 1),
            sAssertCaretAfterZwsp(editor)
          ])),
          Logger.t('From start of anchor text to before anchor to end of anchor but not to previous list item', GeneralSteps.sequence([
            tinyApis.sSetRawContent('<ul><li><a href="#">a</a>b</li><li><a href="#">c</a></li></ul>'),
            tinyApis.sSetCursor([0, 1, 0, 0], 0),
            tinyApis.sNodeChanged,
            tinyActions.sContentKeystroke(Keys.left(), { }),
            tinyApis.sSetCursor([0, 1, 0], 0),
            sAssertCaretAtZwsp(editor),
            tinyActions.sContentKeystroke(Keys.left(), { }),
            tinyApis.sAssertSelection([0, 1, 0], 0, [0, 1, 0], 0),
            sAssertCaretAtZwsp(editor)
          ])),
          Logger.t('From start of anchor to before anchor but not to previous list item anchor', GeneralSteps.sequence([
            tinyApis.sSetRawContent('<ul><li><a href="#">a</a></li><li>b<a href="#">c</a></li></ul>'),
            tinyApis.sSetCursor([0, 1, 1, 0], 0),
            tinyApis.sNodeChanged,
            tinyActions.sContentKeystroke(Keys.left(), { }),
            tinyApis.sSetCursor([0, 1, 0], 1),
            sAssertCaretAtZwsp(editor),
            tinyActions.sContentKeystroke(Keys.left(), { }),
            tinyApis.sAssertSelection([0, 1, 0], 1, [0, 1, 0], 1),
            sAssertCaretAtZwsp(editor)
          ])),
          Logger.t('From end of anchor to after anchor but not to next list item anchor', GeneralSteps.sequence([
            tinyApis.sSetRawContent('<ul><li><a href="#">a</a>b</li><li><a href="#">c</a></li></ul>'),
            tinyApis.sSetCursor([0, 0, 0, 0], 1),
            tinyApis.sNodeChanged,
            tinyActions.sContentKeystroke(Keys.right(), { }),
            tinyApis.sSetCursor([0, 0, 1], 1),
            sAssertCaretAfterZwsp(editor),
            tinyActions.sContentKeystroke(Keys.right(), { }),
            tinyApis.sAssertSelection([0, 0, 1], 1, [0, 0, 1], 1),
            sAssertCaretAfterZwsp(editor)
          ])),

          Logger.t('Arrow keys at anchor + code', GeneralSteps.sequence([
            Logger.t('From start to end inside anchor + code over text', GeneralSteps.sequence([
              tinyApis.sSetRawContent('<p><a href="#"><code>x</code></a></p>'),
              tinyApis.sSetCursor([0, 0, 0, 0], 0),
              tinyApis.sNodeChanged,
              tinyActions.sContentKeystroke(Keys.right(), { }),
              tinyApis.sAssertSelection([0, 0, 0, 0], 1, [0, 0, 0, 0], 1),
              sAssertCaretAtZwsp(editor)
            ])),
            Logger.t('From start to before anchor + code with text', GeneralSteps.sequence([
              tinyApis.sSetRawContent('<p><a href="#"><code>x</code></a></p>'),
              tinyApis.sSetCursor([0, 0, 0, 0], 0),
              tinyApis.sNodeChanged,
              tinyActions.sContentKeystroke(Keys.left(), { }),
              tinyApis.sAssertSelection([0, 0], 0, [0, 0], 0),
              sAssertCaretAtZwsp(editor)
            ])),
            Logger.t('From end to after anchor + code with text', GeneralSteps.sequence([
              tinyApis.sSetRawContent('<p><a href="#"><code>x</code></a></p>'),
              tinyApis.sSetCursor([0, 0, 0, 0], 1),
              tinyApis.sNodeChanged,
              tinyActions.sContentKeystroke(Keys.right(), { }),
              tinyApis.sAssertSelection([0, 1], 1, [0, 1], 1),
              sAssertCaretAfterZwsp(editor)
            ])),
            Logger.t('From end to start inside anchor + code over text', GeneralSteps.sequence([
              tinyApis.sSetRawContent('<p><a href="#"><code>x</code></a></p>'),
              tinyApis.sSetCursor([0, 0, 0, 0], 1),
              tinyApis.sNodeChanged,
              tinyActions.sContentKeystroke(Keys.left(), { }),
              tinyApis.sAssertSelection([0, 0, 0, 0], 1, [0, 0, 0, 0], 1),
              sAssertCaretAfterZwsp(editor)
            ]))
          ])),

          Logger.t('Ctrl+arrow keys at anchor', GeneralSteps.sequence(
            WordSelection.hasSelectionModifyApi(editor) ? [
              Logger.t('Ctrl+Arrow right from inline boundary to next word', GeneralSteps.sequence([
                tinyApis.sSetRawContent('<p>aa <a href="#">bb</a> cc</p>'),
                tinyApis.sSetCursor([0, 1, 0], 2),
                tinyApis.sNodeChanged,
                tinyActions.sContentKeystroke(Keys.right(), { ctrl: !os.isOSX(), alt: os.isOSX() }),
                tinyApis.sAssertSelection([0, 2], 3, [0, 2], 3)
              ])),
              Logger.t('Ctrl+Arrow left from inline boundary to previous word', GeneralSteps.sequence([
                tinyApis.sSetRawContent('<p>aa <a href="#">bb</a> cc</p>'),
                tinyApis.sSetCursor([0, 1, 0], 0),
                tinyApis.sNodeChanged,
                tinyActions.sContentKeystroke(Keys.left(), { ctrl: !os.isOSX(), alt: os.isOSX() }),
                tinyApis.sAssertSelection([0, 0], 0, [0, 0], 0)
              ]))
            ] : []
          ))
        ]))
      ], onSuccess, onFailure);
    }, {
  TinyLoader.setup(function (editor, onSuccess, onFailure) {
    const tinyApis = TinyApis(editor);
    const tinyActions = TinyActions(editor);
    let count;

    // hijack editor.selection.normalize() to count how many times it will be invoked
    const backupNormalize = editor.selection.normalize;
    const normalize = function () {
      count = count === undefined ? 1 : count + 1;
      backupNormalize.apply(this, arguments);
    };
    editor.selection.normalize = normalize;

    const sResetNormalizeCounter = function () {
      return Step.sync(function () {
        count = 0;
      });
    };

    const sAssertNormalizeCounter = function (expected) {
      return Step.sync(function () {
        Assertions.assertEq('checking normalization counter', expected, count);
      });
    };

    const sClickBody = function (editor) {
      return Step.sync(function () {
        const target = editor.getBody();

        editor.fire('mousedown', { target });
        editor.fire('mouseup', { target });
        editor.fire('click', { target });
      });
    };

    Pipeline.async({}, [
      tinyApis.sFocus,

      Logger.t('Test normalization for floated images', GeneralSteps.sequence([
        tinyApis.sSetContent('<p>a<img src="about:blank" style="float: right"></p>'),
        tinyApis.sSetSelection([0], 1, [0], 2),
        Step.sync(function () {
          const selection = editor.selection.getSel();
          Assertions.assertEq('Anchor node should be the paragraph not the text node', 'P', selection.anchorNode.nodeName);
          Assertions.assertEq('Anchor offset should be the element index', 1, selection.anchorOffset);
        })
      ])),

      Logger.t('Normalize on key events when range is collapsed', GeneralSteps.sequence([
        tinyApis.sSetContent('<p>a</p><p>b</p>'),
        tinyApis.sSetSelection([], 1, [], 1),
        tinyActions.sContentKeystroke(Keys.escape(), {}),
        tinyApis.sAssertSelection([1, 0], 0, [1, 0], 0)
      ])),

      Logger.t('Normalize on mouse events when range is expanded', GeneralSteps.sequence([
        tinyApis.sSetContent('<p>a</p><p>b</p>'),
        tinyApis.sSetSelection([], 0, [], 1),
        sClickBody(editor),
        tinyApis.sAssertSelection([0, 0], 0, [0, 0], 1)
      ])),

      Logger.t('Normalize on mouse events when range is collapsed', GeneralSteps.sequence([
        tinyApis.sSetContent('<p>a</p><p>b</p>'),
        tinyApis.sSetSelection([], 1, [], 1),
        sClickBody(editor),
        tinyApis.sAssertSelection([1, 0], 0, [1, 0], 0)
      ])),

      Logger.t('Normalization during operations with modifier keys, should run only once in the end when user releases modifier key.', GeneralSteps.sequence([
        sResetNormalizeCounter(),
        tinyApis.sSetContent('<p><b>a</b><i>a</i></p>'),
        tinyApis.sSetSelection([0, 0, 0], 0, [0, 0], 0),
        Keyboard.sKeyup(Element.fromDom(editor.getDoc()), Keys.left(), { shift: true }),
        sAssertNormalizeCounter(0),
        Keyboard.sKeyup(Element.fromDom(editor.getDoc()), 17, {}), // single ctrl
        sAssertNormalizeCounter(1),
        tinyApis.sAssertSelection([0, 0], 0, [0, 0], 0)
      ]))
    ], onSuccess, onFailure);
  }, {
示例#7
0
UnitTest.asynctest('Browser Test: ui.ButtonsTest', function () {
  const success = arguments[arguments.length - 2];
  const failure = arguments[arguments.length - 1];

  /*
   * PURPOSE
   *
   * There are three buttons. Two have toggling, and one toggling button has a custom action.
   * Ensure that they all fire the right actions and get updated appropriately based on broadcasts.
   */

  const realm = IosRealm(Fun.noop);

  const body = Body.body();
  Attachment.attachSystem(body, realm.system());

  // Make toolbar appear
  Class.add(realm.system().element(), 'tinymce-mobile-fullscreen-maximized');

  const doc = Traverse.owner(body);

  TestStyles.addStyles();

  const unload = function () {
    TestStyles.removeStyles();
    Attachment.detachSystem(realm.system());
  };

  /* The test editor puts execCommand and insertContent calls into the store */
  const tEditor = TestEditor();

  const memAlpha = Memento.record(
    Buttons.forToolbarCommand(tEditor.editor(), 'alpha')
  );

  const memBeta = Memento.record(
    Buttons.forToolbarStateCommand(tEditor.editor(), 'beta')
  );

  const memGamma = Memento.record(
    Buttons.forToolbarStateAction(tEditor.editor(), 'gamma-class', 'gamma-query', function () {
      tEditor.adder('gamma-action')();
    })
  );

  const sClickAlpha = TestUi.sClickComponent(realm, memAlpha);
  const sClickBeta = TestUi.sClickComponent(realm, memBeta);
  const sClickGamma = TestUi.sClickComponent(realm, memGamma);

  const sCheckComponent = function (label, state) {
    return function (memento) {
      return TestUi.sWaitForToggledState(label, state, realm, memento);
    };
  };

  realm.setToolbarGroups([
    {
      label: 'group1',
      items: [
        memAlpha.asSpec(),
        memBeta.asSpec(),
        memGamma.asSpec()
      ]
    }
  ]);

  /*
   * Alpha has no toggling, so just check that when the user clicks on the button, the
   * editor fires execCommand with alpha
   */
  const sTestAlpha = GeneralSteps.sequence([
    tEditor.sAssertEq('Initially empty', [ ]),
    sClickAlpha,
    tEditor.sAssertEq('After clicking on alpha', [
      {
        method: 'execCommand',
        data: {
          alpha: undefined
        }
      }
    ]),
    tEditor.sClear
  ]);

  /*
   * Beta has toggling, so check:
   *  - when the user clicks on the button, execCommand is fired
   *  - when the format change is broadcast, the toggled state changes
   */
  const sTestBeta = GeneralSteps.sequence([
    tEditor.sAssertEq('before beta, store is empty', [ ]),
    sClickBeta,
    tEditor.sAssertEq('After clicking on beta', [
      {
        method: 'execCommand',
        data: {
          beta: undefined
        }
      }
    ]),
    tEditor.sClear,
    sCheckComponent('Initially, beta should be unselected', false)(memBeta),
    // Fire a format change
    TestUi.sBroadcastState(realm, [ TinyChannels.formatChanged() ], 'beta', true),
    sCheckComponent('After broadcast, beta should be selected', true)(memBeta),
    tEditor.sClear
  ]);

  /*
   * Gamma has toggling, and a custom action, so check:
   *  - when the user clicks on the button, the custom action is fired
   *  - when the format change is broadcast, the toggled state changes
   */
  const sTestGamma = GeneralSteps.sequence([
    tEditor.sAssertEq('before gamma, store is empty', [ ]),
    sClickGamma,
    tEditor.sAssertEq('After clicking on gamma', [ 'gamma-action' ]),
    tEditor.sClear,
    sCheckComponent('Initially, gamma should be unselected', false)(memGamma),
    // Fire a format change
    TestUi.sBroadcastState(realm, [ TinyChannels.formatChanged() ], 'gamma-query', true),
    sCheckComponent('After broadcast, gamma should be selected', true)(memGamma)
  ]);

  Pipeline.async({}, [
    GuiSetup.mAddStyles(doc, [
      '.tinymce-mobile-icon-alpha:before { content: "ALPHA"; }',
      '.tinymce-mobile-icon-beta:before { content: "BETA"; }',
      '.tinymce-mobile-icon-gamma-class:before { content: "GAMMA"; }'
    ]),
    TestStyles.sWaitForToolstrip(realm),
    sTestAlpha,
    sTestBeta,
    sTestGamma
  ], function () {
    unload(); success();
  }, failure);
});
  TinyLoader.setup(function (editor, onSuccess, onFailure) {
    Pipeline.async({}, [
      Logger.t('getEditorSettings tests', GeneralSteps.sequence([
        Logger.t('Override defaults plugins', Step.sync(function () {
          const settings = EditorSettings.getEditorSettings(
            editor,
            'id',
            'documentBaseUrl',
            {
              defaultSetting: 'a',
              plugins: ['a']
            },
            {
              validate: false,
              userSetting: 'b'
            }
          );

          Assertions.assertEq('Should have the specified id', 'id', settings.id);
          Assertions.assertEq('Should have the specified documentBaseUrl', 'documentBaseUrl', settings.document_base_url);
          Assertions.assertEq('Should have the specified userSetting', 'b', settings.userSetting);
          Assertions.assertEq('Should have the forced validate setting', true, settings.validate);
          Assertions.assertEq('Should have the default theme', 'modern', settings.theme);
          Assertions.assertEq('Should have the specified default plugin', 'a', settings.plugins);
          Assertions.assertEq('Should have the default setting', 'a', settings.defaultSetting);
        })),

        Logger.t('Override defaults with forced_plugins using arrays', Step.sync(function () {
          const defaultSettings = {
            forced_plugins: ['a', 'b']
          };

          const userSettings = {
            plugins: ['c', 'd']
          };

          const settings = EditorSettings.getEditorSettings(editor, 'id', 'documentBaseUrl', defaultSettings, userSettings);

          Assertions.assertEq('Should be both forced and user plugins', 'a b c d', settings.plugins);
        })),

        Logger.t('Override defaults with forced_plugins using strings', Step.sync(function () {
          const defaultSettings = {
            forced_plugins: 'a b'
          };

          const userSettings = {
            plugins: 'c d'
          };

          const settings = EditorSettings.getEditorSettings(editor, 'id', 'documentBaseUrl', defaultSettings, userSettings);

          Assertions.assertEq('Should be both forced and user plugins', 'a b c d', settings.plugins);
        })),

        Logger.t('Override defaults with forced_plugins using mixed types and spaces', Step.sync(function () {
          const defaultSettings = {
            forced_plugins: '  a   b'
          };

          const userSettings = {
            plugins: [' c ', '  d   e ']
          };

          const settings = EditorSettings.getEditorSettings(editor, 'id', 'documentBaseUrl', defaultSettings, userSettings);

          Assertions.assertEq('Should be both forced and user plugins', 'a b c d e', settings.plugins);
        })),

        Logger.t('Override defaults with just default forced_plugins', Step.sync(function () {
          const defaultSettings = {
            forced_plugins: ['a', 'b']
          };

          const userSettings = {
          };

          const settings = EditorSettings.getEditorSettings(editor, 'id', 'documentBaseUrl', defaultSettings, userSettings);

          Assertions.assertEq('Should be just default plugins', 'a b', settings.plugins);
        })),

        Logger.t('Override defaults with just user plugins', Step.sync(function () {
          const defaultSettings = {
          };

          const userSettings = {
            plugins: ['a', 'b']
          };

          const settings = EditorSettings.getEditorSettings(editor, 'id', 'documentBaseUrl', defaultSettings, userSettings);

          Assertions.assertEq('Should be just user plugins', 'a b', settings.plugins);
        })),

        Logger.t('Override defaults with forced_plugins should not be possible to override', Step.sync(function () {
          const defaultSettings = {
            forced_plugins: ['a', 'b']
          };

          const userSettings = {
            forced_plugins: ['a'],
            plugins: ['c', 'd']
          };

          const settings = EditorSettings.getEditorSettings(editor, 'id', 'documentBaseUrl', defaultSettings, userSettings);

          Assertions.assertEq('Should be just forced and user plugins', 'a b c d', settings.plugins);
        })),

        Logger.t('Getters for varous setting types', Step.sync(function () {
          const settings = EditorSettings.getEditorSettings(
            {},
            'id',
            'documentBaseUrl',
            {
              plugins: ['a']
            },
            {
              string: 'a',
              number: 1,
              boolTrue: true,
              boolFalse: false,
              null: null,
              undef: undefined
            }
          );

          const fakeEditor = {
            settings
          };

          Assertions.assertEq('Should be none for non existing setting', true, EditorSettings.get(fakeEditor, 'non_existing').isNone());
          Assertions.assertEq('Should be none for existing null setting', true, EditorSettings.get(fakeEditor, 'non_existing').isNone());
          Assertions.assertEq('Should be none for existing undefined setting', true, EditorSettings.get(fakeEditor, 'undef').isNone());
          Assertions.assertEq('Should be some for existing string setting', 'a', EditorSettings.get(fakeEditor, 'string').getOrDie());
          Assertions.assertEq('Should be some for existing number setting', 1, EditorSettings.get(fakeEditor, 'number').getOrDie());
          Assertions.assertEq('Should be some for existing bool setting', true, EditorSettings.get(fakeEditor, 'boolTrue').getOrDie());
          Assertions.assertEq('Should be some for existing bool setting', false, EditorSettings.get(fakeEditor, 'boolFalse').getOrDie());
          Assertions.assertEq('Should be none for non existing setting', true, EditorSettings.getString(fakeEditor, 'non_existing').isNone());
          Assertions.assertEq('Should be some for existing string setting', 'a', EditorSettings.getString(fakeEditor, 'string').getOrDie());
          Assertions.assertEq('Should be none for existing number setting', true, EditorSettings.getString(fakeEditor, 'number').isNone());
          Assertions.assertEq('Should be none for existing bool setting', true, EditorSettings.getString(fakeEditor, 'boolTrue').isNone());
        })),

        Logger.t('Mobile override', Step.sync(function () {
          const settings = EditorSettings.getEditorSettings(
            {},
            'id',
            'documentBaseUrl',
            {
              settingB: false
            },
            {
              mobile: {
                settingA: true,
                settingB: true
              }
            }
          );

          const fakeEditor = {
            settings
          };

          Assertions.assertEq('Should only have the mobile setting on touch', EditorSettings.get(fakeEditor, 'settingA').getOr(false), isTouch);
          Assertions.assertEq('Should not have a mobile setting on desktop', EditorSettings.get(fakeEditor, 'settingA').isNone(), !isTouch);
          Assertions.assertEq('Should have the expected mobile setting value on touch', EditorSettings.get(fakeEditor, 'settingB').getOr(false), isTouch);
          Assertions.assertEq('Should have the expected desktop setting on desktop', EditorSettings.get(fakeEditor, 'settingB').getOr(true), isTouch);
        }))
      ])),

      Logger.t('combineSettings tests', GeneralSteps.sequence([
        Logger.t('Merged settings (desktop)', Step.sync(function () {
          Assertions.assertEq(
            'Should be have validate forced and empty plugins the merged settings',
            { a: 1, b: 2, c: 3, validate: true, external_plugins: {}, plugins: '' },
            EditorSettings.combineSettings(false, { a: 1, b: 1, c: 1 }, { b: 2 }, { c: 3 })
          );
        })),

        Logger.t('Merged settings forced_plugins in default override settings (desktop)', Step.sync(function () {
          Assertions.assertEq(
            'Should be have plugins merged with forced plugins',
            { validate: true, external_plugins: {}, forced_plugins: ['a'], plugins: 'a b' },
            EditorSettings.combineSettings(false, {}, { forced_plugins: ['a'] }, { plugins: ['b'] })
          );
        })),

        Logger.t('Merged settings (mobile)', Step.sync(function () {
          Assertions.assertEq(
            'Should be have validate forced and empty plugins the merged settings',
            { a: 1, b: 2, c: 3, validate: true, external_plugins: {}, plugins: '' },
            EditorSettings.combineSettings(true, { a: 1, b: 1, c: 1 }, { b: 2 }, { c: 3 })
          );
        })),

        Logger.t('Merged settings forced_plugins in default override settings (mobile)', Step.sync(function () {
          Assertions.assertEq(
            'Should be have plugins merged with forced plugins',
            { validate: true, external_plugins: {}, forced_plugins: ['a'], plugins: 'a b' },
            EditorSettings.combineSettings(true, {}, { forced_plugins: ['a'] }, { plugins: ['b'] })
          );
        })),

        Logger.t('Merged settings forced_plugins in default override settings with user mobile settings (desktop)', Step.sync(function () {
          Assertions.assertEq(
            'Should not have plugins merged with mobile plugins',
            { validate: true, external_plugins: {}, forced_plugins: ['a'], plugins: 'a b' },
            EditorSettings.combineSettings(false, {}, { forced_plugins: ['a'] }, { plugins: ['b'], mobile: { plugins: ['c'] } })
          );
        })),

        Logger.t('Merged settings forced_plugins in default override settings with user mobile settings (mobile)', Step.sync(function () {
          Assertions.assertEq(
            'Should have forced_plugins merged with mobile plugins but only whitelisted user plugins',
            { validate: true, external_plugins: {}, forced_plugins: ['a'], plugins: 'a lists', theme: 'mobile' },
            EditorSettings.combineSettings(true, {}, { forced_plugins: ['a'] }, { plugins: ['b'], mobile: { plugins: ['lists custom'] } })
          );
        })),

        Logger.t('Merged settings forced_plugins in default override forced_plugins in user settings', Step.sync(function () {
          Assertions.assertEq(
            'Should not have user forced plugins',
            { validate: true, external_plugins: {}, forced_plugins: ['b'], plugins: 'a' },
            EditorSettings.combineSettings(false, {}, { forced_plugins: ['a'] }, { forced_plugins: ['b'] })
          );
        }))
      ])),
      Logger.t('getParam hash (legacy)', Step.sync(function () {
        const editor = new Editor('id', {
          hash1: 'a,b,c',
          hash2: 'a',
          hash3: 'a=b',
          hash4: 'a=b;c=d,e',
          hash5: 'a=b,c=d'
        }, EditorManager);

        Assertions.assertEq('Should be expected object', { a: 'a', b: 'b', c: 'c' }, EditorSettings.getParam(editor, 'hash1', {}, 'hash'));
        Assertions.assertEq('Should be expected object', { a: 'a' }, EditorSettings.getParam(editor, 'hash2', {}, 'hash'));
        Assertions.assertEq('Should be expected object', { a: 'b' }, EditorSettings.getParam(editor, 'hash3', {}, 'hash'));
        Assertions.assertEq('Should be expected object', { a: 'b', c: 'd,e' }, EditorSettings.getParam(editor, 'hash4', {}, 'hash'));
        Assertions.assertEq('Should be expected object', { a: 'b', c: 'd' }, EditorSettings.getParam(editor, 'hash5', {}, 'hash'));
        Assertions.assertEq('Should be expected default object', { b: 2 }, EditorSettings.getParam(editor, 'hash_undefined', { b: 2 }, 'hash'));
      })),
      Logger.t('getParam primary types', Step.sync(function () {
        const editor = new Editor('id', {
          bool: true,
          str: 'a',
          num: 2,
          obj: { a: 1 },
          arr: [ 'a' ],
          fun: () => {}
        }, EditorManager);

        Assertions.assertEq('Should be expected bool', true, EditorSettings.getParam(editor, 'bool', false, 'boolean'));
        Assertions.assertEq('Should be expected string', 'a', EditorSettings.getParam(editor, 'str', 'x', 'string'));
        Assertions.assertEq('Should be expected number', 2, EditorSettings.getParam(editor, 'num', 1, 'number'));
        Assertions.assertEq('Should be expected object', { a: 1 }, EditorSettings.getParam(editor, 'obj', {}, 'object'));
        Assertions.assertEq('Should be expected array', [ 'a' ], EditorSettings.getParam(editor, 'arr', [], 'array'));
        Assertions.assertEq('Should be expected function', 'function', typeof EditorSettings.getParam(editor, 'fun', null, 'function'));
        Assertions.assertEq('Should be expected default bool', false, EditorSettings.getParam(editor, 'bool_undefined', false, 'boolean'));
        Assertions.assertEq('Should be expected default string', 'x', EditorSettings.getParam(editor, 'str_undefined', 'x', 'string'));
        Assertions.assertEq('Should be expected default number', 1, EditorSettings.getParam(editor, 'num_undefined', 1, 'number'));
        Assertions.assertEq('Should be expected default object', {}, EditorSettings.getParam(editor, 'obj_undefined', {}, 'object'));
        Assertions.assertEq('Should be expected default array', [], EditorSettings.getParam(editor, 'arr_undefined', [], 'array'));
        Assertions.assertEq('Should be expected default function', null, EditorSettings.getParam(editor, 'fun_undefined', null, 'function'));
      }))
    ], onSuccess, onFailure);
  }, {
示例#9
0
  TinyLoader.setup(function (editor, onSuccess, onFailure) {
    const tinyApis = TinyApis(editor);
    const tinyUi = TinyUi(editor);

    Pipeline.async({}, [
      Logger.t('ul to ol, cursor only in parent', GeneralSteps.sequence([
        tinyApis.sSetContent('<ul><li>a</li><ul><li>b</li></ul></ul>'),
        tinyApis.sSetCursor([0, 0, 0], 0),
        tinyUi.sClickOnToolbar('click numlist button', 'div[aria-label="Numbered list"] > button'),
        tinyApis.sAssertContent('<ol><li>a</li><ul><li>b</li></ul></ol>'),
        tinyApis.sAssertSelection([0, 0, 0], 0, [0, 0, 0], 0)
      ])),
      Logger.t('ul to ol, selection from parent to sublist', GeneralSteps.sequence([
        tinyApis.sSetContent('<ul><li>a</li><ol><li>b</li></ol></ul>'),
        tinyApis.sSetSelection([0, 0, 0], 0, [0, 1, 0, 0], 1),
        tinyUi.sClickOnToolbar('click numlist button', 'div[aria-label="Numbered list"] > button'),
        tinyApis.sAssertContent('<ol><li>a</li><ol><li>b</li></ol></ol>'),
        tinyApis.sAssertSelection([0, 0, 0], 0, [0, 1, 0, 0], 1)
      ])),
      Logger.t('ol to ul, cursor only in parent', GeneralSteps.sequence([
        tinyApis.sSetContent('<ol><li>a</li><ol><li>b</li></ol></ol>'),
        tinyApis.sSetCursor([0, 0, 0], 0),
        tinyUi.sClickOnToolbar('click bullist button', 'div[aria-label="Bullet list"] > button'),
        tinyApis.sAssertContent('<ul><li>a</li><ol><li>b</li></ol></ul>'),
        tinyApis.sAssertSelection([0, 0, 0], 0, [0, 0, 0], 0)
      ])),
      Logger.t('ol to ul, selection from parent to sublist', GeneralSteps.sequence([
        tinyApis.sSetContent('<ol><li>a</li><ul><li>b</li></ul></ol>'),
        tinyApis.sSetSelection([0, 0, 0], 0, [0, 1, 0, 0], 1),
        tinyUi.sClickOnToolbar('click bullist button', 'div[aria-label="Bullet list"] > button'),
        tinyApis.sAssertContent('<ul><li>a</li><ul><li>b</li></ul></ul>'),
        tinyApis.sAssertSelection([0, 0, 0], 0, [0, 1, 0, 0], 1)
      ])),
      Logger.t('alpha to ol, cursor only in parent', GeneralSteps.sequence([
        tinyApis.sSetContent('<ul style="list-style-type: lower-alpha;"><li>a</li><ol style="list-style-type: lower-alpha;"><li>b</li></ol></ul>'),
        tinyApis.sSetCursor([0, 0, 0], 0),
        tinyUi.sClickOnToolbar('click bullist button', 'div[aria-label="Numbered list"] > button'),
        tinyApis.sAssertContent('<ol><li>a</li><ol style="list-style-type: lower-alpha;"><li>b</li></ol></ol>'),
        tinyApis.sAssertSelection([0, 0, 0], 0, [0, 0, 0], 0)
      ])),
      Logger.t('alpha to ol, selection from parent to sublist', GeneralSteps.sequence([
        tinyApis.sSetContent('<ul style="list-style-type: lower-alpha;"><li>a</li><ol style="list-style-type: lower-alpha;"><li>b</li></ol></ul>'),
        tinyApis.sSetSelection([0, 0, 0], 0, [0, 1, 0, 0], 1),
        tinyUi.sClickOnToolbar('click bullist button', 'div[aria-label="Numbered list"] > button'),
        tinyApis.sAssertContent('<ol><li>a</li><ol><li>b</li></ol></ol>'),
        tinyApis.sAssertSelection([0, 0, 0], 0, [0, 1, 0, 0], 1)
      ])),
      Logger.t('alpha to ul, cursor only in parent', GeneralSteps.sequence([
        tinyApis.sSetContent('<ol style="list-style-type: lower-alpha;"><li>a</li><ol style="list-style-type: lower-alpha;"><li>b</li></ol></ol>'),
        tinyApis.sSetCursor([0, 0, 0], 0),
        tinyUi.sClickOnToolbar('click bullist button', 'div[aria-label="Bullet list"] > button'),
        tinyApis.sAssertContent('<ul><li>a</li><ol style="list-style-type: lower-alpha;"><li>b</li></ol></ul>'),
        tinyApis.sAssertSelection([0, 0, 0], 0, [0, 0, 0], 0)
      ])),
      Logger.t('alpha to ul, selection from parent to sublist', GeneralSteps.sequence([
        tinyApis.sSetContent('<ol style="list-style-type: lower-alpha;"><li>a</li><ol style="list-style-type: lower-alpha;"><li>b</li></ol></ol>'),
        tinyApis.sSetSelection([0, 0, 0], 0, [0, 1, 0, 0], 1),
        tinyUi.sClickOnToolbar('click bullist button', 'div[aria-label="Bullet list"] > button'),
        tinyApis.sAssertContent('<ul><li>a</li><ul><li>b</li></ul></ul>'),
        tinyApis.sAssertSelection([0, 0, 0], 0, [0, 1, 0, 0], 1)
      ]))
    ], onSuccess, onFailure);
  }, {
UnitTest.asynctest('browser.tinymce.core.init.EditorInitializationTest', function () {
  const success = arguments[arguments.length - 2];
  const failure = arguments[arguments.length - 1];
  const suite = LegacyUnit.createSuite();
  const viewBlock = ViewBlock();

  Theme();

  const setup = function () {
    let i, htmlReset = '', odd;
    for (i = 1; i < 9; i++) {
      odd = i % 2 !== 0;
      htmlReset += '<textarea id="elm-' + i + '" class="' + (odd ? 'elm-odd' : 'elm-even') + '"></textarea>';
    }

    viewBlock.attach();
    viewBlock.update(htmlReset);
  };

  const teardown = function (done) {
    window.setTimeout(function () {
      EditorManager.remove();
      done();
    }, 0);
  };

  suite.asyncTest('target (initialised properly)', function (_, done) {
    const elm1 = viewBlock.get().querySelector('#elm-1');

    EditorManager.init({
      target: elm1,
      skin_url: '/project/js/tinymce/skins/lightgray',
      init_instance_callback (ed) {
        LegacyUnit.equalDom(ed.targetElm, elm1);
        teardown(done);
      }
    });
  });

  suite.asyncTest('target (initialise on element without id)', function (_, done) {
    const elm = document.createElement('textarea');
    viewBlock.get().appendChild(elm);

    EditorManager.init({
      target: elm,
      skin_url: '/project/js/tinymce/skins/lightgray',
      init_instance_callback (ed) {
        LegacyUnit.equal(ed.id.length > 0, true, 'editors id set to: ' + ed.id);
        LegacyUnit.equalDom(ed.targetElm, elm);
        teardown(done);
      }
    });
  });

  suite.asyncTest('target (selector option takes precedence over target option)', function (_, done) {
    const elm1 = document.getElementById('elm-1');
    const elm2 = document.getElementById('elm-2');

    EditorManager.init({
      selector: '#elm-2',
      target: elm1,
      skin_url: '/project/js/tinymce/skins/lightgray',
      init_instance_callback (ed) {
        LegacyUnit.equalDom(ed.targetElm, elm2);
        teardown(done);
      }
    });
  });

  suite.asyncTest('selector on non existing targets', function (_, done) {
    EditorManager.init({
      selector: '#non-existing-id',
      skin_url: '/project/js/tinymce/skins/lightgray'
    }).then(function (result) {
      Assertions.assertEq('Should be an result that is zero length', 0, result.length);
      teardown(done);
    });
  });

  suite.asyncTest('selector on an unsupported browser', function (_, done) {
    // Fake IE 8
    const oldIeValue = Env.ie;
    Env.ie = 8;

    EditorManager.init({
      selector: '#elm-2',
      skin_url: '/project/js/tinymce/skins/lightgray'
    }).then(function (result) {
      Assertions.assertEq('Should be an result that is zero length', 0, result.length);
      Env.ie = oldIeValue;
      teardown(done);
    });
  });

  suite.asyncTest('target (each editor should have a different target)', function (_, done) {
    const maxCount = document.querySelectorAll('.elm-even').length;
    const elm1 = document.getElementById('elm-1');
    let count = 0;
    const targets = [];

    EditorManager.init({
      selector: '.elm-even',
      target: elm1,
      skin_url: '/project/js/tinymce/skins/lightgray',
      init_instance_callback (ed) {
        LegacyUnit.equal(ed.targetElm !== elm1, true, 'target option ignored');
        LegacyUnit.equal(Tools.inArray(ed.targetElm, targets), -1);

        targets.push(ed.targetElm);

        if (++count >= maxCount) {
          teardown(done);
        }
      }
    });
  });

  const getSkinCssFilenames = function () {
    return Arr.bind(SelectorFilter.descendants(Element.fromDom(document), 'link'), function (link) {
      const href = Attr.get(link, 'href');
      const fileName = href.split('/').slice(-1).join('');
      const isSkin = href.indexOf('lightgray/') > -1;
      return isSkin ? [ fileName ] : [ ];
    });
  };

  const mCreateInlineModeMultipleInstances = Step.stateful(function (value, next, die) {
    viewBlock.update('<div class="tinymce-editor"><p>a</p></div><div class="tinymce-editor"><p>b</p></div>');

    EditorManager.init({
      selector: '.tinymce-editor',
      inline: true,
      skin_url: '/project/js/tinymce/skins/lightgray'
    }).then(next, die);
  });

  const mAssertEditors = Step.stateful(function (editors: any[], next, die) {
    Assertions.assertHtml('Editor contents should be the first div content', '<p>a</p>', editors[0].getContent());
    Assertions.assertHtml('Editor contents should be the second div content', '<p>b</p>', editors[1].getContent());
    Assertions.assertEq('Editor container should be null', null, editors[0].editorContainer);
    Assertions.assertEq('Editor container should be null', null, editors[1].editorContainer);

    Assertions.assertEq(
      'Should only be two skin files the skin and the content for inline mode',
      ['skin.min.css', 'content.inline.min.css'],
      getSkinCssFilenames()
    );

    const targets = Arr.map(editors, function (editor) {
      return editor.getElement();
    });

    Assertions.assertEq('Targets should be two since there are two editors', 2, targets.length);

    next(targets);
  });

  const sRemoveAllEditors = Step.sync(function () {
    EditorManager.remove();
  });

  const mAssertTargets = Step.stateful(function (targets: any[], next, die) {
    Assertions.assertEq('Targets should be two since there are two editors', 2, targets.length);

    Arr.each(targets, function (target) {
      Assertions.assertEq('Target parent should not be null', true, target.parentNode !== null);
    });

    next({});
  });

  setup();
  Pipeline.async({}, [
    Logger.t('Initialize multiple inline editors and remove them', GeneralSteps.sequence([
      mCreateInlineModeMultipleInstances,
      mAssertEditors,
      sRemoveAllEditors,
      mAssertTargets
    ]))
  ], function () {
    viewBlock.detach();
    success();
  }, failure);
});