From 5425872c68f37f774a843442e58dfe22ae026bb7 Mon Sep 17 00:00:00 2001 From: Daniel Oakman <141111365+danoaky-tiny@users.noreply.github.com> Date: Thu, 20 Jun 2024 14:14:50 +1000 Subject: [PATCH 1/3] INT-3298: Added **/test/ts/**/*.ts to test eslint override --- .eslintrc.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.eslintrc.json b/.eslintrc.json index 2417f77f..1ce535b7 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -45,7 +45,8 @@ }, { "files": [ - "**/*Test.ts" + "**/*Test.ts", + "**/test/ts/**/*.ts" ], "rules": { "max-classes-per-file": "off", From 1ddf48987fdcafd16b5a9b5e617ef54c55937aa5 Mon Sep 17 00:00:00 2001 From: Daniel Oakman <141111365+danoaky-tiny@users.noreply.github.com> Date: Thu, 20 Jun 2024 14:15:13 +1000 Subject: [PATCH 2/3] INT-3298: Moved fakeType to TestHelpers.ts --- .../src/test/ts/alien/TestHelpers.ts | 11 +++++++++++ .../src/test/ts/browser/NgModelTest.ts | 18 ++++++------------ 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/tinymce-angular-component/src/test/ts/alien/TestHelpers.ts b/tinymce-angular-component/src/test/ts/alien/TestHelpers.ts index 16e2da2d..84af50e1 100644 --- a/tinymce-angular-component/src/test/ts/alien/TestHelpers.ts +++ b/tinymce-angular-component/src/test/ts/alien/TestHelpers.ts @@ -2,6 +2,11 @@ import { Fun, Global, Arr, Strings } from '@ephox/katamari'; import { Observable, throwError, timeout } from 'rxjs'; import { ScriptLoader } from '../../../main/ts/utils/ScriptLoader'; import { Attribute, Remove, SelectorFilter, SugarElement } from '@ephox/sugar'; +import { ComponentFixture } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { EditorComponent } from '../../../main/ts/editor/editor.component'; +import { Editor } from 'tinymce'; +import { Keyboard, Keys } from '@ephox/agar'; export const apiKey = Fun.constant( 'qagffr3pkuv17a8on1afax661irst1hbr4e6tbv888sz91jc', @@ -33,3 +38,9 @@ export const deleteTinymce = () => { Arr.each(elements, Remove.remove); }; +export const fakeTypeInEditor = (fixture: ComponentFixture, str: string) => { + const editor: Editor = fixture.debugElement.query(By.directive(EditorComponent)).componentInstance.editor!; + editor.getBody().innerHTML = '

' + str + '

'; + Keyboard.keystroke(Keys.space(), {}, SugarElement.fromDom(editor.getBody())); + fixture.detectChanges(); +}; diff --git a/tinymce-angular-component/src/test/ts/browser/NgModelTest.ts b/tinymce-angular-component/src/test/ts/browser/NgModelTest.ts index 943ab090..c9e2dab3 100644 --- a/tinymce-angular-component/src/test/ts/browser/NgModelTest.ts +++ b/tinymce-angular-component/src/test/ts/browser/NgModelTest.ts @@ -3,24 +3,18 @@ import '../alien/InitTestEnvironment'; import { Component } from '@angular/core'; import { FormsModule, NgModel } from '@angular/forms'; -import { Assertions, Waiter, Keyboard, Keys } from '@ephox/agar'; +import { Assertions, Waiter } from '@ephox/agar'; import { describe, it } from '@ephox/bedrock-client'; -import { SugarElement } from '@ephox/sugar'; import { EditorComponent } from '../../../main/ts/editor/editor.component'; -import { EditorFixture, eachVersionContext, editorHook } from '../alien/TestHooks'; +import { eachVersionContext, editorHook } from '../alien/TestHooks'; +import { fakeTypeInEditor } from '../alien/TestHelpers'; describe('NgModelTest', () => { const assertNgModelState = (prop: 'valid' | 'pristine' | 'touched', expected: boolean, ngModel: NgModel) => { Assertions.assertEq('assert ngModel ' + prop + ' state', expected, ngModel[prop]); }; - const fakeType = (fixture: EditorFixture, str: string) => { - fixture.editor.getBody().innerHTML = '

' + str + '

'; - Keyboard.keystroke(Keys.space(), {}, SugarElement.fromDom(fixture.editor.getBody())); - fixture.detectChanges(); - }; - eachVersionContext([ '4', '5', '6', '7' ], () => { @Component({ standalone: true, @@ -58,7 +52,7 @@ describe('NgModelTest', () => { it('should have correct control flags after interaction', async () => { const fixture = await createFixture(); const ngModel = fixture.ngModel.getOrDie('NgModel not found'); - fakeType(fixture, 'X'); + fakeTypeInEditor(fixture, 'X'); // Should be dirty after user input but remain untouched assertNgModelState('pristine', false, ngModel); assertNgModelState('touched', false, ngModel); @@ -71,7 +65,7 @@ describe('NgModelTest', () => { it('Test outputFormat="text"', async () => { const fixture = await createFixture({ outputFormat: 'text' }); - fakeType(fixture, 'X'); + fakeTypeInEditor(fixture, 'X'); Assertions.assertEq( 'Value bound to content via ngModel should be plain text', 'X', @@ -81,7 +75,7 @@ describe('NgModelTest', () => { it('Test outputFormat="html"', async () => { const fixture = await createFixture({ outputFormat: 'html' }); - fakeType(fixture, 'X'); + fakeTypeInEditor(fixture, 'X'); Assertions.assertEq( 'Value bound to content via ngModel should be html', '

X

', From 926dc8737b734872380f7946e2dbcdc56f067983 Mon Sep 17 00:00:00 2001 From: Daniel Oakman <141111365+danoaky-tiny@users.noreply.github.com> Date: Thu, 20 Jun 2024 14:15:56 +1000 Subject: [PATCH 3/3] INT-3298: Added FormGroup test with default and onpush change detection --- .../src/test/ts/browser/FormControlTest.ts | 137 +++++++++++++++--- 1 file changed, 115 insertions(+), 22 deletions(-) diff --git a/tinymce-angular-component/src/test/ts/browser/FormControlTest.ts b/tinymce-angular-component/src/test/ts/browser/FormControlTest.ts index f88683d6..eb3459f6 100644 --- a/tinymce-angular-component/src/test/ts/browser/FormControlTest.ts +++ b/tinymce-angular-component/src/test/ts/browser/FormControlTest.ts @@ -1,39 +1,132 @@ import '../alien/InitTestEnvironment'; -import { Component } from '@angular/core'; -import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { ChangeDetectionStrategy, Component, ViewChild, ElementRef } from '@angular/core'; +import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; import { Assertions } from '@ephox/agar'; -import { describe, it } from '@ephox/bedrock-client'; +import { context, describe, it } from '@ephox/bedrock-client'; import { EditorComponent } from '../../../main/ts/public_api'; -import { eachVersionContext, editorHook } from '../alien/TestHooks'; +import { eachVersionContext, editorHook, fixtureHook } from '../alien/TestHooks'; +import { By } from '@angular/platform-browser'; +import { first, firstValueFrom, switchMap } from 'rxjs'; +import { Editor } from 'tinymce'; +import { fakeTypeInEditor } from '../alien/TestHelpers'; + +type FormControlProps = Partial>; describe('FormControlTest', () => { - eachVersionContext([ '4', '5', '6', '7' ], () => { - @Component({ - standalone: true, - imports: [ EditorComponent, ReactiveFormsModule ], - template: ``, - }) - class EditorWithFormControl { - public control = new FormControl(); + const assertFormControl = (label: string, control: FormControlProps, expected: FormControlProps) => { + for (const [ key, value ] of Object.entries(expected)) { + Assertions.assertEq(`${label} - ${key}`, value, control[key as keyof FormControlProps]); } - const createFixture = editorHook(EditorWithFormControl); + }; + + eachVersionContext([ '4', '5', '6', '7' ], () => { + [ ChangeDetectionStrategy.Default, ChangeDetectionStrategy.OnPush ].forEach((changeDetection) => { + context(`[formControl] with change detection: ${changeDetection}`, () => { + @Component({ + standalone: true, + imports: [ EditorComponent, ReactiveFormsModule ], + changeDetection, + template: ``, + }) + class EditorWithFormControl { + public control = new FormControl(); + } + const createFixture = editorHook(EditorWithFormControl); + + it('FormControl interaction', async () => { + const fixture = await createFixture(); + + Assertions.assertEq('Expect editor to have no initial value', '', fixture.editor.getContent()); + + fixture.componentInstance.control.setValue('

Some Value

'); + fixture.detectChanges(); + + Assertions.assertEq('Expect editor to have a value', '

Some Value

', fixture.editor.getContent()); - it('FormControl interaction', async () => { - const fixture = await createFixture(); + fixture.componentInstance.control.reset(); + fixture.detectChanges(); - Assertions.assertEq('Expect editor to have no initial value', '', fixture.editor.getContent()); + Assertions.assertEq('Expect editor to be empty after reset', '', fixture.editor.getContent()); + }); + }); - fixture.componentInstance.control.setValue('

Some Value

'); - fixture.detectChanges(); + context(`[formGroup] with change detection: ${changeDetection}`, () => { + @Component({ + standalone: true, + changeDetection, + imports: [ EditorComponent, ReactiveFormsModule ], + template: ` +
+ + + + + `, + }) + class FormWithEditor { + @ViewChild('resetBtn') public resetBtn!: ElementRef; + @ViewChild('submitBtn') public submitBtn!: ElementRef; + public readonly form = new FormGroup({ + editor: new FormControl('', { + validators: Validators.compose([ + // eslint-disable-next-line @typescript-eslint/unbound-method + Validators.required, + Validators.minLength(10), + ]), + }), + }); + } + const createFixture = fixtureHook(FormWithEditor, { imports: [ FormWithEditor ] }); - Assertions.assertEq('Expect editor to have a value', '

Some Value

', fixture.editor.getContent()); + it('interaction', async () => { + const fixture = createFixture(); + fixture.detectChanges(); + const editorComponent: EditorComponent = fixture.debugElement.query( + By.directive(EditorComponent) + ).componentInstance; + const editor = await firstValueFrom( + editorComponent.onInit.pipe( + first(), + switchMap((ev) => new Promise((resolve) => ev.editor.on('SkinLoaded', () => resolve(ev.editor)))) + ) + ); + const form = fixture.componentInstance.form; + const initialProps: FormControlProps = { valid: false, dirty: false, pristine: true, touched: false }; + // const editorCtrl = form.get('editor')!; - fixture.componentInstance.control.reset(); - fixture.detectChanges(); + assertFormControl('Initial form', form, initialProps); + editor.fire('blur'); + assertFormControl('Form after editor blur', form, { ...initialProps, touched: true }); + fixture.componentInstance.resetBtn.nativeElement.click(); + fixture.detectChanges(); + assertFormControl('Form after reset', form, initialProps); - Assertions.assertEq('Expect editor to be empty after reset', '', fixture.editor.getContent()); + fakeTypeInEditor(fixture, 'x'); + assertFormControl('Form after typing one character', form, { + valid: false, + dirty: true, + pristine: false, + touched: false, + }); + editor.fire('blur'); + assertFormControl('Form after editor blur', form, { + valid: false, + dirty: true, + pristine: false, + touched: true, + }); + fakeTypeInEditor(fixture, 'x'.repeat(20)); + assertFormControl('Form after typing 10 characters', form, { + valid: true, + dirty: true, + pristine: false, + touched: true, + }); + Assertions.assertEq('Editor value has expected value', `

${'x'.repeat(20)}

`, form.value.editor); + }); + }); }); }); });