Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

@joe-baudisch joe-baudisch Jul 21, 2025

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const cursor = this._editor.selection.getBookmark(3) as any
if (cursor['start'] && cursor['start'].startsWith('text()')) {
// do nothing because the cursor initially jumps back to first line with 'text()'
// while pasting in text from outside; moving to this bookmark after set content
// creates this cursor jump which should be avoided;
// if you paste in text from inside the editor or write text by keyboard
// the cursor starts with a paragraph tag created by TMCE like
// 'p[element_index]/text()[0]'
} else {
this._editor.setContent(isNullOrUndefined(value) ? '' : value);
try {
this._editor.selection.moveToBookmark(cursor);
} catch (e) {
/* ignore */
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,11 @@ export class EditorComponent extends Events implements AfterViewInit, ControlVal
if (this._editor && this._editor.initialized) {
const cursor = this._editor.selection.getBookmark(3);
this._editor.setContent(isNullOrUndefined(value) ? '' : value);
this._editor.selection.moveToBookmark(cursor);
try {
this._editor.selection.moveToBookmark(cursor);
} catch (e) {
/* ignore */
}
} else {
this.initialValue = value === null ? undefined : value;
}
Expand Down
116 changes: 76 additions & 40 deletions tinymce-angular-component/src/test/ts/browser/FormControlTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@ import { Assertions, Chain, Log, Pipeline } from '@ephox/agar';
import { UnitTest } from '@ephox/bedrock-client';
import { VersionLoader } from '@tinymce/miniature';

import { Editor } from 'tinymce';
import { EditorComponent, EditorModule } from '../../../main/ts/public_api';

UnitTest.asynctest('FormControlTest', (success, failure) => {
@Component({
template: `<editor [formControl]="control"></editor>`
template: `<editor [formControl]="control"></editor>`,
})
class EditorWithFormControl {
public control = new FormControl();
Expand All @@ -21,7 +22,7 @@ UnitTest.asynctest('FormControlTest', (success, failure) => {
interface TestContext {
testComponent: EditorWithFormControl;
fixture: ComponentFixture<EditorWithFormControl>;
editor: any;
editor: Editor;
}

const cSetupEditorWithFormControl = Chain.async<void, TestContext>((_, next) => {
Expand Down Expand Up @@ -53,42 +54,77 @@ UnitTest.asynctest('FormControlTest', (success, failure) => {
TestBed.resetTestingModule();
});

const sTestVersion = (version: '4' | '5' | '6') => VersionLoader.sWithVersion(
version,
Log.chainsAsStep('', 'FormControl interaction ', [
cSetupEditorWithFormControl,
Chain.op((context: TestContext) => {
Assertions.assertEq(
'Expect editor to have no initial value',
'',
context.editor.getContent()
);

context.testComponent.control.setValue('<p>Some Value</p>');
context.fixture.detectChanges();

Assertions.assertEq(
'Expect editor to have a value',
'<p>Some Value</p>',
context.editor.getContent()
);

context.testComponent.control.reset();
context.fixture.detectChanges();

Assertions.assertEq(
'Expect editor to be empty after reset',
'',
context.editor.getContent()
);
}),
cTeardown
])
);
const setFormValueAndReset = Chain.op((context: TestContext) => {
Assertions.assertEq('Expect editor to have no initial value', '', context.editor.getContent());

context.testComponent.control.setValue('<p>Some Value</p>');
context.fixture.detectChanges();

Assertions.assertEq(
'Expect editor to have a value',
'<p>Some Value</p>',
context.editor.getContent()
);

context.testComponent.control.reset();
context.fixture.detectChanges();

Assertions.assertEq('Expect editor to be empty after reset', '', context.editor.getContent());
});

/** Sets content and set cursor at the end, just like when a real user types */
const doFakeType = (str: string) =>
Chain.op((context: TestContext) => {
context.testComponent.control.patchValue(`${str}`);
context.fixture.detectChanges();

const rng = context.editor.dom.createRng();
const firstChild = context.editor.getBody().firstChild?.firstChild as Text;
rng.setStart(firstChild, str.length);
rng.setEnd(firstChild, str.length);

Pipeline.async({}, [
sTestVersion('4'),
sTestVersion('5'),
sTestVersion('6')
], success, failure);
});
context.editor.selection.setRng(rng);
context.fixture.detectChanges();
});

const checkCursor = Chain.op((context: TestContext) => {
const cursorStartPosition = context.editor.selection.getRng().startOffset;
const cursorEndPosition = context.editor.selection.getRng().endOffset;
const content = context.editor.getContent();
const strippedContent = content.replace(/<\/?[^>]+(>|$)/g, '');
Assertions.assertEq(
'Expect cursor start position to be at the end of the content',
cursorStartPosition,
strippedContent.length
);

Assertions.assertEq(
'Expect cursor end position to be at the end of the content',
cursorEndPosition,
strippedContent.length
);
});

const sTestVersion = (version: '4' | '5' | '6') =>
VersionLoader.sWithVersion(
version,
Log.chainsAsStep('', 'FormControl interaction ', [
cSetupEditorWithFormControl,
setFormValueAndReset,
doFakeType('test'),
checkCursor,
cTeardown,
])
);

Pipeline.async(
{},
[
sTestVersion('4'),
sTestVersion('5'),
sTestVersion('6'),
],
success,
failure
);
});
4 changes: 2 additions & 2 deletions tinymce-angular-component/src/test/ts/browser/LoadTinyTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ UnitTest.asynctest('LoadTinyTest', (success, failure) => {
TestBed.resetTestingModule();
});

const cAssertTinymceVersion = (version: '4' | '5' | '6') => Chain.op(() => {
const cAssertTinymceVersion = (version: '4' | '5' | '6' | '7') => Chain.op(() => {
Assertions.assertEq(`Loaded version of TinyMCE should be ${version}`, version, Global.tinymce.majorVersion);
});

Expand All @@ -85,7 +85,7 @@ UnitTest.asynctest('LoadTinyTest', (success, failure) => {
]),
Log.chainsAsStep('Should be able to load TinyMCE from Cloud', '', [
cSetupEditor([ 'apiKey="a-fake-api-key"', 'cloudChannel="6-dev"' ], []),
cAssertTinymceVersion('6'),
cAssertTinymceVersion('7'),
Chain.op(() => {
Assertions.assertEq(
'TinyMCE should have been loaded from Cloud',
Expand Down