Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
19 changes: 2 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,23 +20,8 @@ This package is a thin wrapper around [TinyMCE](https://github.com/tinymce/tinym
|<= 8 |3.x |
|< 5 | Not supported |

### Not yet Zoneless ( >=Angular v21 )
* This wrapper still requires `zone.js` to ensure backward compatibility to older Angular versions. Therefore, if your application uses Angular v21 or higher, it needs to include `provideZoneDetection()` in its providers.

```jsx
import { NgModule, provideZoneChangeDetection } from '@angular/core';

@NgModule({
declarations: [
// ...
],
imports: [
// ...
],
providers: [ provideZoneChangeDetection() ],
bootstrap: [ AppComponent ]
})
```
### Zoneless Support
This wrapper supports Angular's zoneless change detection. No additional configuration is needed — the component works with both zone-based and zoneless applications.

### Issues

Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,41 @@
import 'core-js/features/reflect';

// zone.js is imported here because our test suite needs to cover both zoneless and
// zone.js-based Angular applications. As a component library, our users may run
// either mode, so we must ensure compatibility with both. Since Angular 21, zoneless
// is the default, but zone.js remains supported. Once Angular drops zone.js support
// entirely, this import, ng-zone specific tests and the zone.js devDependency can be removed.
//
// Note: importing zone.js patches native browser APIs (addEventListener, setTimeout,
// setInterval, etc.), but Angular does not use these patches for change detection by
// default. Change detection only relies on zone.js in tests that explicitly configure
// `provideZoneChangeDetection`.
import 'zone.js';
import 'zone.js/plugins/fake-async-test';

import { TestBed } from '@angular/core/testing';
import { BrowserTestingModule, platformBrowserTesting } from '@angular/platform-browser/testing';
import { NgModule, provideZoneChangeDetection } from '@angular/core';
import { NgModule, provideZonelessChangeDetection } from '@angular/core';

// According to Angular docs, TestBed uses zone-based change detection by default
// when zone.js is loaded via polyfills:
// https://angular.dev/guide/zoneless#testing-and-debugging
//
// In practice, this behaviour seems to be driven by Angular's built-in test runners
// (Karma, Vitest) rather than TestBed itself. Since we use Bedrock, we appear to be
// immune to this — zone-based detection does not kick in automatically even with
// zone.js loaded. Nonetheless, we explicitly opt into zoneless change detection here
// to stay aligned with the Angular documentation and to be safe. Zone.js-specific
// tests can override this with `provideZoneChangeDetection` on a per-test basis.
@NgModule({
providers: [ provideZoneChangeDetection() ],
providers: [ provideZonelessChangeDetection() ],
})
class AppTestingModule {}

TestBed.initTestEnvironment(
[ BrowserTestingModule, AppTestingModule ], platformBrowserTesting(),
[ BrowserTestingModule, AppTestingModule ],
platformBrowserTesting(),
{
teardown: { destroyAfterEach: true },
teardown: { destroyAfterEach: true }
}
);
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import '../alien/InitTestEnvironment';

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 { map, merge, timer, first, buffer, Observable, tap, firstValueFrom } from 'rxjs';
import { NgZone } from '@angular/core';
import { eachVersionContext, EditorFixture, editorHook } from '../alien/TestHooks';
import { map, merge, timer, first, buffer, Observable, tap, firstValueFrom, identity } from 'rxjs';
import { NgZone, provideZoneChangeDetection } from '@angular/core';
import { Assertions } from '@ephox/agar';
import { Fun } from '@ephox/katamari';
import { throwTimeout } from '../alien/TestHelpers';
Expand All @@ -16,28 +16,47 @@ describe('EventBlacklistingTest', () => {
tap(() => Assertions.assertEq('Subscribers to events should run within NgZone', true, NgZone.isInAngularZone()))
);

const testEventsShouldBeBoundWhenAllowed = async (fixture: EditorFixture<EditorComponent>, isZoneless: boolean) => {
const pEventsCompleted = firstValueFrom(
merge(
fixture.editorComponent.onKeyUp.pipe(map(Fun.constant('onKeyUp')), isZoneless ? identity : shouldRunInAngularZone),
fixture.editorComponent.onKeyDown.pipe(map(Fun.constant('onKeyDown')), isZoneless ? identity : shouldRunInAngularZone),
fixture.editorComponent.onClick.pipe(map(Fun.constant('onClick')), isZoneless ? identity : shouldRunInAngularZone)
).pipe(throwTimeout(10000, 'Timed out waiting for some event to fire'), buffer(timer(100)), first())
);
fixture.editor.fire('keydown');
fixture.editor.fire('keyclick');
fixture.editor.fire('keyup');
const eventsCompleted = await pEventsCompleted;
Assertions.assertEq('Only one event should have fired', 1, eventsCompleted.length);
Assertions.assertEq('Only keyup should fire', 'onKeyUp', eventsCompleted[0]);
};

eachVersionContext([ '4', '5', '6', '7', '8' ], () => {
const createFixture = editorHook(EditorComponent);
context('zoneless', () => {
const createFixture = editorHook(EditorComponent);
const isZoneless = true;

it('Events should be bound when allowed', async () => {
const fixture = await createFixture({
allowedEvents: 'onKeyUp,onClick,onInit',
ignoreEvents: 'onClick',
it('Events should be bound when allowed', async () => {
const fixture = await createFixture({
allowedEvents: 'onKeyUp,onClick,onInit',
ignoreEvents: 'onClick',
});
await testEventsShouldBeBoundWhenAllowed(fixture, isZoneless);
});
});

const pEventsCompleted = firstValueFrom(
merge(
fixture.editorComponent.onKeyUp.pipe(map(Fun.constant('onKeyUp')), shouldRunInAngularZone),
fixture.editorComponent.onKeyDown.pipe(map(Fun.constant('onKeyDown')), shouldRunInAngularZone),
fixture.editorComponent.onClick.pipe(map(Fun.constant('onClick')), shouldRunInAngularZone)
).pipe(throwTimeout(10000, 'Timed out waiting for some event to fire'), buffer(timer(100)), first())
);
fixture.editor.fire('keydown');
fixture.editor.fire('keyclick');
fixture.editor.fire('keyup');
const eventsCompleted = await pEventsCompleted;
Assertions.assertEq('Only one event should have fired', 1, eventsCompleted.length);
Assertions.assertEq('Only keyup should fire', 'onKeyUp', eventsCompleted[0]);
context('with zone.js', () => {
const createFixture = editorHook(EditorComponent, { providers: [ provideZoneChangeDetection() ] });
const isZoneless = false;

it('Events should be bound when allowed', async () => {
const fixture = await createFixture({
allowedEvents: 'onKeyUp,onClick,onInit',
ignoreEvents: 'onClick',
});
await testEventsShouldBeBoundWhenAllowed(fixture, isZoneless);
});
});
});
});
4 changes: 2 additions & 2 deletions tinymce-angular-component/src/test/ts/browser/NgZoneTest.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import '../alien/InitTestEnvironment';

import { NgZone } from '@angular/core';
import { NgZone, provideZoneChangeDetection } from '@angular/core';
import { Assertions } from '@ephox/agar';
import { describe, it } from '@ephox/bedrock-client';

Expand All @@ -11,7 +11,7 @@ import { throwTimeout } from '../alien/TestHelpers';

describe('NgZoneTest', () => {
eachVersionContext([ '4', '5', '6', '7', '8' ], () => {
const createFixture = fixtureHook(EditorComponent, { imports: [ EditorComponent ] });
const createFixture = fixtureHook(EditorComponent, { imports: [ EditorComponent ], providers: [ provideZoneChangeDetection() ] });

it('Subscribers to events should run within NgZone', async () => {
const fixture = createFixture();
Expand Down
Loading