Skip to content

Commit cbd092a

Browse files
authored
INT-3298: Updated tests to BDD style (#390)
* INT-3298: Added TestHooks.ts * INT-3298: Added TestHelpers.ts * INT-3298: support for using minitature to specify tinymce version * INT-3298: Migrated LoadTinyTest to BDD style * INT-3298: throwTimeout message param is now optional * INT-3298: Migrated NgZoneTest.ts to BDD style * INT-3298: Initial migration changes to NgModelTest.ts * INT-3298: Added a *Test.ts eslint override * INT-3298: Refactored EditorFixture to extend ComponentFixture * INT-3298: Increased skin load timeout * INT-3298: loadedEditor$ is reset in now reset in a beforeEach * INT-3298: Removed old tests in NgModelTest.ts * INT-3298: Enabled `destroyAfterEach` teardown option * INT-3298: Refactored TestHooks.ts to account for teardown option being turned on now * INT-3298: Refactored migrated tests to new TestHooks.ts changes * INT-3298: Refactor to use `eachVersionContext` * INT-3298: `editorHook` can now be used with a single standalone component, removing the need for editorHookStandalone * INT-3298: Migrated EventBlacklistingTest.ts to BDD style Also removed TestStore.ts as it's no longer needed. * INT-3298: Added **/test/ts/**/*.ts to test eslint override * INT-3298: Moved fakeType to TestHelpers.ts * INT-3298: Added FormGroup test with default and onpush change detection * INT-3299: Fixed id prop logging a warning when it shouldn't (#392) * INT-3299: Check that an existing element with an id does not equal the current elementRef * INT-3299: Added captureLogs function * INT-3299: Added chai package for testing as well allowing it in eslint * INT-3299: Added PropTest.ts * INT-3299: Removed a comment * INT-3299: Increased timeout * INT-3299: use a `pTryUntil` instead of `waitForEditorsToLoad` * INT-3298: Added a changelog entry
1 parent 891859e commit cbd092a

File tree

15 files changed

+4285
-4000
lines changed

15 files changed

+4285
-4000
lines changed

.eslintrc.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,24 @@
4242
"quotes": [ "error", "single" ],
4343
"semi": "error"
4444
}
45+
},
46+
{
47+
"files": [
48+
"**/*Test.ts",
49+
"**/test/**/*.ts"
50+
],
51+
"plugins": [
52+
"chai-friendly"
53+
],
54+
"extends": [
55+
"plugin:chai-friendly/recommended"
56+
],
57+
"rules": {
58+
"no-unused-expressions": "off",
59+
"no-console": "off",
60+
"max-classes-per-file": "off",
61+
"@typescript-eslint/no-non-null-assertion": "off"
62+
}
4563
}
4664
],
4765
"extends": [

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88

99
### Fixed
1010
- Added rxjs ^7.4.0 as a peer dependency. Since last major release now uses rxjs v7 imports. #INT-3306
11+
- `id` prop no longer logs a console warning on any use. #INT-3299
1112

1213
## 8.0.0 - 2024-04-29
1314

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,15 @@
4343
"@tinymce/beehive-flow": "^0.19.0",
4444
"@tinymce/eslint-plugin": "^2.3.1",
4545
"@tinymce/miniature": "^6.0.0",
46+
"@types/chai": "^4.3.16",
4647
"@types/node": "^20.11.30",
4748
"autoprefixer": "^10.4.19",
4849
"babel-loader": "^9.1.3",
50+
"chai": "^5.1.1",
4951
"codelyzer": "^6.0.2",
5052
"copyfiles": "^2.4.1",
5153
"core-js": "^3.36.1",
54+
"eslint-plugin-chai-friendly": "^1.0.0",
5255
"eslint-plugin-storybook": "^0.8.0",
5356
"gh-pages": "^6.1.0",
5457
"json": "11.0.0",

tinymce-angular-component/src/main/ts/editor/editor.component.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,8 @@ export class EditorComponent extends Events implements AfterViewInit, ControlVal
157157
const tagName = typeof this.tagName === 'string' ? this.tagName : 'div';
158158
this._element = document.createElement(this.inline ? tagName : 'textarea');
159159
if (this._element) {
160-
if (document.getElementById(this.id)) {
160+
const existingElement = document.getElementById(this.id);
161+
if (existingElement && existingElement !== this._elementRef.nativeElement) {
161162
/* eslint no-console: ["error", { allow: ["warn"] }] */
162163
console.warn(`TinyMCE-Angular: an element with id [${this.id}] already exists. Editors with duplicate Id will not be able to mount`);
163164
}

tinymce-angular-component/src/test/ts/alien/InitTestEnvironment.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,6 @@ import 'zone.js/plugins/fake-async-test';
55
import { TestBed } from '@angular/core/testing';
66
import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing';
77

8-
TestBed.initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting());
8+
TestBed.initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting(), {
9+
teardown: { destroyAfterEach: true },
10+
});
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { Fun, Global, Arr, Strings } from '@ephox/katamari';
2+
import { Observable, throwError, timeout } from 'rxjs';
3+
import { ScriptLoader } from '../../../main/ts/utils/ScriptLoader';
4+
import { Attribute, Remove, SelectorFilter, SugarElement } from '@ephox/sugar';
5+
import { ComponentFixture } from '@angular/core/testing';
6+
import { By } from '@angular/platform-browser';
7+
import { EditorComponent } from '../../../main/ts/editor/editor.component';
8+
import { Editor } from 'tinymce';
9+
import { Keyboard, Keys } from '@ephox/agar';
10+
11+
export const apiKey = Fun.constant('qagffr3pkuv17a8on1afax661irst1hbr4e6tbv888sz91jc');
12+
13+
export const throwTimeout =
14+
(timeoutMs: number, message: string = `Timeout ${timeoutMs}ms`) =>
15+
<T>(source: Observable<T>) =>
16+
source.pipe(
17+
timeout({
18+
first: timeoutMs,
19+
with: () => throwError(() => new Error(message)),
20+
})
21+
);
22+
23+
export const deleteTinymce = () => {
24+
ScriptLoader.reinitialize();
25+
26+
delete Global.tinymce;
27+
delete Global.tinyMCE;
28+
29+
const hasTinyUri = (attrName: string) => (elm: SugarElement<Element>) =>
30+
Attribute.getOpt(elm, attrName).exists((src) => Strings.contains(src, 'tinymce'));
31+
32+
const elements = Arr.flatten([
33+
Arr.filter(SelectorFilter.all('script'), hasTinyUri('src')),
34+
Arr.filter(SelectorFilter.all('link'), hasTinyUri('href')),
35+
]);
36+
37+
Arr.each(elements, Remove.remove);
38+
};
39+
40+
export const captureLogs = async (
41+
method: 'log' | 'warn' | 'debug' | 'error',
42+
fn: () => Promise<void> | void
43+
): Promise<unknown[][]> => {
44+
const original = console[method];
45+
try {
46+
const logs: unknown[][] = [];
47+
console[method] = (...args: unknown[]) => logs.push(args);
48+
await fn();
49+
return logs;
50+
} finally {
51+
console[method] = original;
52+
}
53+
};
54+
55+
export const fakeTypeInEditor = (fixture: ComponentFixture<unknown>, str: string) => {
56+
const editor: Editor = fixture.debugElement.query(By.directive(EditorComponent)).componentInstance.editor!;
57+
editor.getBody().innerHTML = '<p>' + str + '</p>';
58+
Keyboard.keystroke(Keys.space(), {}, SugarElement.fromDom(editor.getBody()));
59+
fixture.detectChanges();
60+
};
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import { after, before, beforeEach, context } from '@ephox/bedrock-client';
2+
import { ComponentFixture, TestBed, TestModuleMetadata } from '@angular/core/testing';
3+
import { Type } from '@angular/core';
4+
import { EditorComponent, Version } from '../../../main/ts/editor/editor.component';
5+
import { firstValueFrom, map, switchMap, tap } from 'rxjs';
6+
import { By } from '@angular/platform-browser';
7+
import { Optional, Singleton } from '@ephox/katamari';
8+
import { VersionLoader } from '@tinymce/miniature';
9+
import { deleteTinymce, throwTimeout } from './TestHelpers';
10+
import { FormsModule, ReactiveFormsModule, NgModel } from '@angular/forms';
11+
import { Editor } from 'tinymce';
12+
13+
export const fixtureHook = <T = unknown>(component: Type<T>, moduleDef: TestModuleMetadata) => {
14+
before(async () => {
15+
await TestBed.configureTestingModule(moduleDef).compileComponents();
16+
});
17+
18+
return () => TestBed.createComponent(component);
19+
};
20+
21+
export const tinymceVersionHook = (version: Version) => {
22+
before(async () => {
23+
await VersionLoader.pLoadVersion(version);
24+
});
25+
after(() => {
26+
deleteTinymce();
27+
});
28+
};
29+
30+
export interface EditorFixture<T> extends ComponentFixture<T> {
31+
editorComponent: EditorComponent;
32+
editor: Editor;
33+
ngModel: Optional<NgModel>;
34+
}
35+
36+
export type CreateEditorFixture<T> = (
37+
props?: Partial<
38+
Omit<
39+
EditorComponent,
40+
`${'on' | 'ng' | 'register' | 'set' | 'write'}${string}` | 'createElement' | 'initialise' | 'editor'
41+
>
42+
>
43+
) => Promise<EditorFixture<T>>;
44+
45+
export const editorHook = <T = unknown>(component: Type<T>, moduleDef: TestModuleMetadata = {
46+
imports: [ component, EditorComponent, FormsModule, ReactiveFormsModule ],
47+
}): CreateEditorFixture<T> => {
48+
const createFixture = fixtureHook(component, moduleDef);
49+
const editorFixture = Singleton.value<EditorFixture<T>>();
50+
beforeEach(() => editorFixture.clear());
51+
52+
return async (props = {}) => {
53+
if (editorFixture.isSet()) {
54+
return editorFixture.get().getOrDie();
55+
}
56+
57+
const fixture = createFixture();
58+
const editorComponent =
59+
fixture.componentInstance instanceof EditorComponent
60+
? fixture.componentInstance
61+
: Optional.from(fixture.debugElement.query(By.directive(EditorComponent)))
62+
.map((v): EditorComponent => v.componentInstance)
63+
.getOrDie('EditorComponent instance not found');
64+
65+
for (const [ key, value ] of Object.entries(props)) {
66+
(editorComponent as any)[key] = value;
67+
}
68+
69+
fixture.detectChanges();
70+
71+
return firstValueFrom(
72+
editorComponent.onInit.pipe(
73+
throwTimeout(10000, `Timed out waiting for editor to load`),
74+
switchMap(
75+
({ editor }) =>
76+
new Promise<Editor>((resolve) => {
77+
if (editor.initialized) {
78+
resolve(editor);
79+
}
80+
editor.once('SkinLoaded', () => resolve(editor));
81+
})
82+
),
83+
map(
84+
(editor): EditorFixture<T> =>
85+
Object.assign(fixture, {
86+
editorComponent,
87+
editor,
88+
ngModel: Optional.from(fixture.debugElement.query(By.directive(EditorComponent))).bind((debugEl) =>
89+
Optional.from(debugEl.injector.get<NgModel>(NgModel, undefined, { optional: true }))
90+
),
91+
})
92+
),
93+
tap(editorFixture.set)
94+
)
95+
);
96+
};
97+
};
98+
99+
export const eachVersionContext = (versions: Version[], fn: (version: Version) => void) =>
100+
versions.forEach((version) =>
101+
context(`With version ${version}`, () => {
102+
tinymceVersionHook(version);
103+
fn(version);
104+
})
105+
);

tinymce-angular-component/src/test/ts/alien/TestStore.ts

Lines changed: 0 additions & 75 deletions
This file was deleted.

0 commit comments

Comments
 (0)