Example #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)));
};
Example #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);
  }, {
Example #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);
  }, {
Example #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);
});
Example #11
0
UnitTest.asynctest('browser.tinymce.core.caret.LineReader', (success, failure) => {
  const viewBlock = ViewBlock();
  const browser = PlatformDetection.detect().browser;

  interface Path {
    path: number[];
    offset: number;
  }

  const cSetHtml = (html: string) => {
    return Chain.op(function () {
      viewBlock.update(html);
    });
  };

  const logPositions = (msg, positions) => {
    Arr.each(positions, (pos) => {
      // tslint:disable-next-line:no-console
      console.log(msg, pos.container(), pos.offset(), pos.getClientRects());
    });
  };

  const cGetPositionsUntilPreviousLine = (path: number[], offset: number) => {
    return Chain.mapper(function (scope) {
      const container = Hierarchy.follow(Element.fromDom(scope.get()), path).getOrDie();
      const pos = CaretPosition(container.dom(), offset);
      return getPositionsUntilPreviousLine(scope.get(), pos);
    });
  };

  const cGetPositionsUntilNextLine = (path: number[], offset: number) => {
    return Chain.mapper(function (scope) {
      const container = Hierarchy.follow(Element.fromDom(scope.get()), path).getOrDie();
      const pos = CaretPosition(container.dom(), offset);
      return getPositionsUntilNextLine(scope.get(), pos);
    });
  };

  const cGetAbovePositions = (path: number[], offset: number) => {
    return Chain.mapper(function (scope) {
      const container = Hierarchy.follow(Element.fromDom(scope.get()), path).getOrDie();
      const pos = CaretPosition(container.dom(), offset);
      return getPositionsAbove(scope.get(), pos);
    });
  };

  const cGetBelowPositions = (path: number[], offset: number) => {
    return Chain.mapper(function (scope) {
      const container = Hierarchy.follow(Element.fromDom(scope.get()), path).getOrDie();
      const pos = CaretPosition(container.dom(), offset);
      return getPositionsBelow(scope.get(), pos);
    });
  };

  const cFindClosestHorizontalPosition = (path: number[], offset: number) => {
    return Chain.mapper(function (positions) {
      const container = Hierarchy.follow(Element.fromDom(viewBlock.get()), path).getOrDie();
      const pos = CaretPosition(container.dom(), offset);
      return findClosestHorizontalPosition(positions, pos);
    });
  };

  const cAssertCaretPositions = (expectedPositions: Path[]) => {
    return Chain.op(function (actualPositions: CaretPosition[]) {
      if (expectedPositions.length !== actualPositions.length) {
        logPositions('cAssertCaretPositions', actualPositions);
      }

      Assertions.assertEq('Should be the expected amount of positions', expectedPositions.length, actualPositions.length);
      Arr.each(expectedPositions, (p: Path, i) => {
        const container = Hierarchy.follow(Element.fromDom(viewBlock.get()), p.path).getOrDie();
        Assertions.assertDomEq('Should be the expected container', container, Element.fromDom(actualPositions[i].container()));
        Assertions.assertEq('Should be the expected offset', p.offset, actualPositions[i].offset());
      });
    });
  };

  const cAssertNone = Chain.op(function (a) {
    Assertions.assertEq('Option return value should be none', true, a.isNone());
  });

  const cAssertLineInfoCaretPositions = (expectedPositions: Path[]) => {
    return Chain.op(function (lineInfo: LineInfo) {
      const actualPositions = lineInfo.positions;

      if (expectedPositions.length !== actualPositions.length) {
        if (expectedPositions.length !== actualPositions.length) {
          logPositions('cAssertLineInfoCaretPositions', actualPositions);
        }
      }

      Assertions.assertEq('Should be the expected amount of positions', expectedPositions.length, actualPositions.length);
      Arr.each(expectedPositions, (p: Path, i) => {
        const container = Hierarchy.follow(Element.fromDom(viewBlock.get()), p.path).getOrDie();
        Assertions.assertDomEq('Should be the expected container', container, Element.fromDom(actualPositions[i].container()));
        Assertions.assertEq('Should be the expected offset', p.offset, actualPositions[i].offset());
      });
    });
  };

  const cAssertBreakPositionNone = Chain.op(function (linebreak: LineInfo) {
    Assertions.assertEq('Should not be a line break position', true, linebreak.breakAt.isNone());
  });

  const cAssertBreakPosition = (path: number[], offset: number) => {
    return Chain.op(function (linebreak: LineInfo) {
      const container = Hierarchy.follow(Element.fromDom(viewBlock.get()), path).getOrDie();
      const breakPos = linebreak.breakAt.getOrDie();

      Assertions.assertDomEq('Should be the expected container', container, Element.fromDom(breakPos.container()));
      Assertions.assertEq('Should be the expected offset', offset, breakPos.offset());
    });
  };

  const cAssertBreakType = (expectedBreakType: BreakType) => {
    return Chain.op(function (linebreak: LineInfo) {
      const actualBreakType = linebreak.breakType;
      Assertions.assertEq('Should be the expected break type',  expectedBreakType, actualBreakType);
    });
  };

  const cAssertCaretPosition = (path: number[], offset: number) => {
    return Chain.op(function (posOption) {
      const container = Hierarchy.follow(Element.fromDom(viewBlock.get()), path).getOrDie();
      const pos = posOption.getOrDie('Needs to return a caret');

      Assertions.assertDomEq('Should be the expected container', container, Element.fromDom(pos.container()));
      Assertions.assertEq('Should be the expected offset', offset, pos.offset());
    });
  };

  const cVisualCaretCheck = (predicate, path: number[], offset: number) => {
    return Chain.mapper(function (scope) {
      const container = Hierarchy.follow(Element.fromDom(scope.get()), path).getOrDie();
      const pos = CaretPosition(container.dom(), offset);
      return predicate(scope.get(), pos);
    });
  };

  const cIsAtFirstLine = Fun.curry(cVisualCaretCheck, isAtFirstLine);
  const cIsAtLastLine = Fun.curry(cVisualCaretCheck, isAtLastLine);

  viewBlock.attach();
  Pipeline.async({}, [
    Logger.t('getPositionsUntilPreviousLine', GeneralSteps.sequence([
      Logger.t('Should be an empty array of positions and no linebreak', Chain.asStep(viewBlock, [
        cSetHtml('<p>a</p>'),
        cGetPositionsUntilPreviousLine([0, 0], 0),
        cAssertLineInfoCaretPositions([]),
        cAssertBreakType(BreakType.Eol),
        cAssertBreakPositionNone
      ])),
      Logger.t('Should be an array with the first position and second position and no linebreak', Chain.asStep(viewBlock, [
        cSetHtml('<p>ab</p>'),
        cGetPositionsUntilPreviousLine([0, 0], 2),
        cAssertLineInfoCaretPositions([
          { path: [0, 0], offset: 0 },
          { path: [0, 0], offset: 1 }
        ]),
        cAssertBreakType(BreakType.Eol),
        cAssertBreakPositionNone
      ])),
      Logger.t('Should be an array with one position from the second line and a break on the first line 1 <br>', Chain.asStep(viewBlock, [
        cSetHtml('<p>a<br>b</p>'),
        cGetPositionsUntilPreviousLine([0, 2], 1),
        cAssertLineInfoCaretPositions([
          { path: [0, 2], offset: 0 }
        ]),
        cAssertBreakType(BreakType.Br),
        cAssertBreakPosition([0], 1)
      ])),
      Logger.t('Should be an array with one position from the second line and a break on the first line 2 <br>', Chain.asStep(viewBlock, [
        cSetHtml('<p>a<br>bc</p>'),
        cGetPositionsUntilPreviousLine([0, 2], 1),
        cAssertLineInfoCaretPositions([
          { path: [0, 2], offset: 0 }
        ]),
        cAssertBreakType(BreakType.Br),
        cAssertBreakPosition([0], 1)
      ])),
      Logger.t('Should be an array with one position from the second line and a break on the first line <p>', Chain.asStep(viewBlock, [
        cSetHtml('<p>a</p><p>b</p>'),
        cGetPositionsUntilPreviousLine([1, 0], 1),
        cAssertLineInfoCaretPositions([
          { path: [1, 0], offset: 0 }
        ]),
        cAssertBreakType(BreakType.Block),
        cAssertBreakPosition([0, 0], 1)
      ])),
      Logger.t('Should be an array with one position from the second line and a break on the first line (wrap)', Chain.asStep(viewBlock, Arr.flatten([
        [
          cSetHtml('<div style="width: 10px">abc def ghi</div>'),
          cGetPositionsUntilPreviousLine([0, 0], 6)
        ],
        browser.isSafari() ? [
          cAssertLineInfoCaretPositions([
            { path: [0, 0], offset: 4 },
            { path: [0, 0], offset: 5 }
          ]),
          cAssertBreakType(BreakType.Wrap),
          cAssertBreakPosition([0, 0], 3)
        ] : [
          cAssertLineInfoCaretPositions([
            { path: [0, 0], offset: 5 }
          ]),
          cAssertBreakType(BreakType.Wrap),
          cAssertBreakPosition([0, 0], 4)
        ]
      ]))),
      Logger.t('Should be an array with zero positions from the second line and a break on the first line', Chain.asStep(viewBlock, Arr.flatten([
        [
          cSetHtml('<div style="width: 10px">abc def ghi</div>'),
          cGetPositionsUntilPreviousLine([0, 0], 5)
        ],
        browser.isSafari() ? [
          cAssertLineInfoCaretPositions([
            { path: [0, 0], offset: 4 }
          ]),
          cAssertBreakType(BreakType.Wrap),
          cAssertBreakPosition([0, 0], 3)
        ] : [
          cAssertLineInfoCaretPositions([]),
          cAssertBreakType(BreakType.Wrap),
          cAssertBreakPosition([0, 0], 4)
        ]
      ])))
    ])),

    Logger.t('getPositionsUntilNextLine', GeneralSteps.sequence([
      Logger.t('Should be an empty array of positions and no linebreak', Chain.asStep(viewBlock, [
        cSetHtml('<p>a</p>'),
        cGetPositionsUntilNextLine([0, 0], 1),
        cAssertLineInfoCaretPositions([]),
        cAssertBreakType(BreakType.Eol),
        cAssertBreakPositionNone
      ])),
      Logger.t('Should be an array with the first position and second position and no linebreak', Chain.asStep(viewBlock, [
        cSetHtml('<p>ab</p>'),
        cGetPositionsUntilNextLine([0, 0], 0),
        cAssertLineInfoCaretPositions([
          { path: [0, 0], offset: 1 },
          { path: [0, 0], offset: 2 }
        ]),
        cAssertBreakType(BreakType.Eol),
        cAssertBreakPositionNone
      ])),
      Logger.t('Should be an array with one position from the first line and a break on the first line <br>', Chain.asStep(viewBlock, [
        cSetHtml('<p>a<br>b</p>'),
        cGetPositionsUntilNextLine([0, 0], 0),
        cAssertLineInfoCaretPositions([
          { path: [0, 0], offset: 1 },
          { path: [0], offset: 1 }
        ]),
        cAssertBreakType(BreakType.Br),
        cAssertBreakPosition([0], 1)
      ])),
      Logger.t('Should be an array with one position from the first line and a break on the first line <br>', Chain.asStep(viewBlock, [
        cSetHtml('<p><input><br>b</p>'),
        cGetPositionsUntilNextLine([0], 0),
        cAssertLineInfoCaretPositions([
          { path: [0], offset: 1 }
        ]),
        cAssertBreakType(BreakType.Br),
        cAssertBreakPosition([0], 1)
      ])),
      Logger.t('Should be an array with one position from the first line and a break on the first line <p>', Chain.asStep(viewBlock, [
        cSetHtml('<p>a</p><p>b</p>'),
        cGetPositionsUntilNextLine([0, 0], 0),
        cAssertLineInfoCaretPositions([
          { path: [0, 0], offset: 1 }
        ]),
        cAssertBreakType(BreakType.Block),
        cAssertBreakPosition([1, 0], 0)
      ])),
      Logger.t('Should be an array with one position from the second line and a break on the last line', Chain.asStep(viewBlock, [
        cSetHtml('<div style="width: 10px">abc def ghi</div>'),
        cGetPositionsUntilNextLine([0, 0], 6),
        cAssertLineInfoCaretPositions([
          { path: [0, 0], offset: 7 }
        ]),
        cAssertBreakType(BreakType.Wrap),
        cAssertBreakPosition([0, 0], 8)
      ])),
      Logger.t('Should be an array with zero positions from the second line and a break on the last line', Chain.asStep(viewBlock, [
        cSetHtml('<div style="width: 10px">abc def ghi</div>'),
        cGetPositionsUntilNextLine([0, 0], 7),
        cAssertLineInfoCaretPositions([]),
        cAssertBreakType(BreakType.Wrap),
        cAssertBreakPosition([0, 0], 8)
      ]))
    ])),

    Logger.t('isAtFirstLine', GeneralSteps.sequence([
      Logger.t('Should return true at first visual position in paragraph', Chain.asStep(viewBlock, [
        cSetHtml('<p>a</p>'),
        cIsAtFirstLine([0, 0], 0),
        Assertions.cAssertEq('Should be true on first position in paragraph', true)
      ])),
      Logger.t('Should return true at second visual position in paragraph', Chain.asStep(viewBlock, [
        cSetHtml('<p>a</p>'),
        cIsAtFirstLine([0, 0], 1),
        Assertions.cAssertEq('Should be true on second position in paragraph', true)
      ])),
      Logger.t('Should return false at second br line in paragraph', Chain.asStep(viewBlock, [
        cSetHtml('<p>a<br>b</p>'),
        cIsAtFirstLine([0, 2], 0),
        Assertions.cAssertEq('Should be false on second line in paragraph', false)
      ])),
      Logger.t('Should return false at second pos after br line in paragraph', Chain.asStep(viewBlock, [
        cSetHtml('<p>a<br>b</p>'),
        cIsAtFirstLine([0, 2], 1),
        Assertions.cAssertEq('Should be false on second line in paragraph', false)
      ])),
      Logger.t('Should return false at second paragraph', Chain.asStep(viewBlock, [
        cSetHtml('<p>a</p><p>b</p>'),
        cIsAtFirstLine([1, 0], 0),
        Assertions.cAssertEq('Should be false on second line in paragraph', false)
      ])),
      Logger.t('Should return false at second line in a wrapped element', Chain.asStep(viewBlock, [
        cSetHtml('<div style="width: 10px">abc def ghi</div>'),
        cIsAtFirstLine([0, 0], 4),
        Assertions.cAssertEq('Should be false on second line in paragraph', false)
      ])),
      Logger.t('Should return true at paragraph in td', Chain.asStep(viewBlock, [
        cSetHtml('<table><tbody><tr><td><p>a</p></td></tr></tbody></table>'),
        cIsAtFirstLine([0, 0, 0, 0, 0, 0], 0),
        Assertions.cAssertEq('Should be true since it is the first line in td', true)
      ])),
      Logger.t('Should return false at second paragraph in td', Chain.asStep(viewBlock, [
        cSetHtml('<table><tbody><tr><td><p>a</p><p>b</p></td></tr></tbody></table>'),
        cIsAtFirstLine([0, 0, 0, 0, 1, 0], 0),
        Assertions.cAssertEq('Should be false since it is the second line in td', false)
      ]))
    ])),

    Logger.t('isAtLastLine', GeneralSteps.sequence([
      Logger.t('Should return true at first visual position in paragraph', Chain.asStep(viewBlock, [
        cSetHtml('<p>a</p>'),
        cIsAtLastLine([0, 0], 0),
        Assertions.cAssertEq('Should be true on first position in paragraph', true)
      ])),
      Logger.t('Should return true at second visual position in paragraph', Chain.asStep(viewBlock, [
        cSetHtml('<p>a</p>'),
        cIsAtLastLine([0, 0], 1),
        Assertions.cAssertEq('Should be true on second position in paragraph', true)
      ])),
      Logger.t('Should return false at before first br line in paragraph', Chain.asStep(viewBlock, [
        cSetHtml('<p>a<br>b</p>'),
        cIsAtLastLine([0, 0], 0),
        Assertions.cAssertEq('Should be false on first line in paragraph', false)
      ])),
      Logger.t('Should return false at first line at second pos before br line in paragraph', Chain.asStep(viewBlock, [
        cSetHtml('<p>a<br>b</p>'),
        cIsAtLastLine([0, 0], 1),
        Assertions.cAssertEq('Should be false on first line in paragraph', false)
      ])),
      Logger.t('Should return false at first paragraph', Chain.asStep(viewBlock, [
        cSetHtml('<p>a</p><p>b</p>'),
        cIsAtLastLine([0, 0], 0),
        Assertions.cAssertEq('Should be false on first paragraph line', false)
      ])),
      Logger.t('Should return false at second line in a wrapped element', Chain.asStep(viewBlock, [
        cSetHtml('<div style="width: 10px">abc def ghi</div>'),
        cIsAtLastLine([0, 0], 6),
        Assertions.cAssertEq('Should be false on second line in paragraph', false)
      ])),
      Logger.t('Should return false at first paragraph in td', Chain.asStep(viewBlock, [
        cSetHtml('<table><tbody><tr><td><p>a</p><p>b</p></td></tr></tbody></table>'),
        cIsAtLastLine([0, 0, 0, 0, 0, 0], 0),
        Assertions.cAssertEq('Should be false since it is the first line in td', false)
      ])),
      Logger.t('Should return true at second paragraph in td', Chain.asStep(viewBlock, [
        cSetHtml('<table><tbody><tr><td><p>a</p><p>b</p></td></tr></tbody></table>'),
        cIsAtLastLine([0, 0, 0, 0, 1, 0], 0),
        Assertions.cAssertEq('Should be true since it is the second line in td', true)
      ]))
    ])),

    Logger.t('getAbovePositions', GeneralSteps.sequence([
      Logger.t('Should return zero positions since there is no line above', Chain.asStep(viewBlock, [
        cSetHtml('<p>a</p>'),
        cGetAbovePositions([0, 0], 1),
        cAssertCaretPositions([])
      ])),
      Logger.t('Should return three positions for the line above', Chain.asStep(viewBlock, [
        cSetHtml('<p>ab</p><p>a</p>'),
        cGetAbovePositions([1, 0], 0),
        cAssertCaretPositions([
          { path: [0, 0], offset: 0 },
          { path: [0, 0], offset: 1 },
          { path: [0, 0], offset: 2 }
        ])
      ])),
      Logger.t('Should return four positions for the line above2', Chain.asStep(viewBlock, [
        cSetHtml('<p>a<input>b</p><p>a</p>'),
        cGetAbovePositions([1, 0], 0),
        cAssertCaretPositions([
          { path: [0, 0], offset: 0 },
          { path: [0, 0], offset: 1 },
          { path: [0], offset: 1 },
          { path: [0], offset: 2 },
          { path: [0, 2], offset: 0 },
          { path: [0, 2], offset: 1 }
        ])
      ]))
    ])),

    Logger.t('getBelowPositions', GeneralSteps.sequence([
      Logger.t('Should return zero positions since there is no line below', Chain.asStep(viewBlock, [
        cSetHtml('<p>a</p>'),
        cGetBelowPositions([0, 0], 0),
        cAssertCaretPositions([])
      ])),
      Logger.t('Should return three positions for the line below', Chain.asStep(viewBlock, [
        cSetHtml('<p>a</p><p>ab</p>'),
        cGetBelowPositions([0, 0], 0),
        cAssertCaretPositions([
          { path: [1, 0], offset: 0 },
          { path: [1, 0], offset: 1 },
          { path: [1, 0], offset: 2 }
        ])
      ])),
      Logger.t('Should return five positions for the line below', Chain.asStep(viewBlock, [
        cSetHtml('<p>a</p><p>a<input>b</p>'),
        cGetBelowPositions([0, 0], 0),
        cAssertCaretPositions([
          { path: [1, 0], offset: 0 },
          { path: [1, 0], offset: 1 },
          { path: [1], offset: 1 },
          { path: [1], offset: 2 },
          { path: [1, 2], offset: 0 },
          { path: [1, 2], offset: 1 }
        ])
      ]))
    ])),

    Logger.t('findClosestHoriontalPosition (above)', GeneralSteps.sequence([
      Logger.t('Should not return a position since there is no above positions', Chain.asStep(viewBlock, [
        cSetHtml('<p>ab</p>'),
        cGetAbovePositions([0, 0], 0),
        cFindClosestHorizontalPosition([0, 0], 0),
        cAssertNone
      ])),
      Logger.t('Should return first caret position on the line above', Chain.asStep(viewBlock, [
        cSetHtml('<p>ab</p><p>cd</p>'),
        cGetAbovePositions([1, 0], 0),
        cFindClosestHorizontalPosition([1, 0], 0),
        cAssertCaretPosition([0, 0], 0)
      ])),
      Logger.t('Should return last caret position on the line above', Chain.asStep(viewBlock, [
        cSetHtml('<p>ab</p><p>cd</p>'),
        cGetAbovePositions([1, 0], 0),
        cFindClosestHorizontalPosition([1, 0], 2),
        cAssertCaretPosition([0, 0], 2)
      ])),
      Logger.t('Should return first indexed caret position on the line above', Chain.asStep(viewBlock, [
        cSetHtml('<p><input></p><p><input></p>'),
        cGetAbovePositions([1], 0),
        cFindClosestHorizontalPosition([1], 0),
        cAssertCaretPosition([0], 0)
      ])),
      Logger.t('Should return first indexed caret position on the line above', Chain.asStep(viewBlock, [
        cSetHtml('<p><input></p><p><input></p>'),
        cGetAbovePositions([1], 0),
        cFindClosestHorizontalPosition([1], 1),
        cAssertCaretPosition([0], 1)
      ])),
      Logger.t('Should return last text node position at the line above', Chain.asStep(viewBlock, [
        cSetHtml('<p>a<input>b</p><p>a<input>b</p>'),
        cGetAbovePositions([1, 2], 0),
        cFindClosestHorizontalPosition([1, 2], 0),
        cAssertCaretPosition([0, 2], 0)
      ]))
    ])),

    Logger.t('findClosestHoriontalPosition (below)', GeneralSteps.sequence([
      Logger.t('Should not return a position since there is no below positions', Chain.asStep(viewBlock, [
        cSetHtml('<p>ab</p>'),
        cGetBelowPositions([0, 0], 0),
        cFindClosestHorizontalPosition([0, 0], 0),
        cAssertNone
      ])),
      Logger.t('Should return first caret position on the line below', Chain.asStep(viewBlock, [
        cSetHtml('<p>ab</p><p>cd</p>'),
        cGetBelowPositions([0, 0], 0),
        cFindClosestHorizontalPosition([0, 0], 0),
        cAssertCaretPosition([1, 0], 0)
      ])),
      Logger.t('Should return last caret position on the line below', Chain.asStep(viewBlock, [
        cSetHtml('<p>ab</p><p>cd</p>'),
        cGetBelowPositions([0, 0], 0),
        cFindClosestHorizontalPosition([0, 0], 2),
        cAssertCaretPosition([1, 0], 2)
      ])),
      Logger.t('Should return first indexed caret position on the line below', Chain.asStep(viewBlock, [
        cSetHtml('<p><input></p><p><input></p>'),
        cGetBelowPositions([0], 0),
        cFindClosestHorizontalPosition([0], 0),
        cAssertCaretPosition([1], 0)
      ])),
      Logger.t('Should return first indexed caret position on the line below', Chain.asStep(viewBlock, [
        cSetHtml('<p><input></p><p><input></p>'),
        cGetBelowPositions([0], 0),
        cFindClosestHorizontalPosition([0], 1),
        cAssertCaretPosition([1], 1)
      ])),
      Logger.t('Should return first text node position at the line below', Chain.asStep(viewBlock, [
        cSetHtml('<p>a<input>b</p><p>a<input>b</p>'),
        cGetBelowPositions([0, 0], 0),
        cFindClosestHorizontalPosition([0, 0], 0),
        cAssertCaretPosition([1, 0], 0)
      ])),
      Logger.t('Should return last text node position at the line below', Chain.asStep(viewBlock, [
        cSetHtml('<p>a<input>b</p><p>a<input>b</p>'),
        cGetBelowPositions([0, 2], 0),
        cFindClosestHorizontalPosition([0, 2], 0),
        cAssertCaretPosition([1, 2], 0)
      ]))
    ]))
  ], function () {
    viewBlock.detach();
    success();
  }, failure);
});
  TinyLoader.setup(function (editor: Editor, onSuccess, onFailure) {
    const tinyApis = TinyApis(editor);

    const sSetupData = GeneralSteps.sequence([
      // '<p>This |is the first paragraph</p><p>This is the second.</p><p>This is| the third.</p>'
      tinyApis.sSetContent('<p>This was the first paragraph</p><p>This is the second.</p><p>This is the third.</p>'),
      tinyApis.sSetSelection([ 0, 0 ], 'This '.length, [ 0, 0 ], 'This was'.length),
      sAnnotate(editor, 'alpha', 'id-one', { anything: 'comment-1' }),

      tinyApis.sSetSelection([ 1, 0 ], 'T'.length, [ 1, 0 ], 'This is'.length),
      sAnnotate(editor, 'alpha', 'id-two', { anything: 'comment-two' }),

      tinyApis.sSetSelection([ 2, 0 ], 'This is the th'.length, [ 2, 0 ], 'This is the thir'.length),
      sAnnotate(editor, 'beta', 'id-three', { something: 'comment-three' }),

      sAssertHtmlContent(tinyApis, [
        `<p>This <span data-mce-annotation="alpha" data-test-anything="comment-1" data-mce-annotation-uid="id-one" class="mce-annotation">was</span> the first paragraph</p>`,

        `<p>T<span data-mce-annotation="alpha" data-test-anything="comment-two" data-mce-annotation-uid="id-two" class="mce-annotation">his is</span> the second.</p>`,

        `<p>This is the th<span data-mce-annotation="beta" data-test-something="comment-three" data-mce-annotation-uid="id-three" class="mce-annotation">ir</span>d.</p>`
      ])
    ]);

    const outside1 = { path: [ 0, 0 ], offset: 'Th'.length };
    const inside1 = { path: [ 0, 1, 0 ], offset: 'i'.length };
    const inside3 = { path: [ 2, 1, 0 ], offset: 'i'.length };

    // Outside: p(0) > text(0) > "Th".length
    // Inside: p(0) > span(1) > text(0) > 'i'.length
    // Inside: p(1) > span(1) > text(0), 'hi'.length
    // Outside: p(1) > text(2) > ' the '.length
    const sTestGetAndRemove = GeneralSteps.sequence([
      tinyApis.sSetSelection(outside1.path, outside1.offset, outside1.path, outside1.offset),
      Waiter.sTryUntil(
        'Nothing active (outside1)',
        tinyApis.sAssertContentPresence({
          '.mce-annotation': 3
        }),
        100,
        1000
      ),

      Logger.t(
        'There should be two alpha annotations',
        sAssertGetAll(editor, {
          'id-one': 1,
          'id-two': 1
        }, 'alpha')
      ),

      Logger.t(
        'There should be one beta annotation',
        sAssertGetAll(editor, {
          'id-three': 1
        }, 'beta')
      ),

      Step.sync(() => {
        editor.annotator.remove('alpha');
      }),

      // Need to wait because nothing should have changed. If we don't wait, we'll get
      // a false positive when the throttling makes the change delayed.
      Step.wait(1000),

      Waiter.sTryUntil(
        'removed alpha, but was not inside alpha',
        tinyApis.sAssertContentPresence({
          '.mce-annotation': 3
        }),
        100,
        1000
      ),
      Logger.t(
        'There should be still be two alpha annotations (because remove only works if you are inside)',
        sAssertGetAll(editor, {
          'id-one': 1,
          'id-two': 1
        }, 'alpha')
      ),
      Logger.t(
        'There should still be one beta annotation',
        sAssertGetAll(editor, {
          'id-three': 1
        }, 'beta')
      ),

      tinyApis.sSetSelection(inside3.path, inside3.offset, inside3.path, inside3.offset),
      Step.sync(() => {
        editor.annotator.remove('beta');
      }),
      Waiter.sTryUntil(
        'removed beta',
        tinyApis.sAssertContentPresence({
          '.mce-annotation': 2
        }),
        100,
        1000
      ),

      Logger.t(
        'There should be still be two alpha annotations (because cursor was inside beta)',
        sAssertGetAll(editor, {
          'id-one': 1,
          'id-two': 1
        }, 'alpha')
      ),
      Logger.t(
        'There should be no beta annotations',
        sAssertGetAll(editor, { }, 'beta')
      ),

      tinyApis.sSetSelection(inside1.path, inside1.offset, inside1.path, inside1.offset),
      Step.sync(() => {
        editor.annotator.remove('alpha');
      }),

      Waiter.sTryUntil(
        'removed alpha, and was inside alpha',
        tinyApis.sAssertContentPresence({
          '.mce-annotation': 1
        }),
        100,
        1000
      ),

      Logger.t(
        'There should now be just one alpha annotation (second one was removed)',
        sAssertGetAll(editor, {
          'id-two': 1
        }, 'alpha')
      ),
      Logger.t(
        'There should be no beta annotations',
        sAssertGetAll(editor, { }, 'beta')
      ),
    ]);

    Pipeline.async({}, [
      tinyApis.sFocus,
      sSetupData,
      sTestGetAndRemove
    ], onSuccess, onFailure);
  }, {
Example #13
0
  TinyLoader.setup(function (editor, onSuccess, onFailure) {
    const tinyApis = TinyApis(editor);
    const tinyActions = TinyActions(editor);

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

      Log.stepsAsStep('TBA', 'Delete keys for image block element', [
        Logger.t('Should place the selection on the image block element on delete before', GeneralSteps.sequence([
          tinyApis.sSetContent('<p>a<img src="about:blank" class="block">b</p>'),
          tinyApis.sSetSelection([0, 0], 1, [0, 0], 1),
          tinyActions.sContentKeystroke(VK.DELETE, {}),
          tinyApis.sAssertSelection([0], 1, [0], 2)
        ])),

        Logger.t('Should place the selection on the image block element on delete before', GeneralSteps.sequence([
          tinyApis.sSetContent('<p>a</p><p><img src="about:blank" class="block"></p><p>b</p>'),
          tinyApis.sSetSelection([0, 0], 1, [0, 0], 1),
          tinyActions.sContentKeystroke(VK.DELETE, {}),
          tinyApis.sAssertSelection([1], 0, [1], 1)
        ]))
      ]),

      Log.stepsAsStep('TBA', 'Backspace keys for image block element', [
        Logger.t('Should place the selection on the image block element on backspace after', GeneralSteps.sequence([
          tinyApis.sSetContent('<p>a<img src="about:blank" class="block">b</p>'),
          tinyApis.sSetSelection([0, 2], 0, [0, 2], 0),
          tinyActions.sContentKeystroke(Keys.backspace(), {}),
          tinyApis.sAssertSelection([0], 1, [0], 2)
        ])),

        Logger.t('Should place the selection on the image block element on backspace after', GeneralSteps.sequence([
          tinyApis.sSetContent('<p>a</p><p><img src="about:blank" class="block"></p><p>b</p>'),
          tinyApis.sSetSelection([2, 0], 0, [2, 0], 0),
          tinyActions.sContentKeystroke(Keys.backspace(), {}),
          tinyApis.sAssertSelection([1], 0, [1], 1)
        ]))
      ]),

      Log.stepsAsStep('TBA', 'Backspace/delete before on non block images should not select the image', [
        Logger.t('Should place the selection on the image block element on backspace after', GeneralSteps.sequence([
          tinyApis.sSetContent('<p>a<img src="about:blank">b</p>'),
          tinyApis.sSetSelection([0, 0], 1, [0, 0], 1),
          tinyActions.sContentKeystroke(VK.DELETE, {}),
          tinyApis.sAssertSelection([0, 0], 1, [0, 0], 1)
        ])),

        Logger.t('Should place the selection on the image block element on backspace after', GeneralSteps.sequence([
          tinyApis.sSetContent('<p>a<img src="about:blank">b</p>'),
          tinyApis.sSetSelection([0, 2], 0, [0, 2], 0),
          tinyActions.sContentKeystroke(Keys.backspace(), {}),
          tinyApis.sAssertSelection([0, 2], 0, [0, 2], 0)
        ]))
      ])
    ], onSuccess, onFailure);
  }, {
Example #14
0
 const sCheckP3 = function (situation) {
   return GeneralSteps.sequence([
     sSetP3,
     sCheckInBullist(situation)
   ]);
 };
Example #15
0
  TinyLoader.setup(function (editor: Editor, onSuccess, onFailure) {
    const tinyApis = TinyApis(editor);

    const sTestAnnotationEvents = (label: string, start: number[], soffset: number, expected: Array<{ uid: string, name: string, state: boolean}>): any => {
      return GeneralSteps.sequence([
        tinyApis.sSetSelection(start, soffset, start, soffset),
        Waiter.sTryUntil(
          label,
          sAssertChanges('sTestAnnotationEvents.sAssertChanges', expected),
          10,
          1000
        ),
      ]);
    };

    const sTestChanges = GeneralSteps.sequence([
      // '<p>This |is the first paragraph</p><p>This is the second.</p><p>This is| the third.</p><p>Spanning |multiple</p><p>par||ag||raphs| now</p>'
      tinyApis.sSetContent([
        '<p>This is the first paragraph</p>',
        '<p>This is the second.</p>',
        '<p>This is the third.</p>',
        '<p>Spanning multiple</p>',
        '<p>paragraphs now</p>'
      ].join('')),
      tinyApis.sSetSelection([ 0, 0 ], 'This '.length, [ 0, 0 ], 'This is'.length),
      sAnnotate(editor, 'alpha', 'id-one', { anything: 'comment-1' }),

      tinyApis.sSetSelection([ 1, 0 ], 'T'.length, [ 1, 0 ], 'This is'.length),
      sAnnotate(editor, 'alpha', 'id-two', { anything: 'comment-two' }),

      tinyApis.sSetSelection([ 2, 0 ], 'This is the th'.length, [ 2, 0 ], 'This is the thir'.length),
      sAnnotate(editor, 'beta', 'id-three', { something: 'comment-three' }),

      tinyApis.sSetSelection([ 3, 0 ], 'Spanning '.length, [ 4, 0 ], 'paragraphs'.length),
      sAnnotate(editor, 'gamma', 'id-four', { something: 'comment-four' }),

      tinyApis.sSetSelection([ 4, 0, 0 ], 'par'.length, [ 4, 0, 0 ], 'parag'.length ),
      sAnnotate(editor, 'delta', 'id-five', { something: 'comment-five' }),

      Step.wait(1000),
      sClearChanges,

      sAssertHtmlContent(tinyApis, [
        `<p>This <span data-mce-annotation="alpha" data-test-anything="comment-1" data-mce-annotation-uid="id-one" class="mce-annotation">is</span> the first paragraph</p>`,

        `<p>T<span data-mce-annotation="alpha" data-test-anything="comment-two" data-mce-annotation-uid="id-two" class="mce-annotation">his is</span> the second.</p>`,

        `<p>This is the th<span data-mce-annotation="beta" data-test-something="comment-three" data-mce-annotation-uid="id-three" class="mce-annotation">ir</span>d.</p>`,

        `<p>Spanning <span data-mce-annotation="gamma" data-test-something="comment-four" data-mce-annotation-uid="id-four" class="mce-annotation">multiple</span></p>`,

        `<p><span data-mce-annotation="gamma" data-test-something="comment-four" data-mce-annotation-uid="id-four" class="mce-annotation">par` +
          `<span data-mce-annotation="delta" data-test-something="comment-five" data-mce-annotation-uid="id-five" class="mce-annotation delta-test">ag</span>` +
          `raphs</span> now</p>`
      ]),

      // Outside: p(0) > text(0) > "Th".length
      // Inside: p(0) > span(1) > text(0) > 'i'.length
      // Inside: p(1) > span(1) > text(0), 'hi'.length
      // Outside: p(1) > text(2) > ' the '.length

      Waiter.sTryUntil(
        'Waiting for no changes',
        sAssertChanges('Should be no changes', [ ]),
        10,
        1000
      ),

      sTestAnnotationEvents(
        'No annotation at cursor',
        [ 0, 0 ], 'Th'.length,
        [
          { state: false, name: 'delta', uid: null },
          { state: false, name: 'gamma', uid: null }
        ]
      ),

      sTestAnnotationEvents(
        'At annotation alpha, id = id-one',
        [ 0, 1, 0 ], 'i'.length,
        [
          { state: false, name: 'delta', uid: null },
          { state: false, name: 'gamma', uid: null },
          { state: true, name: 'alpha', uid: 'id-one' }
        ]
      ),

      sTestAnnotationEvents(
        'At annotation alpha, id = id-two',
        [ 1, 1, 0 ], 'hi'.length,
        [
          { state: false, name: 'delta', uid: null },
          { state: false, name: 'gamma', uid: null },
          { state: true, name: 'alpha', uid: 'id-one' },
          { state: true, name: 'alpha', uid: 'id-two' }
        ]
      ),

      tinyApis.sSetSelection([ 1, 1, 0 ], 'his'.length, [ 1, 1, 0 ], 'his'.length),
      // Give it time to throttle a node change.
      Step.wait(400),
      Waiter.sTryUntil(
        'Moving selection within the same marker (alpha id-two) ... shoud not fire change',
        sAssertChanges('checking changes',
          [
            { state: false, name: 'delta', uid: null },
            { state: false, name: 'gamma', uid: null },
            { state: true, name: 'alpha', uid: 'id-one' },
            { state: true, name: 'alpha', uid: 'id-two' }
          ]
        ),
        10,
        1000
      ),

      sTestAnnotationEvents(
        'Outside annotations again',
        [ 1, 2 ], ' the '.length,
        [
          { state: false, name: 'delta', uid: null },
          { state: false, name: 'gamma', uid: null },
          { state: true, name: 'alpha', uid: 'id-one' },
          { state: true, name: 'alpha', uid: 'id-two' },
          { state: false, name: 'alpha', uid: null }
        ]
      ),

      sTestAnnotationEvents(
        'Inside annotation beta, id = id-three',
        [ 2, 1, 0 ], 'i'.length,
        [
          { state: false, name: 'delta', uid: null },
          { state: false, name: 'gamma', uid: null },
          { state: true, name: 'alpha', uid: 'id-one' },
          { state: true, name: 'alpha', uid: 'id-two' },
          { state: false, name: 'alpha', uid: null },
          { state: true, name: 'beta', uid: 'id-three' }
        ]
      ),

      tinyApis.sSetSelection([ 2, 0 ], 'T'.length, [ 2, 0 ], 'T'.length),
      Waiter.sTryUntil(
        'Moving selection outside all annotations. Should fire null',
        sAssertChanges('checking changes',
          [
            { state: false, name: 'delta', uid: null },
            { state: false, name: 'gamma', uid: null },
            { state: true, name: 'alpha', uid: 'id-one' },
            { state: true, name: 'alpha', uid: 'id-two' },
            { state: false, name: 'alpha', uid: null },
            { state: true, name: 'beta', uid: 'id-three' },
            { state: false, name: 'beta', uid: null }
          ]
        ),
        10,
        1000
      ),

      tinyApis.sSetSelection([ 2, 2 ], 'd'.length, [ 2, 2 ], 'd'.length),
      // Give it time to throttle a node change.
      Step.wait(400),
      Waiter.sTryUntil(
        'Moving selection outside all annotations (again). Should NOT fire null because it already has',
        sAssertChanges('checking changes',
          [
            { state: false, name: 'delta', uid: null },
            { state: false, name: 'gamma', uid: null },
            { state: true, name: 'alpha', uid: 'id-one' },
            { state: true, name: 'alpha', uid: 'id-two' },
            { state: false, name: 'alpha', uid: null },
            { state: true, name: 'beta', uid: 'id-three' },
            { state: false, name: 'beta', uid: null }
          ]
        ),
        10,
        1000
      ),
      sClearChanges,

      tinyApis.sSetSelection([ 4, 0, 1, 0 ], 'a'.length, [ 4, 0, 1, 0 ], 'a'.length),
      // Give it time to throttle a node change.
      Step.wait(400),
      Waiter.sTryUntil(
        'Moving selection inside delta (which is inside gamma)',
        sAssertChanges('checking changes',
          [
            { state: true, name: 'delta', uid: 'id-five' },
            { state: true, name: 'gamma', uid: 'id-four' }
          ]
        ),
        10,
        1000
      ),

      tinyApis.sSetSelection([ 4, 0, 0 ], 'p'.length, [ 4, 0, 0 ], 'p'.length),
      // Give it time to throttle a node change.
      Step.wait(400),
      Waiter.sTryUntil(
        'Moving selection inside just gamma (but not delta)',
        sAssertChanges('checking changes',
          [
            { state: true, name: 'delta', uid: 'id-five' },
            { state: true, name: 'gamma', uid: 'id-four' },
            { state: false, name: 'delta', uid: null }
          ]
        ),
        10,
        1000
      ),
    ]);

    Pipeline.async({}, [
      tinyApis.sFocus,
      sTestChanges
    ], onSuccess, onFailure);
  }, {
  TinyLoader.setup(function (editor, onSuccess, onFailure) {
    const tinyApis = TinyApis(editor);
    const tinyUi = TinyUi(editor);

    Pipeline.async({}, [
      Logger.t('remove margin from p when applying list on it, but leave other styles', GeneralSteps.sequence([
        tinyApis.sSetContent('<p style="color: blue;margin: 30px;margin-right: 30px;margin-bottom: 30px;margin-left: 30px;margin-top: 30px;">test</p>'),
        tinyApis.sSetCursor([0, 0], 0),
        tinyUi.sClickOnToolbar('click bullist button', 'div[aria-label="Bullet list"] button'),
        tinyApis.sAssertContent('<ul><li style="color: blue;">test</li></ul>')
      ])),
      Logger.t('remove padding from p when applying list on it, but leave other styles', GeneralSteps.sequence([
        tinyApis.sSetContent('<p style="color: red;padding: 30px;padding-right: 30px;padding-bottom: 30px;padding-left: 30px;padding-top: 30px;">test</p>'),
        tinyApis.sSetCursor([0, 0], 0),
        tinyUi.sClickOnToolbar('click bullist button', 'div[aria-label="Bullet list"] button'),
        tinyApis.sAssertContent('<ul><li style="color: red;">test</li></ul>')
      ]))
    ], onSuccess, onFailure);
  }, {
  TinyLoader.setup(function (editor, onSuccess, onFailure) {
    const tinyApis = TinyApis(editor);

    Pipeline.async({}, [
      Logger.t('Insert contents on a triple click selection should not produce odd spans', GeneralSteps.sequence([
        tinyApis.sSetContent('<blockquote><p>a</p></blockquote><p>b</p>'),
        tinyApis.sSetSelection([0, 0, 0], 0, [1], 0),
        tinyApis.sExecCommand('mceInsertContent', '<p>c</p>'),
        tinyApis.sAssertContent('<blockquote><p>c</p></blockquote><p>b</p>'),
        tinyApis.sAssertSelection([0, 0, 0], 1, [0, 0, 0], 1)
      ]))
    ], onSuccess, onFailure);
  }, {
UnitTest.asynctest('browser.tinymce.core.delete.BlockMergeBoundary', function () {
  const success = arguments[arguments.length - 2];
  const failure = arguments[arguments.length - 1];
  const viewBlock = ViewBlock();

  const cSetHtml = function (html) {
    return Chain.op(function () {
      viewBlock.update(html);
    });
  };

  const cReadBlockBoundary = function (forward, cursorPath, cursorOffset) {
    return Chain.mapper(function (viewBlock: any) {
      const container = Hierarchy.follow(Element.fromDom(viewBlock.get()), cursorPath).getOrDie();
      const rng = document.createRange();
      rng.setStart(container.dom(), cursorOffset);
      rng.setEnd(container.dom(), cursorOffset);
      return BlockMergeBoundary.read(viewBlock.get(), forward, rng);
    });
  };

  const cAssertBlockBoundaryPositions = function (fromPath, fromOffset, toPath, toOffset) {
    return Chain.op(function (blockBoundaryOption: Option<any>) {
      const fromContainer = Hierarchy.follow(Element.fromDom(viewBlock.get()), fromPath).getOrDie();
      const toContainer = Hierarchy.follow(Element.fromDom(viewBlock.get()), toPath).getOrDie();
      const blockBoundary = blockBoundaryOption.getOrDie();

      Assertions.assertDomEq('Should be expected from container', fromContainer, Element.fromDom(blockBoundary.from().position().container()));
      Assertions.assertEq('Should be expected from offset', fromOffset, blockBoundary.from().position().offset());
      Assertions.assertDomEq('Should be expected to container', toContainer, Element.fromDom(blockBoundary.to().position().container()));
      Assertions.assertEq('Should be expected to offset', toOffset, blockBoundary.to().position().offset());
    });
  };

  const cAssertBlockBoundaryBlocks = function (fromBlockPath, toBlockPath) {
    return Chain.op(function (blockBoundaryOption: Option<any>) {
      const expectedFromBlock = Hierarchy.follow(Element.fromDom(viewBlock.get()), fromBlockPath).getOrDie();
      const expectedToBlock = Hierarchy.follow(Element.fromDom(viewBlock.get()), toBlockPath).getOrDie();
      const blockBoundary = blockBoundaryOption.getOrDie();

      Assertions.assertDomEq('Should be expected from block', expectedFromBlock, blockBoundary.from().block());
      Assertions.assertDomEq('Should be expected to block', expectedToBlock, blockBoundary.to().block());
    });
  };

  const cAssertBlockBoundaryNone = Chain.op(function (blockBoundaryOption: Option<any>) {
    Assertions.assertEq('BlockBoundary should be none', true, blockBoundaryOption.isNone());
  });

  viewBlock.attach();
  Pipeline.async({}, [
    Logger.t('None block boundaries', GeneralSteps.sequence([
      Logger.t('Should be none since it is a single block', Chain.asStep(viewBlock, [
        cSetHtml('<p>a</p>'),
        cReadBlockBoundary(true, [0, 0], 0),
        cAssertBlockBoundaryNone
      ])),
      Logger.t('Should be none since it is a single block', Chain.asStep(viewBlock, [
        cSetHtml('<p>a</p>'),
        cReadBlockBoundary(false, [0, 0], 1),
        cAssertBlockBoundaryNone
      ])),
      Logger.t('Should be none since it is in the middle of a block', Chain.asStep(viewBlock, [
        cSetHtml('<p>ab</p><p>c</p>'),
        cReadBlockBoundary(true, [0, 0], 1),
        cAssertBlockBoundaryNone
      ])),
      Logger.t('Should be none since it is in the middle of a block', Chain.asStep(viewBlock, [
        cSetHtml('<p>c</p><p>ab</p>'),
        cReadBlockBoundary(true, [1, 0], 1),
        cAssertBlockBoundaryNone
      ]))
    ])),

    Logger.t('Some block boundaries', GeneralSteps.sequence([
      Logger.t('Should some between simple blocks (forwards: true)', Chain.asStep(viewBlock, [
        cSetHtml('<p>a</p><p>b</p>'),
        cReadBlockBoundary(true, [0, 0], 1),
        cAssertBlockBoundaryPositions([0, 0], 1, [1, 0], 0),
        cAssertBlockBoundaryBlocks([0], [1])
      ])),
      Logger.t('Should some between simple blocks (forwards: false)', Chain.asStep(viewBlock, [
        cSetHtml('<p>a</p><p>b</p>'),
        cReadBlockBoundary(false, [1, 0], 0),
        cAssertBlockBoundaryPositions([1, 0], 0, [0, 0], 1),
        cAssertBlockBoundaryBlocks([1], [0])
      ])),
      Logger.t('Should some between complex blocks (forwards: true)', Chain.asStep(viewBlock, [
        cSetHtml('<p><em>a</em></p><p><em>b</em></p>'),
        cReadBlockBoundary(true, [0, 0, 0], 1),
        cAssertBlockBoundaryPositions([0, 0, 0], 1, [1, 0, 0], 0),
        cAssertBlockBoundaryBlocks([0], [1])
      ])),
      Logger.t('Should some between complex blocks (forwards: false)', Chain.asStep(viewBlock, [
        cSetHtml('<p><em>a</em></p><p><em>b</em></p>'),
        cReadBlockBoundary(false, [1, 0, 0], 0),
        cAssertBlockBoundaryPositions([1, 0, 0], 0, [0, 0, 0], 1),
        cAssertBlockBoundaryBlocks([1], [0])
      ])),
      Logger.t('Should some between blocks with br (forwards: true)', Chain.asStep(viewBlock, [
        cSetHtml('<p>a<br></p><p>b</p>'),
        cReadBlockBoundary(true, [0, 0], 1),
        cAssertBlockBoundaryPositions([0, 0], 1, [1, 0], 0),
        cAssertBlockBoundaryBlocks([0], [1])
      ]))
    ]))
  ], function () {
    viewBlock.detach();
    success();
  }, failure);
});
Example #19
0
  TinyLoader.setup(function (editor, onSuccess, onFailure) {
    const tinyApis = TinyApis(editor);
    const doc = TinyDom.fromDom(document);

    const sOpenQuickLink = GeneralSteps.sequence([
      Step.sync(() => {
        editor.execCommand('mceLink');
      }),
      // tests were erronously allowed to pass when the quick link dialog would
      // open and very quickly close because this was happing at superhuman
      // speeds. So I'm slowing it down.
      Step.wait(500),
      FocusTools.sTryOnSelector('Selector should be in contextform input', doc, '.tox-toolbar input'),
    ]);

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

      Log.stepsAsStep('TBA', 'Checking that QuickLink can insert a link', [
        sOpenQuickLink,
        FocusTools.sSetActiveValue(doc, 'http://tiny.cloud'),
        Keyboard.sKeydown(doc, Keys.enter(), { }),
        tinyApis.sAssertContentPresence({
          'a[href="http://tiny.cloud"]': 1,
          'a:contains("http://tiny.cloud")': 1
        }),
        UiFinder.sNotExists(Body.body(), '.tox-pop__dialog')
      ]),

      Log.stepsAsStep('TBA', 'Checking that QuickLink can add a link to selected text and keep the current text', [
        tinyApis.sSetContent('<p>Word</p>'),
        tinyApis.sSetSelection([ 0, 0 ], ''.length, [ 0, 0 ], 'Word'.length),
        sOpenQuickLink,
        FocusTools.sSetActiveValue(doc, 'http://tiny.cloud/2'),
        Keyboard.sKeydown(doc, Keys.enter(), { }),
        tinyApis.sAssertContentPresence({
          'a[href="http://tiny.cloud/2"]': 1,
          'a:contains("http://tiny.cloud/2")': 0,
          'a:contains("Word")': 1
        }),
        UiFinder.sNotExists(Body.body(), '.tox-pop__dialog')
      ]),

      Log.stepsAsStep('TBA', 'Checking that QuickLink can add a link to a selected image and keep the current image', [
        tinyApis.sSetContent('<p><img src="image.jpg"></p>'),
        tinyApis.sSelect('img', []),
        sOpenQuickLink,
        FocusTools.sSetActiveValue(doc, 'http://tiny.cloud/2'),
        Keyboard.sKeydown(doc, Keys.enter(), { }),
        tinyApis.sAssertContentPresence({
          'a[href="http://tiny.cloud/2"]': 1,
          'a:contains("http://tiny.cloud/2")': 0,
          'img[src="image.jpg"]': 1
        }),
        UiFinder.sNotExists(Body.body(), '.tox-pop__dialog')
      ]),

      Log.stepsAsStep('TBA', 'Checking that QuickLink can edit an existing link', [
        tinyApis.sSetContent('<p><a href="http://tiny.cloud/3">Word</a></p>'),
        tinyApis.sSetSelection([ 0, 0, 0 ], 'W'.length, [ 0, 0, 0 ], 'Wo'.length),
        sOpenQuickLink,
        FocusTools.sSetActiveValue(doc, 'http://tiny.cloud/changed/3'),
        Keyboard.sKeydown(doc, Keys.enter(), { }),
        tinyApis.sAssertContentPresence({
          'a[href="http://tiny.cloud/changed/3"]': 1,
          'a:contains("http://tiny.cloud/3")': 0,
          'a:contains("Word")': 1
        }),
        UiFinder.sNotExists(Body.body(), '.tox-pop__dialog')
      ]),

      Log.stepsAsStep('TBA', 'Checking that QuickLink can remove an existing link', [
        tinyApis.sSetContent('<p><a href="http://tiny.cloud/4">Word</a></p>'),
        tinyApis.sSetSelection([ 0, 0, 0 ], 'Wor'.length, [ 0, 0, 0 ], 'Wor'.length),
        // TODO FIXME TINY-2691
        // Note the following assert fails on IE
        // tinyApis.sAssertSelection([ 0, 0, 0 ], 'Wor'.length, [ 0, 0, 0 ], 'Wor'.length),
        sOpenQuickLink,
        Keyboard.sKeydown(doc, Keys.tab(), { }),
        Keyboard.sKeydown(doc, Keys.right(), { }),
        Keyboard.sKeydown(doc, Keys.enter(), { }),
        tinyApis.sAssertContentPresence({
          'a': 0,
          'p:contains("Word")': 1
        }),
        UiFinder.sNotExists(Body.body(), '.tox-pop__dialog')
      ]),

      TestLinkUi.sClearHistory
    ], onSuccess, onFailure);
  }, {
    TinyLoader.setup(function (editor, onSuccess, onFailure) {
      const tinyApis = TinyApis(editor);
      const tinyActions = TinyActions(editor);

      Pipeline.async({}, [
        tinyApis.sFocus,
        Logger.t('Arrow keys in figcaption', GeneralSteps.sequence([
          Logger.t('Arrow up from start of figcaption to paragraph before figure', GeneralSteps.sequence([
            tinyApis.sSetContent('<figure><figcaption>a</figcaption></figure>'),
            tinyApis.sSetCursor([0, 0, 0], 0),
            tinyActions.sContentKeystroke(Keys.up(), { }),
            tinyApis.sAssertContent('<p>&nbsp;</p><figure><figcaption>a</figcaption></figure>'),
            tinyApis.sAssertSelection([0], 0, [0], 0)
          ])),
          Logger.t('Arrow down from end of figcaption to paragraph after figure', GeneralSteps.sequence([
            tinyApis.sSetContent('<figure><figcaption>a</figcaption></figure>'),
            tinyApis.sSetCursor([0, 0, 0], 1),
            tinyActions.sContentKeystroke(Keys.down(), { }),
            tinyApis.sAssertContent('<figure><figcaption>a</figcaption></figure><p>&nbsp;</p>'),
            tinyApis.sAssertSelection([1], 0, [1], 0)
          ])),
          Logger.t('Arrow up in middle of figcaption', GeneralSteps.sequence([
            tinyApis.sSetContent('<figure><figcaption>ab</figcaption></figure>'),
            tinyApis.sSetCursor([0, 0, 0], 1),
            tinyActions.sContentKeystroke(Keys.up(), { }),
            tinyApis.sAssertContent('<p>&nbsp;</p><figure><figcaption>ab</figcaption></figure>'),
            tinyApis.sAssertSelection([0], 0, [0], 0)
          ])),
          Logger.t('Arrow down in middle of figcaption', GeneralSteps.sequence([
            tinyApis.sSetContent('<figure><figcaption>ab</figcaption></figure>'),
            tinyApis.sSetCursor([0, 0, 0], 1),
            tinyActions.sContentKeystroke(Keys.down(), { }),
            tinyApis.sAssertContent('<figure><figcaption>ab</figcaption></figure><p>&nbsp;</p>'),
            tinyApis.sAssertSelection([1], 0, [1], 0)
          ])),
          Logger.t('Arrow up at line 2 in figcaption should not insert new block', GeneralSteps.sequence([
            tinyApis.sSetContent('<figure><figcaption>a<br />b</figcaption></figure>'),
            tinyApis.sSetCursor([0, 0, 2], 0),
            tinyActions.sContentKeystroke(Keys.up(), { }),
            tinyApis.sAssertContent('<figure><figcaption>a<br />b</figcaption></figure>'),
            tinyApis.sAssertSelection([0, 0, 2], 0, [0, 0, 2], 0)
          ])),
          Logger.t('Arrow down at line 1 in figcaption should not insert new block', GeneralSteps.sequence([
            tinyApis.sSetContent('<figure><figcaption>a<br />b</figcaption></figure>'),
            tinyApis.sSetCursor([0, 0, 0], 1),
            tinyActions.sContentKeystroke(Keys.down(), { }),
            tinyApis.sAssertContent('<figure><figcaption>a<br />b</figcaption></figure>'),
            tinyApis.sAssertSelection([0, 0, 0], 1, [0, 0, 0], 1)
          ])),
          Logger.t('Arrow down at figcaption with forced_root_block_attrs set', GeneralSteps.sequence([
            tinyApis.sSetSetting('forced_root_block_attrs', { class: 'x' }),
            tinyApis.sSetContent('<figure><figcaption>a</figcaption></figure>'),
            tinyApis.sSetCursor([0, 0, 0], 1),
            tinyActions.sContentKeystroke(Keys.down(), { }),
            tinyApis.sAssertContent('<figure><figcaption>a</figcaption></figure><p class="x">&nbsp;</p>'),
            tinyApis.sAssertSelection([1], 0, [1], 0),
            tinyApis.sDeleteSetting('forced_root_block_attrs')
          ]))
        ]))
      ], onSuccess, onFailure);
    }, {
Example #21
0
    TinyLoader.setup((editor, onSuccess, onFailure) => {
      const tinyApis = TinyApis(editor);
      const tinyActions = TinyActions(editor);

      Pipeline.async({}, [
        tinyApis.sFocus,
        Logger.t('Home key', GeneralSteps.sequence([
          Logger.t('Home key should move caret before cef within the same block', GeneralSteps.sequence([
            tinyApis.sSetContent('<p>123</p><p><span contenteditable="false">CEF</span>456</p>'),
            tinyApis.sSetCursor([1, 1], 3),
            tinyActions.sContentKeystroke(VK.HOME, { }),
            tinyApis.sAssertSelection([1, 0], 0, [1, 0], 0)
          ])),
          Logger.t('Home key should move caret from after cef to before cef', GeneralSteps.sequence([
            tinyApis.sSetContent('<p><span contenteditable="false">CEF</span></p>'),
            tinyApis.sSetCursor([0], 1),
            tinyActions.sContentKeystroke(VK.HOME, { }),
            tinyApis.sAssertSelection([0, 0], 0, [0, 0], 0)
          ])),
          Logger.t('Home key should move caret to before cef from the start of range', GeneralSteps.sequence([
            tinyApis.sSetContent('<p>123</p><p><span contenteditable="false">CEF</span>456<br>789</p>'),
            tinyApis.sSetSelection([1, 1], 3, [1, 1], 3),
            tinyActions.sContentKeystroke(VK.HOME, { }),
            tinyApis.sAssertSelection([1, 0], 0, [1, 0], 0)
          ])),
          Logger.t('Home key should not move caret before cef within the same block if there is a BR in between', GeneralSteps.sequence([
            tinyApis.sSetContent('<p>123</p><p><span contenteditable="false">CEF</span><br>456</p>'),
            tinyApis.sSetCursor([1, 2], 3),
            tinyActions.sContentKeystroke(VK.HOME, { }),
            tinyApis.sAssertSelection([1, 2], 3, [1, 2], 3)
          ])),
          Logger.t('Home key should not move caret if there is no cef', GeneralSteps.sequence([
            tinyApis.sSetContent('<p>123</p>'),
            tinyApis.sSetCursor([0, 0], 1),
            tinyActions.sContentKeystroke(VK.HOME, { }),
            tinyApis.sAssertSelection([0, 0], 1, [0, 0], 1)
          ]))
        ])),
        Logger.t('End key', GeneralSteps.sequence([
          Logger.t('End key should move caret after cef within the same block', GeneralSteps.sequence([
            tinyApis.sSetContent('<p>123<span contenteditable="false">CEF</span></p><p>456</p>'),
            tinyApis.sSetCursor([0, 0], 0),
            tinyActions.sContentKeystroke(VK.END, { }),
            tinyApis.sAssertSelection([0, 2], 1, [0, 2], 1)
          ])),
          Logger.t('End key should move caret from before cef to after cef', GeneralSteps.sequence([
            tinyApis.sSetContent('<p><span contenteditable="false">CEF</span></p>'),
            tinyApis.sSetCursor([0], 0),
            tinyActions.sContentKeystroke(VK.END, { }),
            tinyApis.sAssertSelection([0, 1], 1, [0, 1], 1)
          ])),
          Logger.t('End key should move caret to after cef from the end of range', GeneralSteps.sequence([
            tinyApis.sSetContent('<p>123<br>456<span contenteditable="false">CEF</span></p>'),
            tinyApis.sSetSelection([0, 0], 0, [0, 2], 0),
            tinyActions.sContentKeystroke(VK.END, { }),
            tinyApis.sAssertSelection([0, 4], 1, [0, 4], 1)
          ])),
          Logger.t('End key should not move caret after cef within the same block if there is a BR in between', GeneralSteps.sequence([
            tinyApis.sSetContent('<p>123<br><span contenteditable="false">CEF</span></p><p>456</p>'),
            tinyApis.sSetCursor([0, 0], 0),
            tinyActions.sContentKeystroke(VK.END, { }),
            tinyApis.sAssertSelection([0, 0], 0, [0, 0], 0)
          ])),
          Logger.t('End key should not move caret if there is no cef', GeneralSteps.sequence([
            tinyApis.sSetContent('<p>123</p>'),
            tinyApis.sSetCursor([0, 0], 1),
            tinyActions.sContentKeystroke(VK.END, { }),
            tinyApis.sAssertSelection([0, 0], 1, [0, 0], 1)
          ]))
        ]))
      ], onSuccess, onFailure);
    }, {
Example #22
0
    TinyLoader.setup(function (editor, onSuccess, onFailure) {
      Pipeline.async({}, [
        Logger.t('FakeCaret before/after table', GeneralSteps.sequence(browser.isEdge() || browser.isFirefox() ? [
          Logger.t('Move fake caret left before table', Chain.asStep(editor, [
            ApiChains.cFocus,
            ApiChains.cSetContent('<table><tbody><tr><td>1</td></tr></tbody></table>'),
            ApiChains.cSetCursor([0, 0, 0, 0, 0], 0),
            ApiChains.cAssertContentStructure(buildBody([ table('1') ])),
            ActionChains.cContentKeystroke(Keys.left()),
            ApiChains.cAssertContentStructure(buildBody([ caretBefore(), table('1'), visualCaretBefore() ])),
            ApiChains.cAssertSelection([0], 0, [0], 0)
          ])),
          Logger.t('Move fake caret right after table', Chain.asStep(editor, [
            ApiChains.cFocus,
            ApiChains.cSetContent('<table><tbody><tr><td>1</td></tr></tbody></table>'),
            ApiChains.cSetCursor([0, 0, 0, 0, 0], 1),
            ApiChains.cAssertContentStructure(buildBody([ table('1') ])),
            ActionChains.cContentKeystroke(Keys.right()),
            ApiChains.cAssertContentStructure(buildBody([ table('1'), caretAfter(), visualCaretAfter() ])),
            ApiChains.cAssertSelection([1], 0, [1], 0)
          ])),
          Logger.t('Move fake caret right after table then right again before other table', Chain.asStep(editor, [
            ApiChains.cFocus,
            ApiChains.cSetContent('<table><tbody><tr><td>1</td></tr></tbody></table><table><tbody><tr><td>2</td></tr></tbody></table>'),
            ApiChains.cSetCursor([0, 0, 0, 0, 0], 1),
            ApiChains.cAssertContentStructure(buildBody([ table('1'), table('2') ])),
            ActionChains.cContentKeystroke(Keys.right()),
            ApiChains.cAssertContentStructure(buildBody([ table('1'), caretAfter(), table('2'), visualCaretAfter() ])),
            ApiChains.cAssertSelection([1], 0, [1], 0),
            ActionChains.cContentKeystroke(Keys.right()),
            ApiChains.cAssertContentStructure(buildBody([ table('1'), caretBefore(), table('2'), visualCaretBefore() ])),
            ApiChains.cAssertSelection([1], 0, [1], 0)
          ])),
          Logger.t('Move fake caret left before table then left again after other table', Chain.asStep(editor, [
            ApiChains.cFocus,
            ApiChains.cSetContent('<table><tbody><tr><td>1</td></tr></tbody></table><table><tbody><tr><td>2</td></tr></tbody></table>'),
            ApiChains.cSetCursor([1, 0, 0, 0, 0], 0),
            ApiChains.cAssertContentStructure(buildBody([ table('1'), table('2') ])),
            ActionChains.cContentKeystroke(Keys.left()),
            ApiChains.cAssertContentStructure(buildBody([ table('1'), caretBefore(), table('2'), visualCaretBefore() ])),
            ApiChains.cAssertSelection([1], 0, [1], 0),
            ActionChains.cContentKeystroke(Keys.left()),
            ApiChains.cAssertContentStructure(buildBody([ table('1'), caretAfter(), table('2'), visualCaretAfter() ])),
            ApiChains.cAssertSelection([1], 0, [1], 0)
          ])),
          Logger.t('Move fake up for when table is first element', Chain.asStep(editor, [
            ApiChains.cFocus,
            ApiChains.cSetContent('<table><tbody><tr><td>1</td></tr></tbody></table>'),
            ApiChains.cSetCursor([0, 0, 0, 0, 0], 0),
            ApiChains.cAssertContentStructure(buildBody([ table('1') ])),
            ActionChains.cContentKeystroke(Keys.up()),
            ApiChains.cAssertContentStructure(buildBody([ block, table('1') ])),
            ApiChains.cAssertSelection([0], 0, [0], 0)
          ])),
          Logger.t('Move fake down for when table is last element', Chain.asStep(editor, [
            ApiChains.cFocus,
            ApiChains.cSetContent('<table><tbody><tr><td>1</td></tr></tbody></table>'),
            ApiChains.cSetCursor([0, 0, 0, 0, 0], 1),
            ApiChains.cAssertContentStructure(buildBody([ table('1') ])),
            ActionChains.cContentKeystroke(Keys.down()),
            ApiChains.cAssertContentStructure(buildBody([ table('1'), block ])),
            ApiChains.cAssertSelection([1], 0, [1], 0)
          ])),
          Logger.t('Move fake up for when table is first element but not when caret is not as start', Chain.asStep(editor, [
            ApiChains.cFocus,
            ApiChains.cSetContent('<table><tbody><tr><td>1</td></tr></tbody></table>'),
            ApiChains.cSetCursor([0, 0, 0, 0, 0], 1),
            ApiChains.cAssertContentStructure(buildBody([ table('1') ])),
            ActionChains.cContentKeystroke(Keys.up()),
            ApiChains.cAssertContentStructure(buildBody([ block, table('1') ])),
            ApiChains.cAssertSelection([0], 0, [0], 0)
          ])),
          Logger.t('Move fake down for when table is last element but not when caret is not as end', Chain.asStep(editor, [
            ApiChains.cFocus,
            ApiChains.cSetContent('<table><tbody><tr><td>1</td></tr></tbody></table>'),
            ApiChains.cSetCursor([0, 0, 0, 0, 0], 0),
            ApiChains.cAssertContentStructure(buildBody([ table('1') ])),
            ActionChains.cContentKeystroke(Keys.down()),
            ApiChains.cAssertContentStructure(buildBody([ table('1'), block ])),
            ApiChains.cAssertSelection([1], 0, [1], 0)
          ]))] : []
        )),

        Logger.t('Table cell navigation', GeneralSteps.sequence([
          Logger.t('Should move to the cell above the current cell on key up', Chain.asStep(editor, [
            ApiChains.cFocus,
            ApiChains.cSetContent(`
              <table>
                <tbody>
                  <tr><td>1</td><td>2</td></tr>
                  <tr><td>2</td><td>3</td></tr>
                </tbody>
              </table>
            `),
            ApiChains.cSetCursor([0, 0, 1, 1, 0], 0),
            ActionChains.cContentKeystroke(Keys.up()),
            ApiChains.cAssertSelection([0, 0, 0, 1, 0], 0, [0, 0, 0, 1, 0], 0)
          ])),
          Logger.t('Should move to the cell below the current cell on key down', Chain.asStep(editor, [
            ApiChains.cFocus,
            ApiChains.cSetContent(`
              <table>
                <tbody>
                  <tr><td>1</td><td>2</td></tr>
                  <tr><td>2</td><td>3</td></tr>
                </tbody>
              </table>
            `),
            ApiChains.cSetCursor([0, 0, 0, 1, 0], 0),
            ActionChains.cContentKeystroke(Keys.down()),
            ApiChains.cAssertSelection([0, 0, 1, 1, 0], 0, [0, 0, 1, 1, 0], 0)
          ])),
          Logger.t('Should move to the content above when the caret is a first table row', Chain.asStep(editor, [
            ApiChains.cFocus,
            ApiChains.cSetContent(`
              <p>a<p>
              <table>
                <tbody>
                  <tr><td>1</td><td>2</td></tr>
                  <tr><td>2</td><td>3</td></tr>
                </tbody>
              </table>
            `),
            ApiChains.cSetCursor([1, 0, 0, 1, 0], 0),
            ActionChains.cContentKeystroke(Keys.up()),
            ApiChains.cAssertSelection([0, 0], 1, [0, 0], 1)
          ])),
          Logger.t('Should move to the content below if the caret is a last table row', Chain.asStep(editor, [
            ApiChains.cFocus,
            ApiChains.cSetContent(`
              <table>
                <tbody>
                  <tr><td>1</td><td>2</td></tr>
                  <tr><td>2</td><td>3</td></tr>
                </tbody>
              </table>
              <p>a<p>
            `),
            ApiChains.cSetCursor([0, 0, 1, 1, 0], 0),
            ActionChains.cContentKeystroke(Keys.down()),
            ApiChains.cAssertSelection([1, 0], 1, [1, 0], 1)
          ])),
          Logger.t('Should not move down if the caret is on first line in table cell <br>', Chain.asStep(editor, [
            ApiChains.cFocus,
            ApiChains.cSetContent(`
              <table>
                <tbody>
                  <tr><td>1</td><td>2<br>3</td></tr>
                  <tr><td>4</td><td>5</td></tr>
                </tbody>
              </table>
            `),
            ApiChains.cSetCursor([0, 0, 0, 1, 0], 0),
            ActionChains.cContentKeystroke(Keys.down()),
            ApiChains.cAssertSelection([0, 0, 0, 1, 0], 0, [0, 0, 0, 1, 0], 0)
          ])),
          Logger.t('Should not move up if the caret is on last line in table cell <br>', Chain.asStep(editor, [
            ApiChains.cFocus,
            ApiChains.cSetContent(`
              <table>
                <tbody>
                  <tr><td>1</td><td>2</td></tr>
                  <tr><td>3</td><td>4<br>5</td></tr>
                </tbody>
              </table>
            `),
            ApiChains.cSetCursor([0, 0, 1, 1, 2], 0),
            ActionChains.cContentKeystroke(Keys.up()),
            ApiChains.cAssertSelection([0, 0, 1, 1, 2], 0, [0, 0, 1, 1, 2], 0)
          ])),
          Logger.t('Should not move down if the caret is on first line in table cell <p>', Chain.asStep(editor, [
            ApiChains.cFocus,
            ApiChains.cSetContent(`
              <table>
                <tbody>
                  <tr><td>1</td><td><p>2</p><p>3</p></td></tr>
                  <tr><td>4</td><td>5</td></tr>
                </tbody>
              </table>
            `),
            ApiChains.cSetCursor([0, 0, 0, 1, 0, 0], 0),
            ActionChains.cContentKeystroke(Keys.down()),
            ApiChains.cAssertSelection([0, 0, 0, 1, 0, 0], 0, [0, 0, 0, 1, 0, 0], 0)
          ])),
          Logger.t('Should not move up if the caret is on last line in table cell <p>', Chain.asStep(editor, [
            ApiChains.cFocus,
            ApiChains.cSetContent(`
              <table>
                <tbody>
                  <tr><td>1</td><td>2</td></tr>
                  <tr><td>3</td><td><p>4</p><p>5</p></td></tr>
                </tbody>
              </table>
            `),
            ApiChains.cSetCursor([0, 0, 1, 1, 1, 0], 0),
            ActionChains.cContentKeystroke(Keys.up()),
            ApiChains.cAssertSelection([0, 0, 1, 1, 1, 0], 0, [0, 0, 1, 1, 1, 0], 0)
          ]))
        ]))
      ], onSuccess, onFailure);
    }, {
Example #23
0
UnitTest.asynctest('browser.tinymce.core.focus.EditorFocusTest', function () {
  const success = arguments[arguments.length - 2];
  const failure = arguments[arguments.length - 1];
  const viewBlock = ViewBlock();

  Theme();

  const cCreateInlineEditor = function (html) {
    return Chain.async(function (viewBlock: any, next, die) {
      viewBlock.update(html);

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

  const cFocusEditor = Chain.op(function (editor: any) {
    EditorFocus.focus(editor, false);
  });

  const cFocusElement = function (elementPath) {
    return Chain.op(function (editor: any) {
      const element = Hierarchy.follow(Element.fromDom(editor.getBody()), elementPath).getOrDie();
      element.dom().focus();
    });
  };

  const cSetSelection = function (startPath, startOffset, endPath, endOffset) {
    return Chain.op(function (editor: any) {
      const startContainer = Hierarchy.follow(Element.fromDom(editor.getBody()), startPath).getOrDie();
      const endContainer = Hierarchy.follow(Element.fromDom(editor.getBody()), endPath).getOrDie();
      const rng = editor.dom.createRng();

      rng.setStart(startContainer.dom(), startOffset);
      rng.setEnd(endContainer.dom(), endOffset);

      editor.selection.setRng(rng);
    });
  };

  const cAssertSelection = function (startPath, startOffset, endPath, endOffset) {
    return Chain.op(function (editor: any) {
      const startContainer = Hierarchy.follow(Element.fromDom(editor.getBody()), startPath).getOrDie();
      const endContainer = Hierarchy.follow(Element.fromDom(editor.getBody()), endPath).getOrDie();
      const rng = editor.selection.getRng();

      Assertions.assertDomEq('Should be expected from start container', startContainer, Element.fromDom(rng.startContainer));
      Assertions.assertEq('Should be expected from start offset', startOffset, rng.startOffset);
      Assertions.assertDomEq('Should be expected end container', endContainer, Element.fromDom(rng.endContainer));
      Assertions.assertEq('Should be expected end offset', endOffset, rng.endOffset);
    });
  };

  const cAssertHasFocus = function (elementPath) {
    return Chain.op(function (editor: any) {
      const element = Hierarchy.follow(Element.fromDom(editor.getBody()), elementPath).getOrDie();
      Assertions.assertEq('Should have focus on the editor', true, EditorFocus.hasFocus(editor));
      Assertions.assertDomEq('Should be the expected activeElement', element, Focus.active().getOrDie());
    });
  };

  const cRemoveEditor = Chain.op(function (editor: any) {
    editor.remove();
  });

  viewBlock.attach();
  Pipeline.async({}, [
    Logger.t('Focus editor', GeneralSteps.sequence([
      Logger.t('Focus editor initialized on a table', Chain.asStep(viewBlock, [
        cCreateInlineEditor('<table class="tinymce-editor"><tbody><tr><td>a</td></tr></tbody></table>'),
        cFocusEditor,
        cAssertSelection([0, 0, 0, 0], 0, [0, 0, 0, 0], 0),
        cRemoveEditor
      ])),
      Logger.t('Focus editor initialized on a div with p', Chain.asStep(viewBlock, [
        cCreateInlineEditor('<div class="tinymce-editor"><p>a</p></div>'),
        cFocusEditor,
        cAssertSelection([0, 0], 0, [0, 0], 0),
        cRemoveEditor
      ])),
      Logger.t('Focus editor initialized on a list', Chain.asStep(viewBlock, [
        cCreateInlineEditor('<ul class="tinymce-editor"><li>a</li></ul>'),
        cFocusEditor,
        cAssertSelection([0, 0], 0, [0, 0], 0),
        cRemoveEditor
      ]))
    ])),
    Logger.t('hasFocus', GeneralSteps.sequence([
      Logger.t('Focus on normal paragraph', Chain.asStep(viewBlock, [
        cCreateInlineEditor('<div class="tinymce-editor"><p>a</p></div>'),
        cFocusEditor,
        cAssertHasFocus([]),
        cRemoveEditor
      ])),
      Logger.t('Focus on cE=true inside a cE=false', Chain.asStep(viewBlock, [
        cCreateInlineEditor('<div class="tinymce-editor"><div contenteditable="false">a<div contenteditable="true">b</div></div></div>'),
        cSetSelection([0, 1, 0], 0, [0, 1, 0], 0),
        cFocusElement([0, 1]),
        cAssertHasFocus([0, 1]),
        cRemoveEditor
      ]))
    ]))
  ], function () {
    viewBlock.detach();
    success();
  }, failure);
});
    (editor, onSuccess, onFailure) => {
      const doc = Element.fromDom(document);

      const structureItem = (optText: Option<string>, optIcon: Option<string>) => (s, str, arr) => {
        return s.element('div', {
          classes: [ arr.has('tox-collection__item') ],
          children: Options.cat([
            optIcon.map((icon) => s.element('div', {
              classes: [ arr.has('tox-collection__item-icon') ],
              html: str.is(icon)
            })),

            optText.map((text) => s.element('div', {
              classes: [ arr.has('tox-collection__item-label') ],
              html: str.is(text)
            }))
          ])
        });
      };

      const cFindNthIn = (selector, n) => Chain.binder((elem: Element) => {
        const matches = UiFinder.findAllIn(elem, selector);
        return matches.length > 0 && n < matches.length ? Result.value(matches[n]) :
          Result.error(`Could not find match ${n} of selector: ${selector}`);
      });

      Pipeline.async({ }, Logger.ts(
        'Check structure of collection in a dialog',
        [
          TestHelpers.GuiSetup.mAddStyles(doc, [
            ':focus { outline: 2px solid green; }'
          ]),
          Mouse.sClickOn(Body.body(), '.tox-toolbar button'),
          UiFinder.sWaitForVisible('Waiting for dialog', Body.body(), '[role="dialog"]'),

          FocusTools.sTryOnSelector('Focus should start on input', doc, 'input'),
          Keyboard.sKeydown(doc, Keys.tab(), { }),

          Logger.t(
            'Checking the first collection: columns = 1, list',
            GeneralSteps.sequence([
              Chain.asStep(Body.body(), [
                cFindNthIn('[role="dialog"] .tox-form__group .tox-collection', 0),
                Assertions.cAssertStructure(
                  'Checking structure',
                  ApproxStructure.build((s, str, arr) => {
                    return s.element('div', {
                      classes: [ arr.has('tox-collection'), arr.has('tox-collection--list'), arr.not('tox-menu') ],
                      children: [
                        s.element('div', {
                          classes: [ arr.has('tox-collection__group') ],
                          children: Arr.map([ 'A', 'B', 'C' ], (letter) =>
                            structureItem(Option.some('text-' + letter), Option.some('icon-' + letter))(s, str, arr)
                          )
                        })
                      ]
                    });
                  })
                )
              ]),
              FocusTools.sTryOnSelector('Focus should be on A', doc, '.tox-collection__item:contains(A).tox-collection__item--active'),
              Keyboard.sKeydown(doc, Keys.down(), { }),
              FocusTools.sTryOnSelector('Focus should be on B', doc, '.tox-collection__item:contains(B)'),
              Keyboard.sKeydown(doc, Keys.down(), { }),
              FocusTools.sTryOnSelector('Focus should be on C', doc, '.tox-collection__item:contains(C)'),
            ])
          ),

          // NOTE: We need a layout engine to use flex-wrap navigation.
          navigator.userAgent.indexOf('PhantomJS') > -1 ?
            FocusTools.sSetFocus('Force focus to F on phantom', Body.body(), '.tox-collection__item:contains("F")')
            : Logger.t(
            'Checking the second collection: columns = auto',
            GeneralSteps.sequence([
              Chain.asStep(Body.body(), [
                cFindNthIn('[role="dialog"] .tox-form__group .tox-collection', 1),
                Assertions.cAssertStructure(
                  'Checking structure',
                  ApproxStructure.build((s, str, arr) => {
                    return s.element('div', {
                      classes: [ arr.has('tox-collection'), arr.has('tox-collection--grid'), arr.not('tox-menu') ],
                      children: [
                        s.element('div', {
                          classes: [ arr.has('tox-collection__group') ],
                          children: Arr.map([ 'D', 'E', 'F' ], (letter) =>
                            structureItem(Option.none(), Option.some('icon-' + letter))(s, str, arr)
                          )
                        })
                      ]
                    });
                  })
                )
              ]),
              FocusTools.sTryOnSelector('Focus should be on C', doc, '.tox-collection__item:contains(C)'),
              Keyboard.sKeydown(doc, Keys.tab(), { }),
              FocusTools.sTryOnSelector('Focus should be on D', doc, '.tox-collection__item:contains(D)'),
              Keyboard.sKeydown(doc, Keys.right(), { }),
              FocusTools.sTryOnSelector('Focus should be on E', doc, '.tox-collection__item:contains(E)'),
              Keyboard.sKeydown(doc, Keys.right(), { }),
              FocusTools.sTryOnSelector('Focus should be on F', doc, '.tox-collection__item:contains(F)'),
            ])
          ),

          Logger.t(
            'Checking the third collection: columns = 2',
            GeneralSteps.sequence([
              Chain.asStep(Body.body(), [
                cFindNthIn('[role="dialog"] .tox-form__group .tox-collection', 2),
                Assertions.cAssertStructure(
                  'Checking structure',
                  ApproxStructure.build((s, str, arr) => {
                    return s.element('div', {
                      classes: [ arr.has('tox-collection'), arr.has('tox-collection--grid'), arr.not('tox-menu') ],
                      children: [
                        s.element('div', {
                          classes: [ arr.has('tox-collection__group') ],
                          children: Arr.map([ 'G', 'H' ], (letter) =>
                            structureItem(Option.none(), Option.some('icon-' + letter))(s, str, arr)
                          )
                        }),
                        s.element('div', {
                          classes: [ arr.has('tox-collection__group') ],
                          children: Arr.map([ 'I' ], (letter) =>
                            structureItem(Option.none(), Option.some('icon-' + letter))(s, str, arr)
                          )
                        })
                      ]
                    });
                  })
                )
              ]),
              FocusTools.sTryOnSelector('Focus should be on F', doc, '.tox-collection__item:contains(F)'),
              Keyboard.sKeydown(doc, Keys.tab(), { }),
              FocusTools.sTryOnSelector('Focus should be on G', doc, '.tox-collection__item:contains(G)'),
              Keyboard.sKeydown(doc, Keys.right(), { }),
              FocusTools.sTryOnSelector('Focus should be on H', doc, '.tox-collection__item:contains(H)'),
              Keyboard.sKeydown(doc, Keys.down(), { }),
              FocusTools.sTryOnSelector('Focus should be on I', doc, '.tox-collection__item:contains(I)'),
            ])
          ),

          Log.stepsAsStep('TBA', 'Check focus follows mouse also', [
            Mouse.sHoverOn(Body.body(), '.tox-collection__item:contains(G)'),
            Chain.asStep(Body.body(), [
              UiFinder.cFindIn('.tox-collection__item--active'),
              Chain.op((activeElem) => {
                const value = Attr.get(activeElem, 'data-collection-item-value');
                Assertions.assertEq('Checking selected value', 'g', value);
              })
            ])
          ]),
          TestHelpers.GuiSetup.mRemoveStyles
        ]
      ), onSuccess, onFailure);
    },
Example #25
0
  TinyLoader.setup(function (editor, onSuccess, onFailure) {
    const tinyUi = TinyUi(editor);
    const tinyApis = TinyApis(editor);

    const sDeleteSetting = function (key) {
      return Step.sync(function () {
        delete editor.settings[key];
      });
    };

    Pipeline.async({}, [
      Logger.t('test cdate in snippet with default class', GeneralSteps.sequence([
        tinyApis.sSetSetting('templates', [{ title: 'a', description: 'b', content: '<p class="cdate">x</p>' }]),
        tinyApis.sSetSetting('template_cdate_format', 'fake date'),
        tinyUi.sClickOnToolbar('click on template button', 'div[aria-label="Insert template"] > button'),
        tinyUi.sWaitForPopup('wait for popup', 'div[role="dialog"][aria-label="Insert template"]'),
        tinyUi.sClickOnUi('click on ok button', 'div.mce-primary button'),
        tinyApis.sAssertContent('<p class="cdate">fake date</p>'),
        tinyApis.sSetContent('')
      ])),

      Logger.t('test cdate in snippet with custom class', GeneralSteps.sequence([
        tinyApis.sSetSetting('template_cdate_classes', 'customCdateClass'),
        tinyApis.sSetSetting('templates', [{ title: 'a', description: 'b', content: '<p class="customCdateClass">x</p>' }]),
        tinyApis.sSetSetting('template_cdate_format', 'fake date'),
        tinyUi.sClickOnToolbar('click on template button', 'div[aria-label="Insert template"] > button'),
        tinyUi.sWaitForPopup('wait for popup', 'div[role="dialog"][aria-label="Insert template"]'),
        tinyUi.sClickOnUi('click on ok button', 'div.mce-primary button'),
        tinyApis.sAssertContent('<p class="customCdateClass">fake date</p>'),
        sDeleteSetting('template_cdate_classes'),
        sDeleteSetting('templates'),
        sDeleteSetting('template_cdate_format'),
        tinyApis.sSetContent('')
      ])),

      Logger.t('test mdate updates with each serialization', GeneralSteps.sequence([
        tinyApis.sSetSetting(
          'templates',
          [{ title: 'a', description: 'b', content: '<div class="mceTmpl"><p class="mdate"></p><p class="cdate"></p></div>' }]
        ),
        tinyApis.sSetSetting('template_mdate_format', 'fake modified date'),
        tinyApis.sSetSetting('template_cdate_format', 'fake created date'),
        tinyUi.sClickOnToolbar('click on template button', 'div[aria-label="Insert template"] > button'),
        tinyUi.sWaitForPopup('wait for popup', 'div[role="dialog"][aria-label="Insert template"]'),
        tinyUi.sClickOnUi('click on ok button', 'div.mce-primary button'),
        tinyApis.sAssertContent('<div class="mceTmpl"><p class="mdate">fake modified date</p><p class="cdate">fake created date</p></div>'),
        tinyApis.sSetSetting('template_mdate_format', 'changed modified date'),
        tinyApis.sAssertContent('<div class="mceTmpl"><p class="mdate">changed modified date</p><p class="cdate">fake created date</p></div>'),
        sDeleteSetting('templates'),
        sDeleteSetting('template_mdate_format'),
        sDeleteSetting('template_cdate_template'),
        tinyApis.sSetContent('')
      ])),

      Logger.t('test mdate updates with each serialization with custom class', GeneralSteps.sequence([
        tinyApis.sSetSetting('template_mdate_classes', 'modified'),
        tinyApis.sSetSetting(
          'templates',
          [{ title: 'a', description: 'b', content: '<div class="mceTmpl"><p class="modified"></p><p class="cdate"></p></div>' }]
        ),
        tinyApis.sSetSetting('template_mdate_format', 'fake modified date'),
        tinyApis.sSetSetting('template_cdate_format', 'fake created date'),
        tinyUi.sClickOnToolbar('click on template button', 'div[aria-label="Insert template"] > button'),
        tinyUi.sWaitForPopup('wait for popup', 'div[role="dialog"][aria-label="Insert template"]'),
        tinyUi.sClickOnUi('click on ok button', 'div.mce-primary button'),
        tinyApis.sAssertContent('<div class="mceTmpl"><p class="modified">fake modified date</p><p class="cdate">fake created date</p></div>'),
        tinyApis.sSetSetting('template_mdate_format', 'changed modified date'),
        tinyApis.sAssertContent('<div class="mceTmpl"><p class="modified">changed modified date</p><p class="cdate">fake created date</p></div>'),
        sDeleteSetting('template_mdate_classes'),
        sDeleteSetting('templates'),
        sDeleteSetting('template_mdate_format'),
        sDeleteSetting('template_cdate_template')
      ]))
    ], onSuccess, onFailure);
  }, {
    TinyLoader.setup(function (editor, onSuccess, onFailure) {
      const browser = PlatformDetection.detect().browser;

      Pipeline.async({}, browser.isIE() || browser.isEdge() ? [ // On edge and ie it restores on focusout only
        sAddTestDiv,
        Logger.t('restore even without second nodechange, restores on focusout', Step.sync(function () {
          editor.setContent('<p>a</p><p>b</p>');

          setSelection(editor, [0, 0], 0, [0, 0], 0);
          editor.nodeChanged();

          setSelection(editor, [1, 0], 1, [1, 0], 1);
          focusDiv();

          assertSelection(editor, [0, 0], 0, [0, 0], 0);
        })),
        Logger.t('restore with second nodechange, restores on focusout', Step.sync(function () {
          editor.setContent('<p>a</p><p>b</p>');

          setSelection(editor, [0, 0], 0, [0, 0], 0);
          editor.nodeChanged();

          setSelection(editor, [1, 0], 1, [1, 0], 1);
          editor.nodeChanged();
          focusDiv();

          assertSelection(editor, [1, 0], 1, [1, 0], 1);
        })),
        sRemoveTestDiv
      ] : [ // On the other browsers we test for bookmark saved on nodechange, keyup, mouseup and touchend events
        Logger.t('assert selection after no nodechanged, should not restore', Step.sync(function () {
          editor.setContent('<p>a</p><p>b</p>');
          editor.undoManager.add();
          // In FireFox blurring the editor adds an undo level that triggers a nodechange that creates a bookmark,
          // so by adding an undo level first we keep it from adding a bookmark because the undo manager
          // does not add a new undolevel if it is the same as the previous level.

          setSelection(editor, [0, 0], 0, [0, 0], 0);
          editor.nodeChanged();

          setSelection(editor, [1, 0], 1, [1, 0], 1);
          assertBookmark(editor, [0, 0], 0, [0, 0], 0);
        })),
        Logger.t('assert selection after nodechanged, should restore', Step.sync(function () {
          editor.setContent('<p>a</p><p>b</p>');

          setSelection(editor, [0, 0], 0, [0, 0], 0);
          editor.nodeChanged();

          setSelection(editor, [1, 0], 1, [1, 0], 1);
          editor.nodeChanged();
          assertBookmark(editor, [1, 0], 1, [1, 0], 1);
        })),
        Logger.t('assert selection after keyup, should restore', Step.sync(function () {
          editor.setContent('<p>a</p><p>b</p>');

          setSelection(editor, [0, 0], 0, [0, 0], 0);
          editor.nodeChanged();

          setSelection(editor, [1, 0], 1, [1, 0], 1);
          editor.fire('keyup', { });
          assertBookmark(editor, [1, 0], 1, [1, 0], 1);
        })),
        Logger.t('assert selection after touchend, should restore', GeneralSteps.sequence([
          Step.sync(function () {
            editor.setContent('<p>a</p><p>b</p>');

            setSelection(editor, [0, 0], 0, [0, 0], 0);
            editor.nodeChanged();

            setSelection(editor, [1, 0], 1, [1, 0], 1);
            editor.fire('mouseup', { });
          }),
          sWaitForBookmark(editor, [1, 0], 1, [1, 0], 1)
        ])),
        Logger.t('assert selection after touchend, should restore', GeneralSteps.sequence([
          Step.sync(function () {
            editor.setContent('<p>a</p><p>b</p>');

            setSelection(editor, [0, 0], 0, [0, 0], 0);
            editor.nodeChanged();

            setSelection(editor, [1, 0], 1, [1, 0], 1);
            editor.fire('touchend', { });
          }),
          sWaitForBookmark(editor, [1, 0], 1, [1, 0], 1)
        ])),
        Logger.t('selection with mouseup outside editor body', GeneralSteps.sequence([
          Step.sync(function () {
            editor.setContent('<p>ab</p>');
            setSelection(editor, [0, 0], 0, [0, 0], 1);
            DOMUtils.DOM.fire(document, 'mouseup');
          }),
          sWaitForBookmark(editor, [0, 0], 0, [0, 0], 1)
        ])),
        Logger.t('getSelectionRange event should fire on bookmarked ranges', GeneralSteps.sequence([
          sAddTestDiv,
          Step.sync(() => {
            const modifyRange = (e) => {
              const newRng = document.createRange();
              newRng.selectNodeContents(editor.getBody().lastChild);
              e.range = newRng;
            };

            editor.setContent('<p>a</p><p>b</p>');
            setSelection(editor, [0, 0], 0, [0, 0], 1);
            editor.nodeChanged();
            focusDiv();

            editor.on('getSelectionRange', modifyRange);
            const elm = editor.selection.getNode();
            editor.off('getSelectedRange', modifyRange);

            Assertions.assertEq('Expect event to change the selection from a to b', 'b', elm.innerHTML);
          }),
          sRemoveTestDiv
        ]))
      ], onSuccess, onFailure);
    }, {
  TinyLoader.setup(function (editor, onSuccess, onFailure) {
    const tinyApis = TinyApis(editor);

    Pipeline.async({}, [
      tinyApis.sFocus,
      Logger.t('Delete image forwards', GeneralSteps.sequence([
        tinyApis.sSetContent('<p><img src="#1"></p>'),
        tinyApis.sSetCursor([0], 0),
        sDeleteElementPath(editor, true, [0, 0]),
        tinyApis.sAssertContent(''),
        tinyApis.sAssertSelection([0], 0, [0], 0)
      ])),
      Logger.t('Delete image backwards', GeneralSteps.sequence([
        tinyApis.sSetContent('<p><img src="#1"></p>'),
        tinyApis.sSetCursor([0], 1),
        sDeleteElementPath(editor, false, [0, 0]),
        tinyApis.sAssertContent(''),
        tinyApis.sAssertSelection([0], 0, [0], 0)
      ])),
      Logger.t('Delete first image forwards caret before', GeneralSteps.sequence([
        tinyApis.sSetContent('<p><img src="#1"><img src="#2"></p>'),
        tinyApis.sSetCursor([0], 0),
        sDeleteElementPath(editor, true, [0, 0]),
        tinyApis.sAssertContent('<p><img src="#2" /></p>'),
        tinyApis.sAssertSelection([0], 0, [0], 0)
      ])),
      Logger.t('Delete first image forwards caret after', GeneralSteps.sequence([
        tinyApis.sSetContent('<p><img src="#1"><img src="#2"></p>'),
        tinyApis.sSetCursor([0], 1),
        sDeleteElementPath(editor, true, [0, 0]),
        tinyApis.sAssertContent('<p><img src="#2" /></p>'),
        tinyApis.sAssertSelection([0], 0, [0], 0)
      ])),
      Logger.t('Delete first image backwards', GeneralSteps.sequence([
        tinyApis.sSetContent('<p><img src="#1"><img src="#2"></p>'),
        tinyApis.sSetCursor([0], 2),
        sDeleteElementPath(editor, false, [0, 0]),
        tinyApis.sAssertContent('<p><img src="#2" /></p>'),
        tinyApis.sAssertSelection([0], 0, [0], 0)
      ])),
      Logger.t('Delete second image forwards caret before', GeneralSteps.sequence([
        tinyApis.sSetContent('<p><img src="#1"><img src="#2"></p>'),
        tinyApis.sSetCursor([0], 1),
        sDeleteElementPath(editor, true, [0, 1]),
        tinyApis.sAssertContent('<p><img src="#1" /></p>'),
        tinyApis.sAssertSelection([0], 1, [0], 1)
      ])),
      Logger.t('Delete second image forwards caret after', GeneralSteps.sequence([
        tinyApis.sSetContent('<p><img src="#1"><img src="#2"></p>'),
        tinyApis.sSetCursor([0], 2),
        sDeleteElementPath(editor, true, [0, 1]),
        tinyApis.sAssertContent('<p><img src="#1" /></p>'),
        tinyApis.sAssertSelection([0], 1, [0], 1)
      ])),
      Logger.t('Delete second image backwards caret before', GeneralSteps.sequence([
        tinyApis.sSetContent('<p><img src="#1"><img src="#2"></p>'),
        tinyApis.sSetCursor([0], 1),
        sDeleteElementPath(editor, false, [0, 1]),
        tinyApis.sAssertContent('<p><img src="#1" /></p>'),
        tinyApis.sAssertSelection([0], 1, [0], 1)
      ])),
      Logger.t('Delete second image backwards caret after', GeneralSteps.sequence([
        tinyApis.sSetContent('<p><img src="#1"><img src="#2"></p>'),
        tinyApis.sSetCursor([0], 2),
        sDeleteElementPath(editor, false, [0, 1]),
        tinyApis.sAssertContent('<p><img src="#1" /></p>'),
        tinyApis.sAssertSelection([0], 1, [0], 1)
      ])),
      Logger.t('Delete forwards on paragraph to next paragraph with caret position (text)', GeneralSteps.sequence([
        tinyApis.sSetContent('<p>a</p><p>b</p>'),
        tinyApis.sSetCursor([0, 0], 1),
        sDeleteElementPath(editor, true, [0]),
        tinyApis.sAssertContent('<p>b</p>'),
        tinyApis.sAssertSelection([0, 0], 0, [0, 0], 0)
      ])),
      Logger.t('Delete backwards on paragraph to previous paragraph with caret position (text)', GeneralSteps.sequence([
        tinyApis.sSetContent('<p>a</p><p>b</p>'),
        tinyApis.sSetCursor([1, 0], 0),
        sDeleteElementPath(editor, false, [1]),
        tinyApis.sAssertContent('<p>a</p>'),
        tinyApis.sAssertSelection([0, 0], 1, [0, 0], 1)
      ])),
      Logger.t('Delete forwards on paragraph to previous paragraph with caret position (text)', GeneralSteps.sequence([
        tinyApis.sSetContent('<p>a</p><p>b</p>'),
        tinyApis.sSetCursor([1, 0], 1),
        sDeleteElementPath(editor, true, [1]),
        tinyApis.sAssertContent('<p>a</p>'),
        tinyApis.sAssertSelection([0, 0], 1, [0, 0], 1)
      ])),
      Logger.t('Delete backwards on paragraph to next paragraph with caret position (text)', GeneralSteps.sequence([
        tinyApis.sSetContent('<p>a</p><p>b</p>'),
        tinyApis.sSetCursor([0, 0], 0),
        sDeleteElementPath(editor, false, [0]),
        tinyApis.sAssertContent('<p>b</p>'),
        tinyApis.sAssertSelection([0, 0], 0, [0, 0], 0)
      ])),
      Logger.t('Delete forwards paragraph before paragraph with caret position (element)', GeneralSteps.sequence([
        tinyApis.sSetContent('<p><img src="#1" /></p><p><img src="#2" /></p>'),
        tinyApis.sSetCursor([0], 1),
        sDeleteElementPath(editor, true, [0]),
        tinyApis.sAssertContent('<p><img src="#2" /></p>'),
        tinyApis.sAssertSelection([0], 0, [0], 0)
      ])),
      Logger.t('Delete backwards paragraph after paragraph with caret position (element)', GeneralSteps.sequence([
        tinyApis.sSetContent('<p><img src="#1" /></p><p><img src="#2" /></p>'),
        tinyApis.sSetCursor([1], 0),
        sDeleteElementPath(editor, false, [0]),
        tinyApis.sAssertContent('<p><img src="#2" /></p>'),
        tinyApis.sAssertSelection([0], 0, [0], 0)
      ])),
      Logger.t('Delete backwards on cef block between cef blocks', GeneralSteps.sequence([
        tinyApis.sSetContent('<p contenteditable="false">a</p><p contenteditable="false">b</p><p contenteditable="false">c</p>'),
        tinyApis.sSetSelection([], 1, [], 2),
        sDeleteElementPath(editor, false, [1]),
        tinyApis.sAssertContent('<p contenteditable="false">a</p><p contenteditable="false">c</p>'),
        tinyApis.sAssertSelection([1], 0, [1], 0),
        sAssertCaretDirection(editor, 'after')
      ])),
      Logger.t('Delete forwards on cef block between cef blocks', GeneralSteps.sequence([
        tinyApis.sSetContent('<p contenteditable="false">a</p><p contenteditable="false">b</p><p contenteditable="false">c</p>'),
        tinyApis.sSetSelection([], 1, [], 2),
        sDeleteElementPath(editor, true, [1]),
        tinyApis.sAssertContent('<p contenteditable="false">a</p><p contenteditable="false">c</p>'),
        tinyApis.sAssertSelection([1], 0, [1], 0),
        sAssertCaretDirection(editor, 'before')
      ])),
      Logger.t('Delete element adjacent text nodes forward', GeneralSteps.sequence([
        tinyApis.sSetContent('<p>a<br>b</p>'),
        tinyApis.sSetCursor([0, 0], 1),
        sDeleteElementPath(editor, true, [0, 1]),
        tinyApis.sAssertContent('<p>ab</p>'),
        tinyApis.sAssertSelection([0, 0], 1, [0, 0], 1)
      ])),
      Logger.t('Delete element adjacent text nodes backwards', GeneralSteps.sequence([
        tinyApis.sSetContent('<p>a<br>b</p>'),
        tinyApis.sSetCursor([0, 2], 0),
        sDeleteElementPath(editor, false, [0, 1]),
        tinyApis.sAssertContent('<p>ab</p>'),
        tinyApis.sAssertSelection([0, 0], 1, [0, 0], 1)
      ]))
    ], onSuccess, onFailure);
  }, {
 const sInitialState = (editor) => Logger.t(
   'test inital data',
   GeneralSteps.sequence([
     sOpenDialog(editor),
     UiFinder.sWaitFor('Waiting for dialog to appear', Body.body(), '.tox-dialog-wrap'),
     Chain.asStep(Body.body(), [
       UiFinder.cFindIn('div.tox-dialog'),
       Chain.op((dialog) => {
         Assertions.assertStructure(
           'Full page properties should have this structure',
           ApproxStructure.build((s, str, arr) => {
             return s.element('div', {
               classes: [ arr.has('tox-dialog') ],
               children: [
                 s.element('div', {
                   classes: [ arr.has('tox-dialog__header') ],
                   children: [
                     s.element('div', {
                       html: str.is('Metadata and Document Properties')
                     }),
                     s.element('div', {
                       classes: [ arr.has('tox-dialog__draghandle') ],
                     }),
                     s.element('button', {
                       classes: [ arr.has('tox-button--icon'), arr.has('tox-button--naked') ],
                     })
                   ]
                 }),
                 s.element('div', {
                   classes: [ arr.has('tox-dialog__content-js')],
                   children: [
                     s.element('div', {
                       classes: [ arr.has('tox-dialog__body') ],
                       children: [
                         s.element('div', {
                           classes: [ arr.has('tox-dialog__body-content') ],
                           children: [
                             s.element('div', {
                               classes: [ arr.has('tox-form') ],
                               children: [
                                 s.element('div', {
                                   classes: [ arr.has('tox-form__group') ],
                                   children: [
                                     s.element('label', {
                                       classes: [ arr.has('tox-label') ],
                                       html: str.is('Title')
                                     }),
                                     s.element('input', {
                                       classes: [ arr.has('tox-textfield') ],
                                     })
                                   ]
                                 }),
                                 s.element('div', {
                                   classes: [ arr.has('tox-form__group') ],
                                   children: [
                                     s.element('label', {
                                       classes: [ arr.has('tox-label') ],
                                       html: str.is('Keywords')
                                     }),
                                     s.element('input', {
                                       classes: [ arr.has('tox-textfield') ],
                                     })
                                   ]
                                 }),
                                 s.element('div', {
                                   classes: [ arr.has('tox-form__group') ],
                                   children: [
                                     s.element('label', {
                                       classes: [ arr.has('tox-label') ],
                                       html: str.is('Description')
                                     }),
                                     s.element('input', {
                                       classes: [ arr.has('tox-textfield') ],
                                     })
                                   ]
                                 }),
                                 s.element('div', {
                                   classes: [ arr.has('tox-form__group') ],
                                   children: [
                                     s.element('label', {
                                       classes: [ arr.has('tox-label') ],
                                       html: str.is('Robots')
                                     }),
                                     s.element('input', {
                                       classes: [ arr.has('tox-textfield') ],
                                     })
                                   ]
                                 }),
                                 s.element('div', {
                                   classes: [ arr.has('tox-form__group') ],
                                   children: [
                                     s.element('label', {
                                       classes: [ arr.has('tox-label') ],
                                       html: str.is('Author')
                                     }),
                                     s.element('input', {
                                       classes: [ arr.has('tox-textfield') ],
                                     })
                                   ]
                                 }),
                                 s.element('div', {
                                   classes: [ arr.has('tox-form__group') ],
                                   children: [
                                     s.element('label', {
                                       classes: [ arr.has('tox-label') ],
                                       html: str.is('Encoding')
                                     }),
                                     s.element('input', {
                                       classes: [ arr.has('tox-textfield') ],
                                     })
                                   ]
                                 })
                               ]
                             })
                           ]
                         })
                       ]
                     })
                   ]
                 }),
                 s.element('div', {
                   classes: [ arr.has('tox-dialog__footer') ]
                 })
               ]
             });
           }),
           dialog
         );
       })
     ]),
   ])
 );
UnitTest.asynctest('browser.tinymce.core.dom.ElementTypeTest', function () {
  const success = arguments[arguments.length - 2];
  const failure = arguments[arguments.length - 1];

  const sCheckElement = function (name, predicate, expectedValue) {
    return Step.sync(function () {
      Assertions.assertEq('Should be the expected value for specified element', expectedValue, predicate(Element.fromTag(name)));
    });
  };

  const sCheckText = function (predicate) {
    return Step.sync(function () {
      Assertions.assertEq('Should be false for non element', false, predicate(Element.fromText('text')));
    });
  };

  Pipeline.async({}, [
    Logger.t('Check block elements', GeneralSteps.sequence([
      sCheckElement('p', ElementType.isBlock, true),
      sCheckElement('h1', ElementType.isBlock, true),
      sCheckElement('table', ElementType.isBlock, true),
      sCheckElement('span', ElementType.isBlock, false),
      sCheckElement('b', ElementType.isBlock, false),
      sCheckText(ElementType.isBlock)
    ])),
    Logger.t('Check inline elements', GeneralSteps.sequence([
      sCheckElement('b', ElementType.isInline, true),
      sCheckElement('span', ElementType.isInline, true),
      sCheckElement('p', ElementType.isInline, false),
      sCheckElement('h1', ElementType.isInline, false),
      sCheckText(ElementType.isInline)
    ])),
    Logger.t('Check heading elements', GeneralSteps.sequence([
      sCheckElement('h1', ElementType.isHeading, true),
      sCheckElement('h2', ElementType.isHeading, true),
      sCheckElement('span', ElementType.isHeading, false),
      sCheckElement('table', ElementType.isHeading, false),
      sCheckText(ElementType.isHeading)
    ])),
    Logger.t('Check text block elements', GeneralSteps.sequence([
      sCheckElement('p', ElementType.isTextBlock, true),
      sCheckElement('h1', ElementType.isTextBlock, true),
      sCheckElement('table', ElementType.isTextBlock, false),
      sCheckText(ElementType.isTextBlock)
    ])),
    Logger.t('Check void elements', GeneralSteps.sequence([
      sCheckElement('img', ElementType.isVoid, true),
      sCheckElement('hr', ElementType.isVoid, true),
      sCheckElement('h1', ElementType.isVoid, false),
      sCheckElement('span', ElementType.isVoid, false),
      sCheckText(ElementType.isVoid)
    ])),
    Logger.t('Check table cell elements', GeneralSteps.sequence([
      sCheckElement('th', ElementType.isTableCell, true),
      sCheckElement('td', ElementType.isTableCell, true),
      sCheckElement('h1', ElementType.isTableCell, false),
      sCheckElement('span', ElementType.isTableCell, false),
      sCheckText(ElementType.isTableCell)
    ])),
    Logger.t('Check br elements', GeneralSteps.sequence([
      sCheckElement('br', ElementType.isBr, true),
      sCheckElement('b', ElementType.isBr, false),
      sCheckText(ElementType.isBr)
    ])),
    Logger.t('Check list item elements', GeneralSteps.sequence([
      sCheckElement('br', ElementType.isListItem, false),
      sCheckElement('div', ElementType.isListItem, false),
      sCheckElement('li', ElementType.isListItem, true),
      sCheckElement('dd', ElementType.isListItem, true),
      sCheckElement('dt', ElementType.isListItem, true),
      sCheckText(ElementType.isListItem)
    ])),
    Logger.t('Check list elements', GeneralSteps.sequence([
      sCheckElement('br', ElementType.isList, false),
      sCheckElement('div', ElementType.isList, false),
      sCheckElement('ul', ElementType.isList, true),
      sCheckElement('ol', ElementType.isList, true),
      sCheckElement('dl', ElementType.isList, true),
      sCheckText(ElementType.isList)
    ])),
    Logger.t('Check table section elements', GeneralSteps.sequence([
      sCheckElement('br', ElementType.isTableSection, false),
      sCheckElement('div', ElementType.isTableSection, false),
      sCheckElement('thead', ElementType.isTableSection, true),
      sCheckElement('tbody', ElementType.isTableSection, true),
      sCheckElement('tfoot', ElementType.isTableSection, true),
      sCheckText(ElementType.isTableSection)
    ]))
  ], function () {
    success();
  }, failure);
});
Example #30
0
 const sCheckP1 = function (situation) {
   return GeneralSteps.sequence([
     sSetP1,
     sCheckInNumlist(situation)
   ]);
 };