Example #1
0
UnitTest.asynctest('browser.tinymce.core.selection.SelectionUtilsTest', 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 cHasAllContentsSelected = function (startPath, startOffset, endPath, endOffset) {
    return Chain.mapper(function (viewBlock) {
      const sc = Hierarchy.follow(Element.fromDom(viewBlock.get()), startPath).getOrDie();
      const ec = Hierarchy.follow(Element.fromDom(viewBlock.get()), endPath).getOrDie();
      const rng = document.createRange();

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

      return SelectionUtils.hasAllContentsSelected(Element.fromDom(viewBlock.get()), rng);
    });
  };

  viewBlock.attach();
  Pipeline.async({}, [
    Logger.t('All text is selected in paragraph', Chain.asStep(viewBlock, [
      cSetHtml('<p>a</p>'),
      cHasAllContentsSelected([0, 0], 0, [0, 0], 1),
      Assertions.cAssertEq('Should be true since all contents is selected', true)
    ])),
    Logger.t('All text is selected in paragraph', Chain.asStep(viewBlock, [
      cSetHtml('<p>ab</p>'),
      cHasAllContentsSelected([0, 0], 0, [0, 0], 2),
      Assertions.cAssertEq('Should be true since all contents is selected', true)
    ])),
    Logger.t('All text is selected in paragraph and sub element', Chain.asStep(viewBlock, [
      cSetHtml('<p>a<b>b</b></p>'),
      cHasAllContentsSelected([0, 0], 0, [0, 1, 0], 1),
      Assertions.cAssertEq('Should be true since all contents is selected', true)
    ])),
    Logger.t('All text is selected in paragraph and with traling br', Chain.asStep(viewBlock, [
      cSetHtml('<p>a<br></p>'),
      cHasAllContentsSelected([0, 0], 0, [0, 0], 1),
      Assertions.cAssertEq('Should be true since all contents is selected', true)
    ])),
    Logger.t('Collapsed range in paragraph', Chain.asStep(viewBlock, [
      cSetHtml('<p>a</p>'),
      cHasAllContentsSelected([0, 0], 0, [0, 0], 0),
      Assertions.cAssertEq('Should be false since only some contents is selected', false)
    ])),
    Logger.t('Partial text selection in paragraph', Chain.asStep(viewBlock, [
      cSetHtml('<p>ab</p>'),
      cHasAllContentsSelected([0, 0], 0, [0, 0], 1),
      Assertions.cAssertEq('Should be false since only some contents is selected', false)
    ])),
    Logger.t('Partial text selection in paragraph', Chain.asStep(viewBlock, [
      cSetHtml('<p>ab</p>'),
      cHasAllContentsSelected([0, 0], 1, [0, 0], 2),
      Assertions.cAssertEq('Should be false since only some contents is selected', false)
    ])),
    Logger.t('Partial mixed selection in paragraph', Chain.asStep(viewBlock, [
      cSetHtml('<p>a<b>bc</b></p>'),
      cHasAllContentsSelected([0, 0], 1, [0, 1, 0], 1),
      Assertions.cAssertEq('Should be false since only some contents is selected', false)
    ]))
  ], function () {
    viewBlock.detach();
    success();
  }, failure);
});
Example #2
0
  TinyLoader.setup(function (editor, onSuccess, onFailure) {
    const tinyApis = TinyApis(editor);
    const body = Element.fromDom(editor.getBody());

    Pipeline.async({}, [
      tinyApis.sFocus,
      Logger.t('Enter in paragraph', GeneralSteps.sequence([
        Logger.t('Insert block before', GeneralSteps.sequence([
          tinyApis.sSetContent('<p>ab</p>'),
          tinyApis.sSetCursor([0, 0], 0),
          sInsertNewline(editor, { }),
          tinyApis.sAssertContent('<p>&nbsp;</p><p>ab</p>'),
          tinyApis.sAssertSelection([1, 0], 0, [1, 0], 0)
        ])),
        Logger.t('Split block in the middle', GeneralSteps.sequence([
          tinyApis.sSetContent('<p>ab</p>'),
          tinyApis.sSetCursor([0, 0], 1),
          sInsertNewline(editor, { }),
          tinyApis.sAssertContent('<p>a</p><p>b</p>'),
          tinyApis.sAssertSelection([1, 0], 0, [1, 0], 0)
        ])),
        Logger.t('Insert block after', GeneralSteps.sequence([
          tinyApis.sSetContent('<p>ab</p>'),
          tinyApis.sSetCursor([0, 0], 2),
          sInsertNewline(editor, { }),
          tinyApis.sAssertContent('<p>ab</p><p>&nbsp;</p>'),
          tinyApis.sAssertSelection([1], 0, [1], 0)
        ])),
        Logger.t('Insert block after bookmark', GeneralSteps.sequence([
          tinyApis.sSetRawContent(`<p>${bookmarkSpan}<br data-mce-bogus="1"></p>`),
          tinyApis.sSetCursor([0], 1),
          sInsertNewline(editor, { }),
          Assertions.sAssertStructure('Content should only have one bookmark span', ApproxStructure.build((s, str) => {
            return s.element('body', {
              children: [
                s.element('p', {
                  children: [
                    ApproxStructure.fromHtml(bookmarkSpan),
                    s.element('br', {
                      attrs: {
                        'data-mce-bogus': str.is('1')
                      }
                    })
                  ]
                }),
                s.element('p', {
                  children: [
                    s.element('br', {
                      attrs: {
                        'data-mce-bogus': str.is('1')
                      }
                    })
                  ]
                })
              ]
            });
          }), body),
          tinyApis.sAssertSelection([1], 0, [1], 0)
        ]))
      ])),
      Logger.t('br_newline_selector', GeneralSteps.sequence([
        tinyApis.sSetSetting('br_newline_selector', 'p,div.test'),
        Logger.t('Insert newline where br is forced', GeneralSteps.sequence([
          tinyApis.sSetContent('<p>ab</p>'),
          tinyApis.sSetCursor([0, 0], 1),
          sInsertNewline(editor, { }),
          tinyApis.sNodeChanged,
          tinyApis.sAssertContent('<p>a<br />b</p>')
        ])),
        Logger.t('Insert newline where br is forced', GeneralSteps.sequence([
          tinyApis.sSetContent('<div class="test">ab</div>'),
          tinyApis.sSetCursor([0, 0], 1),
          sInsertNewline(editor, { }),
          tinyApis.sNodeChanged,
          tinyApis.sAssertContent('<div class="test">a<br />b</div>')
        ])),
        Logger.t('Insert newline where br is not forced', GeneralSteps.sequence([
          tinyApis.sSetContent('<div>ab</div>'),
          tinyApis.sSetCursor([0, 0], 1),
          sInsertNewline(editor, { }),
          tinyApis.sNodeChanged,
          tinyApis.sAssertContent('<div>a</div><div>b</div>')
        ])),
        tinyApis.sDeleteSetting('br_newline_selector')
      ])),
      Logger.t('no_newline_selector', GeneralSteps.sequence([
        tinyApis.sSetSetting('no_newline_selector', [ 'p,div.test' ]),
        Logger.t('Insert newline where newline is blocked', GeneralSteps.sequence([
          tinyApis.sSetContent('<p>ab</p>'),
          tinyApis.sSetCursor([0, 0], 1),
          sInsertNewline(editor, { }),
          tinyApis.sNodeChanged,
          tinyApis.sAssertContent('<p>ab</p>')
        ])),
        Logger.t('Insert newline where newline is blocked', GeneralSteps.sequence([
          tinyApis.sSetContent('<div class="test">ab</div>'),
          tinyApis.sSetCursor([0, 0], 1),
          sInsertNewline(editor, { }),
          tinyApis.sNodeChanged,
          tinyApis.sAssertContent('<div class="test">ab</div>')
        ])),
        Logger.t('Insert newline where newline is not blocked', GeneralSteps.sequence([
          tinyApis.sSetContent('<div>ab</div>'),
          tinyApis.sSetCursor([0, 0], 1),
          sInsertNewline(editor, { }),
          tinyApis.sNodeChanged,
          tinyApis.sAssertContent('<div>a</div><div>b</div>')
        ])),
        tinyApis.sDeleteSetting('no_newline_selector')
      ])),
      Logger.t('Insert newline before image in link', GeneralSteps.sequence([
        tinyApis.sSetContent('<p><a href="#">a<img src="about:blank" /></a></p>'),
        tinyApis.sSetCursor([0, 0], 1),
        sInsertNewline(editor, { }),
        tinyApis.sAssertContent('<p><a href="#">a</a></p><p><a href="#"><img src="about:blank" /></a></p>'),
        // For some bizarre IE issue getSelection().addRange() creates a zwsp from nowhere and moves the caret after it
        browser.isIE() ? tinyApis.sAssertSelection([1, 0, 0], 1, [1, 0, 0], 1) : tinyApis.sAssertSelection([1, 0], 0, [1, 0], 0)
      ]))
    ], onSuccess, onFailure);
  }, {
Example #3
0
  TinyLoader.setup(function (editor, onSuccess, onFailure) {
    const api = TinyApis(editor);
    const ui = TinyUi(editor);

    const sInitAndOpenDialog = (content: string, cursorPos: any) => {
      return GeneralSteps.sequence([
        api.sSetSetting('image_advtab', true),
        api.sSetSetting('image_dimensions', false),
        api.sSetContent(content),
        // api.sSetCursor([0], 1),
        api.sSetCursor(cursorPos.elementPath, cursorPos.offset),
        api.sExecCommand('mceImage', true),
        ui.sWaitForPopup('Wait for Image dialog', 'div[role="dialog"]'),
      ]);
    };

    const createTestWithContent = (name: string, content: string, cursorPos: any, data: Partial<ImageDialogData>, expectedContent: string) => {
      return Log.stepsAsStep('TBA', 'Image: ' + name, [
        sInitAndOpenDialog(content, cursorPos),
        Chain.asStep({}, [
          cFillActiveDialog(data, true)
        ]),
        ui.sClickOnUi('click save', 'div[role="dialog"] button:contains("Save")'),
        api.sAssertContent(expectedContent)
      ]);
    };

    const createTestOnEmptyEditor = (name: string, data: Partial<ImageDialogData>, expectedContent: string) => {
      return createTestWithContent(name, '', { elementPath: [0], offset: 0 }, data, expectedContent);
    };

    const createTestUpdatedStyle = (name: string, style: string, assertion: Step<any, any>) => {
      return Log.stepsAsStep('TBA', 'Image: ' + name, [
        sInitAndOpenDialog('', { elementPath: [0], offset: 0 }),
        ui.sClickOnUi('Switch to Advanced tab', '.tox-tab:contains("Advanced")'),
        Chain.asStep(Body.body(), [
          cSetInputValue(advancedTabSelectors.style, style)
        ]),
        assertion,
        ui.sClickOnUi('click save', 'div[role="dialog"] button:contains("Save")'),
      ]);
    };

    const suiteArr = [
      createTestOnEmptyEditor(
        'Advanced image dialog margin space options on empty editor',
        {
          alt: 'alt',
          hspace: '10',
          src: {
            value: 'src',
          },
          vspace: '10'
        },
        '<p><img style="margin: 10px;" src="src" alt="alt" /></p>'
      ),
      createTestOnEmptyEditor(
        'Advanced image dialog border style only options on empty editor',
        {
          alt: 'alt',
          src: {
            value: 'src'
          },
          style: 'border-width: 10px; border-style: solid;'
        },
        '<p><img style="border-width: 10px; border-style: solid;" src="src" alt="alt" /></p>'
      ),
      createTestOnEmptyEditor(
        'Advanced image dialog margin style only options on empty editor',
        {
          alt: 'alt',
          src: {
            value: 'src'
          },
          style: 'margin: 10px;'
        },
        '<p><img style="margin: 10px;" src="src" alt="alt" /></p>'
      ),
      createTestOnEmptyEditor(
        'Advanced image dialog overriden border style options on empty editor',
        {
          alt: 'alt',
          border: '10',
          src: {
            value: 'src'
          },
          style: 'border-width: 15px;'
        },
        '<p><img style="border-width: 10px;" src="src" alt="alt" /></p>'
      ),
      createTestOnEmptyEditor(
        'Advanced image dialog overriden margin style options on empty editor',
        {
          alt: 'alt',
          hspace: '10',
          src: {
            value: 'src'
          },
          style: 'margin-left: 15px; margin-top: 20px;',
          vspace: '10'
        },
        '<p><img style="margin: 10px;" src="src" alt="alt" /></p>'
      ),
      createTestWithContent(
        'Advanced image dialog border option on editor with content',
        '<p>a</p>',
        {
          elementPath: [0],
          offset: 1
        },
        {
          alt: 'alt',
          border: '10',
          borderstyle: 'dashed',
          src: {
            value: 'src'
          }
        },
        '<p>a<img style="border-width: 10px; border-style: dashed;" src="src" alt="alt" /></p>'),
      createTestUpdatedStyle(
        'Advanced image dialog non-shorthand horizontal margin style change test',
        'margin-left: 15px; margin-right: 15px;',
        Chain.asStep({}, [
          cAssertInputValue(advancedTabSelectors.vspace, ''),
          cAssertInputValue(advancedTabSelectors.hspace, '15'),
          cAssertInputValue(advancedTabSelectors.style, 'margin-left: 15px; margin-right: 15px;')
        ])
      ),
      createTestUpdatedStyle(
        'Advanced image dialog non-shorthand vertical margin style change test',
        'margin-top: 15px; margin-bottom: 15px;',
        Chain.asStep({}, [
          cAssertInputValue(advancedTabSelectors.vspace, '15'),
          cAssertInputValue(advancedTabSelectors.hspace, ''),
          cAssertInputValue(advancedTabSelectors.style, 'margin-top: 15px; margin-bottom: 15px;')
        ])
      ),
      createTestUpdatedStyle(
        'Advanced image dialog shorthand margin 1 value style change test',
        'margin: 5px;',
        Chain.asStep({}, [
          cAssertInputValue(advancedTabSelectors.vspace, '5'),
          cAssertInputValue(advancedTabSelectors.hspace, '5'),
          cAssertInputValue(advancedTabSelectors.style, 'margin: 5px;')
        ])
      ),
      createTestUpdatedStyle(
        'Advanced image dialog shorthand margin 2 value style change test',
        'margin: 5px 10px;',
        Chain.asStep({}, [
          cAssertInputValue(advancedTabSelectors.vspace, '5'),
          cAssertInputValue(advancedTabSelectors.hspace, '10'),
          cAssertInputValue(advancedTabSelectors.style, 'margin: 5px 10px 5px 10px;')
        ])
      ),
      createTestUpdatedStyle(
        'Advanced image dialog shorthand margin 3 value style change test',
        'margin: 5px 10px 15px;',
        Chain.asStep({}, [
          cAssertInputValue(advancedTabSelectors.vspace, ''),
          cAssertInputValue(advancedTabSelectors.hspace, '10'),
          cAssertInputValue(advancedTabSelectors.style, 'margin: 5px 10px 15px 10px;')
        ])
      ),
      createTestUpdatedStyle(
        'Advanced image dialog shorthand margin 4 value style change test',
        'margin: 5px 10px 15px 20px;',
        Chain.asStep({}, [
          cAssertInputValue(advancedTabSelectors.vspace, ''),
          cAssertInputValue(advancedTabSelectors.hspace, ''),
          cAssertInputValue(advancedTabSelectors.style, 'margin: 5px 10px 15px 20px;')
        ])
      ),
      createTestUpdatedStyle(
        'Advanced image dialog shorthand margin 4 value style change test',
        'margin: 5px 10px 15px 20px; margin-top: 15px;',
        Chain.asStep({}, [
          cAssertInputValue(advancedTabSelectors.vspace, '15'),
          cAssertInputValue(advancedTabSelectors.hspace, ''),
          cAssertInputValue(advancedTabSelectors.style, 'margin: 15px 10px 15px 20px;')
        ])
      )
    ];
    Pipeline.async({}, suiteArr, onSuccess, onFailure);
  }, {
Example #4
0
 TinyLoader.setup(function (editor, onSuccess, onFailure) {
   Pipeline.async({}, Log.steps('TBA', 'Media: Test media content formats', suite.toSteps(editor)), onSuccess, onFailure);
 }, {
UnitTest.asynctest('browser.tinymce.core.dom.ElementTypeTest', function () {
  const success = arguments[arguments.length - 2];
  const failure = arguments[arguments.length - 1];

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

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

  Pipeline.async({}, [
    Logger.t('Check block elements', GeneralSteps.sequence([
      sCheckElement('p', ElementType.isBlock, true),
      sCheckElement('h1', ElementType.isBlock, true),
      sCheckElement('table', ElementType.isBlock, true),
      sCheckElement('span', ElementType.isBlock, false),
      sCheckElement('b', ElementType.isBlock, false),
      sCheckText(ElementType.isBlock)
    ])),
    Logger.t('Check inline elements', GeneralSteps.sequence([
      sCheckElement('b', ElementType.isInline, true),
      sCheckElement('span', ElementType.isInline, true),
      sCheckElement('p', ElementType.isInline, false),
      sCheckElement('h1', ElementType.isInline, false),
      sCheckText(ElementType.isInline)
    ])),
    Logger.t('Check heading elements', GeneralSteps.sequence([
      sCheckElement('h1', ElementType.isHeading, true),
      sCheckElement('h2', ElementType.isHeading, true),
      sCheckElement('span', ElementType.isHeading, false),
      sCheckElement('table', ElementType.isHeading, false),
      sCheckText(ElementType.isHeading)
    ])),
    Logger.t('Check text block elements', GeneralSteps.sequence([
      sCheckElement('p', ElementType.isTextBlock, true),
      sCheckElement('h1', ElementType.isTextBlock, true),
      sCheckElement('table', ElementType.isTextBlock, false),
      sCheckText(ElementType.isTextBlock)
    ])),
    Logger.t('Check void elements', GeneralSteps.sequence([
      sCheckElement('img', ElementType.isVoid, true),
      sCheckElement('hr', ElementType.isVoid, true),
      sCheckElement('h1', ElementType.isVoid, false),
      sCheckElement('span', ElementType.isVoid, false),
      sCheckText(ElementType.isVoid)
    ])),
    Logger.t('Check table cell elements', GeneralSteps.sequence([
      sCheckElement('th', ElementType.isTableCell, true),
      sCheckElement('td', ElementType.isTableCell, true),
      sCheckElement('h1', ElementType.isTableCell, false),
      sCheckElement('span', ElementType.isTableCell, false),
      sCheckText(ElementType.isTableCell)
    ])),
    Logger.t('Check br elements', GeneralSteps.sequence([
      sCheckElement('br', ElementType.isBr, true),
      sCheckElement('b', ElementType.isBr, false),
      sCheckText(ElementType.isBr)
    ])),
    Logger.t('Check list item elements', GeneralSteps.sequence([
      sCheckElement('br', ElementType.isListItem, false),
      sCheckElement('div', ElementType.isListItem, false),
      sCheckElement('li', ElementType.isListItem, true),
      sCheckElement('dd', ElementType.isListItem, true),
      sCheckElement('dt', ElementType.isListItem, true),
      sCheckText(ElementType.isListItem)
    ])),
    Logger.t('Check list elements', GeneralSteps.sequence([
      sCheckElement('br', ElementType.isList, false),
      sCheckElement('div', ElementType.isList, false),
      sCheckElement('ul', ElementType.isList, true),
      sCheckElement('ol', ElementType.isList, true),
      sCheckElement('dl', ElementType.isList, true),
      sCheckText(ElementType.isList)
    ])),
    Logger.t('Check table section elements', GeneralSteps.sequence([
      sCheckElement('br', ElementType.isTableSection, false),
      sCheckElement('div', ElementType.isTableSection, false),
      sCheckElement('thead', ElementType.isTableSection, true),
      sCheckElement('tbody', ElementType.isTableSection, true),
      sCheckElement('tfoot', ElementType.isTableSection, true),
      sCheckText(ElementType.isTableSection)
    ]))
  ], function () {
    success();
  }, failure);
});
UnitTest.asynctest('browser.tinymce.core.dom.ParentsTest', function () {
  const success = arguments[arguments.length - 2];
  const failure = arguments[arguments.length - 1];

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

      Logger.t('parentsAndSelf scoped', Chain.asStep({}, [
        cCreateStructure('<p><b><i><u>a</u></i></b></p>'),
        cParentsAndSelf([0, 0, 0, 0], [0]),
        cAssertElementNames(['#text', 'u', 'i'])
      ]))
    ]))
  ], function () {
    success();
  }, failure);
});
Example #7
0
UnitTest.asynctest('browser.tinymce.core.AddOnManagerTest', (success, failure) => {
  const suite = LegacyUnit.createSuite();
  let languagePackUrl;

  const patch = function (proto, name, patchFunc) {
    let originalFunc = proto[name];
    let originalFuncs = proto.__originalFuncs;

    if (!originalFuncs) {
      proto.__originalFuncs = originalFuncs = {};
    }

    if (!originalFuncs[name]) {
      originalFuncs[name] = originalFunc;
    } else {
      originalFunc = originalFuncs[name];
    }

    proto[name] = function () {
      const args = Array.prototype.slice.call(arguments);
      args.unshift(originalFunc);
      return patchFunc.apply(this, args);
    };
  };

  const unpatch = function (proto, name?) {
    const originalFuncs = proto.__originalFuncs;

    if (!originalFuncs) {
      return;
    }

    if (name) {
      proto[name] = originalFuncs[name];
      delete originalFuncs[name];
    } else {
      for (const key in originalFuncs) {
        proto[key] = originalFuncs[key];
      }

      delete proto.__originalFuncs;
    }
  };

  const getLanguagePackUrl = function (code, languages?) {
    languagePackUrl = null;
    I18n.setCode(code);
    PluginManager.requireLangPack('plugin', languages);
    return languagePackUrl;
  };

  suite.test('requireLangPack', function () {
    AddOnManager.PluginManager.urls.plugin = '/root';

    LegacyUnit.equal(getLanguagePackUrl('sv_SE'), '/root/langs/sv_SE.js');
    LegacyUnit.equal(getLanguagePackUrl('sv_SE', 'sv,en,us'), '/root/langs/sv.js');
    LegacyUnit.equal(getLanguagePackUrl('sv_SE', 'sv_SE,en_US'), '/root/langs/sv_SE.js');
    LegacyUnit.equal(getLanguagePackUrl('sv'), '/root/langs/sv.js');
    LegacyUnit.equal(getLanguagePackUrl('sv', 'sv'), '/root/langs/sv.js');
    LegacyUnit.equal(getLanguagePackUrl('sv', 'sv,en,us'), '/root/langs/sv.js');
    LegacyUnit.equal(getLanguagePackUrl('sv', 'en,sv,us'), '/root/langs/sv.js');
    LegacyUnit.equal(getLanguagePackUrl('sv', 'en,us,sv'), '/root/langs/sv.js');
    LegacyUnit.strictEqual(getLanguagePackUrl('sv', 'en,us'), null);

    AddOnManager.languageLoad = false;
    LegacyUnit.strictEqual(getLanguagePackUrl('sv', 'sv'), null);
  });

  patch(ScriptLoader.ScriptLoader, 'add', function (origFunc, url) {
    languagePackUrl = url;
  });

  Pipeline.async({}, suite.toSteps({}), function () {
    success();
    unpatch(ScriptLoader.ScriptLoader);
  }, failure);
});
Example #8
0
 TinyLoader.setup(function (editor, onSuccess, onFailure) {
   // This is a weird test that only checks that the skinloaded event is fired even if skin is set to false
   Pipeline.async({}, [
   ], onSuccess, onFailure);
 }, {
  TinyLoader.setup(function (editor, onSuccess, onFailure) {
    const tinyApis = TinyApis(editor);
    const tinyActions = TinyActions(editor);
    let count;

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

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

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

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

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

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

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

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

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

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

      Logger.t('Normalization during operations with modifier keys, should run only once in the end when user releases modifier key.', GeneralSteps.sequence([
        sResetNormalizeCounter(),
        tinyApis.sSetContent('<p><b>a</b><i>a</i></p>'),
        tinyApis.sSetSelection([0, 0, 0], 0, [0, 0], 0),
        Keyboard.sKeyup(Element.fromDom(editor.getDoc()), Keys.left(), { shift: true }),
        sAssertNormalizeCounter(0),
        Keyboard.sKeyup(Element.fromDom(editor.getDoc()), 17, {}), // single ctrl
        sAssertNormalizeCounter(1),
        tinyApis.sAssertSelection([0, 0], 0, [0, 0], 0)
      ]))
    ], onSuccess, onFailure);
  }, {
Example #10
0
  TinyLoader.setup(function (editor: Editor, onSuccess, onFailure) {
    const tinyApis = TinyApis(editor);

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

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

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

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

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

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

      Step.wait(1000),
      sClearChanges,

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    Pipeline.async({}, [
      tinyApis.sFocus,
      sTestChanges
    ], onSuccess, onFailure);
  }, {
 TinyLoader.setup(function (editor, onSuccess, onFailure) {
   Pipeline.async({}, [
     Logger.t('Branding element should not exist', UiFinder.sNotExists(Element.fromDom(editor.getContainer()), '.mce-branding'))
   ], onSuccess, onFailure);
 }, {
UnitTest.asynctest('browser.tinymce.core.html.NodeTest', function () {
  const success = arguments[arguments.length - 2];
  const failure = arguments[arguments.length - 1];
  const suite = LegacyUnit.createSuite();

  const ok = function (value, label?) {
    return LegacyUnit.equal(value, true, label);
  };

  suite.test('construction', function () {
    let node;

    node = new Node('#text', 3);
    LegacyUnit.equal(node.name, '#text');
    LegacyUnit.equal(node.type, 3);

    node = new Node('#comment', 8);
    LegacyUnit.equal(node.name, '#comment');
    LegacyUnit.equal(node.type, 8);

    node = new Node('b', 1);
    LegacyUnit.equal(node.name, 'b');
    LegacyUnit.equal(node.type, 1);
    LegacyUnit.deepEqual(node.attributes, []);

    node = new Node('#pi', 7);
    LegacyUnit.equal(node.name, '#pi');
    LegacyUnit.equal(node.type, 7);

    node = new Node('#doctype', 10);
    LegacyUnit.equal(node.name, '#doctype');
    LegacyUnit.equal(node.type, 10);

    node = new Node('#cdata', 4);
    LegacyUnit.equal(node.name, '#cdata');
    LegacyUnit.equal(node.type, 4);

    node = new Node('#frag', 11);
    LegacyUnit.equal(node.name, '#frag');
    LegacyUnit.equal(node.type, 11);
  });

  suite.test('append inside empty node', function () {
    let root, node;

    root = new Node('#frag', 11);
    node = root.append(new Node('b', 1));
    LegacyUnit.equal(root.firstChild.parent === root, true);
    LegacyUnit.equal(root.firstChild.next, undefined);
    LegacyUnit.equal(root.firstChild.prev, undefined);
    LegacyUnit.equal(root.firstChild.firstChild, undefined);
    LegacyUnit.equal(root.firstChild.lastChild, undefined);
    LegacyUnit.equal(node.parent === root, true);
    LegacyUnit.equal(node.next, undefined);
    LegacyUnit.equal(node.prev, undefined);
    LegacyUnit.equal(node.firstChild, undefined);
    LegacyUnit.equal(node.lastChild, undefined);
  });

  suite.test('append node after node', function () {
    let root, node, node2;

    root = new Node('#frag', 11);
    node2 = root.append(new Node('a', 1));
    node = root.append(new Node('b', 1));
    ok(root.firstChild.parent === root, 'root.firstChild.parent === root');
    ok(root.firstChild === node2, 'root.firstChild');
    ok(root.lastChild === node, 'root.firstChild');
    ok(root.firstChild.next === node, 'root.firstChild.next');
    LegacyUnit.equal(root.firstChild.prev, undefined, 'root.firstChild.prev');
    LegacyUnit.equal(root.firstChild.firstChild, undefined, 'root.firstChild.firstChild');
    LegacyUnit.equal(root.firstChild.lastChild, undefined, 'root.firstChild.lastChild');
    ok(node2.parent === root, 'node2.parent === root');
    ok(node2.next === node, 'node2.next');
    LegacyUnit.equal(node2.prev, undefined, 'node2.prev');
    LegacyUnit.equal(node2.firstChild, undefined, 'node2.firstChild');
    LegacyUnit.equal(node2.lastChild, undefined, 'node2.lastChild');
    ok(node.parent === root, 'node.parent === root');
    LegacyUnit.equal(node.next, undefined, 'node.next');
    ok(node.prev === node2, 'node.prev');
    LegacyUnit.equal(node.firstChild, undefined, 'node.firstChild');
    LegacyUnit.equal(node.lastChild, undefined, 'node.lastChild');
  });

  suite.test('append existing node before other existing node', function () {
    let root, node, node2;

    root = new Node('#frag', 11);
    node = root.append(new Node('a', 1));
    node2 = root.append(new Node('b', 1));
    root.append(node);
    ok(root.firstChild === node2, 'root.firstChild');
    ok(root.lastChild === node, 'root.lastChild');
    LegacyUnit.equal(node.next, null, 'node.next');
    ok(node.prev === node2, 'node.prev');
    ok(node.parent === root, 'node.parent');
    ok(node2.parent === root, 'node2.parent');
    LegacyUnit.equal(node2.prev, undefined, 'node2.prev');
    ok(node2.next === node, 'node2.next');
  });

  suite.test('remove unattached node', function () {
    ok(!new Node('#text', 3).remove().parent);
  });

  suite.test('remove single child', function () {
    let root, node;

    root = new Node('#frag', 11);
    node = root.append(new Node('p', 1));
    node = root.firstChild.remove();
    LegacyUnit.equal(root.firstChild, undefined);
    LegacyUnit.equal(root.lastChild, undefined);
    LegacyUnit.equal(node.parent, null);
    LegacyUnit.equal(node.next, null);
    LegacyUnit.equal(node.prev, null);
    LegacyUnit.equal(node.name, 'p');
  });

  suite.test('remove middle node', function () {
    let root, node, node2, node3;

    root = new Node('#frag', 11);
    node = root.append(new Node('a', 1));
    node2 = root.append(new Node('b', 1));
    node3 = root.append(new Node('c', 1));
    node2.remove();
    LegacyUnit.equal(node2.parent, null);
    LegacyUnit.equal(node2.next, null);
    LegacyUnit.equal(node2.prev, null);
    ok(root.firstChild === node, 'root.firstChild');
    ok(root.lastChild === node3, 'root.lastChild');
    ok(node.next === node3, 'node.next');
    LegacyUnit.equal(node.prev, undefined, 'node.prev');
    LegacyUnit.equal(node3.prev === node, true, 'node3.prev');
    LegacyUnit.equal(node3.next, undefined, 'node3.next');
  });

  suite.test('insert after last', function () {
    let fragment, root, node, node2;

    fragment = new Node('#frag', 11);
    root = fragment.append(new Node('body', 1));
    node = root.append(new Node('a', 1));
    node2 = root.insert(new Node('x', 1), node);
    ok(root.firstChild === node, 'root.firstChild');
    ok(root.lastChild === node2, 'root.lastChild');
    ok(node.next === node2, 'node.next');
    ok(node2.prev === node, 'node2.prev');
    ok(node2.parent === root, 'node3.next');
  });

  suite.test('insert before first', function () {
    let fragment, root, node, node2;

    fragment = new Node('#frag', 11);
    root = fragment.append(new Node('body', 1));
    node = root.append(new Node('a', 1));
    node2 = root.insert(new Node('x', 1), node, true);
    ok(root.firstChild === node2, 'root.firstChild');
    ok(root.lastChild === node, 'root.lastChild');
    ok(node2.parent === root, 'node2.lastChild');
    ok(node2.next === node, 'node2.next');
    ok(node2.prev === undefined, 'node2.prev');
    ok(node.parent === root, 'node.lastChild');
    ok(node.next === undefined, 'node.next');
    ok(node.prev === node2, 'node.prev');
  });

  suite.test('insert before second', function () {
    let fragment, root, node, node2, node3;

    fragment = new Node('#frag', 11);
    root = fragment.append(new Node('body', 1));
    node = root.append(new Node('a', 1));
    node2 = root.append(new Node('b', 1));
    node3 = root.insert(new Node('x', 1), node2, true);
    ok(root.firstChild === node, 'root.firstChild');
    ok(root.lastChild === node2, 'root.lastChild');
    ok(node3.parent === root, 'node3.parent');
    ok(node3.next === node2, 'node3.next');
    ok(node3.prev === node, 'node3.prev');
  });

  suite.test('insert after and between two nodes', function () {
    let root, node, node2, node3, fragment;

    fragment = new Node('#frag', 11);
    root = fragment.append(new Node('body', 1));
    node = root.append(new Node('a', 1));
    node2 = root.append(new Node('b', 1));
    node3 = root.insert(new Node('x', 1), node);
    ok(root.firstChild === node, 'root.firstChild');
    ok(root.lastChild === node2, 'root.lastChild');
    ok(node.next === node3, 'node.next');
    ok(node2.prev === node3, 'node2.prev');
    ok(node3.parent === root, 'node3.next');
    ok(node3.next === node2, 'node3.next');
    ok(node3.prev === node, 'node3.prev');
  });

  suite.test('replace single child', function () {
    let root, node1, node2;

    root = new Node('#frag', 11);
    node1 = root.append(new Node('b', 1));
    node2 = root.append(new Node('em', 1));
    node1.replace(node2);
    ok(root.firstChild === node2, 'root.firstChild');
    ok(root.lastChild === node2, 'root.lastChild');
    ok(node2.parent === root, 'node2.parent');
    ok(!node2.next, 'node2.next');
    ok(!node2.prev, 'node2.prev');
  });

  suite.test('replace first child', function () {
    let root, node1, node2, node3;

    root = new Node('#frag', 11);
    node1 = root.append(new Node('b', 1));
    node2 = root.append(new Node('em', 1));
    node3 = root.append(new Node('b', 1));
    node1.replace(node2);
    ok(root.firstChild === node2, 'root.firstChild');
    ok(root.lastChild === node3, 'root.lastChild');
    ok(node2.parent === root, 'node2.parent');
    ok(node2.next === node3, 'node2.next');
    ok(!node2.prev, 'node2.prev');
  });

  suite.test('replace last child', function () {
    let root, node1, node2, node3;

    root = new Node('#frag', 11);
    node1 = root.append(new Node('b', 1));
    node3 = root.append(new Node('b', 1));
    node2 = root.append(new Node('em', 1));
    node3.replace(node2);
    ok(root.firstChild === node1, 'root.firstChild');
    ok(root.lastChild === node2, 'root.lastChild');
    ok(node2.parent === root, 'node2.parent');
    ok(!node2.next, 'node2.next');
    ok(node2.prev === node1, 'node2.prev');
  });

  suite.test('replace middle child', function () {
    let root, node1, node2, node3, node4;

    root = new Node('#frag', 11);
    node1 = root.append(new Node('b', 1));
    node2 = root.append(new Node('b', 1));
    node3 = root.append(new Node('b', 1));
    node4 = root.append(new Node('em', 1));
    node2.replace(node4);
    ok(root.firstChild === node1, 'root.firstChild');
    ok(root.lastChild === node3, 'root.lastChild');
    ok(node4.parent === root, 'node4.parent');
    ok(node4.next === node3, 'node4.next');
    ok(node4.prev === node1, 'node4.prev');
  });

  suite.test('attr', function () {
    let node;

    node = new Node('b', 1);
    LegacyUnit.deepEqual(node.attributes, []);
    node.attr('attr1', 'value1');
    LegacyUnit.equal(node.attr('attr1'), 'value1');
    LegacyUnit.equal(node.attr('attr2'), undefined);
    LegacyUnit.deepEqual(node.attributes, [{ name: 'attr1', value: 'value1' }]);
    LegacyUnit.deepEqual(node.attributes.map, { attr1: 'value1' });

    node = new Node('b', 1);
    LegacyUnit.deepEqual(node.attributes, []);
    node.attr('attr1', 'value1');
    node.attr('attr1', 'valueX');
    LegacyUnit.equal(node.attr('attr1'), 'valueX');
    LegacyUnit.deepEqual(node.attributes, [{ name: 'attr1', value: 'valueX' }]);
    LegacyUnit.deepEqual(node.attributes.map, { attr1: 'valueX' });

    node = new Node('b', 1);
    LegacyUnit.deepEqual(node.attributes, []);
    node.attr('attr1', 'value1');
    node.attr('attr2', 'value2');
    LegacyUnit.equal(node.attr('attr1'), 'value1');
    LegacyUnit.equal(node.attr('attr2'), 'value2');
    LegacyUnit.deepEqual(node.attributes, [{ name: 'attr1', value: 'value1' }, { name: 'attr2', value: 'value2' }]);
    LegacyUnit.deepEqual(node.attributes.map, { attr1: 'value1', attr2: 'value2' });

    node = new Node('b', 1);
    LegacyUnit.deepEqual(node.attributes, []);
    node.attr('attr1', 'value1');
    node.attr('attr1', null);
    LegacyUnit.equal(node.attr('attr1'), undefined);
    LegacyUnit.deepEqual(node.attributes, []);
    LegacyUnit.deepEqual(node.attributes.map, {});

    node = new Node('b', 1);
    node.attr({ a: '1', b: '2' });
    LegacyUnit.deepEqual(node.attributes, [{ name: 'a', value: '1' }, { name: 'b', value: '2' }]);
    LegacyUnit.deepEqual(node.attributes.map, { a: '1', b: '2' });

    node = new Node('b', 1);
    node.attr(null);
    LegacyUnit.deepEqual(node.attributes, []);
    LegacyUnit.deepEqual(node.attributes.map, {});
  });

  suite.test('clone', function () {
    let root, node, clone;

    node = new Node('#text', 3);
    node.value = 'value';
    clone = node.clone();
    LegacyUnit.equal(clone.name, '#text');
    LegacyUnit.equal(clone.type, 3);
    LegacyUnit.equal(clone.value, 'value');
    LegacyUnit.equal(clone.parent, undefined);
    LegacyUnit.equal(clone.next, undefined);
    LegacyUnit.equal(clone.prev, undefined);

    root = new Node('#frag', 11);
    node = new Node('#text', 3);
    node.value = 'value';
    root.append(node);
    LegacyUnit.equal(clone.name, '#text');
    LegacyUnit.equal(clone.type, 3);
    LegacyUnit.equal(clone.value, 'value');
    LegacyUnit.equal(clone.parent, undefined);
    LegacyUnit.equal(clone.next, undefined);
    LegacyUnit.equal(clone.prev, undefined);

    node = new Node('b', 1);
    node.attr('id', 'id');
    node.attr('class', 'class');
    node.attr('title', 'title');
    clone = node.clone();
    LegacyUnit.equal(clone.name, 'b');
    LegacyUnit.equal(clone.type, 1);
    LegacyUnit.deepEqual(clone.attributes, [{ name: 'class', value: 'class' }, { name: 'title', value: 'title' }]);
    LegacyUnit.deepEqual(clone.attributes.map, { class: 'class', title: 'title' });
  });

  suite.test('unwrap', function () {
    let root, node1, node2, node3;

    root = new Node('#frag', 11);
    node1 = root.append(new Node('b', 1));
    node2 = node1.append(new Node('em', 1));
    node1.unwrap();
    ok(root.firstChild === node2, 'root.firstChild');
    ok(root.lastChild === node2, 'root.lastChild');
    ok(node2.parent === root, 'node2.parent');

    root = new Node('#frag', 11);
    node1 = root.append(new Node('b', 1));
    node2 = node1.append(new Node('em', 1));
    node3 = node1.append(new Node('span', 1));
    node1.unwrap();
    ok(root.firstChild === node2, 'root.firstChild');
    ok(root.lastChild === node3, 'root.lastChild');
    ok(node2.parent === root, 'node2.parent');
    ok(node3.parent === root, 'node3.parent');
  });

  suite.test('empty', function () {
    let root, node1;

    root = new Node('#frag', 11);
    node1 = root.append(new Node('b', 1));
    node1.empty();
    ok(root.firstChild === node1, 'root.firstChild');
    ok(root.lastChild === node1, 'root.firstChild');
    ok(!node1.firstChild, 'node1.firstChild');
    ok(!node1.lastChild, 'node1.firstChild');
  });

  suite.test('isEmpty', function () {
    let root, node1, node2;

    root = new Node('#frag', 11);
    node1 = root.append(new Node('p', 1));
    node2 = node1.append(new Node('b', 1));
    ok(root.isEmpty({ img: 1 }), 'Is empty 1');
    ok(node1.isEmpty({ img: 1 }), 'Is empty 2');

    root = new Node('#frag', 11);
    node1 = root.append(new Node('p', 1));
    node2 = node1.append(new Node('img', 1));
    ok(!root.isEmpty({ img: 1 }), 'Is not empty 1');
    ok(!node1.isEmpty({ img: 1 }), 'Is not empty 2');

    root = new Node('#frag', 11);
    node1 = root.append(new Node('p', 1));
    node2 = node1.append(new Node('#text', 3));
    node2.value = 'X';
    ok(!root.isEmpty({ img: 1 }), 'Is not empty 3');
    ok(!node1.isEmpty({ img: 1 }), 'Is not empty 4');

    root = new Node('#frag', 11);
    node1 = root.append(new Node('p', 1));
    node2 = node1.append(new Node('#text', 3));
    node2.value = '';
    ok(root.isEmpty({ img: 1 }), 'Is empty 4');
    ok(node1.isEmpty({ img: 1 }), 'Is empty 5');

    root = new Node('#frag', 11);
    node1 = root.append(new Node('a', 1)).attr('name', 'x');
    ok(!root.isEmpty({ img: 1 }), 'Contains anchor with name attribute.');

    const isSpan = function (node) {
      return node.name === 'span';
    };

    root = new Node('#frag', 11);
    node1 = root.append(new Node('span', 1));
    LegacyUnit.equal(root.isEmpty({ img: 1 }, {}, isSpan), false, 'Should be false since the predicate says true.');

    root = new Node('#frag', 11);
    node1 = root.append(new Node('b', 1));
    LegacyUnit.equal(root.isEmpty({ img: 1 }, {}, isSpan), true, 'Should be true since the predicate says false.');
  });

  Pipeline.async({}, suite.toSteps({}), function () {
    success();
  }, failure);
});
UnitTest.asynctest('tinymce.plugins.paste.browser.PasteBin', function () {
  const success = arguments[arguments.length - 2];
  const failure = arguments[arguments.length - 1];

  Theme();
  PastePlugin();

  const cases = [
    {
      label: 'TINY-1162: testing nested paste bins',
      content: '<div id="mcepastebin" contenteditable="true" data-mce-bogus="all" data-mce-style="position: absolute; top: 0.40000057220458984px;width: 10px; height: 10px; overflow: hidden; opacity: 0" style="position: absolute; top: 0.40000057220458984px;width: 10px; height: 10px; overflow: hidden; opacity: 0"><div id="mcepastebin" data-mce-bogus="all" data-mce-style="position: absolute; top: 0.40000057220458984px;width: 10px; height: 10px; overflow: hidden; opacity: 0" style="position: absolute; top: 0.40000057220458984px;width: 10px; height: 10px; overflow: hidden; opacity: 0">a</div><div id="mcepastebin" data-mce-bogus="all" data-mce-style="position: absolute; top: 0.40000057220458984px;width: 10px; height: 10px; overflow: hidden; opacity: 0" style="position: absolute; top: 0.40000057220458984px;width: 10px; height: 10px; overflow: hidden; opacity: 0">b</div></div>',
      result: '<div>a</div><div>b</div>'
    },
    {
      label: 'TINY-1162: testing adjacent paste bins',
      content: '<div id="mcepastebin" contenteditable="true" data-mce-bogus="all" data-mce-style="position: absolute; top: 0.40000057220458984px;width: 10px; height: 10px; overflow: hidden; opacity: 0" style="position: absolute; top: 0.40000057220458984px;width: 10px; height: 10px; overflow: hidden; opacity: 0"><p>a</p><p>b</p></div><div id="mcepastebin" contenteditable="true" data-mce-bogus="all" data-mce-style="position: absolute; top: 0.40000057220458984px;width: 10px; height: 10px; overflow: hidden; opacity: 0" style="position: absolute; top: 0.40000057220458984px;width: 10px; height: 10px; overflow: hidden; opacity: 0"><p>c</p></div>',
      result: '<p>a</p><p>b</p><p>c</p>'
    }
  ];

  const viewBlock = ViewBlock();

  const cCreateEditorFromSettings = function (settings?, html?) {
    return Chain.on(function (viewBlock, next, die) {
      const randomId = Id.generate('tiny');
      html = html || '<textarea></textarea>';

      viewBlock.update(html);
      viewBlock.get().firstChild.id = randomId;

      EditorManager.init(Merger.merge(settings || {}, {
        selector: '#' + randomId,
        add_unload_trigger: false,
        indent: false,
        plugins: 'paste',
        skin_url: '/project/js/tinymce/skins/lightgray',
        setup (editor) {
          editor.on('SkinLoaded', function () {
            setTimeout(function () {
              next(Chain.wrap(editor));
            }, 0);
          });
        }
      }));
    });
  };

  const cCreateEditorFromHtml = function (html, settings) {
    return cCreateEditorFromSettings(settings, html);
  };

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

  const cAssertCases = function (cases) {
    return Chain.op(function (editor) {
      const pasteBin = PasteBin(editor);
      Obj.each(cases, function (c, i) {
        editor.getBody().innerHTML = c.content;
        Assertions.assertEq(c.label || 'Asserting paste bin case ' + i, c.result, pasteBin.getHtml());
        pasteBin.remove();
      });
    });
  };

  viewBlock.attach();

  Pipeline.async({}, [
    Chain.asStep(viewBlock, [
      cCreateEditorFromSettings(),
      cAssertCases(cases),
      cRemoveEditor()
    ]),

    // TINY-1208/TINY-1209: same cases, but for inline editor
    Chain.asStep(viewBlock, [
      cCreateEditorFromHtml('<div>some text</div>', { inline: true }),
      cAssertCases(cases),
      cRemoveEditor()
    ])
  ], function () {
    viewBlock.detach();
    success();
  }, failure);
});
Example #14
0
UnitTest.asynctest('WindowManager:tabbed-dialog Test', (success, failure) => {
  const helpers = TestExtras();
  const windowManager = WindowManager.setup(helpers.extras);

  const cAssertFormContents = (label: string, f: (s, str, arr) => StructAssert) => {
    return Chain.op((tabview: Element) => {
      Assertions.assertStructure(
        'Checking tabview: ' + label,
        ApproxStructure.build((s, str, arr) => {
          return s.element('div', {
            classes: [ arr.has('tox-dialog__body-content') ],
            children: [
              s.element('div', {
                classes: [ arr.has('tox-form') ],
                children: [
                  s.element('div', {
                    classes: [ arr.has('tox-form__group') ],
                    children: [
                      f(s, str, arr)
                    ]
                  })
                ]
              })
            ]
          });
        }),
        tabview
      );
    });
  };

  NamedChain.direct('tabview', Chain.op((tabview) => {

  }), '_'),

  Pipeline.async({ }, [
    Step.sync(() => {
      windowManager.open({
        title: 'Custom Dialog',
        body: {
          type: 'tabpanel',
          tabs: [
            {
              title: 'Basic',
              items: [
                {
                  name: 'basic1',
                  type: 'input'
                }
              ]
            },
            {
              title: 'Advanced',
              items: [
                {
                  name: 'advanced1',
                  type: 'textarea'
                }
              ]
            }
          ]
        },
        buttons: [
          {
            type: 'custom',
            name: 'gotoBasic',
            text: '-> Basic',
            disabled: false
          },
          {
            type: 'custom',
            name: 'gotoAdvanced',
            text: '-> Advanced',
            disabled: false
          },
          {
            type: 'cancel',
            text: 'Cancel'
          },
          {
            type: 'submit',
            text: 'Save'
          }
        ],
        initialData: {
          basic1: 'First tab value',
          advanced1: 'Textarea value'
        },
        onAction: (api, a) => {
          const target = a.name === 'gotoBasic' ? 'Basic' : 'Advanced';
          api.showTab(target);
        },
        onSubmit: () => {

        }
      }, {}, () => {});
    }),

    Chain.asStep({ }, [
      NamedChain.asChain([
        NamedChain.writeValue('page', Body.body()),
        NamedChain.direct('page', UiFinder.cFindIn('[role="dialog"]'), 'dialog'),
        NamedChain.direct('dialog', UiFinder.cFindIn('[role="tab"]:contains("Basic")'), 'basicTab'),
        NamedChain.direct('dialog', UiFinder.cFindIn('[role="tab"]:contains("Advanced")'), 'advancedTab'),
        NamedChain.direct('dialog', UiFinder.cFindIn('[role="tabpanel"]'), 'tabview'),
        NamedChain.direct('dialog', Mouse.cClickOn('button:contains("-> Basic")'), '_'),
        NamedChain.direct('tabview', cAssertFormContents('Clicking Basic button', (s, str, arr) => {
          return s.element('input', {
            value: str.is('First tab value')
          });
        }), '_'),

        NamedChain.direct('dialog', Mouse.cClickOn('button:contains("-> Advanced")'), '_'),
        NamedChain.direct('tabview', cAssertFormContents('Clicking Advanced button (not tab)', (s, str, arr) => {
          return s.element('textarea', {
            value: str.is('Textarea value')
          });
        }), '_'),

        NamedChain.direct('dialog', Mouse.cClickOn('button:contains("-> Basic")'), '_'),
        NamedChain.direct('tabview', cAssertFormContents('Clicking Basic button again (not tab)', (s, str, arr) => {
          return s.element('input', {
            value: str.is('First tab value')
          });
        }), '_'),
      ])
    ])
  ], () => {
    helpers.destroy();
    success();
  }, failure);
});
Example #15
0
  TinyLoader.setup(function (editor, onSuccess, onFailure) {
    const tinyUi = TinyUi(editor);
    const tinyApis = TinyApis(editor);

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

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

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

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

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

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

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

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

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

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

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

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

      TestLinkUi.sClearHistory
    ], onSuccess, onFailure);
  }, {
Example #17
0
UnitTest.asynctest('browser.tinymce.core.html.SaxParserTest', function () {
  const success = arguments[arguments.length - 2];
  const failure = arguments[arguments.length - 1];
  const suite = LegacyUnit.createSuite();

  const writer = Writer(), schema = Schema();

  const createCounter = function (writer) {
    const counts: any = {};

    return {
      counts,

      comment (text) {
        if ('comment' in counts) {
          counts.comment++;
        } else {
          counts.comment = 1;
        }

        writer.comment(text);
      },

      cdata (text) {
        if ('cdata' in counts) {
          counts.cdata++;
        } else {
          counts.cdata = 1;
        }

        writer.cdata(text);
      },

      text (text, raw) {
        if ('text' in counts) {
          counts.text++;
        } else {
          counts.text = 1;
        }

        writer.text(text, raw);
      },

      start (name, attrs, empty) {
        if ('start' in counts) {
          counts.start++;
        } else {
          counts.start = 1;
        }

        writer.start(name, attrs, empty);
      },

      end (name) {
        if ('end' in counts) {
          counts.end++;
        } else {
          counts.end = 1;
        }

        writer.end(name);
      },

      pi (name, text) {
        if ('pi' in counts) {
          counts.pi++;
        } else {
          counts.pi = 1;
        }

        writer.pi(name, text);
      },

      doctype (text) {
        if ('doctype:' in counts) {
          counts.doctype++;
        } else {
          counts.doctype = 1;
        }

        writer.doctype(text);
      }
    };
  };

  suite.test('Parse elements', function () {
    let counter, parser;

    counter = createCounter(writer);
    parser = SaxParser(counter, schema);
    writer.reset();
    parser.parse(
      '<span id=id1 title="title value" class=\'class1 class2\' data-value="value1" ' +
      'MYATTR="val1" myns:myattr="val2" disabled empty=""></span>'
    );
    LegacyUnit.equal(
      writer.getContent(),
      '<span id="id1" title="title value" class="class1 class2" data-value="value1" myattr="val1" ' +
      'myns:myattr="val2" disabled="disabled" empty=""></span>',
      'Parse attribute formats.'
    );
    LegacyUnit.deepEqual(counter.counts, { start: 1, end: 1 }, 'Parse attribute formats counts.');

    counter = createCounter(writer);
    parser = SaxParser(counter, schema);
    writer.reset();
    parser.parse('<b href=\'"&amp;<>\'></b>');
    LegacyUnit.equal(writer.getContent(), '<b href="&quot;&amp;&lt;&gt;"></b>', 'Parse attributes with <> in them.');
    LegacyUnit.deepEqual(counter.counts, { start: 1, end: 1 }, 'Parse attributes with <> in them (count).');

    counter = createCounter(writer);
    parser = SaxParser(counter, schema);
    writer.reset();
    parser.parse('<span title=" "class=" "></span>');
    LegacyUnit.equal(writer.getContent(), '<span title=" " class=" "></span>', 'Parse compressed attributes.');
    LegacyUnit.deepEqual(counter.counts, { start: 1, end: 1 }, 'Parse compressed attributes (count).');

    counter = createCounter(writer);
    parser = SaxParser(counter, schema);
    writer.reset();
    parser.parse('<span title></span>');
    LegacyUnit.equal(writer.getContent(), '<span title=""></span>', 'Single empty attribute.');
    LegacyUnit.deepEqual(counter.counts, { start: 1, end: 1 }, 'Single empty attributes (count).');

    counter = createCounter(writer);
    parser = SaxParser(counter, schema);
    writer.reset();
    parser.parse('<span class="class" title></span>');
    LegacyUnit.equal(writer.getContent(), '<span class="class" title=""></span>', 'Empty attribute at end.');
    LegacyUnit.deepEqual(counter.counts, { start: 1, end: 1 }, 'Empty attribute at end (count).');

    counter = createCounter(writer);
    parser = SaxParser(counter, schema);
    writer.reset();
    parser.parse('<span title class="class"></span>');
    LegacyUnit.equal(writer.getContent(), '<span title="" class="class"></span>', 'Empty attribute at start.');
    LegacyUnit.deepEqual(counter.counts, { start: 1, end: 1 }, 'Empty attribute at start (count).');

    counter = createCounter(writer);
    parser = SaxParser(counter, schema);
    writer.reset();
    parser.parse('<img src="test">');
    LegacyUnit.equal(writer.getContent(), '<img src="test" />', 'Parse empty element.');
    LegacyUnit.deepEqual(counter.counts, { start: 1 }, 'Parse empty element counts.');

    counter = createCounter(writer);
    parser = SaxParser(counter, schema);
    writer.reset();
    parser.parse('<img\nsrc="test"\ntitle="row1\nrow2">');
    LegacyUnit.equal(writer.getContent(), '<img src="test" title="row1\nrow2" />', 'Parse attributes with linebreak.');
    LegacyUnit.deepEqual(counter.counts, { start: 1 }, 'Parse attributes with linebreak counts.');

    counter = createCounter(writer);
    parser = SaxParser(counter, schema);
    writer.reset();
    parser.parse('<img     \t  \t   src="test"     \t  \t   title="\t    row1\t     row2">');
    LegacyUnit.equal(writer.getContent(), '<img src="test" title="\t    row1\t     row2" />', 'Parse attributes with whitespace.');
    LegacyUnit.deepEqual(counter.counts, { start: 1 }, 'Parse attributes with whitespace counts.');

    counter = createCounter(writer);
    parser = SaxParser(counter, schema);
    writer.reset();
    parser.parse('<myns:mytag>text</myns:mytag>');
    LegacyUnit.equal(writer.getContent(), '<myns:mytag>text</myns:mytag>', 'Parse element with namespace.');
    LegacyUnit.deepEqual(counter.counts, { start: 1, text: 1, end: 1 }, 'Parse element with namespace counts.');

    counter = createCounter(writer);
    parser = SaxParser(counter, schema);
    writer.reset();
    parser.parse('<myns-mytag>text</myns-mytag>');
    LegacyUnit.equal(writer.getContent(), '<myns-mytag>text</myns-mytag>', 'Parse element with dash name.');
    LegacyUnit.deepEqual(counter.counts, { start: 1, text: 1, end: 1 }, 'Parse element with dash name counts.');

    counter = createCounter(writer);
    parser = SaxParser(counter, schema);
    writer.reset();
    parser.parse('<myns_mytag>text</myns_mytag>');
    LegacyUnit.equal(writer.getContent(), '<myns_mytag>text</myns_mytag>', 'Parse element with underscore name.');
    LegacyUnit.deepEqual(counter.counts, { start: 1, text: 1, end: 1 }, 'Parse element with underscore name counts.');

    counter = createCounter(writer);
    parser = SaxParser(counter, schema);
    writer.reset();
    parser.parse('text1<p>text2<b>text3</p>text4</b>text5');
    LegacyUnit.equal(writer.getContent(), 'text1<p>text2<b>text3</b></p>text4text5', 'Parse tag soup 1.');
    LegacyUnit.deepEqual(counter.counts, { text: 5, start: 2, end: 2 }, 'Parse tag soup 1 counts.');

    counter = createCounter(writer);
    parser = SaxParser(counter, schema);
    writer.reset();
    parser.parse('text1<P>text2<B>text3</p>text4</b>text5');
    LegacyUnit.equal(writer.getContent(), 'text1<p>text2<b>text3</b></p>text4text5', 'Parse tag soup 2.');
    LegacyUnit.deepEqual(counter.counts, { text: 5, start: 2, end: 2 }, 'Parse tag soup 2 counts.');

    counter = createCounter(writer);
    parser = SaxParser(counter, schema);
    writer.reset();
    parser.parse('text1<P>text2<B>tex<t3</p>te>xt4</b>text5');
    LegacyUnit.equal(writer.getContent(), 'text1<p>text2<b>tex&lt;t3</b></p>te&gt;xt4text5', 'Parse tag soup 3.');
    LegacyUnit.deepEqual(counter.counts, { text: 5, start: 2, end: 2 }, 'Parse tag soup 3 counts.');

    counter = createCounter(writer);
    parser = SaxParser(counter, schema);
    writer.reset();
    parser.parse('text1<p>text2<b>text3');
    LegacyUnit.equal(writer.getContent(), 'text1<p>text2<b>text3</b></p>', 'Parse tag soup 4.');
    LegacyUnit.deepEqual(counter.counts, { text: 3, start: 2, end: 2 }, 'Parse tag soup 4 counts.');

    counter = createCounter(writer);
    parser = SaxParser(counter, schema);
    writer.reset();
    parser.parse('text1<script>text2');
    LegacyUnit.equal(writer.getContent(), 'text1<script>text2</s' + 'cript>', 'Parse tag soup 5.');
    LegacyUnit.deepEqual(counter.counts, { text: 2, start: 1, end: 1 }, 'Parse tag soup 5 counts.');

    counter = createCounter(writer);
    parser = SaxParser(counter, schema);
    writer.reset();
    parser.parse('text1<style>text2');
    LegacyUnit.equal(writer.getContent(), 'text1<style>text2</st' + 'yle>', 'Parse tag soup 6.');
    LegacyUnit.deepEqual(counter.counts, { text: 2, start: 1, end: 1 }, 'Parse tag soup 6 counts.');

    counter = createCounter(writer);
    parser = SaxParser(counter, schema);
    writer.reset();
    parser.parse('text1<span title="<test" data-test="test>"></span>');
    LegacyUnit.equal(
      writer.getContent(),
      'text1<span title="&lt;test" data-test="test&gt;"></span>',
      'Parse element with </> in attributes.'
    );
    LegacyUnit.deepEqual(counter.counts, { text: 1, start: 1, end: 1 }, 'Parse element with </> in attributes counts.');

    counter = createCounter(writer);
    parser = SaxParser(counter, schema);
    writer.reset();
    parser.parse('text\n<SC' + 'RIPT type=mce-text/javascript>// <![CDATA[\nalert(\'HELLO WORLD!\');\n// ]]></SC' + 'RIPT>');
    LegacyUnit.equal(
      writer.getContent(),
      'text\n<sc' + 'ript type="mce-text/javascript">// <![CDATA[\nalert(\'HELLO WORLD!\');\n// ]]></sc' + 'ript>',
      'Parse cdata script.'
    );
    LegacyUnit.deepEqual(counter.counts, { text: 2, start: 1, end: 1 }, 'Parse cdata script counts.');

    counter = createCounter(writer);
    parser = SaxParser(counter, schema);
    writer.reset();
    parser.parse('text1<noscript>te<br>xt2</noscript>text3');
    LegacyUnit.equal(writer.getContent(), 'text1<noscript>te<br>xt2</noscript>text3', 'Parse noscript elements.');
    LegacyUnit.deepEqual(counter.counts, { text: 3, start: 1, end: 1 }, 'Parse noscript elements counts.');

    counter = createCounter(writer);
    parser = SaxParser(counter, schema);
    writer.reset();
    parser.parse('<p>a</p><p /><p>b</p>');
    LegacyUnit.equal(writer.getContent(), '<p>a</p><p></p><p>b</p>', 'Parse invalid closed element.');
    LegacyUnit.deepEqual(counter.counts, { text: 2, start: 3, end: 3 }, 'Parse invalid closed element counts.');

    counter = createCounter(writer);
    parser = SaxParser(counter, schema);
    writer.reset();
    parser.parse('<br><br /><br/>');
    LegacyUnit.equal(writer.getContent(), '<br /><br /><br />', 'Parse short ended elements.');
    LegacyUnit.deepEqual(counter.counts, { start: 3 }, 'Parse short ended elements counts.');

    counter = createCounter(writer);
    parser = SaxParser(counter, schema);
    writer.reset();
    parser.parse('<p ></p>');
    LegacyUnit.equal(writer.getContent(), '<p></p>', 'Parse start elements with whitespace only attribs part.');
    LegacyUnit.deepEqual(counter.counts, { start: 1, end: 1 }, 'Parse start elements with whitespace only attribs part (counts).');
  });

  suite.test('Parse style elements', function () {
    let counter, parser;

    counter = createCounter(writer);
    parser = SaxParser(counter, schema);
    writer.reset();
    parser.parse('text1<em><style>// <b>tag</b></st' + 'yle>text2</em>');
    LegacyUnit.equal(writer.getContent(), 'text1<em><style>// <b>tag</b></st' + 'yle>text2</em>', 'Parse style element.');
    LegacyUnit.deepEqual(counter.counts, { start: 2, end: 2, text: 3 }, 'Parse style element counts.');

    counter = createCounter(writer);
    parser = SaxParser(counter, schema);
    writer.reset();
    parser.parse('text1<em><style id="id">// <b>tag</b></st' + 'yle>text2</em>');
    LegacyUnit.equal(
      writer.getContent(),
      'text1<em><style id="id">// <b>tag</b></st' + 'yle>text2</em>',
      'Parse style element with attributes.'
    );
    LegacyUnit.deepEqual(counter.counts, { text: 3, start: 2, end: 2 }, 'Parse style element with attributes counts.');

    counter = createCounter(writer);
    parser = SaxParser(counter, schema);
    writer.reset();
    parser.parse('text1<em><style></st' + 'yle>text2</span>');
    LegacyUnit.equal(writer.getContent(), 'text1<em><style></st' + 'yle>text2</em>', 'Parse empty style element.');
    LegacyUnit.deepEqual(counter.counts, { text: 2, start: 2, end: 2 }, 'Parse empty style element counts.');

    counter = createCounter(writer);
    parser = SaxParser(Tools.extend({ validate: true }, counter), Schema({ invalid_elements: 'style' }));
    writer.reset();
    parser.parse('text1<em><style>text2</st' + 'yle>text3</em>');
    LegacyUnit.equal(writer.getContent(), 'text1<em>text3</em>', 'Parse invalid style element.');
    LegacyUnit.deepEqual(counter.counts, { text: 2, start: 1, end: 1 }, 'Parse invalid style element (count).');
  });

  suite.test('Parse script elements', function () {
    let counter, parser;

    counter = createCounter(writer);
    parser = SaxParser(counter, schema);
    writer.reset();
    parser.parse('text1<em><script>// <b>tag</b></s' + 'cript>text2</em>');
    LegacyUnit.equal(writer.getContent(), 'text1<em><script>// <b>tag</b></s' + 'cript>text2</em>', 'Parse script element.');
    LegacyUnit.deepEqual(counter.counts, { start: 2, end: 2, text: 3 }, 'Parse script element counts.');

    counter = createCounter(writer);
    parser = SaxParser(counter, schema);
    writer.reset();
    parser.parse('text1<em><script id="id">// <b>tag</b></s' + 'cript>text2</em>');
    LegacyUnit.equal(
      writer.getContent(),
      'text1<em><script id="id">// <b>tag</b></s' + 'cript>text2</em>',
      'Parse script element with attributes.'
    );
    LegacyUnit.deepEqual(counter.counts, { start: 2, end: 2, text: 3 }, 'Parse script element with attributes counts.');

    counter = createCounter(writer);
    parser = SaxParser(counter, schema);
    writer.reset();
    parser.parse('text1<em><script></s' + 'cript>text2</em>');
    LegacyUnit.equal(writer.getContent(), 'text1<em><script></s' + 'cript>text2</em>', 'Parse empty script element.');
    LegacyUnit.deepEqual(counter.counts, { text: 2, start: 2, end: 2 }, 'Parse empty script element counts.');

    counter = createCounter(writer);
    parser = SaxParser(Tools.extend({ validate: true }, counter), Schema({ invalid_elements: 'script' }));
    writer.reset();
    parser.parse('text1<em><s' + 'cript>text2</s' + 'cript>text3</em>');
    LegacyUnit.equal(writer.getContent(), 'text1<em>text3</em>', 'Parse invalid script element.');
    LegacyUnit.deepEqual(counter.counts, { text: 2, start: 1, end: 1 }, 'Parse invalid script element (count).');
  });

  suite.test('Parse text', function () {
    let counter, parser;

    counter = createCounter(writer);
    parser = SaxParser(counter, schema);
    writer.reset();
    parser.parse('');
    LegacyUnit.equal(writer.getContent(), '', 'Parse empty.');
    LegacyUnit.deepEqual(counter.counts, {}, 'Parse empty counts.');

    counter = createCounter(writer);
    parser = SaxParser(counter, schema);
    writer.reset();
    parser.parse('text');
    LegacyUnit.equal(writer.getContent(), 'text', 'Parse single text node.');
    LegacyUnit.deepEqual(counter.counts, { text: 1 }, 'Parse single text node counts.');

    counter = createCounter(writer);
    parser = SaxParser(counter, schema);
    writer.reset();
    parser.parse('<b>text</b>');
    LegacyUnit.equal(writer.getContent(), '<b>text</b>', 'Parse wrapped text.');
    LegacyUnit.deepEqual(counter.counts, { start: 1, text: 1, end: 1 }, 'Parse wrapped text counts');

    counter = createCounter(writer);
    parser = SaxParser(counter, schema);
    writer.reset();
    parser.parse('text1<b>text2</b>');
    LegacyUnit.equal(writer.getContent(), 'text1<b>text2</b>', 'Parse text at start.');
    LegacyUnit.deepEqual(counter.counts, { start: 1, text: 2, end: 1 }, 'Parse text at start counts.');

    counter = createCounter(writer);
    parser = SaxParser(counter, schema);
    writer.reset();
    parser.parse('<b>text1</b>text2');
    LegacyUnit.equal(writer.getContent(), '<b>text1</b>text2', 'Parse text at end.');
    LegacyUnit.deepEqual(counter.counts, { start: 1, end: 1, text: 2 }, 'Parse text at end counts.');
  });

  suite.test('Parsing comments', function () {
    let counter, parser;

    counter = createCounter(writer);
    parser = SaxParser(counter, schema);
    writer.reset();
    parser.parse('<!-- comment value -->');
    LegacyUnit.equal(writer.getContent(), '<!-- comment value -->', 'Parse comment with value.');
    LegacyUnit.deepEqual(counter.counts, { comment: 1 }, 'Parse comment with value count.');

    counter = createCounter(writer);
    parser = SaxParser(counter, schema);
    writer.reset();
    parser.parse('<!---->');
    LegacyUnit.equal(writer.getContent(), '', 'Parse comment without value.');
    LegacyUnit.deepEqual(counter.counts, {}, 'Parse comment without value count.');

    counter = createCounter(writer);
    parser = SaxParser(counter, schema);
    writer.reset();
    parser.parse('<!--<b></b>-->');
    LegacyUnit.equal(writer.getContent(), '<!--<b></b>-->', 'Parse comment with tag inside.');
    LegacyUnit.deepEqual(counter.counts, { comment: 1 }, 'Parse comment with tag inside counts.');

    counter = createCounter(writer);
    parser = SaxParser(counter, schema);
    writer.reset();
    parser.parse('<b>a<!-- value -->b</b>');
    LegacyUnit.equal(writer.getContent(), '<b>a<!-- value -->b</b>', 'Parse comment with tags around it.');
    LegacyUnit.deepEqual(counter.counts, { comment: 1, text: 2, start: 1, end: 1 }, 'Parse comment with tags around it counts.');
  });

  suite.test('Parsing cdata', function () {
    let counter, parser;

    counter = createCounter(writer);
    parser = SaxParser(counter, schema);
    writer.reset();
    parser.parse('<![CDATA[test text]]>');
    LegacyUnit.equal(writer.getContent(), '<![CDATA[test text]]>', 'Parse cdata with value.');
    LegacyUnit.deepEqual(counter.counts, { cdata: 1 }, 'Parse cdata with value counts.');

    counter = createCounter(writer);
    parser = SaxParser(counter, schema);
    writer.reset();
    parser.parse('<![CDATA[]]>');
    LegacyUnit.equal(writer.getContent(), '', 'Parse cdata without value.');
    LegacyUnit.deepEqual(counter.counts, {}, 'Parse cdata without value counts.');

    counter = createCounter(writer);
    parser = SaxParser(counter, schema);
    writer.reset();
    parser.parse('<![CDATA[<b>a</b>]]>');
    LegacyUnit.equal(writer.getContent(), '<![CDATA[<b>a</b>]]>', 'Parse cdata with tag inside.');
    LegacyUnit.deepEqual(counter.counts, { cdata: 1 }, 'Parse cdata with tag inside counts.');

    counter = createCounter(writer);
    parser = SaxParser(counter, schema);
    writer.reset();
    parser.parse('<b>a<![CDATA[value]]>b</b>');
    LegacyUnit.equal(writer.getContent(), '<b>a<![CDATA[value]]>b</b>', 'Parse cdata with tags around it.');
    LegacyUnit.deepEqual(counter.counts, { cdata: 1, start: 1, end: 1, text: 2 }, 'Parse cdata with tags around it counts.');
  });

  suite.test('Parse PI', function () {
    let counter, parser;

    counter = createCounter(writer);
    parser = SaxParser(counter, schema);
    writer.reset();
    parser.parse('<?xml version="1.0" encoding="UTF-8" ?>text1');
    LegacyUnit.equal(writer.getContent(), '<?xml version="1.0" encoding="UTF-8" ?>text1', 'Parse PI with attributes.');
    LegacyUnit.deepEqual(counter.counts, { pi: 1, text: 1 }, 'Parse PI with attributes counts.');

    counter = createCounter(writer);
    parser = SaxParser(counter, schema);
    writer.reset();
    parser.parse('<?xml?>text1');
    LegacyUnit.equal(writer.getContent(), '<?xml?>text1', 'Parse PI with no data.');
    LegacyUnit.deepEqual(counter.counts, { pi: 1, text: 1 }, 'Parse PI with data counts.');

    counter = createCounter(writer);
    parser = SaxParser(counter, schema);
    writer.reset();
    parser.parse('<?xml somevalue/>text1');
    LegacyUnit.equal(writer.getContent(), '<?xml somevalue?>text1', 'Parse PI with IE style ending.');
    LegacyUnit.deepEqual(counter.counts, { pi: 1, text: 1 }, 'Parse PI with IE style ending counts.');
  });

  suite.test('Parse doctype', function () {
    let counter, parser;

    counter = createCounter(writer);
    parser = SaxParser(counter, schema);
    writer.reset();
    parser.parse(
      '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">text1'
    );
    LegacyUnit.equal(
      writer.getContent(),
      '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">text1',
      'Parse DOCTYPE.'
    );
    LegacyUnit.deepEqual(counter.counts, { doctype: 1, text: 1 }, 'Parse HTML5 DOCTYPE counts.');

    counter = createCounter(writer);
    parser = SaxParser(counter, schema);
    writer.reset();
    parser.parse('<!DOCTYPE html>text1');
    LegacyUnit.equal(writer.getContent(), '<!DOCTYPE html>text1', 'Parse HTML5 DOCTYPE.');
    LegacyUnit.deepEqual(counter.counts, { doctype: 1, text: 1 }, 'Parse HTML5 DOCTYPE counts.');
  });

  suite.test('Parse (validate)', function () {
    let counter, parser;

    counter = createCounter(writer);
    counter.validate = true;
    parser = SaxParser(counter, schema);
    writer.reset();
    parser.parse('<invalid1>123<invalid2 />456<span title="title" invalid3="value">789</span>012</invalid1>');
    LegacyUnit.equal(writer.getContent(), '123456<span title="title">789</span>012', 'Parse invalid elements and attributes.');
    LegacyUnit.deepEqual(counter.counts, { start: 1, end: 1, text: 4 }, 'Parse invalid elements and attributes counts.');
  });

  suite.test('Self closing', function () {
    let counter, parser;

    counter = createCounter(writer);
    counter.validate = true;
    parser = SaxParser(counter, schema);
    writer.reset();
    parser.parse('<ul><li>1<li><b>2</b><li><em><b>3</b></em></ul>');
    LegacyUnit.equal(
      writer.getContent(),
      '<ul><li>1</li><li><b>2</b></li><li><em><b>3</b></em></li></ul>',
      'Parse list with self closing items.'
    );
  });

  suite.test('Preserve internal elements', function () {
    let counter, parser, schema;

    schema = Schema({ valid_elements: 'b' });
    counter = createCounter(writer);
    counter.validate = true;
    parser = SaxParser(counter, schema);
    writer.reset();
    parser.parse('<span id="id"><b>text</b></span><span id="id" data-mce-type="something"></span>');
    LegacyUnit.equal(
      writer.getContent(),
      '<b>text</b><span id="id" data-mce-type="something"></span>',
      'Preserve internal span element without any span schema rule.'
    );

    schema = Schema({ valid_elements: 'b,span[class]' });
    counter = createCounter(writer);
    counter.validate = true;
    parser = SaxParser(counter, schema);
    writer.reset();
    parser.parse('<span id="id" class="class"><b>text</b></span><span id="id" data-mce-type="something"></span>');
    LegacyUnit.equal(
      writer.getContent(),
      '<span class="class"><b>text</b></span><span id="id" data-mce-type="something"></span>',
      'Preserve internal span element with a span schema rule.'
    );
  });

  suite.test('Remove internal elements', function () {
    let counter, parser, schema;

    schema = Schema({ valid_elements: 'b' });
    counter = createCounter(writer);
    counter.validate = true;
    counter.remove_internals = true;
    parser = SaxParser(counter, schema);
    writer.reset();
    parser.parse('<span id="id"><b>text</b></span><span id="id" data-mce-type="something"></span>');
    LegacyUnit.equal(writer.getContent(), '<b>text</b>', 'Remove internal span element without any span schema rule.');

    schema = Schema({ valid_elements: 'b,span[class]' });
    counter = createCounter(writer);
    counter.validate = true;
    counter.remove_internals = true;
    parser = SaxParser(counter, schema);
    writer.reset();
    parser.parse('<span id="id" class="class"><b>text</b></span><span id="id" data-mce-type="something"></span>');
    LegacyUnit.equal(
      writer.getContent(),
      '<span class="class"><b>text</b></span>',
      'Remove internal span element with a span schema rule.'
    );

    // Reset
    counter.remove_internals = false;
  });

  suite.test('Parse attr with backslash #5436', function () {
    let counter, parser;

    counter = createCounter(writer);
    parser = SaxParser(counter, schema);
    writer.reset();
    parser.parse('<a title="\\" href="h">x</a>');
    LegacyUnit.equal(writer.getContent(), '<a title="\\" href="h">x</a>');
  });

  suite.test('Parse no attributes span before strong', function () {
    let counter, parser;

    counter = createCounter(writer);
    counter.validate = true;
    parser = SaxParser(counter, schema);
    writer.reset();
    parser.parse('<p><span>A</span> <strong>B</strong></p>');
    LegacyUnit.equal(writer.getContent(), '<p>A <strong>B</strong></p>');
  });

  suite.test('Conditional comments (allowed)', function () {
    let counter, parser;

    counter = createCounter(writer);
    counter.validate = false;
    counter.allow_conditional_comments = true;
    parser = SaxParser(counter, schema);

    writer.reset();
    parser.parse('<!--[if gte IE 4]>alert(1)<![endif]-->');
    LegacyUnit.equal(writer.getContent(), '<!--[if gte IE 4]>alert(1)<![endif]-->');
  });

  suite.test('Conditional comments (denied)', function () {
    let counter, parser;

    counter = createCounter(writer);
    counter.validate = false;
    counter.allow_conditional_comments = false;
    parser = SaxParser(counter, schema);

    writer.reset();
    parser.parse('<!--[if gte IE 4]>alert(1)<![endif]-->');
    LegacyUnit.equal(writer.getContent(), '<!-- [if gte IE 4]>alert(1)<![endif]-->');

    writer.reset();
    parser.parse('<!--[if !IE]>alert(1)<![endif]-->');
    LegacyUnit.equal(writer.getContent(), '<!-- [if !IE]>alert(1)<![endif]-->');

    writer.reset();
    parser.parse('<!--[iF !IE]>alert(1)<![endif]-->');
    LegacyUnit.equal(writer.getContent(), '<!-- [iF !IE]>alert(1)<![endif]-->');
  });

  suite.test('Parse script urls (allowed)', function () {
    let counter, parser;

    counter = createCounter(writer);
    counter.validate = false;
    counter.allow_script_urls = true;
    parser = SaxParser(counter, schema);
    writer.reset();
    parser.parse(
      '<a href="javascript:alert(1)">1</a>' +
      '<a href=" 2 ">2</a>' +
      '<a href="data:text/html;base64,PHN2Zy9vbmxvYWQ9YWxlcnQoMik+">3</a>'
    );
    LegacyUnit.equal(
      writer.getContent(),
      '<a href="javascript:alert(1)">1</a><a href=" 2 ">2</a>' +
      '<a href="data:text/html;base64,PHN2Zy9vbmxvYWQ9YWxlcnQoMik+">3</a>'
    );
  });

  suite.test('Parse script urls (allowed html data uris)', function () {
    let counter, parser;

    counter = createCounter(writer);
    counter.validate = false;
    counter.allow_html_data_urls = true;
    parser = SaxParser(counter, schema);
    writer.reset();
    parser.parse(
      '<a href="javascript:alert(1)">1</a>' +
      '<a href="data:text/html;base64,PHN2Zy9vbmxvYWQ9YWxlcnQoMik+">2</a>' +
      '<a href="data:image/svg+xml;base64,x">3</a>'
    );
    LegacyUnit.equal(
      writer.getContent(),
      '<a>1</a>' +
      '<a href="data:text/html;base64,PHN2Zy9vbmxvYWQ9YWxlcnQoMik+">2</a>' +
      '<a href="data:image/svg+xml;base64,x">3</a>'
    );
  });

  suite.test('Parse script urls (disallow svg data image uris)', function () {
    let counter, parser;

    counter = createCounter(writer);
    counter.validate = false;
    counter.allow_html_data_urls = false;
    counter.allow_svg_data_urls = false;
    parser = SaxParser(counter, schema);
    writer.reset();
    parser.parse(
      '<a href="data:image/svg+xml;base64,x">1</a>'
    );
    LegacyUnit.equal(
      writer.getContent(),
      '<a>1</a>'
    );
  });

  suite.test('Parse script urls (denied)', function () {
    let counter, parser;

    counter = createCounter(writer);
    counter.validate = false;
    parser = SaxParser(counter, schema);

    writer.reset();
    parser.parse(
      '<a href="jAvaScript:alert(1)">1</a>' +
      '<a href="vbscript:alert(2)">2</a>' +
      '<a href="java\u0000script:alert(3)">3</a>' +
      '<a href="\njavascript:alert(4)">4</a>' +
      '<a href="java\nscript:alert(5)">5</a>' +
      '<a href="java\tscript:alert(6)">6</a>' +
      '<a href="%6aavascript:alert(7)">7</a>' +
      '<a href="data:text/html;base64,PHN2Zy9vbmxvYWQ9YWxlcnQoMik+">8</a>' +
      '<a href=" dAt%61: tExt/html  ; bAse64 , PHN2Zy9vbmxvYWQ9YWxlcnQoMik+">9</a>' +
      '<object data="data:text/html;base64,PHN2Zy9vbmxvYWQ9YWxlcnQoMik+">10</object>' +
      '<button formaction="javascript:alert(11)">11</button>' +
      '<table background="javascript:alert(12)"><tr><tr>12</tr></tr></table>' +
      '<a href="mhtml:13">13</a>' +
      '<a xlink:href="jAvaScript:alert(1)">14</a>' +
      '<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7">' +
      '<a href="%E3%82%AA%E3%83%BC%E3%83">Invalid url</a>'
    );

    LegacyUnit.equal(
      writer.getContent(),
      '<a>1</a><a>2</a><a>3</a><a>4</a><a>5</a><a>6</a><a>7</a><a>8</a><a>9</a>' +
      '<object>10</object><button>11</button><table><tr></tr><tr>12</tr></table><a>13</a><a>14</a>' +
      '<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" />' +
      '<a href="%E3%82%AA%E3%83%BC%E3%83">Invalid url</a>'
    );
  });

  suite.test('Parse away bogus elements', function () {
    const testBogusSaxParse = function (inputHtml, outputHtml, counters) {
      let counter, parser;

      counter = createCounter(writer);
      counter.validate = true;
      parser = SaxParser(counter, schema);
      writer.reset();
      parser.parse(inputHtml);
      LegacyUnit.equal(writer.getContent(), outputHtml);
      LegacyUnit.deepEqual(counter.counts, counters);
    };

    testBogusSaxParse('a<b data-mce-bogus="1">b</b>c', 'abc', { text: 3 });
    testBogusSaxParse('a<b data-mce-bogus="true">b</b>c', 'abc', { text: 3 });
    testBogusSaxParse('a<b data-mce-bogus="1"></b>c', 'ac', { text: 2 });
    testBogusSaxParse('a<b data-mce-bogus="all">b</b>c', 'ac', { text: 2 });
    testBogusSaxParse('a<b data-mce-bogus="all"><!-- x --><?xml?></b>c', 'ac', { text: 2 });
    testBogusSaxParse('a<b data-mce-bogus="all"><b>b</b></b>c', 'ac', { text: 2 });
    testBogusSaxParse('a<b data-mce-bogus="all"><br>b</b><b>c</b>', 'a<b>c</b>', { start: 1, end: 1, text: 2 });
    testBogusSaxParse('a<b data-mce-bogus="all"><img>b</b><b>c</b>', 'a<b>c</b>', { start: 1, end: 1, text: 2 });
    testBogusSaxParse('a<b data-mce-bogus="all"><b attr="x">b</b></b>c', 'ac', { text: 2 });
    testBogusSaxParse('a<b data-mce-bogus="all"></b>c', 'ac', { text: 2 });
    testBogusSaxParse('a<b data-mce-bogus="all"></b><b>c</b>', 'a<b>c</b>', { start: 1, end: 1, text: 2 });
  });

  suite.test('remove bogus elements even if not part of valid_elements', () => {
    const schema = Schema({ valid_elements: 'p,span,' });
    const writer = Writer();
    const counter = createCounter(writer);
    const parser = SaxParser(counter, schema);
    parser.parse('<p>a <div data-mce-bogus="all">&nbsp;<span contenteditable="false">X</span>&nbsp;</div>b</p>');
    LegacyUnit.equal(writer.getContent(), '<p>a b</p>');
    LegacyUnit.deepEqual(counter.counts, { start: 1, end: 1, text: 2 });
  });

  suite.test('findEndTag', function () {
    const testFindEndTag = function (html, startIndex, expectedIndex) {
      LegacyUnit.equal(SaxParser.findEndTag(schema, html, startIndex), expectedIndex);
    };

    testFindEndTag('<b>', 3, 3);
    testFindEndTag('<img>', 3, 3);
    testFindEndTag('<b></b>', 3, 7);
    testFindEndTag('<b><img></b>', 3, 12);
    testFindEndTag('<b><!-- </b> --></b>', 3, 20);
    testFindEndTag('<span><b><i>a<img>b</i><b>c</b></b></span>', 9, 35);
  });

  suite.test('parse XSS PI', function () {
    let counter, parser;

    counter = createCounter(writer);
    counter.validate = false;
    parser = SaxParser(counter, schema);

    writer.reset();
    parser.parse(
      '<?xml><iframe SRC=&#106&#97&#118&#97&#115&#99&#114&#105&#112&#116&#58&#97&#108&#101&#114&#116&#40&#39&#88&#83&#83&#39&#41>?>'
    );

    LegacyUnit.equal(
      writer.getContent(),
      '<?xml &gt;&lt;iframe SRC=&amp;#106&amp;#97&amp;#118&amp;#97&amp;#115&amp;#99&amp;#114&amp;#105&amp;#112&amp;' +
      '#116&amp;#58&amp;#97&amp;#108&amp;#101&amp;#114&amp;#116&amp;#40&amp;#39&amp;#88&amp;#83&amp;#83&amp;#39&amp;#41&gt;?>'
    );
  });

  suite.test('aria attributes', function () {
    let counter, parser;

    counter = createCounter(writer);
    parser = SaxParser(Tools.extend({ validate: true }, counter), schema);
    writer.reset();
    parser.parse('<span aria-label="test" role="myrole" unsupportedattr="2">a</span>');
    LegacyUnit.equal(
      writer.getContent(),
      '<span aria-label="test" role="myrole">a</span>'
    );
  });

  suite.test('Parse elements with numbers', function () {
    let counter, parser;

    counter = createCounter(writer);
    parser = SaxParser(counter, schema);
    writer.reset();
    parser.parse('<a5>text</a5>');
    LegacyUnit.equal(writer.getContent(), '<a5>text</a5>', 'Parse element with numbers.');
    LegacyUnit.deepEqual(counter.counts, { start: 1, text: 1, end: 1 }, 'Parse element with numbers counts.');
  });

  suite.test('Parse internal elements with disallowed attributes', function () {
    let counter, parser;

    counter = createCounter(writer);
    parser = SaxParser(counter, schema);
    writer.reset();
    parser.parse('<b data-mce-type="test" id="x" style="color: red" src="1" data="2" onclick="3"></b>');
    LegacyUnit.equal(writer.getContent(), '<b data-mce-type="test" id="x" style="color: red"></b>');
    LegacyUnit.deepEqual(counter.counts, { start: 1, end: 1 });
  });

  suite.test('Parse cdata with comments and trim those comments away', function () {
    let counter, parser;

    counter = createCounter(writer);
    parser = SaxParser(counter, schema);
    writer.reset();
    parser.parse('<![CDATA[<!--x--><!--y-->--><!--]]>');
    LegacyUnit.equal(writer.getContent(), '<![CDATA[xy]]>');
    LegacyUnit.deepEqual(counter.counts, { cdata: 1 });
  });

  suite.test('Parse special elements', function () {
    let counter, parser;

    const specialHtml = (
      '<b>' +
      '<textarea></b></textarea><title></b></title><script></b></script>' +
      '<noframes></b></noframes><noscript></b></noscript><style></b></style>' +
      '<xmp></b></xmp>' +
      '<noembed></b></noembed>' +
      '</b>'
    );

    counter = createCounter(writer);
    parser = SaxParser(counter, schema);
    writer.reset();
    parser.parse(specialHtml);
    LegacyUnit.equal(writer.getContent(), specialHtml);
    LegacyUnit.deepEqual(counter.counts, { start: 9, text: 8, end: 9 });
  });

  suite.test('Parse malformed elements that start with numbers', function () {
    let counter, parser;

    counter = createCounter(writer);
    parser = SaxParser(counter, schema);
    writer.reset();
    parser.parse('a <2 b b b b b b b b b b b b b b b b b b b b b b');
    LegacyUnit.equal(writer.getContent(), 'a &lt;2 b b b b b b b b b b b b b b b b b b b b b b');

    counter = createCounter(writer);
    parser = SaxParser(counter, schema);
    writer.reset();
    parser.parse('a <2b>a</2b> b');
    LegacyUnit.equal(writer.getContent(), 'a &lt;2b&gt;a&lt;/2b&gt; b');
  });

  suite.test('Parse malformed elements without an end', function () {
    let counter, parser;

    counter = createCounter(writer);
    parser = SaxParser(counter, schema);
    writer.reset();
    parser.parse('<b b b b b b b b b b b b b b b b b b b b b b b');
    LegacyUnit.equal(
      writer.getContent(),
      '&lt;b b b b b b b b b b b b b b b b b b b b b b b'
    );

    counter = createCounter(writer);
    parser = SaxParser(counter, schema);
    writer.reset();
    parser.parse('a a<b c');
    LegacyUnit.equal(
      writer.getContent(),
      'a a&lt;b c'
    );
  });

  Pipeline.async({}, suite.toSteps({}), function () {
    success();
  }, failure);
});
Example #18
0
UnitTest.asynctest('browser.tinymce.core.CaretContainerTest', function () {
  const success = arguments[arguments.length - 2];
  const failure = arguments[arguments.length - 1];
  const suite = LegacyUnit.createSuite();
  const viewBlock = ViewBlock();

  if (!Env.ceFalse) {
    return;
  }

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

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

  suite.test('isCaretContainer', function () {
    LegacyUnit.equal(CaretContainer.isCaretContainer(document.createTextNode('text')), false);
    LegacyUnit.equal(CaretContainer.isCaretContainer(DomQuery('<span></span>')[0]), false);
    LegacyUnit.equal(CaretContainer.isCaretContainer(DomQuery('<span data-mce-caret="1"></span>')[0]), true);
    LegacyUnit.equal(CaretContainer.isCaretContainer(DomQuery('<span data-mce-caret="1">x</span>')[0].firstChild), true);
    LegacyUnit.equal(CaretContainer.isCaretContainer(document.createTextNode(Zwsp.ZWSP)), true);
  });

  suite.test('isCaretContainerBlock', function () {
    LegacyUnit.equal(CaretContainer.isCaretContainerBlock(document.createTextNode('text')), false);
    LegacyUnit.equal(CaretContainer.isCaretContainerBlock(DomQuery('<span></span>')[0]), false);
    LegacyUnit.equal(CaretContainer.isCaretContainerBlock(DomQuery('<span data-mce-caret="1"></span>')[0]), true);
    LegacyUnit.equal(CaretContainer.isCaretContainerBlock(DomQuery('<span data-mce-caret="1">a</span>')[0].firstChild), true);
    LegacyUnit.equal(CaretContainer.isCaretContainerBlock(document.createTextNode(Zwsp.ZWSP)), false);
  });

  suite.test('isCaretContainerInline', function () {
    LegacyUnit.equal(CaretContainer.isCaretContainerInline(document.createTextNode('text')), false);
    LegacyUnit.equal(CaretContainer.isCaretContainerInline(DomQuery('<span></span>')[0]), false);
    LegacyUnit.equal(CaretContainer.isCaretContainerInline(DomQuery('<span data-mce-caret="1"></span>')[0]), false);
    LegacyUnit.equal(CaretContainer.isCaretContainerInline(DomQuery('<span data-mce-caret="1">a</span>')[0].firstChild), false);
    LegacyUnit.equal(CaretContainer.isCaretContainerInline(document.createTextNode(Zwsp.ZWSP)), true);
  });

  suite.test('insertInline before element', function () {
    setupHtml('<span contentEditable="false">1</span>');
    LegacyUnit.equalDom(CaretContainer.insertInline(getRoot().firstChild, true), getRoot().firstChild);
    LegacyUnit.equal(CaretContainer.isCaretContainerInline(getRoot().firstChild), true);
  });

  suite.test('insertInline after element', function () {
    setupHtml('<span contentEditable="false">1</span>');
    LegacyUnit.equalDom(CaretContainer.insertInline(getRoot().firstChild, false), getRoot().lastChild);
    LegacyUnit.equal(CaretContainer.isCaretContainerInline(getRoot().lastChild), true);
  });

  suite.test('insertInline between elements', function () {
    setupHtml('<span contentEditable="false">1</span><span contentEditable="false">1</span>');
    LegacyUnit.equalDom(CaretContainer.insertBlock('p', getRoot().lastChild, true), getRoot().childNodes[1]);
    LegacyUnit.equal(CaretContainer.isCaretContainerBlock(getRoot().childNodes[1]), true);
  });

  suite.test('insertInline before element with ZWSP', function () {
    setupHtml('abc' + Zwsp.ZWSP + '<span contentEditable="false">1</span>');
    LegacyUnit.equalDom(CaretContainer.insertInline(getRoot().lastChild, true), getRoot().childNodes[1]);
    LegacyUnit.equal(CaretContainer.isCaretContainerInline(getRoot().firstChild), false);
    LegacyUnit.equal(CaretContainer.isCaretContainerInline(getRoot().childNodes[1]), true);
  });

  suite.test('insertInline after element with ZWSP', function () {
    setupHtml('<span contentEditable="false">1</span>' + Zwsp.ZWSP + 'abc');
    LegacyUnit.equalDom(CaretContainer.insertInline(getRoot().firstChild, false), getRoot().childNodes[1]);
    LegacyUnit.equal(CaretContainer.isCaretContainerInline(getRoot().lastChild), false);
    LegacyUnit.equal(CaretContainer.isCaretContainerInline(getRoot().childNodes[1]), true);
  });

  suite.test('insertBlock before element', function () {
    setupHtml('<span contentEditable="false">1</span>');
    LegacyUnit.equalDom(CaretContainer.insertBlock('p', getRoot().firstChild, true), getRoot().firstChild);
    LegacyUnit.equal(CaretContainer.isCaretContainerBlock(getRoot().firstChild), true);
  });

  suite.test('insertBlock after element', function () {
    setupHtml('<span contentEditable="false">1</span>');
    LegacyUnit.equalDom(CaretContainer.insertBlock('p', getRoot().firstChild, false), getRoot().lastChild);
    LegacyUnit.equal(CaretContainer.isCaretContainerBlock(getRoot().lastChild), true);
  });

  suite.test('insertBlock between elements', function () {
    setupHtml('<span contentEditable="false">1</span><span contentEditable="false">1</span>');
    LegacyUnit.equalDom(CaretContainer.insertInline(getRoot().lastChild, true), getRoot().childNodes[1]);
    LegacyUnit.equal(CaretContainer.isCaretContainerInline(getRoot().childNodes[1]), true);
  });

  suite.test('startsWithCaretContainer', function () {
    setupHtml(Zwsp.ZWSP + 'abc');
    LegacyUnit.equal(CaretContainer.startsWithCaretContainer(getRoot().firstChild), true);
  });

  suite.test('endsWithCaretContainer', function () {
    setupHtml('abc');
    const textNode = viewBlock.get().firstChild as Text;
    textNode.appendData(Zwsp.ZWSP);
    LegacyUnit.equal(CaretContainer.endsWithCaretContainer(getRoot().firstChild), true);
  });

  suite.test('hasContent', function () {
    setupHtml('<span contentEditable="false">1</span>');
    const caretContainerBlock = CaretContainer.insertBlock('p', getRoot().firstChild, true);
    LegacyUnit.equal(CaretContainer.hasContent(caretContainerBlock), false);
    caretContainerBlock.insertBefore(document.createTextNode('a'), caretContainerBlock.firstChild);
    LegacyUnit.equal(CaretContainer.hasContent(caretContainerBlock), true);
  });

  suite.test('showCaretContainerBlock', function () {
    setupHtml('<span contentEditable="false">1</span>');
    const caretContainerBlock = CaretContainer.insertBlock('p', getRoot().firstChild, true) as HTMLElement;
    caretContainerBlock.insertBefore(document.createTextNode('a'), caretContainerBlock.firstChild);
    CaretContainer.showCaretContainerBlock(caretContainerBlock);
    LegacyUnit.equal(caretContainerBlock.outerHTML, '<p>a</p>');
  });

  suite.test('prependInline', function () {
    setupHtml('a');
    const caretContainerTextNode = CaretContainer.prependInline(getRoot().firstChild) as Text;
    LegacyUnit.equal(caretContainerTextNode.data, Zwsp.ZWSP + 'a');
  });

  suite.test('prependInline 2', function () {
    setupHtml('<b>a</b>');
    LegacyUnit.equal(CaretContainer.prependInline(getRoot().firstChild), null);
    LegacyUnit.equal(CaretContainer.prependInline(null), null);
  });

  suite.test('appendInline', function () {
    setupHtml('a');
    const caretContainerTextNode = CaretContainer.appendInline(getRoot().firstChild) as Text;
    LegacyUnit.equal(caretContainerTextNode.data, 'a' + Zwsp.ZWSP);
  });

  suite.test('isBeforeInline', function () {
    setupHtml(Zwsp.ZWSP + 'a');
    LegacyUnit.equal(CaretContainer.isBeforeInline(CaretPosition(getRoot().firstChild, 0)), true);
    LegacyUnit.equal(CaretContainer.isBeforeInline(CaretPosition(getRoot().firstChild, 1)), false);
  });

  suite.test('isAfterInline', function () {
    setupHtml(Zwsp.ZWSP + 'a');
    LegacyUnit.equal(CaretContainer.isAfterInline(CaretPosition(getRoot().firstChild, 1)), true);
    LegacyUnit.equal(CaretContainer.isAfterInline(CaretPosition(getRoot().firstChild, 0)), false);
  });

  viewBlock.attach();
  Pipeline.async({}, suite.toSteps({}), function () {
    viewBlock.detach();
    success();
  }, failure);
});
Example #19
0
UnitTest.asynctest('browser.tinymce.core.html.WriterTest', function () {
  const success = arguments[arguments.length - 2];
  const failure = arguments[arguments.length - 1];
  const suite = LegacyUnit.createSuite();

  suite.test('Comment', function () {
    let writer;

    writer = Writer();
    writer.comment('text');
    LegacyUnit.equal(writer.getContent(), '<!--text-->');

    writer = Writer();
    writer.comment('');
    LegacyUnit.equal(writer.getContent(), '<!---->');
  });

  suite.test('CDATA', function () {
    let writer;

    writer = Writer();
    writer.cdata('text');
    LegacyUnit.equal(writer.getContent(), '<![CDATA[text]]>');

    writer = Writer();
    writer.cdata('');
    LegacyUnit.equal(writer.getContent(), '<![CDATA[]]>');
  });

  suite.test('PI', function () {
    let writer;

    writer = Writer();
    writer.pi('xml', 'someval');
    LegacyUnit.equal(writer.getContent(), '<?xml someval?>');

    writer = Writer();
    writer.pi('xml');
    LegacyUnit.equal(writer.getContent(), '<?xml?>');

    writer = Writer();
    writer.pi('xml', 'encoding="UTF-8" < >');
    LegacyUnit.equal(writer.getContent(), '<?xml encoding="UTF-8" &lt; &gt;?>');
  });

  suite.test('Doctype', function () {
    let writer;

    writer = Writer();
    writer.doctype(' text');
    LegacyUnit.equal(writer.getContent(), '<!DOCTYPE text>');

    writer = Writer();
    writer.doctype('');
    LegacyUnit.equal(writer.getContent(), '<!DOCTYPE>');
  });

  suite.test('Text', function () {
    let writer;

    writer = Writer();
    writer.text('te<xt');
    LegacyUnit.equal(writer.getContent(), 'te&lt;xt');

    writer = Writer();
    writer.text('');
    LegacyUnit.equal(writer.getContent(), '');
  });

  suite.test('Text raw', function () {
    let writer;

    writer = Writer();
    writer.text('te<xt', true);
    LegacyUnit.equal(writer.getContent(), 'te<xt');

    writer = Writer();
    writer.text('', true);
    LegacyUnit.equal(writer.getContent(), '');
  });

  suite.test('Start', function () {
    let writer;

    writer = Writer();
    writer.start('b');
    LegacyUnit.equal(writer.getContent(), '<b>');

    writer = Writer();
    writer.start('b', [{ name: 'attr1', value: 'value1' }, { name: 'attr2', value: 'value2' }]);
    LegacyUnit.equal(writer.getContent(), '<b attr1="value1" attr2="value2">');

    writer = Writer();
    writer.start('b', [{ name: 'attr1', value: 'val<"ue1' }]);
    LegacyUnit.equal(writer.getContent(), '<b attr1="val&lt;&quot;ue1">');

    writer = Writer();
    writer.start('img', [{ name: 'attr1', value: 'value1' }, { name: 'attr2', value: 'value2' }], true);
    LegacyUnit.equal(writer.getContent(), '<img attr1="value1" attr2="value2" />');

    writer = Writer();
    writer.start('br', null, true);
    LegacyUnit.equal(writer.getContent(), '<br />');
  });

  suite.test('End', function () {
    let writer;

    writer = Writer();
    writer.end('b');
    LegacyUnit.equal(writer.getContent(), '</b>');
  });

  suite.test('Indentation', function () {
    let writer;

    writer = Writer({ indent: true, indent_before: 'p', indent_after: 'p' });
    writer.start('p');
    writer.start('span');
    writer.text('a');
    writer.end('span');
    writer.end('p');
    writer.start('p');
    writer.text('a');
    writer.end('p');
    LegacyUnit.equal(writer.getContent(), '<p><span>a</span></p>\n<p>a</p>');

    writer = Writer({ indent: true, indent_before: 'p', indent_after: 'p' });
    writer.start('p');
    writer.text('a');
    writer.end('p');
    LegacyUnit.equal(writer.getContent(), '<p>a</p>');
  });

  suite.test('Entities', function () {
    let writer;

    writer = Writer();
    writer.start('p', [{ name: 'title', value: '<>"\'&\u00e5\u00e4\u00f6' }]);
    writer.text('<>"\'&\u00e5\u00e4\u00f6');
    writer.end('p');
    LegacyUnit.equal(writer.getContent(), '<p title="&lt;&gt;&quot;\'&amp;\u00e5\u00e4\u00f6">&lt;&gt;"\'&amp;\u00e5\u00e4\u00f6</p>');

    writer = Writer({ entity_encoding: 'numeric' });
    writer.start('p', [{ name: 'title', value: '<>"\'&\u00e5\u00e4\u00f6' }]);
    writer.text('<>"\'&\u00e5\u00e4\u00f6');
    writer.end('p');
    LegacyUnit.equal(writer.getContent(), '<p title="&lt;&gt;&quot;\'&amp;&#229;&#228;&#246;">&lt;&gt;"\'&amp;&#229;&#228;&#246;</p>');

    writer = Writer({ entity_encoding: 'named' });
    writer.start('p', [{ name: 'title', value: '<>"\'&\u00e5\u00e4\u00f6' }]);
    writer.text('<>"\'&\u00e5\u00e4\u00f6');
    writer.end('p');
    LegacyUnit.equal(writer.getContent(), '<p title="&lt;&gt;&quot;\'&amp;&aring;&auml;&ouml;">&lt;&gt;"\'&amp;&aring;&auml;&ouml;</p>');
  });

  Pipeline.async({}, suite.toSteps({}), function () {
    success();
  }, failure);
});
Example #20
0
UnitTest.asynctest('Browser Test: ui.ButtonsTest', function () {
  const success = arguments[arguments.length - 2];
  const failure = arguments[arguments.length - 1];

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

  const realm = IosRealm(Fun.noop);

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

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

  const doc = Traverse.owner(body);

  TestStyles.addStyles();

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

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

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

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

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

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

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

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

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

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

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

  Pipeline.async({}, [
    GuiSetup.mAddStyles(doc, [
      '.tinymce-mobile-icon-alpha:before { content: "ALPHA"; }',
      '.tinymce-mobile-icon-beta:before { content: "BETA"; }',
      '.tinymce-mobile-icon-gamma-class:before { content: "GAMMA"; }'
    ]),
    TestStyles.sWaitForToolstrip(realm),
    sTestAlpha,
    sTestBeta,
    sTestGamma
  ], function () {
    unload(); success();
  }, failure);
});
 TinyLoader.setup(function (editor, onSuccess, onFailure) {
   Pipeline.async({}, [
     sCheckButtonType(editor, 'splitbutton')
   ], onSuccess, onFailure);
 }, {
Example #22
0
    TinyLoader.setup((editor, onSuccess, onFailure) => {
      const tinyApis = TinyApis(editor);
      const tinyActions = TinyActions(editor);

      Pipeline.async({}, [
        tinyApis.sFocus,
        Logger.t('Home key', GeneralSteps.sequence([
          Logger.t('Home key should move caret before cef within the same block', GeneralSteps.sequence([
            tinyApis.sSetContent('<p>123</p><p><span contenteditable="false">CEF</span>456</p>'),
            tinyApis.sSetCursor([1, 1], 3),
            tinyActions.sContentKeystroke(VK.HOME, { }),
            tinyApis.sAssertSelection([1, 0], 0, [1, 0], 0)
          ])),
          Logger.t('Home key should move caret from after cef to before cef', GeneralSteps.sequence([
            tinyApis.sSetContent('<p><span contenteditable="false">CEF</span></p>'),
            tinyApis.sSetCursor([0], 1),
            tinyActions.sContentKeystroke(VK.HOME, { }),
            tinyApis.sAssertSelection([0, 0], 0, [0, 0], 0)
          ])),
          Logger.t('Home key should move caret to before cef from the start of range', GeneralSteps.sequence([
            tinyApis.sSetContent('<p>123</p><p><span contenteditable="false">CEF</span>456<br>789</p>'),
            tinyApis.sSetSelection([1, 1], 3, [1, 1], 3),
            tinyActions.sContentKeystroke(VK.HOME, { }),
            tinyApis.sAssertSelection([1, 0], 0, [1, 0], 0)
          ])),
          Logger.t('Home key should not move caret before cef within the same block if there is a BR in between', GeneralSteps.sequence([
            tinyApis.sSetContent('<p>123</p><p><span contenteditable="false">CEF</span><br>456</p>'),
            tinyApis.sSetCursor([1, 2], 3),
            tinyActions.sContentKeystroke(VK.HOME, { }),
            tinyApis.sAssertSelection([1, 2], 3, [1, 2], 3)
          ])),
          Logger.t('Home key should not move caret if there is no cef', GeneralSteps.sequence([
            tinyApis.sSetContent('<p>123</p>'),
            tinyApis.sSetCursor([0, 0], 1),
            tinyActions.sContentKeystroke(VK.HOME, { }),
            tinyApis.sAssertSelection([0, 0], 1, [0, 0], 1)
          ]))
        ])),
        Logger.t('End key', GeneralSteps.sequence([
          Logger.t('End key should move caret after cef within the same block', GeneralSteps.sequence([
            tinyApis.sSetContent('<p>123<span contenteditable="false">CEF</span></p><p>456</p>'),
            tinyApis.sSetCursor([0, 0], 0),
            tinyActions.sContentKeystroke(VK.END, { }),
            tinyApis.sAssertSelection([0, 2], 1, [0, 2], 1)
          ])),
          Logger.t('End key should move caret from before cef to after cef', GeneralSteps.sequence([
            tinyApis.sSetContent('<p><span contenteditable="false">CEF</span></p>'),
            tinyApis.sSetCursor([0], 0),
            tinyActions.sContentKeystroke(VK.END, { }),
            tinyApis.sAssertSelection([0, 1], 1, [0, 1], 1)
          ])),
          Logger.t('End key should move caret to after cef from the end of range', GeneralSteps.sequence([
            tinyApis.sSetContent('<p>123<br>456<span contenteditable="false">CEF</span></p>'),
            tinyApis.sSetSelection([0, 0], 0, [0, 2], 0),
            tinyActions.sContentKeystroke(VK.END, { }),
            tinyApis.sAssertSelection([0, 4], 1, [0, 4], 1)
          ])),
          Logger.t('End key should not move caret after cef within the same block if there is a BR in between', GeneralSteps.sequence([
            tinyApis.sSetContent('<p>123<br><span contenteditable="false">CEF</span></p><p>456</p>'),
            tinyApis.sSetCursor([0, 0], 0),
            tinyActions.sContentKeystroke(VK.END, { }),
            tinyApis.sAssertSelection([0, 0], 0, [0, 0], 0)
          ])),
          Logger.t('End key should not move caret if there is no cef', GeneralSteps.sequence([
            tinyApis.sSetContent('<p>123</p>'),
            tinyApis.sSetCursor([0, 0], 1),
            tinyActions.sContentKeystroke(VK.END, { }),
            tinyApis.sAssertSelection([0, 0], 1, [0, 0], 1)
          ]))
        ]))
      ], onSuccess, onFailure);
    }, {
  TinyLoader.setup(function (editor, onSuccess, onFailure) {
    const tinyApis = TinyApis(editor);

    Pipeline.async({}, [
      tinyApis.sFocus,
      Logger.t('Delete image forwards', GeneralSteps.sequence([
        tinyApis.sSetContent('<p><img src="#1"></p>'),
        tinyApis.sSetCursor([0], 0),
        sDeleteElementPath(editor, true, [0, 0]),
        tinyApis.sAssertContent(''),
        tinyApis.sAssertSelection([0], 0, [0], 0)
      ])),
      Logger.t('Delete image backwards', GeneralSteps.sequence([
        tinyApis.sSetContent('<p><img src="#1"></p>'),
        tinyApis.sSetCursor([0], 1),
        sDeleteElementPath(editor, false, [0, 0]),
        tinyApis.sAssertContent(''),
        tinyApis.sAssertSelection([0], 0, [0], 0)
      ])),
      Logger.t('Delete first image forwards caret before', GeneralSteps.sequence([
        tinyApis.sSetContent('<p><img src="#1"><img src="#2"></p>'),
        tinyApis.sSetCursor([0], 0),
        sDeleteElementPath(editor, true, [0, 0]),
        tinyApis.sAssertContent('<p><img src="#2" /></p>'),
        tinyApis.sAssertSelection([0], 0, [0], 0)
      ])),
      Logger.t('Delete first image forwards caret after', GeneralSteps.sequence([
        tinyApis.sSetContent('<p><img src="#1"><img src="#2"></p>'),
        tinyApis.sSetCursor([0], 1),
        sDeleteElementPath(editor, true, [0, 0]),
        tinyApis.sAssertContent('<p><img src="#2" /></p>'),
        tinyApis.sAssertSelection([0], 0, [0], 0)
      ])),
      Logger.t('Delete first image backwards', GeneralSteps.sequence([
        tinyApis.sSetContent('<p><img src="#1"><img src="#2"></p>'),
        tinyApis.sSetCursor([0], 2),
        sDeleteElementPath(editor, false, [0, 0]),
        tinyApis.sAssertContent('<p><img src="#2" /></p>'),
        tinyApis.sAssertSelection([0], 0, [0], 0)
      ])),
      Logger.t('Delete second image forwards caret before', GeneralSteps.sequence([
        tinyApis.sSetContent('<p><img src="#1"><img src="#2"></p>'),
        tinyApis.sSetCursor([0], 1),
        sDeleteElementPath(editor, true, [0, 1]),
        tinyApis.sAssertContent('<p><img src="#1" /></p>'),
        tinyApis.sAssertSelection([0], 1, [0], 1)
      ])),
      Logger.t('Delete second image forwards caret after', GeneralSteps.sequence([
        tinyApis.sSetContent('<p><img src="#1"><img src="#2"></p>'),
        tinyApis.sSetCursor([0], 2),
        sDeleteElementPath(editor, true, [0, 1]),
        tinyApis.sAssertContent('<p><img src="#1" /></p>'),
        tinyApis.sAssertSelection([0], 1, [0], 1)
      ])),
      Logger.t('Delete second image backwards caret before', GeneralSteps.sequence([
        tinyApis.sSetContent('<p><img src="#1"><img src="#2"></p>'),
        tinyApis.sSetCursor([0], 1),
        sDeleteElementPath(editor, false, [0, 1]),
        tinyApis.sAssertContent('<p><img src="#1" /></p>'),
        tinyApis.sAssertSelection([0], 1, [0], 1)
      ])),
      Logger.t('Delete second image backwards caret after', GeneralSteps.sequence([
        tinyApis.sSetContent('<p><img src="#1"><img src="#2"></p>'),
        tinyApis.sSetCursor([0], 2),
        sDeleteElementPath(editor, false, [0, 1]),
        tinyApis.sAssertContent('<p><img src="#1" /></p>'),
        tinyApis.sAssertSelection([0], 1, [0], 1)
      ])),
      Logger.t('Delete forwards on paragraph to next paragraph with caret position (text)', GeneralSteps.sequence([
        tinyApis.sSetContent('<p>a</p><p>b</p>'),
        tinyApis.sSetCursor([0, 0], 1),
        sDeleteElementPath(editor, true, [0]),
        tinyApis.sAssertContent('<p>b</p>'),
        tinyApis.sAssertSelection([0, 0], 0, [0, 0], 0)
      ])),
      Logger.t('Delete backwards on paragraph to previous paragraph with caret position (text)', GeneralSteps.sequence([
        tinyApis.sSetContent('<p>a</p><p>b</p>'),
        tinyApis.sSetCursor([1, 0], 0),
        sDeleteElementPath(editor, false, [1]),
        tinyApis.sAssertContent('<p>a</p>'),
        tinyApis.sAssertSelection([0, 0], 1, [0, 0], 1)
      ])),
      Logger.t('Delete forwards on paragraph to previous paragraph with caret position (text)', GeneralSteps.sequence([
        tinyApis.sSetContent('<p>a</p><p>b</p>'),
        tinyApis.sSetCursor([1, 0], 1),
        sDeleteElementPath(editor, true, [1]),
        tinyApis.sAssertContent('<p>a</p>'),
        tinyApis.sAssertSelection([0, 0], 1, [0, 0], 1)
      ])),
      Logger.t('Delete backwards on paragraph to next paragraph with caret position (text)', GeneralSteps.sequence([
        tinyApis.sSetContent('<p>a</p><p>b</p>'),
        tinyApis.sSetCursor([0, 0], 0),
        sDeleteElementPath(editor, false, [0]),
        tinyApis.sAssertContent('<p>b</p>'),
        tinyApis.sAssertSelection([0, 0], 0, [0, 0], 0)
      ])),
      Logger.t('Delete forwards paragraph before paragraph with caret position (element)', GeneralSteps.sequence([
        tinyApis.sSetContent('<p><img src="#1" /></p><p><img src="#2" /></p>'),
        tinyApis.sSetCursor([0], 1),
        sDeleteElementPath(editor, true, [0]),
        tinyApis.sAssertContent('<p><img src="#2" /></p>'),
        tinyApis.sAssertSelection([0], 0, [0], 0)
      ])),
      Logger.t('Delete backwards paragraph after paragraph with caret position (element)', GeneralSteps.sequence([
        tinyApis.sSetContent('<p><img src="#1" /></p><p><img src="#2" /></p>'),
        tinyApis.sSetCursor([1], 0),
        sDeleteElementPath(editor, false, [0]),
        tinyApis.sAssertContent('<p><img src="#2" /></p>'),
        tinyApis.sAssertSelection([0], 0, [0], 0)
      ])),
      Logger.t('Delete backwards on cef block between cef blocks', GeneralSteps.sequence([
        tinyApis.sSetContent('<p contenteditable="false">a</p><p contenteditable="false">b</p><p contenteditable="false">c</p>'),
        tinyApis.sSetSelection([], 1, [], 2),
        sDeleteElementPath(editor, false, [1]),
        tinyApis.sAssertContent('<p contenteditable="false">a</p><p contenteditable="false">c</p>'),
        tinyApis.sAssertSelection([1], 0, [1], 0),
        sAssertCaretDirection(editor, 'after')
      ])),
      Logger.t('Delete forwards on cef block between cef blocks', GeneralSteps.sequence([
        tinyApis.sSetContent('<p contenteditable="false">a</p><p contenteditable="false">b</p><p contenteditable="false">c</p>'),
        tinyApis.sSetSelection([], 1, [], 2),
        sDeleteElementPath(editor, true, [1]),
        tinyApis.sAssertContent('<p contenteditable="false">a</p><p contenteditable="false">c</p>'),
        tinyApis.sAssertSelection([1], 0, [1], 0),
        sAssertCaretDirection(editor, 'before')
      ])),
      Logger.t('Delete element adjacent text nodes forward', GeneralSteps.sequence([
        tinyApis.sSetContent('<p>a<br>b</p>'),
        tinyApis.sSetCursor([0, 0], 1),
        sDeleteElementPath(editor, true, [0, 1]),
        tinyApis.sAssertContent('<p>ab</p>'),
        tinyApis.sAssertSelection([0, 0], 1, [0, 0], 1)
      ])),
      Logger.t('Delete element adjacent text nodes backwards', GeneralSteps.sequence([
        tinyApis.sSetContent('<p>a<br>b</p>'),
        tinyApis.sSetCursor([0, 2], 0),
        sDeleteElementPath(editor, false, [0, 1]),
        tinyApis.sAssertContent('<p>ab</p>'),
        tinyApis.sAssertSelection([0, 0], 1, [0, 0], 1)
      ]))
    ], onSuccess, onFailure);
  }, {
Example #24
0
  TinyLoader.setup(function (editor, onSuccess, onFailure) {
    const tinyApis = TinyApis(editor);

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

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

          EditorContent.setContent(editor, tree);
          Assertions.assertHtml('Should be expected tree html', '<p>tree</p>', EditorContent.getContent(editor));
        })
      ])),
      Logger.t('setContent tree filtered', GeneralSteps.sequence([
        tinyApis.sSetContent('<p>tree</p>'),
        Step.sync(() => {
          EditorContent.setContent(editor, getFontTree());
          Assertions.assertHtml('Should be expected filtered html', '<span style="font-size: 300%;">x</span>', EditorContent.getContent(editor));
        })
      ])),
      Logger.t('setContent tree using public api', GeneralSteps.sequence([
        tinyApis.sSetContent('<p>tree</p>'),
        Step.sync(() => {
          editor.setContent(getFontTree());
          Assertions.assertHtml('Should be expected filtered html', '<span style="font-size: 300%;">x</span>', EditorContent.getContent(editor));
        })
      ]))
    ], onSuccess, onFailure);
  }, {
Example #25
0
    function (realm, apis, toolbar, socket, buttons, onSuccess, onFailure) {

      const sSetS1 = apis.sSetSelection([ 0, 0 ], 'n'.length, [ 0, 0 ], 'n'.length);
      const sSetS2 = apis.sSetSelection([ 0, 1, 0 ], 'for'.length, [ 0, 1, 0 ], 'for'.length);

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

      const sTestFormatter = function (openTag, closeTag, name) {
        const sCheckS1 = function (situation) {
          return GeneralSteps.sequence([
            sSetS1,
            sCheckComponent(situation, false)(buttons[name])
          ]);
        };

        const sCheckS2 = function (situation) {
          return GeneralSteps.sequence([
            sSetS2,
            sCheckComponent(situation, true)(buttons[name])
          ]);
        };

        return GeneralSteps.sequence([
          apis.sSetContent(
            '<p>no format <' + openTag + '>format</' + closeTag + '>'
          ),
          sCheckS1('initial selection in text'),
          sCheckS2('normal >>> ' + name),
          sCheckS1(name + ' >>> normal'),

          TestUi.sClickComponent(realm, buttons[name]),
          sCheckComponent('"no" converted to ' + name, true)(buttons[name]),
          TestUi.sClickComponent(realm, buttons[name]),
          sCheckComponent('"no" reverted to normal', false)(buttons[name]),

          apis.sSetSelection([ 0, 1 + 1, 0 ], 'for'.length, [ 0, 1 + 1, 0 ], 'for'.length),
          sCheckComponent('moving back to ' + name, true)(buttons[name]),
          TestUi.sClickComponent(realm, buttons[name]),
          sCheckComponent('converting ' + name + ' to normal', false)(buttons[name]),
          TestUi.sClickComponent(realm, buttons[name]),
          sCheckComponent('reverting normal back to ' + name, true)(buttons[name])
        ]);
      };

      Pipeline.async({}, browser.isIE() || browser.isEdge() ? [] : [
        TestHelpers.GuiSetup.mAddStyles(Traverse.owner(body), [
          '.tinymce-mobile-toolbar-button { padding: 2px; border: 1px solid black; background: white; }',
          '.tinymce-mobile-toolbar-button.tinymce-mobile-toolbar-button-selected { background: #cadbee; }',
          '.tinymce-mobile-icon-bold:before { content: "BOLD"; }',
          '.tinymce-mobile-icon-italic:before { content: "ITALIC"; }',
          '.tinymce-mobile-icon-underline:before { content: "UNDERLINE"; }'
        ]),
        apis.sFocus,

        sTestFormatter('strong', 'strong', 'bold'),
        sTestFormatter('em', 'em', 'italic'),
        sTestFormatter('span style="text-decoration: underline;"', 'span', 'underline'),
        TestHelpers.GuiSetup.mRemoveStyles
      ], onSuccess, onFailure);
    }
Example #26
0
UnitTest.asynctest('browser.tinymce.core.focus.EditorFocusTest', function () {
  const success = arguments[arguments.length - 2];
  const failure = arguments[arguments.length - 1];
  const viewBlock = ViewBlock();

  Theme();

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

          Logger.t('Ctrl+arrow keys at anchor', GeneralSteps.sequence(
            WordSelection.hasSelectionModifyApi(editor) ? [
              Logger.t('Ctrl+Arrow right from inline boundary to next word', GeneralSteps.sequence([
                tinyApis.sSetRawContent('<p>aa <a href="#">bb</a> cc</p>'),
                tinyApis.sSetCursor([0, 1, 0], 2),
                tinyApis.sNodeChanged,
                tinyActions.sContentKeystroke(Keys.right(), { ctrl: !os.isOSX(), alt: os.isOSX() }),
                tinyApis.sAssertSelection([0, 2], 3, [0, 2], 3)
              ])),
              Logger.t('Ctrl+Arrow left from inline boundary to previous word', GeneralSteps.sequence([
                tinyApis.sSetRawContent('<p>aa <a href="#">bb</a> cc</p>'),
                tinyApis.sSetCursor([0, 1, 0], 0),
                tinyApis.sNodeChanged,
                tinyActions.sContentKeystroke(Keys.left(), { ctrl: !os.isOSX(), alt: os.isOSX() }),
                tinyApis.sAssertSelection([0, 0], 0, [0, 0], 0)
              ]))
            ] : []
          ))
        ]))
      ], onSuccess, onFailure);
    }, {
Example #29
0
 TinyLoader.setup(function (editor, onSuccess, onFailure) {
   Pipeline.async({}, suite.toSteps(editor), onSuccess, onFailure);
 }, {
Example #30
0
UnitTest.asynctest('browser.tinymce.core.geom.ClientRectTest', function () {
  const success = arguments[arguments.length - 2];
  const failure = arguments[arguments.length - 1];
  const suite = LegacyUnit.createSuite();

  const rect = function (x, y, w, h) {
    return {
      left: x,
      top: y,
      bottom: y + h,
      right: x + w,
      width: w,
      height: h
    };
  };

  suite.test('clone', function () {
    LegacyUnit.deepEqual(ClientRect.clone(rect(10, 20, 30, 40)), rect(10, 20, 30, 40));
    LegacyUnit.deepEqual(ClientRect.clone(rect(10.1, 20.1, 30.1, 40.1)), rect(10, 20, 30, 40));
  });

  suite.test('collapse', function () {
    LegacyUnit.deepEqual(ClientRect.collapse(rect(10, 20, 30, 40), true), rect(10, 20, 0, 40));
    LegacyUnit.deepEqual(ClientRect.collapse(rect(10, 20, 30, 40), false), rect(40, 20, 0, 40));
  });

  suite.test('isAbove', function () {
    LegacyUnit.equal(ClientRect.isAbove(rect(10, 70, 10, 40), rect(20, 40, 10, 20)), false);
    LegacyUnit.equal(ClientRect.isAbove(rect(10, 40, 10, 20), rect(20, 70, 10, 40)), true);
  });

  suite.test('isAbove intersects', function () {
    LegacyUnit.equal(ClientRect.isAbove(rect(10, 20, 10, 10), rect(20, 20, 10, 10)), false);
    LegacyUnit.equal(ClientRect.isAbove(rect(10, 20, 10, 40), rect(20, 20, 10, 10)), false);
    LegacyUnit.equal(ClientRect.isAbove(rect(10, 20, 10, 10), rect(20, 20, 10, 40)), false);
    LegacyUnit.equal(ClientRect.isAbove(rect(10, 10, 10, 10), rect(20, 15, 10, 10)), false);
    LegacyUnit.equal(ClientRect.isAbove(rect(10, 15, 10, 10), rect(20, 20, 10, 10)), false);
    LegacyUnit.equal(ClientRect.isAbove(rect(10, 10, 10, 10), rect(20, 20, 10, 10)), true);
    LegacyUnit.equal(ClientRect.isAbove(rect(10, 10, 10, 10), rect(20, 16, 10, 10)), true);
  });

  suite.test('isBelow', function () {
    LegacyUnit.equal(ClientRect.isBelow(rect(10, 70, 10, 40), rect(20, 40, 10, 20)), true);
    LegacyUnit.equal(ClientRect.isBelow(rect(10, 40, 10, 20), rect(20, 70, 10, 40)), false);
  });

  suite.test('isBelow intersects', function () {
    LegacyUnit.equal(ClientRect.isBelow(rect(10, 30, 10, 20), rect(20, 10, 10, 20)), true);
    LegacyUnit.equal(ClientRect.isBelow(rect(10, 30, 10, 20), rect(20, 10, 10, 25)), true);
    LegacyUnit.equal(ClientRect.isBelow(rect(10, 15, 10, 20), rect(20, 30, 10, 20)), false);
    LegacyUnit.equal(ClientRect.isBelow(rect(10, 29, 10, 20), rect(20, 10, 10, 30)), false);
    LegacyUnit.equal(ClientRect.isBelow(rect(10, 30, 10, 20), rect(20, 10, 10, 30)), true);
    LegacyUnit.equal(ClientRect.isBelow(rect(10, 20, 10, 20), rect(20, 10, 10, 30)), false);
  });

  suite.test('isLeft', function () {
    LegacyUnit.equal(ClientRect.isLeft(rect(10, 20, 30, 40), rect(20, 20, 30, 40)), true);
    LegacyUnit.equal(ClientRect.isLeft(rect(20, 20, 30, 40), rect(10, 20, 30, 40)), false);
  });

  suite.test('isRight', function () {
    LegacyUnit.equal(ClientRect.isRight(rect(10, 20, 30, 40), rect(20, 20, 30, 40)), false);
    LegacyUnit.equal(ClientRect.isRight(rect(20, 20, 30, 40), rect(10, 20, 30, 40)), true);
  });

  suite.test('compare', function () {
    LegacyUnit.equal(ClientRect.compare(rect(10, 70, 10, 40), rect(10, 40, 10, 20)), 1);
    LegacyUnit.equal(ClientRect.compare(rect(10, 40, 10, 20), rect(10, 70, 10, 40)), -1);
    LegacyUnit.equal(ClientRect.compare(rect(5, 10, 10, 10), rect(10, 10, 10, 10)), -1);
    LegacyUnit.equal(ClientRect.compare(rect(15, 10, 10, 10), rect(10, 10, 10, 10)), 1);
  });

  suite.test('containsXY', function () {
    LegacyUnit.equal(ClientRect.containsXY(rect(10, 70, 10, 40), 1, 2), false);
    LegacyUnit.equal(ClientRect.containsXY(rect(10, 70, 10, 40), 15, 2), false);
    LegacyUnit.equal(ClientRect.containsXY(rect(10, 70, 10, 40), 25, 2), false);
    LegacyUnit.equal(ClientRect.containsXY(rect(10, 70, 10, 40), 10, 70), true);
    LegacyUnit.equal(ClientRect.containsXY(rect(10, 70, 10, 40), 20, 110), true);
    LegacyUnit.equal(ClientRect.containsXY(rect(10, 70, 10, 40), 15, 75), true);
  });

  Pipeline.async({}, suite.toSteps({}), function () {
    success();
  }, failure);
});