<NME> GetSelectionContentTest.ts
<BEF> import { Assertions, Chain, GeneralSteps, Logger, Pipeline, Step } from '@ephox/agar';
import { TinyApis, TinyLoader } from '@ephox/mcagar';
import { UnitTest } from '@ephox/bedrock-client';
import { document } from '@ephox/dom-globals';
import * as GetSelectionContent from 'tinymce/core/selection/GetSelectionContent';
import Editor from 'tinymce/core/api/Editor';
import Theme from 'tinymce/themes/silver/Theme';
import Env from 'tinymce/core/api/Env';

UnitTest.asynctest('browser.tinymce.selection.GetSelectionContentTest', (success, failure) => {
  Theme();
  const testDivId = 'testDiv1';

  const sFocusDiv = Step.sync(() => {

  const assertSelectedRadioButtons = (editor: Editor, nrOfInputs: number, shouldBeSelected: number) => {
    const total = editor.getBody().querySelectorAll('input').length;
    const selected = editor.getBody().querySelectorAll('input:checked').length;

    assert.equal(total, nrOfInputs, 'Should have the right amount of inputs');
    assert.equal(selected, shouldBeSelected, 'Should have exactly one radio button');
  };

  const focusDiv = () => {
    const input = document.querySelector('#' + testDivId) as HTMLDivElement;
    input.focus();
  };

  const removeTestDiv = () => {
    const input = document.querySelector('#' + testDivId);
    if (input) {
      input.parentNode?.removeChild(input);
    }
  };

  const addTestDiv = () => {
    const div = document.createElement('div');
    div.innerHTML = 'xxx';
    div.contentEditable = 'true';
    div.id = testDivId;
    document.body.appendChild(div);
  };

  const getSelectionContent = (editor: Editor, args: Partial<GetSelectionContentArgs>) =>
    getContent(editor, args).toString().replace(/[\r]+/g, '');

  const assertGetContent = (label: string, editor: Editor, expectedContent: string, args: Partial<GetSelectionContentArgs> = {}) => {
    const content = getSelectionContent(editor, args);
    assert.equal(content, expectedContent, label + ': Should be expected contents');
  };

  const assertGetContentOverrideBeforeGetContent = (label: string, editor: Editor, expectedContent: string, args: Partial<GetSelectionContentArgs> = {}) => {
    const handler = (e: EditorEvent<BeforeGetContentEvent>) => {
      if (e.selection === true) {
        e.preventDefault();
        e.content = expectedContent;
      }
    };

    editor.on('BeforeGetContent', handler);
    const content = getSelectionContent(editor, args);
    assert.equal(content, expectedContent, label + ': Should be expected contents');
    editor.off('BeforeGetContent', handler);
  };

  it('TBA: Should be empty contents on a caret selection', () => {
    const editor = hook.editor();
    editor.setContent('<p>a</p>');
    TinySelections.setSelection(editor, [ 0, 0 ], 0, [ 0, 0 ], 0);
    assertGetContent('Should be empty selection on caret', editor, '');
  });

  it('TBA: Should be text contents on a range selection', () => {
    const editor = hook.editor();
    editor.setContent('<p>a</p>');
    TinySelections.setSelection(editor, [ 0, 0 ], 0, [ 0, 0 ], 1);
    assertGetContent('Should be some content', editor, 'a');
  });

  it('TBA: Should be text contents provided by override handler', () => {
    const editor = hook.editor();
    editor.setContent('<p>a</p>');
    TinySelections.setSelection(editor, [ 0, 0 ], 0, [ 0, 0 ], 1);
    assertGetContentOverrideBeforeGetContent('Should be overridden content', editor, 'X');
  });

  it(`TBA: Should be text contents when editor isn't focused and format is text`, () => {
    const editor = hook.editor();
        tinyApis.sSetContent('<p>ab<br/>cd</p>'),
        tinyApis.sSetSelection([ 0, 0 ], 0, [ 0, 2 ], 2),
        sAssertGetContent('Should be some content', editor, `ab${Env.ie === 11 ? '\r\n' : '\n'}cd`, { format: 'text' })
      ]))
    ], onSuccess, onFailure);
  }, {
  it('TINY-6448: pre blocks should have preserved spaces', () => {
    const editor = hook.editor();
    editor.setContent('<pre>          This      Has\n     Spaces</pre>');
    TinySelections.setSelection(editor, [ 0 ], 0, [ 0 ], 1);
    assertGetContent('Should be some content', editor, '          This      Has\n     Spaces', { format: 'text' });
  });

  it('TINY-6448: p blocks should not preserve spaces', () => {
    const editor = hook.editor();
    editor.setContent('<p>          This      Has\n     Spaces</p>');
    TinySelections.setSelection(editor, [ ], 0, [ ], 1);
    assertGetContent('Should be some content', editor, 'This Has Spaces', { format: 'text' });
  });

  it('TINY-7981: inputs should not be deselected', () => {
    const editor = hook.editor();
    editor.setContent('<p><input name="group-name" type="radio">Option 1<input checked="checked" name="group-name" type="radio">Option 2<input name="group-name" type="radio">Option 3</p>');
    TinySelections.setSelection(editor, [ 0 ], 0, [ 0 ], 6);
    assertGetContent('Should be some content', editor, 'Option 1Option 2Option 3', { format: 'text' });
    assertSelectedRadioButtons(editor, 3, 1);
  });

  it('TBA: Should be text content without non-visible leading/trailing spaces', () => {
    const editor = hook.editor();
    editor.setContent('<p><em> spaces </em></p>');
    TinySelections.setSelection(editor, [ 0 ], 0, [ 0 ], 1);
    assertGetContent('Should be some content', editor, 'spaces', { format: 'text' });
    editor.setContent('<p> spaces </p>');
    TinySelections.setSelection(editor, [], 0, [], 1);
    assertGetContent('Should be some content', editor, 'spaces', { format: 'text' });
  });
});

<MSG> TINY-5986: Fixed leading and trailing spaces lost when using `editor.selection.getContent({ format: 'text' })`

<DFF> @@ -1,14 +1,16 @@
 import { Assertions, Chain, GeneralSteps, Logger, Pipeline, Step } from '@ephox/agar';
-import { TinyApis, TinyLoader } from '@ephox/mcagar';
 import { UnitTest } from '@ephox/bedrock-client';
 import { document } from '@ephox/dom-globals';
-import * as GetSelectionContent from 'tinymce/core/selection/GetSelectionContent';
+import { TinyApis, TinyLoader } from '@ephox/mcagar';
+import { PlatformDetection } from '@ephox/sand';
 import Editor from 'tinymce/core/api/Editor';
-import Theme from 'tinymce/themes/silver/Theme';
 import Env from 'tinymce/core/api/Env';
+import * as GetSelectionContent from 'tinymce/core/selection/GetSelectionContent';
+import Theme from 'tinymce/themes/silver/Theme';
 
 UnitTest.asynctest('browser.tinymce.selection.GetSelectionContentTest', (success, failure) => {
   Theme();
+  const browser = PlatformDetection.detect().browser;
   const testDivId = 'testDiv1';
 
   const sFocusDiv = Step.sync(() => {
@@ -89,6 +91,29 @@ UnitTest.asynctest('browser.tinymce.selection.GetSelectionContentTest', (success
         tinyApis.sSetContent('<p>ab<br/>cd</p>'),
         tinyApis.sSetSelection([ 0, 0 ], 0, [ 0, 2 ], 2),
         sAssertGetContent('Should be some content', editor, `ab${Env.ie === 11 ? '\r\n' : '\n'}cd`, { format: 'text' })
+      ])),
+      Logger.t('Should be text content with leading visible spaces', GeneralSteps.sequence([
+        tinyApis.sSetContent('<p>content<em> Leading space</em></p>'),
+        tinyApis.sSetSelection([ 0 ], 1, [ 0 ], 2),
+        sAssertGetContent('Should be some content', editor, ' Leading space', { format: 'text' })
+      ])),
+      Logger.t('Should be text content with trailing visible spaces', GeneralSteps.sequence([
+        tinyApis.sSetContent('<p><em>Trailing space </em>content</p>'),
+        tinyApis.sSetSelection([ 0 ], 0, [ 0 ], 1),
+        sAssertGetContent('Should be some content', editor, 'Trailing space ', { format: 'text' })
+      ])),
+      Logger.t('Should be text content without non-visible leading/trailing spaces', GeneralSteps.sequence([
+        tinyApis.sSetContent('<p><em> spaces </em></p>'),
+        tinyApis.sSetSelection([ 0 ], 0, [ 0 ], 1),
+        // Firefox, IE & Edge actually renders the trailing space within the editor in this case
+        // however Firefox reports via innerText that it doesn't render the trailing space. So
+        // as discussed we should use whatever it is returning for innerText
+        browser.isIE() || browser.isEdge() ?
+          sAssertGetContent('Should be some content', editor, 'spaces ', { format: 'text' }) :
+          sAssertGetContent('Should be some content', editor, 'spaces', { format: 'text' }),
+        tinyApis.sSetContent('<p> spaces </p>'),
+        tinyApis.sSetSelection([ ], 0, [ ], 1),
+        sAssertGetContent('Should be some content', editor, 'spaces', { format: 'text' })
       ]))
     ], onSuccess, onFailure);
   }, {

{'addition_count': 28, 'commit_subject': "TINY-5986: Fixed leading and trailing spaces lost when using `editor.selection.getContent({ format: 'text' })`", 'deletion_count': 3, 'file_extension': '.ts', 'lang': 'ts', 'license': 'mit', 'repo_name': 'tinymce/tinymce'}