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);
  }, {
Example #2
0
  TinyLoader.setup(function (editor, onSuccess, onFailure) {
    const tinyApis = TinyApis(editor);
    const inlineFormat = [{ inline: 'b' }];
    const blockFormat = [{ block: 'div' }];
    const selectorFormat = [{ selector: 'div', classes: 'b' }];
    const selectorFormatCollapsed = [{ selector: 'div', classes: 'b', collapsed: true }];

    Pipeline.async({}, [
      tinyApis.sFocus,
      Logger.t('Expand inline format words', GeneralSteps.sequence([
        Logger.t('In middle of single word in paragraph', Chain.asStep(editor, [
          cSetRawContent('<p>ab</p>'),
          cExpandRng([0, 0], 1, [0, 0], 1, inlineFormat, false),
          cAssertRange(editor, [], 0, [], 1)
        ])),
        Logger.t('In middle of single word in paragraph with paragraph siblings', Chain.asStep(editor, [
          cSetRawContent('<p>a</p><p>bc</p><p>de</p>'),
          cExpandRng([1, 0], 1, [1, 0], 1, inlineFormat, false),
          cAssertRange(editor, [], 1, [], 2)
        ])),
        Logger.t('In middle of single word wrapped in b', Chain.asStep(editor, [
          cSetRawContent('<p><b>ab</b></p>'),
          cExpandRng([0, 0, 0], 1, [0, 0, 0], 1, inlineFormat, false),
          cAssertRange(editor, [], 0, [], 1)
        ])),
        Logger.t('In middle of first word', Chain.asStep(editor, [
          cSetRawContent('<p>ab cd</p>'),
          cExpandRng([0, 0], 1, [0, 0], 1, inlineFormat, false),
          cAssertRange(editor, [], 0, [0, 0], 2)
        ])),
        Logger.t('In middle of last word', Chain.asStep(editor, [
          cSetRawContent('<p>ab cd</p>'),
          cExpandRng([0, 0], 4, [0, 0], 4, inlineFormat, false),
          cAssertRange(editor, [0, 0], 3, [], 1)
        ])),
        Logger.t('In middle of middle word', Chain.asStep(editor, [
          cSetRawContent('<p>ab cd ef</p>'),
          cExpandRng([0, 0], 4, [0, 0], 4, inlineFormat, false),
          cAssertRange(editor, [0, 0], 3, [0, 0], 5)
        ])),
        Logger.t('In middle of word with bold siblings expand to sibling spaces', Chain.asStep(editor, [
          cSetRawContent('<p><b>ab </b>cd<b> ef</b></p>'),
          cExpandRng([0, 1], 1, [0, 1], 1, inlineFormat, false),
          cAssertRange(editor, [0, 0, 0], 3, [0, 2, 0], 0)
        ])),
        Logger.t('In middle of word with block sibling and inline sibling expand to sibling space to the right', Chain.asStep(editor, [
          cSetRawContent('<div><p>ab </p>cd<b> ef</b></div>'),
          cExpandRng([0, 1], 1, [0, 1], 1, inlineFormat, false),
          cAssertRange(editor, [0, 1], 0, [0, 2, 0], 0)
        ])),
        Logger.t('In middle of word with block sibling and inline sibling expand to sibling space to the left', Chain.asStep(editor, [
          cSetRawContent('<div><b>ab </b>cd<p> ef</p></div>'),
          cExpandRng([0, 1], 1, [0, 1], 1, inlineFormat, false),
          cAssertRange(editor, [0, 0, 0], 3, [0, 1], 2)
        ])),
        Logger.t('In middle of middle word separated by nbsp characters', Chain.asStep(editor, [
          cSetRawContent('<p>ab\u00a0cd\u00a0ef</p>'),
          cExpandRng([0, 0], 4, [0, 0], 4, inlineFormat, false),
          cAssertRange(editor, [0, 0], 3, [0, 0], 5)
        ])),
        Logger.t('In empty paragraph', Chain.asStep(editor, [
          cSetRawContent('<p><br></p>'),
          cExpandRng([0], 0, [0], 0, inlineFormat, false),
          cAssertRange(editor, [], 0, [], 1)
        ])),
        Logger.t('Fully selected word', Chain.asStep(editor, [
          cSetRawContent('<p>ab</p>'),
          cExpandRng([0, 0], 0, [0, 0], 2, inlineFormat, false),
          cAssertRange(editor, [], 0, [], 1)
        ])),
        Logger.t('Partially selected word', Chain.asStep(editor, [
          cSetRawContent('<p>abc</p>'),
          cExpandRng([0, 0], 1, [0, 0], 2, inlineFormat, false),
          cAssertRange(editor, [0, 0], 1, [0, 0], 2)
        ])),
        Logger.t('Whole word selected wrapped in multiple inlines', Chain.asStep(editor, [
          cSetRawContent('<p><b><i>c</i></b></p>'),
          cExpandRng([0, 0, 0, 0], 0, [0, 0, 0, 0], 1, inlineFormat, false),
          cAssertRange(editor, [], 0, [], 1)
        ])),
        Logger.t('Whole word inside td', Chain.asStep(editor, [
          cSetRawContent('<table><tbody><tr><td>a</td></tr></tbody></table>'),
          cExpandRng([0, 0, 0, 0, 0], 0, [0, 0, 0, 0, 0], 1, inlineFormat, false),
          cAssertRange(editor, [0, 0, 0], 0, [0, 0, 0], 1)
        ])),
        Logger.t('In middle of single word in paragraph (index based)', Chain.asStep(editor, [
          cSetRawContent('<p>ab</p>'),
          cExpandRng([0], 0, [0], 1, inlineFormat, false),
          cAssertRange(editor, [], 0, [], 1)
        ])),
        Logger.t('In middle of single word wrapped in bold in paragraph (index based)', Chain.asStep(editor, [
          cSetRawContent('<p><b>ab</b></p>'),
          cExpandRng([0], 0, [0], 1, inlineFormat, false),
          cAssertRange(editor, [], 0, [], 1)
        ])),
        Logger.t('In middle of word inside bookmark then exclude bookmark', Chain.asStep(editor, [
          cSetRawContent('<p><span data-mce-type="bookmark">ab cd ef</span></p>'),
          cExpandRng([0, 0, 0], 3, [0, 0, 0], 5, inlineFormat, false),
          cAssertRange(editor, [], 0, [], 1)
        ]))
      ])),

      Logger.t('Expand inline format words (remove format)', GeneralSteps.sequence([
        Logger.t('In middle of single word in paragraph', Chain.asStep(editor, [
          cSetRawContent('<p>ab</p>'),
          cExpandRng([0, 0], 1, [0, 0], 1, inlineFormat, true),
          cAssertRange(editor, [], 0, [], 1)
        ]))
      ])),

      Logger.t('Expand block format', GeneralSteps.sequence([
        Logger.t('In middle word', Chain.asStep(editor, [
          cSetRawContent('<p>ab cd ef</p>'),
          cExpandRng([0, 0], 4, [0, 0], 4, blockFormat, false),
          cAssertRange(editor, [], 0, [], 1)
        ])),
        Logger.t('In middle bold word', Chain.asStep(editor, [
          cSetRawContent('<p>ab <b>cd</b> ef</p>'),
          cExpandRng([0, 1, 0], 1, [0, 1, 0], 1, blockFormat, false),
          cAssertRange(editor, [], 0, [], 1)
        ])),
        Logger.t('Whole word inside td', Chain.asStep(editor, [
          cSetRawContent('<table><tbody><tr><td>a</td></tr></tbody></table>'),
          cExpandRng([0, 0, 0, 0, 0], 0, [0, 0, 0, 0, 0], 1, blockFormat, false),
          cAssertRange(editor, [0, 0, 0], 0, [0, 0, 0], 1)
        ]))
      ])),

      Logger.t('Expand selector format', GeneralSteps.sequence([
        Logger.t('Do not expand over element if selector does not match', Chain.asStep(editor, [
          cSetRawContent('<p>ab</p>'),
          cExpandRng([0, 0], 1, [0, 0], 1, selectorFormat, false),
          cAssertRange(editor, [0, 0], 0, [0, 0], 2)
        ])),
        Logger.t('Do not expand outside of element if selector does not match - from bookmark at middle', Chain.asStep(editor, [
          cSetRawContent('<p>a<span data-mce-type="bookmark">&#65279;</span>b</p>'),
          cExpandRng([0, 1, 0], 0, [0, 1, 0], 0, selectorFormat, false),
          cAssertRange(editor, [0, 0], 0, [0, 2], 1)
        ])),
        Logger.t('Do not expand outside of element if selector does not match - from bookmark at start', Chain.asStep(editor, [
          cSetRawContent('<p><span data-mce-type="bookmark">&#65279;</span>ab</p>'),
          cExpandRng([0, 0, 0], 0, [0, 0, 0], 0, selectorFormat, false),
          cAssertRange(editor, [0], 0, [0, 1], 2)
        ])),
        Logger.t('Do not expand outside of element if selector does not match - from bookmark at end', Chain.asStep(editor, [
          cSetRawContent('<p>ab<span data-mce-type="bookmark">&#65279;</span></p>'),
          cExpandRng([0, 1, 0], 0, [0, 1, 0], 0, selectorFormat, false),
          cAssertRange(editor, [0, 0], 0, [0], 2)
        ])),
        Logger.t('Expand since selector matches', Chain.asStep(editor, [
          cSetRawContent('<div>ab</div>'),
          cExpandRng([0, 0], 1, [0, 0], 1, selectorFormat, false),
          cAssertRange(editor, [], 0, [], 1)
        ])),
        Logger.t('Expand since selector matches non collapsed', Chain.asStep(editor, [
          cSetRawContent('<div>ab</div>'),
          cExpandRng([0, 0], 1, [0, 0], 2, selectorFormat, false),
          cAssertRange(editor, [], 0, [], 1)
        ]))
      ])),

      Logger.t('Expand selector format with collapsed property', GeneralSteps.sequence([
        Logger.t('Expand since selector matches collapsed on collapsed format', Chain.asStep(editor, [
          cSetRawContent('<div>ab</div>'),
          cExpandRng([0, 0], 1, [0, 0], 1, selectorFormatCollapsed, false),
          cAssertRange(editor, [], 0, [], 1)
        ])),
        Logger.t('Expand since selector matches non collapsed on collapsed format', Chain.asStep(editor, [
          cSetRawContent('<div>ab</div>'),
          cExpandRng([0, 0], 1, [0, 0], 2, selectorFormatCollapsed, false),
          cAssertRange(editor, [0, 0], 1, [0, 0], 2)
        ]))
      ]))
    ], onSuccess, onFailure);
  }, {
Example #3
0
UnitTest.asynctest('browser.tinymce.core.keyboard.InlineUtilsTest', function () {
  const success = arguments[arguments.length - 2];
  const failure = arguments[arguments.length - 1];
  const ZWSP = Zwsp.ZWSP;

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

  const cNormalizePosition = function (forward, path, offset) {
    return Chain.mapper(function (elm: any) {
      const container = Hierarchy.follow(elm, path).getOrDie();
      const pos = CaretPosition(container.dom(), offset);
      return { pos: InlineUtils.normalizePosition(forward, pos), elm };
    });
  };

  const cAssertPosition = function (path, expectedOffset) {
    return Chain.mapper(function (elmPos: any) {
      const expectedContainer = Hierarchy.follow(elmPos.elm, path).getOrDie();
      Assertions.assertDomEq('Should be expected container', Element.fromDom(elmPos.pos.container()), expectedContainer);
      Assertions.assertEq('Should be expected offset', elmPos.pos.offset(), expectedOffset);
      return {};
    });
  };

  const cSplitAt = function (path, offset) {
    return Chain.mapper(function (elm: any) {
      const textNode = Hierarchy.follow(elm, path).getOrDie();
      textNode.dom().splitText(offset);
      return elm;
    });
  };

  const createFakeEditor = function (settings) {
    return {
      settings
    } as any;
  };

  Pipeline.async({}, [
    Logger.t('isInlineTarget with various editor settings', Step.sync(function () {
      Assertions.assertEq('Links should be inline target', true, InlineUtils.isInlineTarget(createFakeEditor({ }), Element.fromHtml('<a href="a">').dom()));
      Assertions.assertEq('Code should be inline target', true, InlineUtils.isInlineTarget(createFakeEditor({ }), Element.fromHtml('<code>').dom()));
      Assertions.assertEq('None link anchor should not be inline target', false, InlineUtils.isInlineTarget(createFakeEditor({ }), Element.fromHtml('<a>').dom()));
      Assertions.assertEq('Bold should not be inline target', false, InlineUtils.isInlineTarget(createFakeEditor({ }), Element.fromHtml('<b>').dom()));
      Assertions.assertEq('Bold should be inline target if configured', true, InlineUtils.isInlineTarget(createFakeEditor({
        inline_boundaries_selector: 'b'
      }), Element.fromHtml('<b>').dom()));
      Assertions.assertEq('Italic should be inline target if configured', true, InlineUtils.isInlineTarget(createFakeEditor({
        inline_boundaries_selector: 'b,i'
      }), Element.fromHtml('<i>').dom()));
    })),

    Logger.t('normalizePosition on text forwards', GeneralSteps.sequence([
      Logger.t('normalizePosition start of zwsp before text', Chain.asStep({}, [
        cCreateElement('<p>' + ZWSP + 'a</p>'),
        cNormalizePosition(true, [0], 0),
        cAssertPosition([0], 1)
      ])),
      Logger.t('normalizePosition end of zwsp before text', Chain.asStep({}, [
        cCreateElement('<p>' + ZWSP + 'a</p>'),
        cNormalizePosition(true, [0], 1),
        cAssertPosition([0], 1)
      ])),
      Logger.t('normalizePosition start of zwsp after text', Chain.asStep({}, [
        cCreateElement('<p>a' + ZWSP + '</p>'),
        cNormalizePosition(true, [0], 1),
        cAssertPosition([0], 2)
      ])),
      Logger.t('normalizePosition end of zwsp after text', Chain.asStep({}, [
        cCreateElement('<p>a' + ZWSP + '</p>'),
        cNormalizePosition(true, [0], 2),
        cAssertPosition([0], 2)
      ]))
    ])),

    Logger.t('normalizePosition on text backwards', GeneralSteps.sequence([
      Logger.t('normalizePosition end of zwsp after text', Chain.asStep({}, [
        cCreateElement('<p>a' + ZWSP + '</p>'),
        cNormalizePosition(false, [0], 2),
        cAssertPosition([0], 1)
      ])),
      Logger.t('normalizePosition start of zwsp after text', Chain.asStep({}, [
        cCreateElement('<p>a' + ZWSP + '</p>'),
        cNormalizePosition(false, [0], 1),
        cAssertPosition([0], 1)
      ])),
      Logger.t('normalizePosition end of zwsp before text', Chain.asStep({}, [
        cCreateElement('<p>' + ZWSP + 'a</p>'),
        cNormalizePosition(false, [0], 1),
        cAssertPosition([0], 0)
      ])),
      Logger.t('normalizePosition start of zwsp before text', Chain.asStep({}, [
        cCreateElement('<p>' + ZWSP + 'a</p>'),
        cNormalizePosition(false, [0], 0),
        cAssertPosition([0], 0)
      ]))
    ])),

    Logger.t('normalizePosition on element forwards', GeneralSteps.sequence([
      Logger.t('normalizePosition start of zwsp before element', Chain.asStep({}, [
        cCreateElement('<p>' + ZWSP + '<input></p>'),
        cNormalizePosition(true, [0], 0),
        cAssertPosition([], 1)
      ])),
      Logger.t('normalizePosition end of zwsp before element', Chain.asStep({}, [
        cCreateElement('<p>' + ZWSP + '<input></p>'),
        cNormalizePosition(true, [0], 1),
        cAssertPosition([], 1)
      ])),
      Logger.t('normalizePosition start of zwsp after element', Chain.asStep({}, [
        cCreateElement('<p><input>' + ZWSP + '</p>'),
        cNormalizePosition(true, [1], 0),
        cAssertPosition([], 2)
      ])),
      Logger.t('normalizePosition end of zwsp after element', Chain.asStep({}, [
        cCreateElement('<p><input>' + ZWSP + '</p>'),
        cNormalizePosition(true, [1], 1),
        cAssertPosition([], 2)
      ]))
    ])),

    Logger.t('normalizePosition on element backwards', GeneralSteps.sequence([
      Logger.t('normalizePosition end of zwsp after element', Chain.asStep({}, [
        cCreateElement('<p><input>' + ZWSP + '</p>'),
        cNormalizePosition(false, [1], 1),
        cAssertPosition([], 1)
      ])),
      Logger.t('normalizePosition start of zwsp after element', Chain.asStep({}, [
        cCreateElement('<p><input>' + ZWSP + '</p>'),
        cNormalizePosition(false, [1], 0),
        cAssertPosition([], 1)
      ])),
      Logger.t('normalizePosition end of zwsp before element', Chain.asStep({}, [
        cCreateElement('<p>' + ZWSP + '<input></p>'),
        cNormalizePosition(false, [0], 1),
        cAssertPosition([], 0)
      ])),
      Logger.t('normalizePosition start of zwsp before element', Chain.asStep({}, [
        cCreateElement('<p>' + ZWSP + '<input></p>'),
        cNormalizePosition(false, [0], 0),
        cAssertPosition([], 0)
      ]))
    ])),

    Logger.t('normalizePosition on text forwards', GeneralSteps.sequence([
      Logger.t('normalizePosition start of zwsp before text', Chain.asStep({}, [
        cCreateElement('<p>' + ZWSP + 'a</p>'),
        cSplitAt([0], 1),
        cNormalizePosition(true, [0], 0),
        cAssertPosition([1], 0)
      ])),
      Logger.t('normalizePosition end of zwsp before text', Chain.asStep({}, [
        cCreateElement('<p>' + ZWSP + 'a</p>'),
        cSplitAt([0], 1),
        cNormalizePosition(true, [0], 1),
        cAssertPosition([1], 0)
      ])),
      Logger.t('normalizePosition start of zwsp after text', Chain.asStep({}, [
        cCreateElement('<p>a' + ZWSP + '</p>'),
        cSplitAt([0], 1),
        cNormalizePosition(true, [1], 0),
        cAssertPosition([], 2)
      ])),
      Logger.t('normalizePosition end of zwsp after text', Chain.asStep({}, [
        cCreateElement('<p>a' + ZWSP + '</p>'),
        cSplitAt([0], 1),
        cNormalizePosition(true, [1], 1),
        cAssertPosition([], 2)
      ]))
    ]))
  ], function () {
    success();
  }, failure);
});
Example #4
0
 const sRedo = function (editor) {
   return Logger.t('Redo', Step.sync(function () {
     editor.undoManager.redo();
   }));
 };
  TinyLoader.setup(function (editor, onSuccess, onFailure) {
    const tinyApis = TinyApis(editor);

    Pipeline.async({}, [
      Logger.t('Insert key in text with in nbsp text node', GeneralSteps.sequence([
        Logger.t('Nbsp at first character position', GeneralSteps.sequence([
          Logger.t('Insert in text node with nbsp at start of body', GeneralSteps.sequence([
            tinyApis.sFocus,
            tinyApis.sSetRawContent('&nbsp;a'),
            tinyApis.sSetCursor([0], 2),
            sFireInsert(editor),
            tinyApis.sAssertSelection([0], 2, [0], 2),
            tinyApis.sAssertContent('&nbsp;a')
          ])),
          Logger.t('Insert in text in node with leading nbsp after inline with trailing space', GeneralSteps.sequence([
            tinyApis.sFocus,
            tinyApis.sSetRawContent('a<em>b </em>&nbsp;c'),
            tinyApis.sSetCursor([2], 2),
            sFireInsert(editor),
            tinyApis.sAssertSelection([2], 2, [2], 2),
            tinyApis.sAssertContent('a<em>b </em>&nbsp;c')
          ])),
          Logger.t('Insert in text in node with leading nbsp after inline', GeneralSteps.sequence([
            tinyApis.sFocus,
            tinyApis.sSetRawContent('a<em>b</em>&nbsp;c'),
            tinyApis.sSetCursor([2], 2),
            sFireInsert(editor),
            tinyApis.sAssertSelection([2], 2, [2], 2),
            tinyApis.sAssertContent('a<em>b</em> c')
          ])),
          Logger.t('Insert in text in node with leading nbsp after inline with trailing nbsp', GeneralSteps.sequence([
            tinyApis.sFocus,
            tinyApis.sSetRawContent('a<em>b&nbsp;</em>&nbsp;c'),
            tinyApis.sSetCursor([2], 2),
            sFireInsert(editor),
            tinyApis.sAssertSelection([2], 2, [2], 2),
            tinyApis.sAssertContent('a<em>b&nbsp;</em> c')
          ])),
          Logger.t('Insert at beginning of text node with leading nbsp after a br', GeneralSteps.sequence([
            tinyApis.sFocus,
            tinyApis.sSetRawContent('a<br />&nbsp;b'),
            tinyApis.sSetCursor([2], 0),
            sFireInsert(editor),
            tinyApis.sAssertSelection([2], 0, [2], 0),
            tinyApis.sAssertContent('a<br />&nbsp;b')
          ])),
          Logger.t('Insert at beginning of text node with leading nbsp within inline element followed by br', GeneralSteps.sequence([
            tinyApis.sFocus,
            tinyApis.sSetRawContent('a<br /><em>&nbsp;b</em>'),
            tinyApis.sSetCursor([2, 0], 0),
            sFireInsert(editor),
            tinyApis.sAssertSelection([2, 0], 0, [2, 0], 0),
            tinyApis.sAssertContent('a<br /><em>&nbsp;b</em>')
          ]))
        ])),

        Logger.t('Nbsp at last character position', GeneralSteps.sequence([
          Logger.t('Insert in text node with nbsp at end of body', GeneralSteps.sequence([
            tinyApis.sFocus,
            tinyApis.sSetRawContent('a&nbsp;'),
            tinyApis.sSetCursor([0], 0),
            sFireInsert(editor),
            tinyApis.sAssertSelection([0], 0, [0], 0),
            tinyApis.sAssertContent('a&nbsp;')
          ])),
          Logger.t('Insert in text in node with leading nbsp after inline with trailing space', GeneralSteps.sequence([
            tinyApis.sFocus,
            tinyApis.sSetRawContent('a&nbsp;<em> b</em>c'),
            tinyApis.sSetCursor([0], 0),
            sFireInsert(editor),
            tinyApis.sAssertSelection([0], 0, [0], 0),
            tinyApis.sAssertContent('a&nbsp;<em> b</em>c')
          ])),
          Logger.t('Insert in text in node with trailing nbsp before inline', GeneralSteps.sequence([
            tinyApis.sFocus,
            tinyApis.sSetRawContent('a&nbsp;<em>b</em>c'),
            tinyApis.sSetCursor([0], 0),
            sFireInsert(editor),
            tinyApis.sAssertSelection([0], 0, [0], 0),
            tinyApis.sAssertContent('a <em>b</em>c')
          ])),
          Logger.t('Insert in text in node with trailing nbsp before inline with leading nbsp', GeneralSteps.sequence([
            tinyApis.sFocus,
            tinyApis.sSetRawContent('a&nbsp;<em>&nbsp;b</em>c'),
            tinyApis.sSetCursor([0], 0),
            sFireInsert(editor),
            tinyApis.sAssertSelection([0], 0, [0], 0),
            tinyApis.sAssertContent('a <em>&nbsp;b</em>c')
          ])),
          Logger.t('Insert in text in node with single middle nbsp', GeneralSteps.sequence([
            tinyApis.sFocus,
            tinyApis.sSetRawContent('a&nbsp;b'),
            tinyApis.sSetCursor([0], 3),
            sFireInsert(editor),
            tinyApis.sAssertSelection([0], 3, [0], 3),
            tinyApis.sAssertContent('a b')
          ])),
          Logger.t('Insert in text in node with multiple middle nbsp', GeneralSteps.sequence([
            tinyApis.sFocus,
            tinyApis.sSetRawContent('a&nbsp;b&nbsp;c&nbsp;d'),
            tinyApis.sSetCursor([0], 7),
            sFireInsert(editor),
            tinyApis.sAssertSelection([0], 7, [0], 7),
            tinyApis.sAssertContent('a b c d')
          ]))
        ])),
      ]))
    ], onSuccess, onFailure);
  }, {
Example #6
0
 const steps = function (editor, tinyApis) {
   return [
     tinyApis.sFocus,
     Logger.t('Public Selection API', GeneralSteps.sequence([
       Logger.t('Scroll to element align to bottom', GeneralSteps.sequence([
         sScrollReset(editor),
         sSetContent(editor, tinyApis, '<div style="height: 1000px">a</div><div style="height: 50px">b</div><div style="height: 1000px">a</div>'),
         sScrollIntoView(editor, 'div:nth-child(2)', false),
         sAssertScrollPosition(editor, 0, 975)
       ])),
       Logger.t('Scroll to element align to top', GeneralSteps.sequence([
         sScrollReset(editor),
         sSetContent(editor, tinyApis, '<div style="height: 1000px">a</div><div style="height: 50px">b</div><div style="height: 1000px">a</div>'),
         sScrollIntoView(editor, 'div:nth-child(2)', true),
         sAssertScrollPosition(editor, 0, 925)
       ]))
     ])),
     Logger.t('Private ScrollIntoView API', GeneralSteps.sequence([
       Logger.t('Scroll to element align to bottom', GeneralSteps.sequence([
         sScrollReset(editor),
         sSetContent(editor, tinyApis, '<div style="height: 1000px">a</div><div style="height: 50px">b</div><div style="height: 1000px">a</div>'),
         sScrollIntoViewPrivateApi(editor, 'div:nth-child(2)', false),
         sAssertScrollPosition(editor, 0, 975)
       ])),
       Logger.t('Scroll to element align to top', GeneralSteps.sequence([
         sScrollReset(editor),
         sSetContent(editor, tinyApis, '<div style="height: 1000px">a</div><div style="height: 50px">b</div><div style="height: 1000px">a</div>'),
         sScrollIntoViewPrivateApi(editor, 'div:nth-child(2)', true),
         sAssertScrollPosition(editor, 0, 925)
       ]))
     ])),
     Logger.t('Override scrollIntoView event', GeneralSteps.sequence([
       Logger.t('Scroll to element align to bottom', GeneralSteps.sequence([
         sScrollReset(editor),
         sSetContent(editor, tinyApis, '<div style="height: 1000px">a</div><div style="height: 50px">b</div><div style="height: 1000px">a</div>'),
         mBindScrollIntoViewEvent(editor),
         sScrollIntoView(editor, 'div:nth-child(2)', false),
         mAssertScrollIntoViewEventInfo(editor, 'div:nth-child(2)', false),
         sAssertScrollPosition(editor, 0, 0)
       ])),
       Logger.t('Scroll to element align to top', GeneralSteps.sequence([
         sScrollReset(editor),
         sSetContent(editor, tinyApis, '<div style="height: 1000px">a</div><div style="height: 50px">b</div><div style="height: 1000px">a</div>'),
         mBindScrollIntoViewEvent(editor),
         sScrollIntoView(editor, 'div:nth-child(2)', true),
         mAssertScrollIntoViewEventInfo(editor, 'div:nth-child(2)', true),
         sAssertScrollPosition(editor, 0, 0)
       ])),
       Logger.t('Scroll to element align to bottom (private api)', GeneralSteps.sequence([
         sScrollReset(editor),
         sSetContent(editor, tinyApis, '<div style="height: 1000px">a</div><div style="height: 50px">b</div><div style="height: 1000px">a</div>'),
         mBindScrollIntoViewEvent(editor),
         sScrollIntoViewPrivateApi(editor, 'div:nth-child(2)', false),
         mAssertScrollIntoViewEventInfo(editor, 'div:nth-child(2)', false),
         sAssertScrollPosition(editor, 0, 0)
       ])),
       Logger.t('Scroll to element align to top (private api)', GeneralSteps.sequence([
         sScrollReset(editor),
         sSetContent(editor, tinyApis, '<div style="height: 1000px">a</div><div style="height: 50px">b</div><div style="height: 1000px">a</div>'),
         mBindScrollIntoViewEvent(editor),
         sScrollIntoViewPrivateApi(editor, 'div:nth-child(2)', true),
         mAssertScrollIntoViewEventInfo(editor, 'div:nth-child(2)', true),
         sAssertScrollPosition(editor, 0, 0)
       ]))
     ]))
   ];
 };
 (editor, onSuccess, onFailure) => {
   Pipeline.async({ }, Logger.ts(
     'Check structure of font format',
     [
       Mouse.sClickOn(Body.body(), '.tox-toolbar button'),
       UiFinder.sWaitForVisible('Waiting for dialog', Body.body(), '[role="dialog"]'),
       Mouse.sClickOn(Body.body(), 'button:contains("Make Busy")'),
       Waiter.sTryUntil(
         'Waiting for busy structure to match expected',
         Chain.asStep(Body.body(), [
           UiFinder.cFindIn('[role="dialog"]'),
           Assertions.cAssertStructure(
             'Checking dialog structure to see where "busy" is',
             ApproxStructure.build((s, str, arr) => {
               return s.element('div', {
                 classes: [ arr.has('tox-dialog') ],
                 children: [
                   s.element('div', {
                     classes: [ arr.has('tox-dialog__header') ]
                   }),
                   s.element('div', {
                     classes: [ arr.has('tox-dialog__content-js') ]
                   }),
                   s.element('div', {
                     classes: [ arr.has('tox-dialog__footer') ]
                   }),
                   s.element('div', {
                     classes: [ arr.has('tox-dialog__busy-spinner') ],
                     children: [
                       s.element('div', {
                         classes: [ arr.has('tox-spinner') ],
                         children: [
                           // The three loading dots
                           s.element('div', { }),
                           s.element('div', { }),
                           s.element('div', { })
                         ]
                       })
                     ]
                   })
                 ]
               });
             })
           )
         ]),
         100,
         3000
       ),
       Step.sync(() => {
         testDialogApi.get().unblock();
       }),
       Waiter.sTryUntil(
         'Waiting for busy structure to go away',
         Chain.asStep(Body.body(), [
           UiFinder.cFindIn('[role="dialog"]'),
           Assertions.cAssertStructure(
             'Checking dialog structure to see where "busy" is',
             ApproxStructure.build((s, str, arr) => {
               return s.element('div', {
                 classes: [ arr.has('tox-dialog') ],
                 children: [
                   s.element('div', {
                     classes: [ arr.has('tox-dialog__header') ]
                   }),
                   s.element('div', {
                     classes: [ arr.has('tox-dialog__content-js') ]
                   }),
                   s.element('div', {
                     classes: [ arr.has('tox-dialog__footer') ]
                   })
                 ]
               });
             })
           )
         ]),
         100,
         3000
       )
     ]
   ), onSuccess, onFailure);
 },
Example #8
0
 const sAssertCalcCols = function (editor, colors, expected) {
   return Logger.t(`Assert calced cols: ${expected}`, Step.sync(function () {
     const sqrt = ColorSwatch.calcCols(colors);
     RawAssertions.assertEq('should be same', expected, sqrt);
   }));
 };
  TinyLoader.setup(function (editor, onSuccess, onFailure) {
    const tinyApis = TinyApis(editor);
    const tinyActions = TinyActions(editor);

    Pipeline.async({}, [
      Logger.t('Up navigation', GeneralSteps.sequence([
        Logger.t('Arrow up on first position in table cell', GeneralSteps.sequence([
          tinyApis.sFocus,
          tinyApis.sSetContent('<table><tbody><tr><td>a</td><td>b</td></tr></tbody></table>'),
          tinyApis.sSetCursor([0, 0, 0, 0, 0], 0),
          tinyActions.sContentKeystroke(Keys.up(), {}),
          tinyApis.sAssertSelection([0], 0, [0], 0),
          tinyApis.sAssertContent('<p>&nbsp;</p><table><tbody><tr><td>a</td><td>b</td></tr></tbody></table>')
        ])),
        Logger.t('Arrow up on first position in table cell to caption', GeneralSteps.sequence([
          tinyApis.sFocus,
          tinyApis.sSetContent('<table><caption>a</caption><tbody><tr><td>b</td></tr></tbody></table>'),
          tinyApis.sSetCursor([0, 1, 0, 0, 0], 0),
          tinyActions.sContentKeystroke(Keys.up(), {}),
          tinyApis.sAssertSelection([0, 0, 0], 0, [0, 0, 0], 0),
          tinyApis.sAssertContent('<table><caption>a</caption><tbody><tr><td>b</td></tr></tbody></table>')
        ])),
        Logger.t('Arrow up on second position in first table cell', GeneralSteps.sequence([
          tinyApis.sFocus,
          tinyApis.sSetContent('<table><tbody><tr><td>a</td><td>b</td></tr></tbody></table>'),
          tinyApis.sSetCursor([0, 0, 0, 0, 0], 1),
          tinyActions.sContentKeystroke(Keys.up(), {}),
          tinyApis.sAssertSelection([0], 0, [0], 0),
          tinyApis.sAssertContent('<p>&nbsp;</p><table><tbody><tr><td>a</td><td>b</td></tr></tbody></table>')
        ])),
        Logger.t('Arrow up on first position in first table cell on the second row', GeneralSteps.sequence([
          tinyApis.sFocus,
          tinyApis.sSetContent('<table><tbody><tr><td>a</td><td>b</td></tr><tr><td>c</td><td>d</td></tr></tbody></table>'),
          tinyApis.sSetCursor([0, 0, 1, 0, 0], 0),
          tinyActions.sContentKeystroke(Keys.up(), {}),
          tinyApis.sAssertSelection([0, 0, 0, 0, 0], 0, [0, 0, 0, 0, 0], 0),
          tinyApis.sAssertContent('<table><tbody><tr><td>a</td><td>b</td></tr><tr><td>c</td><td>d</td></tr></tbody></table>')
        ])),
      ])),
      Logger.t('Down navigation', GeneralSteps.sequence([
        Logger.t('Arrow down on last position in last table cell', GeneralSteps.sequence([
          tinyApis.sFocus,
          tinyApis.sSetContent('<table><tbody><tr><td>a</td><td>b</td></tr></tbody></table>'),
          tinyApis.sSetCursor([0, 0, 0, 1, 0], 1),
          tinyActions.sContentKeystroke(Keys.down(), {}),
          tinyApis.sAssertSelection([1], 0, [1], 0),
          tinyApis.sAssertContent('<table><tbody><tr><td>a</td><td>b</td></tr></tbody></table><p>&nbsp;</p>')
        ])),
        Logger.t('Arrow down on last position in last table cell with br', GeneralSteps.sequence([
          tinyApis.sFocus,
          tinyApis.sSetContent('<table><tbody><tr><td>a</td><td>b<br></td></tr></tbody></table>'),
          tinyApis.sSetCursor([0, 0, 0, 1, 0], 1),
          tinyActions.sContentKeystroke(Keys.down(), {}),
          tinyApis.sAssertSelection([1], 0, [1], 0),
          tinyApis.sAssertContent('<table><tbody><tr><td>a</td><td>b</td></tr></tbody></table><p>&nbsp;</p>')
        ])),
        Logger.t('Arrow down on second last position in last table cell', GeneralSteps.sequence([
          tinyApis.sFocus,
          tinyApis.sSetContent('<table><tbody><tr><td>a</td><td>b</td></tr></tbody></table>'),
          tinyApis.sSetCursor([0, 0, 0, 1, 0], 0),
          tinyActions.sContentKeystroke(Keys.down(), {}),
          tinyApis.sAssertSelection([1], 0, [1], 0),
          tinyApis.sAssertContent('<table><tbody><tr><td>a</td><td>b</td></tr></tbody></table><p>&nbsp;</p>')
        ])),
        Logger.t('Arrow down on last position in last table cell on the first row', GeneralSteps.sequence([
          tinyApis.sFocus,
          tinyApis.sSetContent('<table><tbody><tr><td>a</td><td>b</td></tr><tr><td>c</td><td>d</td></tr></tbody></table>'),
          tinyApis.sSetCursor([0, 0, 0, 1, 0], 1),
          tinyActions.sContentKeystroke(Keys.down(), {}),
          tinyApis.sAssertSelection([0, 0, 1, 1, 0], 1, [0, 0, 1, 1, 0], 1),
          tinyApis.sAssertContent('<table><tbody><tr><td>a</td><td>b</td></tr><tr><td>c</td><td>d</td></tr></tbody></table>')
        ])),
      ]))
    ], onSuccess, onFailure);
  }, {
Example #10
0
 const sAssertColors = function (input, expected) {
   return Logger.t(`Assert colors: ${expected}`, Step.sync(function () {
     const colors = Settings.mapColors(input);
     RawAssertions.assertEq('should be same', expected, colors);
   }));
 };
Example #11
0
 const sAssertCols = function (editor, expected) {
   return Logger.t(`Assert color cols: ${expected}`, Step.sync(function () {
     const colors = ColorSwatch.getColorCols(editor);
     RawAssertions.assertEq('should be same', expected, colors);
   }));
 };
Example #12
0
 const sResetLocalStorage = function () {
   return Logger.t(`Reset local storage`, Step.sync(function () {
     LocalStorage.removeItem('tinymce-custom-colors');
   }));
 };
Example #13
0
  TinyLoader.setup(function (editor, onSuccess, onFailure) {
    const tinyApis = TinyApis(editor);
    const tinyActions = TinyActions(editor);

    Pipeline.async({}, [
      Logger.t('Space key around inline boundary elements', GeneralSteps.sequence([
        Logger.t('Press space at beginning of inline boundary inserting nbsp', GeneralSteps.sequence([
          tinyApis.sFocus,
          tinyApis.sSetContent('<p>a <a href="#">b</a> c</p>'),
          tinyApis.sSetCursor([0, 1, 0], 0),
          tinyApis.sNodeChanged,
          tinyActions.sContentKeystroke(Keys.space(), {}),
          tinyApis.sAssertSelection([0, 1, 0], 1, [0, 1, 0], 1),
          tinyApis.sAssertContent('<p>a <a href="#">&nbsp;b</a> c</p>')
        ])),
        Logger.t('Press space at end of inline boundary inserting nbsp', GeneralSteps.sequence([
          tinyApis.sFocus,
          tinyApis.sSetContent('<p>a <a href="#">b</a> c</p>'),
          tinyApis.sSetCursor([0, 1, 0], 1),
          tinyApis.sNodeChanged,
          tinyActions.sContentKeystroke(Keys.space(), {}),
          tinyApis.sAssertSelection([0, 1, 0], 2, [0, 1, 0], 2),
          tinyApis.sAssertContent('<p>a <a href="#">b&nbsp;</a> c</p>')
        ])),
        Logger.t('Press space at beginning of inline boundary inserting space', GeneralSteps.sequence([
          tinyApis.sFocus,
          tinyApis.sSetContent('<p>a<a href="#">b</a>c</p>'),
          tinyApis.sSetCursor([0, 1, 0], 0),
          tinyApis.sNodeChanged,
          tinyActions.sContentKeystroke(Keys.space(), {}),
          tinyApis.sAssertSelection([0, 1, 0], 1, [0, 1, 0], 1),
          tinyApis.sAssertContent('<p>a<a href="#"> b</a>c</p>')
        ])),
        Logger.t('Press space at end of inline boundary inserting space', GeneralSteps.sequence([
          tinyApis.sFocus,
          tinyApis.sSetContent('<p>a<a href="#">b</a>c</p>'),
          tinyApis.sSetCursor([0, 1, 0], 1),
          tinyApis.sNodeChanged,
          tinyActions.sContentKeystroke(Keys.space(), {}),
          tinyApis.sAssertSelection([0, 1, 0], 2, [0, 1, 0], 2),
          tinyApis.sAssertContent('<p>a<a href="#">b </a>c</p>')
        ])),
        Logger.t('Press space at start of inline boundary with leading space inserting nbsp', GeneralSteps.sequence([
          tinyApis.sFocus,
          tinyApis.sSetContent('<p>a<a href="#"> b</a>c</p>'),
          tinyApis.sSetCursor([0, 1, 0], 0),
          tinyApis.sNodeChanged,
          tinyActions.sContentKeystroke(Keys.space(), {}),
          tinyApis.sAssertSelection([0, 1, 0], 1, [0, 1, 0], 1),
          tinyApis.sAssertContent('<p>a<a href="#">&nbsp; b</a>c</p>')
        ])),
        Logger.t('Press space at end of inline boundary with trailing space inserting nbsp', GeneralSteps.sequence([
          tinyApis.sFocus,
          tinyApis.sSetContent('<p>a<a href="#">b </a>c</p>'),
          tinyApis.sSetCursor([0, 1, 0], 2),
          tinyApis.sNodeChanged,
          tinyActions.sContentKeystroke(Keys.space(), {}),
          tinyApis.sAssertSelection([0, 1, 0], 3, [0, 1, 0], 3),
          tinyApis.sAssertContent('<p>a<a href="#">b &nbsp;</a>c</p>')
        ]))
      ]))
    ], onSuccess, onFailure);
  }, {
Example #14
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: any) {
      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: any) {
      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: any) {
      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: any) {
      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: CaretPosition[]) {
      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: Option<any>) {
    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: Option<any>) {
      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: any) {
      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);
});
Example #15
0
UnitTest.asynctest('atomic.tinymce.core.keyboard.MatchKeysTest', function () {
  const success = arguments[arguments.length - 2];
  const failure = arguments[arguments.length - 1];
  const state = Cell([]);

  const event = function (evt) {
    return Merger.merge({
      shiftKey: false,
      altKey: false,
      ctrlKey: false,
      metaKey: false,
      keyCode: 0
    }, evt);
  };

  const handleAction = function (value) {
    return function () {
      state.set(state.get().concat([value]));
      return true;
    };
  };

  const sTestMatch = function (patterns, event, expectedData) {
    return Step.sync(function () {
      state.set([]);

      const matches = MatchKeys.match(patterns, event);
      Assertions.assertEq('Should have some matches', true, matches.length > 0);

      Arr.find(matches, function (pattern) {
        return pattern.action();
      });

      Assertions.assertEq('Should have the expected state', expectedData, state.get());
    });
  };

  const sTestMatchNone = function (patterns, event) {
    return Step.sync(function () {
      Assertions.assertEq(
        'Should not produce any matches',
        0,
        MatchKeys.match(patterns, event).length
      );
    });
  };

  const sTestExecute = function (patterns, event, expectedData, expectedMatch) {
    return Step.sync(function () {
      state.set([]);

      const result = MatchKeys.execute(patterns, event);
      Assertions.assertEq('Should be expected match', expectedMatch, result.getOrDie());
      Assertions.assertEq('Should have the expected state', expectedData, state.get());
    });
  };

  const actionA = handleAction('a');
  const actionB = handleAction('b');

  Pipeline.async({}, [
    sTestMatchNone([], {}),
    sTestMatchNone([], event({ keyCode: 65 })),
    sTestMatchNone([{ keyCode: 65, action: actionA }], event({ keyCode: 13 })),
    sTestMatch([{ keyCode: 65, action: actionA }], event({ keyCode: 65 }), ['a']),
    sTestMatch([{ keyCode: 65, shiftKey: true, action: actionA }], event({ keyCode: 65, shiftKey: true }), ['a']),
    sTestMatch([{ keyCode: 65, altKey: true, action: actionA }], event({ keyCode: 65, altKey: true }), ['a']),
    sTestMatch([{ keyCode: 65, ctrlKey: true, action: actionA }], event({ keyCode: 65, ctrlKey: true }), ['a']),
    sTestMatch([{ keyCode: 65, metaKey: true, action: actionA }], event({ keyCode: 65, metaKey: true }), ['a']),
    sTestMatch(
      [
        { keyCode: 65, ctrlKey: true, metaKey: true, altKey: true, action: actionA },
        { keyCode: 65, ctrlKey: true, metaKey: true, action: actionB }
      ],
      event({ keyCode: 65, metaKey: true, ctrlKey: true }),
      ['b']
    ),
    sTestExecute(
      [
        { keyCode: 65, ctrlKey: true, metaKey: true, altKey: true, action: actionA },
        { keyCode: 65, ctrlKey: true, metaKey: true, action: actionB }
      ],
      event({ keyCode: 65, metaKey: true, ctrlKey: true }),
      ['b'],
      { shiftKey: false, altKey: false, ctrlKey: true, metaKey: true, keyCode: 65, action: actionB }
    ),
    Logger.t('Action wrapper helper', Step.sync(function () {
      const action = MatchKeys.action(function () {
        return Array.prototype.slice.call(arguments, 0);
      }, 1, 2, 3);

      Assertions.assertEq('Should return the parameters passed in', [1, 2, 3], action());
    }))
  ], function () {
    success();
  }, failure);
});
  TinyLoader.setup(function (editor, onSuccess, onFailure) {
    const tinyApis = TinyApis(editor);
    const tinyActions = TinyActions(editor);

    const steps = Utils.withTeardown([
      Logger.t('space on ** without content does nothing', GeneralSteps.sequence([
        Utils.sSetContentAndPressSpace(tinyApis, tinyActions, '**'),
        tinyApis.sAssertContent('<p>**</p>')
      ])),
      Logger.t('Italic format on single word using space 1', GeneralSteps.sequence([
        tinyApis.sSetContent('<p>*a&nbsp; *&nbsp;</p>'),
        tinyApis.sFocus,
        tinyApis.sSetCursor([0, 0], 6),
        tinyActions.sContentKeystroke(Keys.space(), {}),
        tinyApis.sAssertContentStructure(ApproxStructure.build(function (s, str) {
          return Utils.bodyStruct([
            s.element('p', {
              children: [
                s.element('em', {
                  children: [
                    s.text(str.is('a'))
                  ]
                }),
                s.text(str.is('\u00a0')),
                s.text(str.is(' ')),
                s.text(str.is('\u00a0'))
              ]
            })
          ]);
        }))
      ])),
      Logger.t('Italic format on single word using space 2', GeneralSteps.sequence([
        Utils.sSetContentAndPressSpace(tinyApis, tinyActions, '*a*\u00a0'),
        tinyApis.sAssertContentStructure(Utils.inlineStructHelper('em', 'a')),
        tinyApis.sAssertSelection([0, 1], 1, [0, 1], 1)
      ])),
      Logger.t('Bold format on single word using space', GeneralSteps.sequence([
        Utils.sSetContentAndPressSpace(tinyApis, tinyActions, '**a**\u00a0'),
        tinyApis.sAssertContentStructure(Utils.inlineStructHelper('strong', 'a'))
      ])),
      Logger.t('Bold/italic format on single word using space', GeneralSteps.sequence([
        Utils.sSetContentAndPressSpace(tinyApis, tinyActions, '***a***\u00a0'),
        tinyApis.sAssertContentStructure(ApproxStructure.build(function (s, str) {
          return Utils.bodyStruct([
            s.element('p', {
              children: [
                s.element('em', {
                  children: [
                    s.element('strong', {
                      children: [
                        s.text(str.is('a'))
                      ]
                    })
                  ]
                }),
                s.text(str.is('\u00a0'))
              ]
            })
          ]);
        }))
      ])),
      Logger.t('Bold format on multiple words using space', GeneralSteps.sequence([
        Utils.sSetContentAndPressSpace(tinyApis, tinyActions, '**a b**\u00a0'),
        tinyApis.sAssertContentStructure(Utils.inlineStructHelper('strong', 'a b'))
      ])),
      Logger.t('Bold format on single word using enter', GeneralSteps.sequence([
        Utils.sSetContentAndPressEnter(tinyApis, tinyActions, '**a**'),
        tinyApis.sAssertContentStructure(Utils.inlineBlockStructHelper('strong', 'a'))
      ])),
      Logger.t('Bold/italic format on single word using enter', GeneralSteps.sequence([
        Utils.sSetContentAndPressEnter(tinyApis, tinyActions, '***a***'),
        tinyApis.sAssertContentStructure(ApproxStructure.build(function (s, str) {
          return Utils.bodyStruct([
            s.element('p', {
              children: [
                s.element('em', {
                  children: [
                    s.element('strong', {
                      children: [
                        s.text(str.is('a')),
                        s.anything()
                      ]
                    })
                  ]
                })
              ]
            }),
            s.anything()
          ]);
        }))
      ])),
      Logger.t('H1 format on single word node using enter', GeneralSteps.sequence([
        Utils.sSetContentAndPressEnter(tinyApis, tinyActions, '# a'),
        tinyApis.sAssertContentStructure(Utils.blockStructHelper('h1', ' a'))
      ])),
      Logger.t('H2 format on single word node using enter', GeneralSteps.sequence([
        Utils.sSetContentAndPressEnter(tinyApis, tinyActions, '## a'),
        tinyApis.sAssertContentStructure(Utils.blockStructHelper('h2', ' a'))
      ])),
      Logger.t('H3 format on single word node using enter', GeneralSteps.sequence([
        Utils.sSetContentAndPressEnter(tinyApis, tinyActions, '### a'),
        tinyApis.sAssertContentStructure(Utils.blockStructHelper('h3', ' a'))
      ])),
      Logger.t('H4 format on single word node using enter', GeneralSteps.sequence([
        Utils.sSetContentAndPressEnter(tinyApis, tinyActions, '#### a'),
        tinyApis.sAssertContentStructure(Utils.blockStructHelper('h4', ' a'))
      ])),
      Logger.t('H5 format on single word node using enter', GeneralSteps.sequence([
        Utils.sSetContentAndPressEnter(tinyApis, tinyActions, '##### a'),
        tinyApis.sAssertContentStructure(Utils.blockStructHelper('h5', ' a'))
      ])),
      Logger.t('H6 format on single word node using enter', GeneralSteps.sequence([
        Utils.sSetContentAndPressEnter(tinyApis, tinyActions, '###### a'),
        tinyApis.sAssertContentStructure(Utils.blockStructHelper('h6', ' a'))
      ])),
      Logger.t('OL format on single word node using enter', GeneralSteps.sequence([
        Utils.sSetContentAndPressEnter(tinyApis, tinyActions, '1. a'),
        tinyApis.sAssertContentPresence({ ol: 1, li: 2 })
      ])),
      Logger.t('UL format on single word node using enter', GeneralSteps.sequence([
        Utils.sSetContentAndPressEnter(tinyApis, tinyActions, '* a'),
        tinyApis.sAssertContentPresence({ ul: 1, li: 2 })
      ])),
      Logger.t('enter with uncollapsed range does not insert list', GeneralSteps.sequence([
        tinyApis.sSetContent('<p>* ab</p>'),
        tinyApis.sFocus,
        tinyApis.sSetSelection([0, 0], 3, [0, 0], 4),
        tinyActions.sContentKeystroke(Keys.enter(), {}),
        tinyApis.sAssertContentPresence({ ul: 0 })
      ])),
      Logger.t('enter with only pattern does not insert list', GeneralSteps.sequence([
        tinyApis.sSetContent('<p>*</p>'),
        tinyApis.sFocus,
        tinyApis.sSetCursor([0, 0], 1),
        tinyActions.sContentKeystroke(Keys.enter(), {}),
        tinyApis.sAssertContentPresence({ ul: 0 })
      ])),
      Logger.t('test inline and block at the same time', GeneralSteps.sequence([
        Utils.sSetContentAndPressEnter(tinyApis, tinyActions, '* **important list**'),
        tinyApis.sAssertContentPresence({ ul: 1, strong: 2 })
      ])),
      Logger.t('getPatterns/setPatterns', Step.sync(function () {
        editor.plugins.textpattern.setPatterns([
            { start: '#', format: 'h1' },
            { start: '##', format: 'h2' },
            { start: '###', format: 'h3' }
        ]);

        Assertions.assertEq(
            'should be the same',
            editor.plugins.textpattern.getPatterns(),
          [
            {
              format: 'h3',
              start: '###'
            },
            {
              format: 'h2',
              start: '##'
            },
            {
              format: 'h1',
              start: '#'
            }
          ]
          );
      }))
    ], tinyApis.sSetContent(''));

    Pipeline.async({}, steps, onSuccess, onFailure);
  }, {
  TinyLoader.setup(function (editor, onSuccess, onFailure) {
    const tinyApis = TinyApis(editor);

    Pipeline.async({}, [
      tinyApis.sFocus,
      Logger.t('Backspace on collapsed range should be a noop', GeneralSteps.sequence([
        tinyApis.sSetContent('<p>a</p>'),
        tinyApis.sSetCursor([0, 0], 1),
        sBackspaceNoop(editor),
        tinyApis.sAssertContent('<p>a</p>'),
        tinyApis.sAssertSelection([0, 0], 1, [0, 0], 1)
      ])),
      Logger.t('Delete on collapsed range should be a noop', GeneralSteps.sequence([
        tinyApis.sSetContent('<p>a</p>'),
        tinyApis.sSetCursor([0, 0], 1),
        sDeleteNoop(editor),
        tinyApis.sAssertContent('<p>a</p>'),
        tinyApis.sAssertSelection([0, 0], 1, [0, 0], 1)
      ])),
      Logger.t('Backspace on range between simple blocks should merge', GeneralSteps.sequence([
        tinyApis.sSetContent('<p>a</p><p>b</p>'),
        tinyApis.sSetSelection([0, 0], 1, [1, 0], 0),
        sBackspace(editor),
        tinyApis.sAssertContent('<p>ab</p>'),
        tinyApis.sAssertSelection([0, 0], 1, [0, 0], 1)
      ])),
      Logger.t('Delete on range between simple blocks should merge', GeneralSteps.sequence([
        tinyApis.sSetContent('<p>a</p><p>b</p>'),
        tinyApis.sSetSelection([0, 0], 1, [1, 0], 0),
        sDelete(editor),
        tinyApis.sAssertContent('<p>ab</p>'),
        tinyApis.sAssertSelection([0, 0], 1, [0, 0], 1)
      ])),
      Logger.t('Backspace from red span to h1 should merge', GeneralSteps.sequence([
        tinyApis.sSetContent('<h1>ab</h1><p><span style="color: red;">cd</span></p>'),
        tinyApis.sSetSelection([0, 0], 1, [1, 0, 0], 1),
        sBackspace(editor),
        tinyApis.sAssertContent('<h1>a<span style="color: red;">d</span></h1>'),
        tinyApis.sAssertSelection([0, 0], 1, [0, 0], 1)
      ])),
      Logger.t('Delete from red span to h1 should merge', GeneralSteps.sequence([
        tinyApis.sSetContent('<p><span style="color: red;">ab</span></p><h1>cd</h1>'),
        tinyApis.sSetSelection([0, 0, 0], 1, [1, 0], 1),
        sDelete(editor),
        tinyApis.sAssertContent('<p><span style="color: red;">a</span>d</p>'),
        tinyApis.sAssertSelection([0, 0, 0], 1, [0, 0, 0], 1)
      ])),
      Logger.t('Delete from li to li should merge', GeneralSteps.sequence([
        tinyApis.sSetContent('<ul><li>ab</li><li>cd</li></ul>'),
        tinyApis.sSetSelection([0, 0, 0], 1, [0, 1, 0], 1),
        sDelete(editor),
        tinyApis.sAssertContent('<ul><li>ad</li></ul>'),
        tinyApis.sAssertSelection([0, 0, 0], 1, [0, 0, 0], 1)
      ])),
      Logger.t('Delete from nested li to li should merge', GeneralSteps.sequence([
        tinyApis.sSetContent('<ul><li>ab<ul><li>cd</li></ul></li></ul>'),
        tinyApis.sSetSelection([0, 0, 0], 1, [0, 0, 1, 0, 0], 1),
        sDelete(editor),
        tinyApis.sAssertContent('<ul><li>ad</li></ul>'),
        tinyApis.sAssertSelection([0, 0, 0], 1, [0, 0, 0], 1)
      ])),
      Logger.t('Delete from li to nested li should merge', GeneralSteps.sequence([
        tinyApis.sSetContent('<ul><li>ab<ul><li>cd</li></ul></li><li>ef</li></ul>'),
        tinyApis.sSetSelection([0, 0, 1, 0, 0], 1, [0, 1, 0], 1),
        sDelete(editor),
        tinyApis.sAssertContent('<ul><li>ab<ul><li>cf</li></ul></li></ul>'),
        tinyApis.sAssertSelection([0, 0, 1, 0, 0], 1, [0, 0, 1, 0, 0], 1)
      ])),
      Logger.t('Delete from deep nested li to li should merge', GeneralSteps.sequence([
        tinyApis.sSetContent('<ul><li>ab<ul><li>cd<ul><li>ef</li></li></ul></li></ul>'),
        tinyApis.sSetSelection([0, 0, 0], 1, [0, 0, 1, 0, 1, 0, 0], 1),
        sDelete(editor),
        tinyApis.sAssertContent('<ul><li>af</li></ul>'),
        tinyApis.sAssertSelection([0, 0, 0], 1, [0, 0, 0], 1)
      ])),
      Logger.t('Delete on selection of everything should empty editor', GeneralSteps.sequence([
        tinyApis.sSetContent('<p>a</p><p>b</p>'),
        tinyApis.sSetSelection([0, 0], 0, [1, 0], 1),
        sDelete(editor),
        tinyApis.sAssertContent(''),
        tinyApis.sAssertSelection([0], 0, [0], 0),
        tinyApis.sAssertContentStructure(ApproxStructure.build(function (s, str) {
          return s.element('body', {
            children: [
              s.element('p', { children: [ s.element('br', { attrs: { 'data-mce-bogus': str.is('1') } }) ] })
            ]
          });
        }))
      ])),
      Logger.t('Backspace selected paragraphs in td should produce an padded empty cell and also not delete the whole table', GeneralSteps.sequence([
        tinyApis.sSetContent('<table><tbody><tr><td><p>a</p><p>b</p></td></tr></tbody></table>'),
        tinyApis.sSetSelection([0, 0, 0, 0, 0, 0], 0, [0, 0, 0, 0, 1, 0], 1),
        sBackspace(editor),
        tinyApis.sAssertContent('<table><tbody><tr><td><p>&nbsp;</p></td></tr></tbody></table>'),
        tinyApis.sAssertSelection([0, 0, 0, 0, 0], 0, [0, 0, 0, 0, 0], 0)
      ])),
      Logger.t('Delete selected paragraphs in td should produce an padded empty cell and also not delete the whole table', GeneralSteps.sequence([
        tinyApis.sSetContent('<table><tbody><tr><td><p>a</p><p>b</p></td></tr></tbody></table>'),
        tinyApis.sSetSelection([0, 0, 0, 0, 0, 0], 0, [0, 0, 0, 0, 1, 0], 1),
        sDelete(editor),
        tinyApis.sAssertContent('<table><tbody><tr><td><p>&nbsp;</p></td></tr></tbody></table>'),
        tinyApis.sAssertSelection([0, 0, 0, 0, 0], 0, [0, 0, 0, 0, 0], 0)
      ]))
    ], onSuccess, onFailure);
  }, {
Example #18
0
 const sInsertTable = function (editor, cols, rows) {
   return Logger.t('Insert table ' + cols + 'x' + rows, Step.sync(function () {
     editor.plugins.table.insertTable(cols, rows);
   }));
 };
Example #19
0
  TinyLoader.setup(function (editor, onSuccess, onFailure) {
    const tinyApis = TinyApis(editor);

    Pipeline.async({}, [
      Logger.t('Enter inside inline boundary link', GeneralSteps.sequence([
        Logger.t('Insert br at beginning of inline boundary link', GeneralSteps.sequence([
          tinyApis.sFocus,
          tinyApis.sSetContent('<p>a<a href="#">b</a>c</p>'),
          tinyApis.sSetCursor([0, 1, 0], 0),
          tinyApis.sNodeChanged,
          sInsertBr(editor),
          tinyApis.sAssertSelection([0, 2, 0], 1, [0, 2, 0], 1),
          tinyApis.sAssertContent('<p>a<br /><a href="#">b</a>c</p>')
        ])),
        Logger.t('Insert br in middle inline boundary link', GeneralSteps.sequence([
          tinyApis.sFocus,
          tinyApis.sSetContent('<p>a<a href="#">bc</a>d</p>'),
          tinyApis.sSetCursor([0, 1, 0], 1),
          tinyApis.sNodeChanged,
          sInsertBr(editor),
          tinyApis.sAssertSelection([0, 1], 2, [0, 1], 2),
          tinyApis.sAssertContent('<p>a<a href="#">b<br />c</a>d</p>')
        ])),
        Logger.t('Insert br at end of inline boundary link', GeneralSteps.sequence([
          tinyApis.sFocus,
          tinyApis.sSetContent('<p>a<a href="#">b</a>c</p>'),
          tinyApis.sSetCursor([0, 1, 0], 1),
          tinyApis.sNodeChanged,
          sInsertBr(editor),
          tinyApis.sAssertSelection([0], 3, [0], 3),
          tinyApis.sAssertContent('<p>a<a href="#">b</a><br /><br />c</p>')
        ])),
        Logger.t('Insert br at end of inline boundary link with trailing br', GeneralSteps.sequence([
          tinyApis.sFocus,
          sSetRawContent(editor, '<p>a<a href="#">b</a><br /></p>'),
          tinyApis.sSetCursor([0, 1, 0], 1),
          tinyApis.sNodeChanged,
          sInsertBr(editor),
          tinyApis.sAssertSelection([0], 3, [0], 3),
          tinyApis.sAssertContent('<p>a<a href="#">b</a><br /><br /></p>')
        ]))
      ])),
      Logger.t('Enter inside inline boundary code', GeneralSteps.sequence([
        Logger.t('Insert br at beginning of boundary code', GeneralSteps.sequence([
          tinyApis.sFocus,
          tinyApis.sSetContent('<p>a<code>b</code>c</p>'),
          tinyApis.sSetCursor([0, 1, 0], 0),
          tinyApis.sNodeChanged,
          sInsertBr(editor),
          tinyApis.sAssertSelection([0, 1], 2, [0, 1], 2),
          tinyApis.sAssertContent('<p>a<code><br />b</code>c</p>')
        ])),
        Logger.t('Insert br at middle of boundary code', GeneralSteps.sequence([
          tinyApis.sFocus,
          tinyApis.sSetContent('<p>a<code>bc</code>d</p>'),
          tinyApis.sSetCursor([0, 1, 0], 1),
          tinyApis.sNodeChanged,
          sInsertBr(editor),
          tinyApis.sAssertSelection([0, 1], 2, [0, 1], 2),
          tinyApis.sAssertContent('<p>a<code>b<br />c</code>d</p>')
        ])),
        Logger.t('Insert br at end of boundary code', GeneralSteps.sequence([
          tinyApis.sFocus,
          tinyApis.sSetContent('<p>a<code>b</code>c</p>'),
          tinyApis.sSetCursor([0, 1, 0], 1),
          tinyApis.sNodeChanged,
          sInsertBr(editor),
          tinyApis.sAssertSelection([0, 1, 2], 0, [0, 1, 2], 0),
          tinyApis.sAssertContent('<p>a<code>b<br /></code>c</p>')
        ]))
      ]))
    ], onSuccess, onFailure);
  }, {
UnitTest.asynctest('browser.tinymce.core.delete.MergeBlocksTest', 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 cAssertHtml = function (expectedHtml) {
    return Chain.op(function () {
      Assertions.assertHtml('Should equal html', expectedHtml, viewBlock.get().innerHTML);
    });
  };

  const cMergeBlocks = function (forward, block1Path, block2Path) {
    return Chain.mapper(function (viewBlock) {
      const block1 = Hierarchy.follow(Element.fromDom(viewBlock.get()), block1Path).getOrDie();
      const block2 = Hierarchy.follow(Element.fromDom(viewBlock.get()), block2Path).getOrDie();
      return MergeBlocks.mergeBlocks(Element.fromDom(viewBlock.get()), forward, block1, block2);
    });
  };

  const cAssertPosition = function (expectedPath, expectedOffset) {
    return Chain.op(function (position) {
      const container = Hierarchy.follow(Element.fromDom(viewBlock.get()), expectedPath).getOrDie();

      Assertions.assertDomEq('Should be expected container', container, Element.fromDom(position.getOrDie().container()));
      Assertions.assertEq('Should be expected offset', expectedOffset, position.getOrDie().offset());
    });
  };

  viewBlock.attach();
  Pipeline.async({}, [
    Logger.t('Merge forward', GeneralSteps.sequence([
      Logger.t('Merge two simple blocks', Chain.asStep(viewBlock, [
        cSetHtml('<p>a</p><p>b</p>'),
        cMergeBlocks(true, [0], [1]),
        cAssertPosition([0, 0], 1),
        cAssertHtml('<p>ab</p>')
      ])),

      Logger.t('Merge two simple blocks with br', Chain.asStep(viewBlock, [
        cSetHtml('<p>a<br></p><p>b</p>'),
        cMergeBlocks(true, [0], [1]),
        cAssertPosition([0, 0], 1),
        cAssertHtml('<p>ab</p>')
      ])),

      Logger.t('Merge two complex blocks', Chain.asStep(viewBlock, [
        cSetHtml('<p><b>a</b><i>b</i></p><p><b>c</b><i>d</i></p>'),
        cMergeBlocks(true, [0], [1]),
        cAssertPosition([0, 1, 0], 1),
        cAssertHtml('<p><b>a</b><i>b</i><b>c</b><i>d</i></p>')
      ])),

      Logger.t('Merge two headers blocks', Chain.asStep(viewBlock, [
        cSetHtml('<h1>a</h1><h2>b</h2>'),
        cMergeBlocks(true, [0], [1]),
        cAssertPosition([0, 0], 1),
        cAssertHtml('<h1>ab</h1>')
      ])),

      Logger.t('Merge two headers blocks first one empty', Chain.asStep(viewBlock, [
        cSetHtml('<h1><br></h1><h2>b</h2>'),
        cMergeBlocks(true, [0], [1]),
        cAssertPosition([0, 0], 0),
        cAssertHtml('<h2>b</h2>')
      ])),

      Logger.t('Merge two headers blocks second one empty', Chain.asStep(viewBlock, [
        cSetHtml('<h1>a</h1><h2><br></h2>'),
        cMergeBlocks(true, [0], [1]),
        cAssertPosition([0, 0], 1),
        cAssertHtml('<h1>a</h1>')
      ])),

      Logger.t('Merge two headers complex blocks', Chain.asStep(viewBlock, [
        cSetHtml('<h1>a<b>b</b></h1><h2>c<b>d</b></h2>'),
        cMergeBlocks(true, [0], [1]),
        cAssertPosition([0, 1, 0], 1),
        cAssertHtml('<h1>a<b>b</b>c<b>d</b></h1>')
      ])),

      Logger.t('Merge two headers blocks first one empty second one complex', Chain.asStep(viewBlock, [
        cSetHtml('<h1><br></h1><h2>a<b>b</b></h2>'),
        cMergeBlocks(true, [0], [1]),
        cAssertPosition([0, 0], 0),
        cAssertHtml('<h2>a<b>b</b></h2>')
      ])),

      Logger.t('Merge two headers blocks second one empty first one complex', Chain.asStep(viewBlock, [
        cSetHtml('<h1>a<b>b</b></h1><h2><br></h2>'),
        cMergeBlocks(true, [0], [1]),
        cAssertPosition([0, 1, 0], 1),
        cAssertHtml('<h1>a<b>b</b></h1>')
      ])),

      Logger.t('Merge two list items', Chain.asStep(viewBlock, [
        cSetHtml('<ul><li>a<b>b</b></li><li>c<b>d</b></li></ul>'),
        cMergeBlocks(true, [0, 0], [0, 1]),
        cAssertPosition([0, 0, 1, 0], 1),
        cAssertHtml('<ul><li>a<b>b</b>c<b>d</b></li></ul>')
      ])),

      Logger.t('Merge paragraph into list item', Chain.asStep(viewBlock, [
        cSetHtml('<ul><li>a<b>b</b></li></ul><p>c<b>d</b></p>'),
        cMergeBlocks(true, [0, 0], [1]),
        cAssertPosition([0, 0, 1, 0], 1),
        cAssertHtml('<ul><li>a<b>b</b>c<b>d</b></li></ul>')
      ]))
    ])),

    Logger.t('Merge backwards', GeneralSteps.sequence([
      Logger.t('Merge two simple blocks', Chain.asStep(viewBlock, [
        cSetHtml('<p>a</p><p>b</p>'),
        cMergeBlocks(false, [1], [0]),
        cAssertPosition([0, 0], 1),
        cAssertHtml('<p>ab</p>')
      ])),

      Logger.t('Merge two simple blocks with br', Chain.asStep(viewBlock, [
        cSetHtml('<p>a<br></p><p>b</p>'),
        cMergeBlocks(false, [1], [0]),
        cAssertPosition([0, 0], 1),
        cAssertHtml('<p>ab</p>')
      ])),

      Logger.t('Merge two complex blocks', Chain.asStep(viewBlock, [
        cSetHtml('<p><b>a</b><i>b</i></p><p><b>c</b><i>d</i></p>'),
        cMergeBlocks(false, [1], [0]),
        cAssertPosition([0, 1, 0], 1),
        cAssertHtml('<p><b>a</b><i>b</i><b>c</b><i>d</i></p>')
      ])),

      Logger.t('Merge two headers blocks', Chain.asStep(viewBlock, [
        cSetHtml('<h1>a</h1><h2>b</h2>'),
        cMergeBlocks(false, [1], [0]),
        cAssertPosition([0, 0], 1),
        cAssertHtml('<h1>ab</h1>')
      ])),

      Logger.t('Merge two headers blocks first one empty', Chain.asStep(viewBlock, [
        cSetHtml('<h1>a</h1><h2><br></h2>'),
        cMergeBlocks(false, [1], [0]),
        cAssertPosition([0, 0], 1),
        cAssertHtml('<h1>a</h1>')
      ])),

      Logger.t('Merge two headers blocks second one empty', Chain.asStep(viewBlock, [
        cSetHtml('<h1><br></h1><h2>b</h2>'),
        cMergeBlocks(false, [1], [0]),
        cAssertPosition([0, 0], 0),
        cAssertHtml('<h2>b</h2>')
      ])),

      Logger.t('Merge two headers complex blocks', Chain.asStep(viewBlock, [
        cSetHtml('<h1>a<b>b</b></h1><h2>c<b>d</b></h2>'),
        cMergeBlocks(false, [1], [0]),
        cAssertPosition([0, 1, 0], 1),
        cAssertHtml('<h1>a<b>b</b>c<b>d</b></h1>')
      ])),

      Logger.t('Merge two headers blocks first one empty second one complex', Chain.asStep(viewBlock, [
        cSetHtml('<h1>a<b>b</b></h1><h2><br></h2>'),
        cMergeBlocks(false, [1], [0]),
        cAssertPosition([0, 1, 0], 1),
        cAssertHtml('<h1>a<b>b</b></h1>')
      ])),

      Logger.t('Merge two headers blocks second one empty first one complex', Chain.asStep(viewBlock, [
        cSetHtml('<h1><br></h1><h2>a<b>b</b></h2>'),
        cMergeBlocks(false, [1], [0]),
        cAssertPosition([0, 0], 0),
        cAssertHtml('<h2>a<b>b</b></h2>')
      ])),

      Logger.t('Merge two list items', Chain.asStep(viewBlock, [
        cSetHtml('<ul><li>a<b>b</b></li><li>c<b>d</b></li></ul>'),
        cMergeBlocks(false, [0, 1], [0, 0]),
        cAssertPosition([0, 0, 1, 0], 1),
        cAssertHtml('<ul><li>a<b>b</b>c<b>d</b></li></ul>')
      ])),

      Logger.t('Merge list item into paragraph', Chain.asStep(viewBlock, [
        cSetHtml('<p>a<b>b</b></p><ul><li>c<b>d</b></li></ul>'),
        cMergeBlocks(false, [1, 0], [0]),
        cAssertPosition([0, 1, 0], 1),
        cAssertHtml('<p>a<b>b</b>c<b>d</b></p>')
      ])),

      Logger.t('Merge h1 into parent wrapper div', Chain.asStep(viewBlock, [
        cSetHtml('<div>a<h1>b</h1></div>'),
        cMergeBlocks(false, [0, 1], [0]),
        cAssertPosition([0, 0], 1),
        cAssertHtml('<div>ab</div>')
      ])),

      Logger.t('Merge h1 inside a div into parent wrapper div', Chain.asStep(viewBlock, [
        cSetHtml('<div>a<div><h1>b</h1></div></div>'),
        cMergeBlocks(false, [0, 1, 0], [0]),
        cAssertPosition([0, 0], 1),
        cAssertHtml('<div>ab</div>')
      ])),

      Logger.t('Merge div > div > div > h1 into root div', Chain.asStep(viewBlock, [
        cSetHtml('<div>a<div><div><h1>b</h1></div></div></div>'),
        cMergeBlocks(false, [0, 1, 0, 0], [0]),
        cAssertPosition([0, 0], 1),
        cAssertHtml('<div>ab</div>')
      ])),

      Logger.t('Merge children until we find a block', Chain.asStep(viewBlock, [
        cSetHtml('<div>a</div><div>b<h1>c</h1></div>'),
        cMergeBlocks(false, [1], [0]),
        cAssertPosition([0, 0], 1),
        cAssertHtml('<div>ab</div><div><h1>c</h1></div>')
      ]))
    ]))
  ], function () {
    viewBlock.detach();
    success();
  }, failure);
});
Example #21
0
  TinyLoader.setup(function (editor, onSuccess, onFailure) {
    const tinyApis = TinyApis(editor);
    Pipeline.async({}, [
      tinyApis.sFocus,

      Logger.t('resize table height by dragging bottom', GeneralSteps.sequence([
        tinyApis.sSetContent('<table style="border-collapse: collapse;border: 0;"><tbody><tr><td style="height:45px;">a</td></tr><tr><td style="height:45px;">a</td></tr></tbody></table>'),
        sSetStateFrom(editor, [0, 0, 0, 0]),
        sWaitForSelection(editor, tinyApis),
        sMouseover(Element.fromDom(editor.getBody()), 'td'),
        sDragDropBlocker(Element.fromDom(editor.getDoc().documentElement), 'div[data-row="0"]', 0, 50),
        sAssertSizeChange(editor, [0, 0, 0, 0], { dh: 50, dw: 0 }),
        sAssertNoDataStyle(editor, [0]),
        sResetState
      ])),

      Logger.t('resize table width by dragging right side', GeneralSteps.sequence([
        tinyApis.sSetContent('<table style="border-collapse: collapse;border: 0;"><tbody><tr><td style="height:45px;">a</td></tr><tr><td style="height:45px;">a</td></tr></tbody></table>'),
        sSetStateFrom(editor, [0, 0, 0, 0]),
        sWaitForSelection(editor, tinyApis),
        sMouseover(Element.fromDom(editor.getBody()), 'td'),
        sDragDropBlocker(Element.fromDom(editor.getDoc().documentElement), 'div[data-column="0"]', 50, 0),
        sAssertSizeChange(editor, [0, 0, 0, 0], { dh: 0, dw: 50 }),
        sAssertNoDataStyle(editor, [0]),
        sResetState
      ])),

      Logger.t('Resize table bigger with handle, then resize row height bigger by dragging middle border', GeneralSteps.sequence([
        tinyApis.sSetContent(tableHtml),
        sSetStateFrom(editor, [0]),
        sWaitForSelection(editor, tinyApis),
        sDragDrop(Element.fromDom(editor.getBody()), '#mceResizeHandlese', 50, 50),
        sMouseover(Element.fromDom(editor.getBody()), 'td'),
        sDragDropBlocker(Element.fromDom(editor.getDoc().documentElement), 'div[data-row="0"]', 0, 50),
        sAssertSizeChange(editor, [0], { dh: 100, dw: 50 }),
        sAssertNoDataStyle(editor, [0]),
        sResetState
      ])),

      Logger.t('Resize table bigger with handle, then resize row height smaller by dragging middle border', GeneralSteps.sequence([
        tinyApis.sSetContent(tableHtml),
        sSetStateFrom(editor, [0]),
        sWaitForSelection(editor, tinyApis),
        sDragDrop(Element.fromDom(editor.getBody()), '#mceResizeHandlese', 50, 50),
        sMouseover(Element.fromDom(editor.getBody()), 'td'),
        sDragDropBlocker(Element.fromDom(editor.getDoc().documentElement), 'div[data-row="0"]', 0, -30),
        sAssertSizeChange(editor, [0], { dh: 20, dw: 50 }),
        sAssertNoDataStyle(editor, [0]),
        sResetState
      ])),

      Logger.t('Resize table bigger with handle, then resize column width bigger by dragging middle border', GeneralSteps.sequence([
        tinyApis.sSetContent(tableHtml),
        sSetStateFrom(editor, [0]),
        sWaitForSelection(editor, tinyApis),
        sDragDrop(Element.fromDom(editor.getBody()), '#mceResizeHandlese', 50, 50),
        sMouseover(Element.fromDom(editor.getBody()), 'td'),
        sDragDropBlocker(Element.fromDom(editor.getDoc().documentElement), 'div[data-column="0"]', 50, 0),
        sAssertSizeChange(editor, [0], { dh: 50, dw: 50 }),
        sAssertNoDataStyle(editor, [0]),
        sResetState
      ])),

      Logger.t('Resize table bigger with handle, then resize column width smaller by dragging middle border', GeneralSteps.sequence([
        tinyApis.sSetContent(tableHtml),
        sSetStateFrom(editor, [0]),
        sWaitForSelection(editor, tinyApis),
        sDragDrop(Element.fromDom(editor.getBody()), '#mceResizeHandlese', 50, 50),
        sMouseover(Element.fromDom(editor.getBody()), 'td'),
        sDragDropBlocker(Element.fromDom(editor.getDoc().documentElement), 'div[data-column="0"]', -30, 0),
        sAssertSizeChange(editor, [0], { dh: 50, dw: 50 }),
        sAssertNoDataStyle(editor, [0]),
        sResetState
      ])),

      Logger.t('Resize table smaller with handle, then resize row height bigger by dragging middle border', GeneralSteps.sequence([
        tinyApis.sSetContent(tableHtml),
        sSetStateFrom(editor, [0]),
        sWaitForSelection(editor, tinyApis),
        sDragDrop(Element.fromDom(editor.getBody()), '#mceResizeHandlese', -10, -10),
        sMouseover(Element.fromDom(editor.getBody()), 'td'),
        sDragDropBlocker(Element.fromDom(editor.getDoc().documentElement), 'div[data-row="0"]', 0, 50),
        sAssertSizeChange(editor, [0], { dh: 40, dw: -10 }),
        sAssertNoDataStyle(editor, [0]),
        sResetState
      ])),

      Logger.t('Resize table smaller with handle, then resize row height smaller by dragging middle border', GeneralSteps.sequence([
        tinyApis.sSetContent(tableHtml),
        sSetStateFrom(editor, [0]),
        sWaitForSelection(editor, tinyApis),
        sDragDrop(Element.fromDom(editor.getBody()), '#mceResizeHandlese', -10, -10),
        sMouseover(Element.fromDom(editor.getBody()), 'td'),
        sDragDropBlocker(Element.fromDom(editor.getDoc().documentElement), 'div[data-row="0"]', 0, -20),
        sAssertSizeChange(editor, [0], { dh: -30, dw: -10 }),
        sAssertNoDataStyle(editor, [0]),
        sResetState
      ])),

      Logger.t('Resize table smaller with handle, then resize column width bigger by dragging middle border', GeneralSteps.sequence([
        tinyApis.sSetContent(tableHtml),
        sSetStateFrom(editor, [0]),
        sWaitForSelection(editor, tinyApis),
        sDragDrop(Element.fromDom(editor.getBody()), '#mceResizeHandlese', -10, -10),
        sMouseover(Element.fromDom(editor.getBody()), 'td'),
        sDragDropBlocker(Element.fromDom(editor.getDoc().documentElement), 'div[data-column="0"]', 50, 0),
        sAssertSizeChange(editor, [0], { dh: -10, dw: -10 }),
        sAssertNoDataStyle(editor, [0]),
        sResetState
      ])),

      Logger.t('Resize table smaller with handle, then resize column width smaller by dragging middle border', GeneralSteps.sequence([
        tinyApis.sSetContent(tableHtml),
        sSetStateFrom(editor, [0]),
        sWaitForSelection(editor, tinyApis),
        sDragDrop(Element.fromDom(editor.getBody()), '#mceResizeHandlese', -10, -10),
        sMouseover(Element.fromDom(editor.getBody()), 'td'),
        sDragDropBlocker(Element.fromDom(editor.getDoc().documentElement), 'div[data-column="0"]', -20, 0),
        sAssertSizeChange(editor, [0], { dh: -10, dw: -10 }),
        sAssertNoDataStyle(editor, [0]),
        sResetState
      ]))
    ], onSuccess, onFailure);
  }, {
  TinyLoader.setup(function (editor, onSuccess, onFailure) {
    const tinyApis = TinyApis(editor);

    Pipeline.async({}, [
      tinyApis.sFocus,
      Logger.t('Backspace in same block should remain unchanged', GeneralSteps.sequence([
        tinyApis.sSetContent('<p>a</p>'),
        tinyApis.sSetCursor([0, 0], 1),
        sBackspaceNoop(editor),
        tinyApis.sAssertContent('<p>a</p>'),
        tinyApis.sAssertSelection([0, 0], 1, [0, 0], 1)
      ])),
      Logger.t('Delete in same block should remain unchanged', GeneralSteps.sequence([
        tinyApis.sSetContent('<p>a</p>'),
        tinyApis.sSetCursor([0, 0], 1),
        sDeleteNoop(editor),
        tinyApis.sAssertContent('<p>a</p>'),
        tinyApis.sAssertSelection([0, 0], 1, [0, 0], 1)
      ])),
      Logger.t('Backspace between blocks with different parents should not merge', GeneralSteps.sequence([
        tinyApis.sSetContent('<p>a</p><div><p>b</p></div>'),
        tinyApis.sSetCursor([1, 0, 0], 0),
        sBackspaceNoop(editor),
        tinyApis.sAssertContent('<p>a</p><div><p>b</p></div>'),
        tinyApis.sAssertSelection([1, 0, 0], 0, [1, 0, 0], 0)
      ])),
      Logger.t('Delete between blocks with different parents should not merge', GeneralSteps.sequence([
        tinyApis.sSetContent('<p>a</p><div><p>b</p></div>'),
        tinyApis.sSetCursor([0, 0], 1),
        sDeleteNoop(editor),
        tinyApis.sAssertContent('<p>a</p><div><p>b</p></div>'),
        tinyApis.sAssertSelection([0, 0], 1, [0, 0], 1)
      ])),
      Logger.t('Backspace between textblock and non text block should not merge', GeneralSteps.sequence([
        tinyApis.sSetContent('<p>a</p><ul><li>b</li></ul>'),
        tinyApis.sSetCursor([1, 0, 0], 0),
        sBackspaceNoop(editor),
        tinyApis.sAssertContent('<p>a</p><ul><li>b</li></ul>'),
        tinyApis.sAssertSelection([1, 0, 0], 0, [1, 0, 0], 0)
      ])),
      Logger.t('Delete between textblock and non text block should not merge', GeneralSteps.sequence([
        tinyApis.sSetContent('<p>a</p><ul><li>b</li></ul>'),
        tinyApis.sSetCursor([0, 0], 1),
        sDeleteNoop(editor),
        tinyApis.sAssertContent('<p>a</p><ul><li>b</li></ul>'),
        tinyApis.sAssertSelection([0, 0], 1, [0, 0], 1)
      ])),
      Logger.t('Backspace on range between blocks should not merge (handled elsewhere)', GeneralSteps.sequence([
        tinyApis.sSetContent('<p>a</p><p>b</p>'),
        tinyApis.sSetSelection([0, 0], 1, [1, 0], 0),
        sBackspaceNoop(editor),
        tinyApis.sAssertContent('<p>a</p><p>b</p>'),
        tinyApis.sAssertSelection([0, 0], 1, [1, 0], 0)
      ])),
      Logger.t('Delete on range between blocks should not merge (handled elsewhere)', GeneralSteps.sequence([
        tinyApis.sSetContent('<p>a</p><p>b</p>'),
        tinyApis.sSetSelection([0, 0], 1, [1, 0], 0),
        sDeleteNoop(editor),
        tinyApis.sAssertContent('<p>a</p><p>b</p>'),
        tinyApis.sAssertSelection([0, 0], 1, [1, 0], 0)
      ])),
      Logger.t('Backspace between simple blocks should merge', GeneralSteps.sequence([
        tinyApis.sSetContent('<p>a</p><p>b</p>'),
        tinyApis.sSetCursor([1, 0], 0),
        sBackspace(editor),
        tinyApis.sAssertContent('<p>ab</p>'),
        tinyApis.sAssertSelection([0, 0], 1, [0, 0], 1)
      ])),
      Logger.t('Delete between simple blocks should merge', GeneralSteps.sequence([
        tinyApis.sSetContent('<p>a</p><p>b</p>'),
        tinyApis.sSetCursor([0, 0], 1),
        sDelete(editor),
        tinyApis.sAssertContent('<p>ab</p>'),
        tinyApis.sAssertSelection([0, 0], 1, [0, 0], 1)
      ])),
      Logger.t('Backspace between complex blocks should merge', GeneralSteps.sequence([
        tinyApis.sSetContent('<p>a<em>b</em></p><p><em>c</em>d</p>'),
        tinyApis.sSetCursor([1, 0, 0], 0),
        sBackspace(editor),
        tinyApis.sAssertContent('<p>a<em>b</em><em>c</em>d</p>'),
        tinyApis.sAssertSelection([0, 1, 0], 1, [0, 1, 0], 1)
      ])),
      Logger.t('Delete between complex blocks should merge', GeneralSteps.sequence([
        tinyApis.sSetContent('<p><em>a</em>b</p><p>c<em>d</em></p>'),
        tinyApis.sSetCursor([0, 1], 1),
        sDelete(editor),
        tinyApis.sAssertContent('<p><em>a</em>bc<em>d</em></p>'),
        tinyApis.sAssertSelection([0, 1], 1, [0, 1], 1)
      ])),
      Logger.t('Backspace from red span to h1 should merge', GeneralSteps.sequence([
        tinyApis.sSetContent('<h1>a</h1><p><span style="color: red;">b</span></p>'),
        tinyApis.sSetCursor([1, 0, 0], 0),
        sBackspace(editor),
        tinyApis.sAssertContent('<h1>a<span style="color: red;">b</span></h1>'),
        tinyApis.sAssertSelection([0, 0], 1, [0, 0], 1)
      ])),
      Logger.t('Delete from red span to h1 should merge', GeneralSteps.sequence([
        tinyApis.sSetContent('<p><span style="color: red;">a</span></p><h1>b</h1>'),
        tinyApis.sSetCursor([0, 0, 0], 1),
        sDelete(editor),
        tinyApis.sAssertContent('<p><span style="color: red;">a</span>b</p>'),
        tinyApis.sAssertSelection([0, 0, 0], 1, [0, 0, 0], 1)
      ])),
      Logger.t('Backspace from block into block with trailing br should merge', GeneralSteps.sequence([
        sSetRawContent(editor, '<p>a<br></p><p>b</p>'),
        tinyApis.sSetCursor([1, 0], 0),
        sBackspace(editor),
        tinyApis.sAssertContentStructure(
          ApproxStructure.build(function (s, str, arr) {
            return s.element('body', {
              children: [
                s.element('p', {
                  children: [
                    s.text(str.is('a')),
                    s.text(str.is('b'))
                  ]
                })
              ]
            });
          })
        ),
        tinyApis.sAssertSelection([0, 0], 1, [0, 0], 1)
      ])),
      Logger.t('Delete from block into block with trailing br should merge', GeneralSteps.sequence([
        sSetRawContent(editor, '<p>a<br></p><p>b</p>'),
        tinyApis.sSetCursor([0, 0], 1),
        sDelete(editor),
        tinyApis.sAssertContentStructure(
          ApproxStructure.build(function (s, str, arr) {
            return s.element('body', {
              children: [
                s.element('p', {
                  children: [
                    s.text(str.is('a')),
                    s.text(str.is('b'))
                  ]
                })
              ]
            });
          })
        ),
        tinyApis.sAssertSelection([0, 0], 1, [0, 0], 1)
      ])),
      Logger.t('Backspace from empty block into content block should merge', GeneralSteps.sequence([
        sSetRawContent(editor, '<p>a</p><p><br></p>'),
        tinyApis.sSetCursor([1], 0),
        sBackspace(editor),
        tinyApis.sAssertContentStructure(
          ApproxStructure.build(function (s, str, arr) {
            return s.element('body', {
              children: [
                s.element('p', {
                  children: [
                    s.text(str.is('a'))
                  ]
                })
              ]
            });
          })
        ),
        tinyApis.sAssertSelection([0, 0], 1, [0, 0], 1)
      ])),
      Logger.t('Delete from empty block into content block should merge', GeneralSteps.sequence([
        sSetRawContent(editor, '<p><br></p><p>a</p>'),
        tinyApis.sSetCursor([0], 0),
        sDelete(editor),
        tinyApis.sAssertContentStructure(
          ApproxStructure.build(function (s, str, arr) {
            return s.element('body', {
              children: [
                s.element('p', {
                  children: [
                    s.text(str.is('a'))
                  ]
                })
              ]
            });
          })
        ),
        tinyApis.sAssertSelection([0, 0], 0, [0, 0], 0)
      ])),
      Logger.t('Backspace between empty blocks should merge', GeneralSteps.sequence([
        sSetRawContent(editor, '<p><br></p><p><br></p>'),
        tinyApis.sSetCursor([1], 0),
        sBackspace(editor),
        tinyApis.sAssertContentStructure(
          ApproxStructure.build(function (s, str, arr) {
            return s.element('body', {
              children: [
                s.element('p', {
                  children: [
                    s.element('br', {})
                  ]
                })
              ]
            });
          })
        ),
        tinyApis.sAssertSelection([0], 0, [0], 0)
      ])),
      Logger.t('Delete between empty blocks should merge', GeneralSteps.sequence([
        sSetRawContent(editor, '<p><br></p><p><br></p>'),
        tinyApis.sSetCursor([0], 0),
        sDelete(editor),
        tinyApis.sAssertContentStructure(
          ApproxStructure.build(function (s, str, arr) {
            return s.element('body', {
              children: [
                s.element('p', {
                  children: [
                    s.element('br', {})
                  ]
                })
              ]
            });
          })
        ),
        tinyApis.sAssertSelection([0], 0, [0], 0)
      ]))
    ], onSuccess, onFailure);
  }, {
UnitTest.asynctest('browser.tinymce.core.delete.CefDeleteActionTest', (success, failure) => {
  const viewBlock = ViewBlock();

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

  const cReadAction = 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 CefDeleteAction.read(viewBlock.get(), forward, rng);
    });
  };

  const cAssertRemoveElementAction = function (elementPath) {
    return Chain.op(function (actionOption: Option<any>) {
      const element = Hierarchy.follow(Element.fromDom(viewBlock.get()), elementPath).getOrDie();
      const action = actionOption.getOrDie();
      Assertions.assertEq('Should be expected action type', 'remove', actionName(action));
      Assertions.assertDomEq('Should be expected element', element, actionValue(action));
    });
  };

  const cAssertMoveToElementAction = function (elementPath) {
    return Chain.op(function (actionOption: Option<any>) {
      const element = Hierarchy.follow(Element.fromDom(viewBlock.get()), elementPath).getOrDie();
      const action = actionOption.getOrDie();
      Assertions.assertEq('Should be expected action type', 'moveToElement', actionName(action));
      Assertions.assertDomEq('Should be expected element', element, actionValue(action));
    });
  };

  const cAssertMoveToPositionAction = function (elementPath, offset) {
    return Chain.op(function (actionOption: Option<any>) {
      const container = Hierarchy.follow(Element.fromDom(viewBlock.get()), elementPath).getOrDie();
      const action = actionOption.getOrDie();
      Assertions.assertEq('Should be expected action type', 'moveToPosition', actionName(action));
      Assertions.assertDomEq('Should be expected container', container, Element.fromDom(actionValue(action).container()));
      Assertions.assertEq('Should be expected offset', offset, actionValue(action).offset());
    });
  };

  const cAssertActionNone = Chain.op(function (actionOption: Option<any>) {
    Assertions.assertEq('Action value should be none', true, actionOption.isNone());
  });

  const actionName = function (action) {
    return action.fold(
      Fun.constant('remove'),
      Fun.constant('moveToElement'),
      Fun.constant('moveToPosition')
    );
  };

  const actionValue = function (action) {
    return action.fold(
      Element.fromDom,
      Element.fromDom,
      Fun.identity
    );
  };

  viewBlock.attach();
  Pipeline.async({}, [
    Logger.t('None actions where caret is not near a cef element', GeneralSteps.sequence([
      Logger.t('Should be no action since it not next to ce=false', Chain.asStep(viewBlock, [
        cSetHtml('<p>a</p>'),
        cReadAction(true, [0, 0], 0),
        cAssertActionNone
      ])),
      Logger.t('Should be no action since it not next to ce=false', Chain.asStep(viewBlock, [
        cSetHtml('<p>a</p>'),
        cReadAction(false, [0, 0], 0),
        cAssertActionNone
      ])),
      Logger.t('Should be no action since it not next to ce=false', Chain.asStep(viewBlock, [
        cSetHtml('<p>a</p>'),
        cReadAction(true, [0, 0], 1),
        cAssertActionNone
      ])),
      Logger.t('Should be no action since it not next to ce=false', Chain.asStep(viewBlock, [
        cSetHtml('<p>a</p>'),
        cReadAction(false, [0, 0], 1),
        cAssertActionNone
      ])),
      Logger.t('Should be no action since it not next to ce=false', Chain.asStep(viewBlock, [
        cSetHtml('<p>a</p><p contenteditable="false">b</p>'),
        cReadAction(true, [0, 0], 0),
        cAssertActionNone
      ])),
      Logger.t('Should be no action since it not next to ce=false', Chain.asStep(viewBlock, [
        cSetHtml('<p>a</p><p contenteditable="false">b</p>'),
        cReadAction(false, [0, 0], 0),
        cAssertActionNone
      ])),
      Logger.t('Should be no action since it not next to ce=false', Chain.asStep(viewBlock, [
        cSetHtml('<p contenteditable="false">a</p><p>b</p>'),
        cReadAction(true, [1, 0], 1),
        cAssertActionNone
      ])),
      Logger.t('Should be no action since it not next to ce=false', Chain.asStep(viewBlock, [
        cSetHtml('<p contenteditable="false">a</p><p>b</p>'),
        cReadAction(false, [1, 0], 1),
        cAssertActionNone
      ])),
      Logger.t('Should be no action since it is after the last ce=false', Chain.asStep(viewBlock, [
        cSetHtml('<p contenteditable="false">a</p><p contenteditable="false">b</p>'),
        cReadAction(true, [], 2),
        cAssertActionNone
      ])),
      Logger.t('Should be no action since it is before the first ce=false', Chain.asStep(viewBlock, [
        cSetHtml('<p contenteditable="false">a</p><p contenteditable="false">b</p>'),
        cReadAction(false, [], 0),
        cAssertActionNone
      ]))
    ])),

    Logger.t('MoveToElement actions where caret is near a cef element', GeneralSteps.sequence([
      Logger.t('Should be moveToElement action since it next to a ce=false', Chain.asStep(viewBlock, [
        cSetHtml('<p>a</p><p contenteditable="false">b</p>'),
        cReadAction(true, [0, 0], 1),
        cAssertMoveToElementAction([1])
      ])),
      Logger.t('Should be moveToElement action since it next to a ce=false', Chain.asStep(viewBlock, [
        cSetHtml('<p contenteditable="false">b</p><p>a</p>'),
        cReadAction(false, [1, 0], 0),
        cAssertMoveToElementAction([0])
      ])),
      Logger.t('Should be moveToElement action since it next to a ce=false', Chain.asStep(viewBlock, [
        cSetHtml('<p><em>a</em></p><p contenteditable="false">b</p>'),
        cReadAction(true, [0, 0, 0], 1),
        cAssertMoveToElementAction([1])
      ])),
      Logger.t('Should be moveToElement action since it next to a ce=false', Chain.asStep(viewBlock, [
        cSetHtml('<p contenteditable="false">b</p><p><em>a</em></p>'),
        cReadAction(false, [1, 0, 0], 0),
        cAssertMoveToElementAction([0])
      ])),
      Logger.t('Should be moveToElement since it is delete after ce=false before another ce=false', Chain.asStep(viewBlock, [
        cSetHtml('<p contenteditable="false">b</p><p data-mce-caret="after"><br></p><p contenteditable="false">b</p>'),
        cReadAction(true, [1], 0),
        cAssertMoveToElementAction([2])
      ])),
      Logger.t('Should be moveToElement since it is backspace before a ce=false element before a ce=false', Chain.asStep(viewBlock, [
        cSetHtml('<p contenteditable="false">b</p><p data-mce-caret="before"><br></p><p contenteditable="false">b</p>'),
        cReadAction(false, [1], 0),
        cAssertMoveToElementAction([0])
      ]))
    ])),

    Logger.t('RemoveElement actions where caret is near a cef element', GeneralSteps.sequence([
      Logger.t('Should be removeElement action since it next to a ce=false', Chain.asStep(viewBlock, [
        cSetHtml('<p contenteditable="false">a</p><p contenteditable="false">b</p>'),
        cReadAction(true, [], 0),
        cAssertRemoveElementAction([0])
      ])),
      Logger.t('Should be removeElement action since it next to a ce=false', Chain.asStep(viewBlock, [
        cSetHtml('<p contenteditable="false">a</p><p contenteditable="false">b</p>'),
        cReadAction(true, [], 1),
        cAssertRemoveElementAction([1])
      ])),
      Logger.t('Should be removeElement action since it next to a ce=false', Chain.asStep(viewBlock, [
        cSetHtml('<p contenteditable="false">a</p><p contenteditable="false">b</p>'),
        cReadAction(false, [], 2),
        cAssertRemoveElementAction([1])
      ])),
      Logger.t('Should be removeElement action since it next to a ce=false', Chain.asStep(viewBlock, [
        cSetHtml('<p contenteditable="false">a</p><p contenteditable="false">b</p>'),
        cReadAction(false, [], 1),
        cAssertRemoveElementAction([0])
      ])),
      Logger.t('Should be removeElement since it is backspace after a ce=false', Chain.asStep(viewBlock, [
        cSetHtml('<p contenteditable="false">b</p><p data-mce-caret="after"><br></p><p contenteditable="false">b</p>'),
        cReadAction(false, [1], 0),
        cAssertRemoveElementAction([0])
      ])),
      Logger.t('Should be removeElement since it is delete before a ce=false', Chain.asStep(viewBlock, [
        cSetHtml('<p contenteditable="false">b</p><p data-mce-caret="before"><br></p><p contenteditable="false">b</p>'),
        cReadAction(true, [1], 0),
        cAssertRemoveElementAction([2])
      ])),
      Logger.t('Should be removeElement since the block you are deleting from is empty', Chain.asStep(viewBlock, [
        cSetHtml('<p contenteditable="false">b</p><p><br></p><p contenteditable="false">b</p>'),
        cReadAction(true, [1], 0),
        cAssertRemoveElementAction([1])
      ])),
      Logger.t('Should be removeElement since the block you are deleting from is empty', Chain.asStep(viewBlock, [
        cSetHtml('<p contenteditable="false">b</p><p><br></p><p contenteditable="false">b</p>'),
        cReadAction(false, [1], 0),
        cAssertRemoveElementAction([1])
      ])),
      Logger.t('Should be removeElement since caret positioned before BR', Chain.asStep(viewBlock, [
        cSetHtml('<p><span contenteditable="false">A</span>\ufeff<br /><span contenteditable="false">B</span></p>'),
        cReadAction(true, [0], 1),
        cAssertRemoveElementAction([0, 2])
      ])),
      Logger.t('Should be removeElement since caret positioned after BR', Chain.asStep(viewBlock, [
        cSetHtml('<p><span contenteditable="false">A</span><br />\ufeff<span contenteditable="false">B</span></p>'),
        cReadAction(false, [0], 3),
        cAssertRemoveElementAction([0, 1])
      ]))
    ])),

    Logger.t('moveToPosition actions where caret is to be moved from cef to normal content between blocks', GeneralSteps.sequence([
      Logger.t('Should be moveToPosition action since we are after a ce=false and moving forwards to normal content', Chain.asStep(viewBlock, [
        cSetHtml('<p contenteditable="false">a</p><p>b</p>'),
        cReadAction(true, [], 1),
        cAssertMoveToPositionAction([1, 0], 0)
      ])),
      Logger.t('Should be moveToPosition action since we are before a ce=false and moving backwards to normal content', Chain.asStep(viewBlock, [
        cSetHtml('<p>a</p><p contenteditable="false">b</p>'),
        cReadAction(false, [], 1),
        cAssertMoveToPositionAction([0, 0], 1)
      ]))
    ])),

    Logger.t('Delete after inline cef should not do anything', GeneralSteps.sequence([
      Logger.t('Should be no action since it is a delete after cef to text', Chain.asStep(viewBlock, [
        cSetHtml('<p><b contenteditable="false">a</b>b</p>'),
        cReadAction(true, [0], 1),
        cAssertActionNone
      ])),
      Logger.t('Should be no action since it is a delete after cef to no position', Chain.asStep(viewBlock, [
        cSetHtml('<p><b contenteditable="false">a</b></p>'),
        cReadAction(true, [0], 1),
        cAssertActionNone
      ]))
    ])),

    Logger.t('Backspace before inline cef should not do anything', GeneralSteps.sequence([
      Logger.t('Should be no action since it is a backspace before cef to text', Chain.asStep(viewBlock, [
        cSetHtml('<p>a<b contenteditable="false">b</b></p>'),
        cReadAction(false, [0, 0], 1),
        cAssertActionNone
      ])),
      Logger.t('Should be no action since it is a backspace before cef to no position', Chain.asStep(viewBlock, [
        cSetHtml('<p><b contenteditable="false">a</b></p>'),
        cReadAction(false, [0], 0),
        cAssertActionNone
      ]))
    ])),

    Logger.t('Backspace/delete into cef table cell should not remove the cell', GeneralSteps.sequence([
      Logger.t('Should be no action since it is a backspace before cef to text', Chain.asStep(viewBlock, [
        cSetHtml('<table><tbody><tr><td contenteditable="false">1</td><td>2</td></tr></tbody></table>'),
        cReadAction(false, [0, 0, 0, 1, 0], 0),
        cAssertActionNone
      ])),
      Logger.t('Should be no action since it is a backspace before cef to text', Chain.asStep(viewBlock, [
        cSetHtml('<table><tbody><tr><td>1</td><td contenteditable="false">2</td></tr></tbody></table>'),
        cReadAction(true, [0, 0, 0, 0, 0], 1),
        cAssertActionNone
      ]))
    ])),

    Logger.t('Backspace/delete into cef list item should not remove the item', GeneralSteps.sequence([
      Logger.t('Should be no action since it is a backspace before cef to text', Chain.asStep(viewBlock, [
        cSetHtml('<ul><li contenteditable="false">1</li><li>2</li></ul>'),
        cReadAction(false, [0, 1, 0], 0),
        cAssertActionNone
      ])),
      Logger.t('Should be no action since it is a backspace before cef to text', Chain.asStep(viewBlock, [
        cSetHtml('<ul><li>1</li><li contenteditable="false">2</li></ul>'),
        cReadAction(true, [0, 0, 0], 1),
        cAssertActionNone
      ]))
    ])),

    Logger.t('Should not produce actions if cefs are inline between blocks', GeneralSteps.sequence([
      Logger.t('Should be no action since delete from after inline cef to before inline cef is handled by merge', Chain.asStep(viewBlock, [
        cSetHtml('<p><b contenteditable="false">a</b></p><p><b contenteditable="false">b</b></p>'),
        cReadAction(true, [0], 1),
        cAssertActionNone
      ])),
      Logger.t('Should be no action since backspace from before inline cef to after inline cef is handled by merge', Chain.asStep(viewBlock, [
        cSetHtml('<p><b contenteditable="false">a</b></p><p><b contenteditable="false">b</b></p>'),
        cReadAction(false, [1], 0),
        cAssertActionNone
      ])),
      Logger.t('Should be no action since delete from after inline cef to normal content is handled by merge', Chain.asStep(viewBlock, [
        cSetHtml('<p><b contenteditable="false">a</b></p><p>b</p>'),
        cReadAction(true, [0], 1),
        cAssertActionNone
      ])),
      Logger.t('Should be no action since backspace from before inline cef to normal content is handled by merge', Chain.asStep(viewBlock, [
        cSetHtml('<p>a</p><p><b contenteditable="false">b</b></p>'),
        cReadAction(false, [1], 0),
        cAssertActionNone
      ]))
    ])),
  ], function () {
    viewBlock.detach();
    success();
  }, failure);
});
UnitTest.asynctest('browser.tinymce.core.delete.TableDeleteActionTest', function () {
  const success = arguments[arguments.length - 2];
  const failure = arguments[arguments.length - 1];

  const cFromHtml = function (html, startPath, startOffset, endPath, endOffset) {
    return Chain.mapper(function () {
      const elm = Element.fromHtml(html);
      const sc = Hierarchy.follow(elm, startPath).getOrDie();
      const ec = Hierarchy.follow(elm, endPath).getOrDie();
      const rng = document.createRange();

      rng.setStart(sc.dom(), startOffset);
      rng.setEnd(ec.dom(), endOffset);

      return TableDeleteAction.getActionFromRange(elm, rng);
    });
  };

  const fail = function (message) {
    return Fun.constant(Result.error(message));
  };

  const cAssertNone = Chain.op(function (x) {
    Assertions.assertEq('Is none', true, x.isNone());
  });

  const cExtractActionCells = Chain.binder(function (actionOpt) {
    return actionOpt
        .fold(
          fail('unexpected nothing'),
          function (action) {
            return action.fold(
              fail('unexpected action'),
              function (xs) {
                const cellString = Arr.map(xs, Html.getOuter).join('');

                return Result.value(cellString);
              }
            );
          }
        );
  });

  const cExtractTableFromDeleteAction = Chain.binder(function (actionOpt) {
    return actionOpt
      .fold(
        fail('unexpected nothing'),
        function (action) {
          return action.fold(
            function (table) {
              return Result.value(Html.getOuter(table));
            },
            fail('unexpected action')
          );
        }
      );
  });

  Pipeline.async({}, [
    Logger.t('collapsed range should return none', Chain.asStep({}, [
      cFromHtml('<table><tbody><tr><td>a</td><td>b</td><td>c</td></tr></tbody></table>', [0, 0, 0, 0], 0, [0, 0, 0, 0], 0),
      cAssertNone
    ])),

    Logger.t('select two out of three cells returns the emptycells action', Chain.asStep({}, [
      cFromHtml('<table><tbody><tr><td>a</td><td>b</td><td>c</td></tr></tbody></table>', [0, 0, 0, 0], 0, [0, 0, 1, 0], 1),
      cExtractActionCells,
      Assertions.cAssertEq('Should be cells', '<td>a</td><td>b</td>')
    ])),

    Logger.t('select two out of three cells returns the emptycells action', Chain.asStep({}, [
      cFromHtml('<table><tbody><tr><th>a</th><th>b</th><th>c</th></tr></tbody></table>', [0, 0, 0, 0], 0, [0, 0, 1, 0], 1),
      cExtractActionCells,
      Assertions.cAssertEq('Should be cells', '<th>a</th><th>b</th>')
    ])),

    Logger.t('select three out of three cells returns the removeTable action', Chain.asStep({}, [
      cFromHtml('<table><tbody><tr><td>a</td><td>b</td><td>c</td></tr></tbody></table>', [0, 0, 0, 0], 0, [0, 0, 2, 0], 1),
      cExtractTableFromDeleteAction,
      Assertions.cAssertEq('should be table', '<table><tbody><tr><td>a</td><td>b</td><td>c</td></tr></tbody></table>')
    ])),

    Logger.t('select between rows, not all cells', Chain.asStep({}, [
      cFromHtml(
        '<table><tbody><tr><th>a</th><th>b</th><th>c</th></tr><tr><td>d</td><td>e</td><td>f</td></tr></tbody></table>',
        [0, 0, 1, 0], 0, [0, 1, 0, 0], 1
      ),
      cExtractActionCells,
      Assertions.cAssertEq('should be cells', '<th>b</th><th>c</th><td>d</td>')
    ])),

    Logger.t('select between rows, all cells', Chain.asStep({}, [
      cFromHtml(
        '<table><tbody><tr><th>a</th><th>b</th><th>c</th></tr><tr><td>d</td><td>e</td><td>f</td></tr></tbody></table>',
        [0, 0, 0, 0], 0, [0, 1, 2, 0], 1
      ),
      cExtractTableFromDeleteAction,
      Assertions.cAssertEq('should be table', '<table><tbody><tr><th>a</th><th>b</th><th>c</th></tr><tr><td>d</td><td>e</td><td>f</td></tr></tbody></table>')
    ]))
  ], function () {
    success();
  }, failure);
});
Example #25
0
UnitTest.asynctest('browser.tinymce.core.dom.ElementTypeTest', (success, failure) => {
  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 tables', GeneralSteps.sequence([
      sCheckElement('b', ElementType.isTable, false),
      sCheckElement('p', ElementType.isTable, false),
      sCheckElement('table', ElementType.isTable, true),
      sCheckText(ElementType.isTable)
    ])),
    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)
    ])),
    Logger.t('Check whitespace preserve elements', GeneralSteps.sequence([
      sCheckElement('br', ElementType.isWsPreserveElement, false),
      sCheckElement('div', ElementType.isWsPreserveElement, false),
      sCheckElement('pre', ElementType.isWsPreserveElement, true),
      sCheckElement('textarea', ElementType.isWsPreserveElement, true),
      sCheckText(ElementType.isWsPreserveElement)
    ]))
  ], function () {
    success();
  }, failure);
});
  TinyLoader.setup(function (editor, onSuccess, onFailure) {
    const ui = TinyUi(editor);
    const api = TinyApis(editor);

    const sEnterUrl = function (url) {
      return Step.sync(function () {
        const input: any = document.activeElement;

        input.value = url;
        DOMUtils.DOM.fire(input, 'change');
      });
    };

    const sInsertLink = function (url) {
      return GeneralSteps.sequence([
        ui.sClickOnToolbar('click link button', 'div[aria-label="Insert/edit link"] > button'),
        ui.sWaitForPopup('wait for link dialog', 'div[aria-label="Insert link"][role="dialog"]'),
        sEnterUrl(url),
        ui.sClickOnUi('click ok button', 'button:contains("Ok")')
      ]);
    };

    const getDialogByElement = function (element) {
      return Arr.find(editor.windowManager.getWindows(), function (win) {
        return element.dom().id === win._id;
      });
    };

    const cAssertDialogContents = function (data) {
      return Chain.on(function (element, next, die) {
        getDialogByElement(element).fold(die, function (win) {
          Assertions.assertEq('asserting dialog contents', data, win.toJSON());
          next(Chain.wrap(element));
        });
      });
    };

    Pipeline.async({}, [
      Logger.t('doesn\'t add rel noopener stuff with allow_unsafe_link_target: true', GeneralSteps.sequence([
        api.sSetSetting('allow_unsafe_link_target', true),
        sInsertLink('http://www.google.com'),
        api.sAssertContentPresence({ 'a[rel="noopener"]': 0, 'a': 1 }),
        api.sSetContent('')
      ])),

      Logger.t('adds if allow_unsafe_link_target: false', GeneralSteps.sequence([
        api.sSetSetting('allow_unsafe_link_target', false),
        sInsertLink('http://www.google.com'),
        api.sAssertContentPresence({ 'a[rel="noopener"]': 1 }),
        api.sSetContent('')
      ])),

      Logger.t('...and if it\'s undefined', GeneralSteps.sequence([
        api.sSetSetting('allow_unsafe_link_target', undefined),
        sInsertLink('http://www.google.com'),
        api.sAssertContentPresence({ 'a[rel="noopener"]': 1 })
      ])),

      Logger.t('allow_unsafe_link_target=false: node filter normalizes and secures rel on SetContent', GeneralSteps.sequence([
        api.sSetSetting('allow_unsafe_link_target', false),
        api.sSetContent('<a href="http://www.google.com" target="_blank" rel="nofollow alternate">Google</a>'),
        api.sAssertContent('<p><a href="http://www.google.com" target="_blank" rel="alternate nofollow noopener">Google</a></p>'),
        api.sSetContent('')
      ])),

      Logger.t('allow_unsafe_link_target=false: proper option selected for defined rel_list', GeneralSteps.sequence([
        api.sSetSetting('allow_unsafe_link_target', false),
        api.sSetSetting('rel_list', [
          { title: 'Lightbox', value: 'lightbox' },
          { title: 'Test rel', value: 'alternate nofollow' },
          { title: 'Table of contents', value: 'toc' }
        ]),
        api.sSetContent('<a href="http://www.google.com" target="_blank" rel="nofollow alternate">Google</a>'),
        api.sSelect('p', [0]),
        ui.sClickOnToolbar('click link button', 'div[aria-label="Insert/edit link"] > button'),
        Chain.asStep({}, [
          ui.cWaitForPopup('wait for link dialog', 'div[aria-label="Insert link"][role="dialog"]'),
          cAssertDialogContents({
            text: 'Google',
            title: '',
            href: 'http://www.google.com',
            target: '_blank',
            rel: 'alternate nofollow noopener'
          })
        ]),
        ui.sClickOnUi('click ok button', 'button:contains("Ok")')
      ]))

    ], onSuccess, onFailure);
  }, {
UnitTest.asynctest('browser.tinymce.core.CaretContainerRemoveTest', function () {
  const success = arguments[arguments.length - 2];
  const failure = arguments[arguments.length - 1];
  const viewBlock = ViewBlock();

  if (!Env.ceFalse) {
    return;
  }

  const getRoot = function () {
    return viewBlock.get();
  };

  const setupHtml = function (html) {
    viewBlock.update(html);
  };

  const sTestRemove = Logger.t(
    'Remove',
    Step.sync(function () {
      setupHtml('<span contentEditable="false">1</span>');

      CaretContainer.insertInline(getRoot().firstChild, true);
      Assertions.assertEq('Should be inline container', true, CaretContainer.isCaretContainerInline(getRoot().firstChild));

      CaretContainerRemove.remove(getRoot().firstChild);
      Assertions.assertEq('Should not be inline container', false, CaretContainer.isCaretContainerInline(getRoot().firstChild));
    })
  );

  const sTestRemoveAndRepositionBlockAtOffset = Logger.t(
    'removeAndReposition block in same parent at offset',
    Step.sync(function () {
      setupHtml('<span contentEditable="false">1</span>');

      CaretContainer.insertBlock('p', getRoot().firstChild, true);
      Assertions.assertEq('Should be block container', true, CaretContainer.isCaretContainerBlock(getRoot().firstChild));

      const pos = CaretContainerRemove.removeAndReposition(getRoot().firstChild, new CaretPosition(getRoot(), 0));
      Assertions.assertEq('Should be unchanged offset', 0, pos.offset());
      Assertions.assertDomEq('Should be unchanged container', Element.fromDom(getRoot()), Element.fromDom(pos.container()));
      Assertions.assertEq('Should not be block container', false, CaretContainer.isCaretContainerBlock(getRoot().firstChild));
    })
  );

  const sTestRemoveAndRepositionBeforeOffset = Logger.t(
    'removeAndReposition block in same parent before offset',
    Step.sync(function () {
      setupHtml('<span contentEditable="false">1</span><span contentEditable="false">2</span>');

      CaretContainer.insertBlock('p', getRoot().childNodes[1], true);
      Assertions.assertEq('Should be block container', true, CaretContainer.isCaretContainerBlock(getRoot().childNodes[1]));

      const pos = CaretContainerRemove.removeAndReposition(getRoot().childNodes[1], new CaretPosition(getRoot(), 0));
      Assertions.assertEq('Should be unchanged offset', 0, pos.offset());
      Assertions.assertDomEq('Should be unchanged container', Element.fromDom(getRoot()), Element.fromDom(pos.container()));
      Assertions.assertEq('Should not be block container', false, CaretContainer.isCaretContainerBlock(getRoot().childNodes[1]));
    })
  );

  const sTestRemoveAndRepositionAfterOffset = Logger.t(
    'removeAndReposition block in same parent after offset',
    Step.sync(function () {
      setupHtml('<span contentEditable="false">1</span><span contentEditable="false">2</span>');

      CaretContainer.insertBlock('p', getRoot().childNodes[1], true);
      Assertions.assertEq('Should be block container', true, CaretContainer.isCaretContainerBlock(getRoot().childNodes[1]));

      const pos = CaretContainerRemove.removeAndReposition(getRoot().childNodes[1], new CaretPosition(getRoot(), 3));
      Assertions.assertEq('Should be changed offset', 2, pos.offset());
      Assertions.assertDomEq('Should be unchanged container', Element.fromDom(getRoot()), Element.fromDom(pos.container()));
      Assertions.assertEq('Should not be block container', false, CaretContainer.isCaretContainerBlock(getRoot().childNodes[1]));
    })
  );

  viewBlock.attach();
  Pipeline.async({}, [
    sTestRemove,
    sTestRemoveAndRepositionBlockAtOffset,
    sTestRemoveAndRepositionBeforeOffset,
    sTestRemoveAndRepositionAfterOffset
  ], function () {
    viewBlock.detach();
    success();
  }, failure);
});
    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((editor, onSuccess, onFailure) => {
    const tinyApis = TinyApis(editor);
    const tinyUi = TinyUi(editor);

    Pipeline.async({}, [
      Logger.t('no attributes without setting', GeneralSteps.sequence([
        tinyApis.sFocus,
        tinyUi.sClickOnMenu('click table menu', 'span:contains("Table")'),
        tinyUi.sClickOnUi('click table menu', 'div[role="menu"] span:contains("Table")'),
        tinyUi.sClickOnUi('click table grid', 'a#mcegrid11'),
        TableTestUtils.sAssertTableStructure(editor, ApproxStructure.build((s, str, arr) => {
          return s.element('table', {
            styles: {
              'width': str.is('100%'),
              'border-collapse': str.is('collapse')
            },
            attrs: {
              border: str.is('1')
            },
            children: [
              s.element('tbody', {
                children: [
                  s.element('tr', {
                    children: [
                      s.element('td', {
                        styles: {
                          width: str.is('100%')
                        },
                        children: [
                          s.element('br', {})
                        ]
                      })
                    ]
                  })
                ]
              })
            ]
          });
        })),
        tinyApis.sSetContent('')
      ])),

      Logger.t('test default title attribute', GeneralSteps.sequence([
        tinyApis.sFocus,
        tinyApis.sSetSetting('table_default_attributes', { title: 'x' }),
        tinyUi.sClickOnMenu('click table menu', 'span:contains("Table")'),
        tinyUi.sClickOnUi('click table menu', 'div[role="menu"] span:contains("Table")'),
        tinyUi.sClickOnUi('click table grid', 'a#mcegrid11'),
        TableTestUtils.sAssertTableStructure(editor, ApproxStructure.build((s, str, arr) => {
          return s.element('table', {
            styles: {
              'width': str.is('100%'),
              'border-collapse': str.is('collapse')
            },
            attrs: {
              border: str.none('Should not have the default border'),
              title: str.is('x')
            },
            children: [
              s.element('tbody', {
                children: [
                  s.element('tr', {
                    children: [
                      s.element('td', {
                        styles: {
                          width: str.is('100%')
                        },
                        children: [
                          s.element('br', {})
                        ]
                      })
                    ]
                  })
                ]
              })
            ]
          });
        })),
        tinyApis.sSetContent('')
      ]))
    ], onSuccess, onFailure);
  }, {
  TinyLoader.setup(function (editor, onSuccess, onFailure) {
    const ui = TinyUi(editor);
    const api = TinyApis(editor);

    const sAssertElementStructure = function (selector, expected) {
      return Step.sync(function () {
        const body = editor.getBody();
        body.normalize(); // consolidate text nodes

        Assertions.assertStructure(
          'Asserting HTML structure of the element: ' + selector,
          ApproxStructure.fromHtml(expected),
          SelectorFind.descendant(Element.fromDom(body), selector).getOrDie('Nothing in the Editor matches selector: ' + selector)
        );
      });
    };

    const cWaitForDialog = function (label) {
      // looking for dialogs by aria-label
      return ui.cWaitForPopup('wait for ' + label + ' dialog', 'div[aria-label="' + label + '"][role="dialog"]');
    };

    const cFakeEventOn = function (event) {
      return Chain.op(function (elm) {
        DOMUtils.DOM.fire(elm.dom(), event);
      });
    };

    Pipeline.async({}, [
      Logger.t('Table properties dialog (get data from plain table)', GeneralSteps.sequence([
        api.sSetContent('<table><tr><td>X</td></tr></table>'),
        api.sSetCursor([0, 0, 0], 0),
        api.sExecCommand('mceTableProps'),
        Chain.asStep({}, [
          cWaitForDialog('Table properties'),
          ui.cAssertDialogContents({
            align: '',
            border: '',
            caption: false,
            cellpadding: '',
            cellspacing: '',
            height: '',
            width: '',
            backgroundColor: '',
            borderColor: '',
            borderStyle: '',
            style: ''
          }),
          ui.cSubmitDialog()
        ])
      ])),

      Logger.t('Table properties dialog (get/set data from/to plain table, no adv tab)', GeneralSteps.sequence([
        api.sSetSetting('table_advtab', false),
        api.sSetContent('<table><tr><td>X</td></tr></table>'),
        api.sSetCursor([0, 0, 0], 0),
        api.sExecCommand('mceTableProps'),
        Chain.asStep({}, [
          cWaitForDialog('Table properties'),
          ui.cAssertDialogContents({
            align: '',
            border: '',
            caption: false,
            cellpadding: '',
            cellspacing: '',
            height: '',
            width: ''
          }),
          ui.cFillDialogWith({
            width: '100',
            height: '101'
          }),
          ui.cSubmitDialog()
        ]),
        sAssertElementStructure('table', '<table style="width: 100px; height: 101px;"><tbody><tr><td>X</td></tr></tbody></table>'),
        api.sDeleteSetting('table_advtab')
      ])),

      Logger.t('Table cell properties dialog (get/set data from/to plain table, no adv tab)', GeneralSteps.sequence([
        api.sSetSetting('table_cell_advtab', false),
        api.sSetContent('<table><tr><td>X</td></tr></table>'),
        api.sSetCursor([0, 0, 0], 0),
        api.sExecCommand('mceTableCellProps'),
        Chain.asStep({}, [
          cWaitForDialog('Cell properties'),
          ui.cAssertDialogContents({
            width: '',
            height: '',
            type: 'td',
            scope: '',
            align: '',
            valign: ''
          }),
          ui.cFillDialogWith({
            width: '100',
            height: '101'
          }),
          ui.cSubmitDialog()
        ]),
        sAssertElementStructure('table', '<table><tbody><tr><td style="width: 100px; height: 101px;">X</td></tr></tbody></table>'),
        api.sDeleteSetting('table_cell_advtab')
      ])),

      Logger.t('Table row properties dialog (get/set data from/to plain table, no adv tab)', GeneralSteps.sequence([
        api.sSetSetting('table_row_advtab', false),
        api.sSetContent('<table><tr><td>X</td></tr></table>'),
        api.sSetCursor([0, 0, 0], 0),
        api.sExecCommand('mceTableRowProps'),
        Chain.asStep({}, [
          cWaitForDialog('Row properties'),
          ui.cAssertDialogContents({
            type: 'tbody',
            align: '',
            height: ''
          }),
          ui.cFillDialogWith({
            width: '100',
            height: '101'
          }),
          ui.cSubmitDialog()
        ]),
        sAssertElementStructure('table', '<table><tbody><tr style="height: 101px;"><td>X</td></tr></tbody></table>'),
        api.sDeleteSetting('table_row_advtab')
      ])),

      Logger.t('Table properties dialog (get/set data from/to plain table, class list)', GeneralSteps.sequence([
        api.sSetSetting('table_class_list', [{ title: 'Class1', value: 'class1' }]),
        api.sSetContent('<table><tr><td>X</td></tr></table>'),
        api.sSetCursor([0, 0, 0], 0),
        api.sExecCommand('mceTableProps'),
        Chain.asStep({}, [
          Chain.fromParent(cWaitForDialog('Table properties'), [
            Chain.fromChains([
              ui.cAssertDialogContents({
                align: '',
                border: '',
                caption: false,
                cellpadding: '',
                cellspacing: '',
                height: '',
                width: '',
                backgroundColor: '',
                borderColor: '',
                borderStyle: '',
                style: '',
                class: ''
              })
            ]),
            Chain.fromChains([
              UiFinder.cFindIn('label:contains("Width") + input'),
              UiControls.cSetValue('100px'),
              cFakeEventOn('change')
            ]),
            Chain.fromChains([
              UiFinder.cFindIn('label:contains("Height") + input'),
              UiControls.cSetValue('101px'),
              cFakeEventOn('change')
            ]),
            Chain.fromChains([
              UiFinder.cFindIn('label:contains("Class") + div > button'),
              Mouse.cClick
            ])
          ])
        ]),
        Chain.asStep(TinyDom.fromDom(document.body), [
          UiFinder.cFindIn('div[role="menuitem"] > span:contains("Class1")'),
          Mouse.cClick
        ]),
        Chain.asStep({}, [
          cWaitForDialog('Table properties'),
          UiFinder.cFindIn('button:contains("Ok")'),
          Mouse.cClick
        ]),
        sAssertElementStructure('table', '<table class="class1" style="width: 100px; height: 101px;"><tbody><tr><td>X</td></tr></tbody></table>'),
        api.sDeleteSetting('table_class_list')
      ])),

      Logger.t('Table properties dialog (get data from full table)', GeneralSteps.sequence([
        api.sSetContent(
          '<table style="width: 100px; height: 101px;" border="4" cellspacing="2" cellpadding="3">' +
          '<caption>&nbsp;</caption>' +
          '<tbody>' +
          '<tr>' +
          '<td>&nbsp;</td>' +
          '</tr>' +
          '</tbody>' +
          '</table>'
        ),
        api.sSetCursor([0, 0, 0], 0),
        api.sExecCommand('mceTableProps'),
        Chain.asStep({}, [
          cWaitForDialog('Table properties'),
          ui.cAssertDialogContents({
            align: '',
            border: '4',
            caption: true,
            cellpadding: '3',
            cellspacing: '2',
            height: '101px',
            width: '100px',
            backgroundColor: '',
            borderColor: '',
            borderStyle: '',
            style: 'width: 100px; height: 101px;'
          }),
          ui.cSubmitDialog()
        ])
      ])),

      Logger.t('Table properties dialog (add caption)', GeneralSteps.sequence([
        api.sSetContent('<table><tr><td>X</td></tr></table>'),
        api.sSetCursor([0, 0, 0], 0),
        api.sExecCommand('mceTableProps'),
        Chain.asStep({}, [
          cWaitForDialog('Table properties'),
          ui.cFillDialogWith({
            caption: true
          }),
          ui.cSubmitDialog()
        ]),
        api.sAssertContent('<table><caption>&nbsp;</caption><tbody><tr><td>X</td></tr></tbody></table>')
      ])),

      Logger.t('Table properties dialog (remove caption)', GeneralSteps.sequence([
        api.sSetContent('<table><caption>&nbsp;</caption><tr><td>X</td></tr></table>'),
        api.sSetCursor([0, 0, 0], 0),
        api.sExecCommand('mceTableProps'),
        Chain.asStep({}, [
          cWaitForDialog('Table properties'),
          ui.cFillDialogWith({
            caption: false
          }),
          ui.cSubmitDialog()
        ]),
        sAssertElementStructure('table', '<table><tbody><tr><td>X</td></tr></tbody></table>')
      ])),

      Logger.t('Table properties dialog (change size in pixels)', GeneralSteps.sequence([
        api.sSetContent('<table><tr><td>X</td></tr></table>'),
        api.sSetCursor([0, 0, 0], 0),
        api.sExecCommand('mceTableProps'),
        Chain.asStep({}, [
          cWaitForDialog('Table properties'),
          ui.cFillDialogWith({
            width: 100,
            height: 101
          }),
          ui.cSubmitDialog()
        ]),
        sAssertElementStructure('table', '<table style="width: 100px; height: 101px;"><tbody><tr><td>X</td></tr></tbody></table>')
      ])),

      Logger.t('Table properties dialog (change size in %)', GeneralSteps.sequence([
        api.sSetContent('<table><tr><td>X</td></tr></table>'),
        api.sSetCursor([0, 0, 0], 0),
        api.sExecCommand('mceTableProps'),
        Chain.asStep({}, [
          cWaitForDialog('Table properties'),
          ui.cFillDialogWith({
            width: '100%',
            height: '101%'
          }),
          ui.cSubmitDialog()
        ]),
        sAssertElementStructure('table', '<table style="width: 100%; height: 101%;"><tbody><tr><td>X</td></tr></tbody></table>')
      ])),

      Logger.t('Table properties dialog (change: border,cellpadding,cellspacing,align,backgroundColor,borderColor)', GeneralSteps.sequence([
        api.sSetContent('<table style="border-color: red; background-color: blue"><tr><td>X</td></tr></table>'),
        api.sSetCursor([0, 0, 0], 0),
        api.sExecCommand('mceTableProps'),
        Chain.asStep({}, [
          cWaitForDialog('Table properties'),
          ui.cFillDialogWith({
            border: '1',
            cellpadding: '2',
            cellspacing: '3',
            align: 'right'
          }),
          ui.cSubmitDialog()
        ]),
        sAssertElementStructure(
          'table',
          '<table style="float: right; border-color: red; background-color: blue;" border="1" cellspacing="3" cellpadding="2">' +
          '<tbody><tr><td>X</td></tr></tbody></table>'
        )
      ])),

      Logger.t('Table properties dialog css border', GeneralSteps.sequence([
        api.sSetSetting('table_style_by_css', true),
        api.sSetContent('<table><tr><td>X</td><td>Z</td></tr></table>'),
        api.sSetCursor([0, 0, 0], 0),
        api.sExecCommand('mceTableProps'),
        Chain.asStep({}, [
          cWaitForDialog('Table properties'),
          ui.cFillDialogWith({
            border: '1',
            borderColor: 'green'
          }),
          ui.cSubmitDialog()
        ]),
        sAssertElementStructure(
          'table',
          '<table style=\"border-color: green; border-width: 1px;\" data-mce-border-color=\"green\" data-mce-border=\"1\">' +
          '<tbody><tr><td style=\"border-color: green; border-width: 1px;\">X</td>' +
          '<td style=\"border-color: green; border-width: 1px;\">Z</td></tr></tbody>' +
          '</table>'
        ),
        api.sDeleteSetting('table_style_by_css')
      ])),

      Logger.t('changing the style field on adv tab changes the height and width', GeneralSteps.sequence([
        api.sSetContent('<table><tr><td>X</td></tr></table>'),
        api.sSetCursor([0, 0, 0], 0),
        api.sExecCommand('mceTableProps'),
        Chain.asStep({}, [
          Chain.fromParent(cWaitForDialog('Table properties'),
            [
              Chain.fromChains([
                UiFinder.cFindIn('label:contains("Width") + input'),
                UiControls.cGetValue,
                Assertions.cAssertEq('should be changed automatically on style change', '')
              ]),
              Chain.fromChains([
                UiFinder.cFindIn('label:contains("Height") + input'),
                UiControls.cGetValue,
                Assertions.cAssertEq('should be changed automatically on style change', '')
              ]),
              Chain.fromChains([
                UiFinder.cFindIn('label:contains("Style") + input'),
                UiControls.cSetValue('width: 200px; height: 201px;'),
                cFakeEventOn('change')
              ]),
              Chain.fromChains([
                UiFinder.cFindIn('label:contains("Width") + input'),
                UiControls.cGetValue,
                Assertions.cAssertEq('should be changed automatically on style change', '200px')
              ]),
              Chain.fromChains([
                UiFinder.cFindIn('label:contains("Height") + input'),
                UiControls.cGetValue,
                Assertions.cAssertEq('should be changed automatically on style change', '201px')
              ]),
              Chain.fromChains([
                UiFinder.cFindIn('button:contains("Ok")'),
                Mouse.cClick
              ])
            ]
          )
        ]),
        sAssertElementStructure('table', '<table style="width: 200px; height: 201px;"><tbody><tr><td>X</td></tr></tbody></table>')
      ]))

    ], onSuccess, onFailure);
  }, {