Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for the lock mechanism #212

Merged
merged 4 commits into from
Apr 12, 2022
Merged
Show file tree
Hide file tree
Changes from 3 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
2 changes: 1 addition & 1 deletion dist/ckeditor.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dist/ckeditor.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
],
"devDependencies": {
"@babel/core": "^7.17.7",
"@ckeditor/ckeditor5-build-classic": "^33.0.0",
"@ckeditor/ckeditor5-build-classic": "^34.0.0",
"@ckeditor/ckeditor5-dev-env": "^28.1.1",
"@ckeditor/ckeditor5-dev-utils": "^28.1.1",
"@vue/test-utils": "^2.0.0-rc.17",
Expand Down
8 changes: 8 additions & 0 deletions scripts/utils/getkarmaconfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
/* eslint-env node */

const path = require( 'path' );
const webpack = require( 'webpack' );

const options = parseArguments( process.argv.slice( 2 ) );

Expand All @@ -18,6 +19,13 @@ module.exports = function getKarmaConfig() {
const webpackConfig = {
mode: 'development',

plugins: [
new webpack.DefinePlugin( {
__VUE_OPTIONS_API__: true,
__VUE_PROD_DEVTOOLS__: false
} )
],

module: {
rules: [
{
Expand Down
44 changes: 34 additions & 10 deletions src/ckeditor.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,34 @@
* For licensing, see LICENSE.md.
*/

/* global console */
/* global window, console */

import { h, markRaw } from 'vue';
import { debounce } from 'lodash-es';

const SAMPLE_READ_ONLY_LOCK_ID = 'Integration Sample';
const INPUT_EVENT_DEBOUNCE_WAIT = 300;

export default {
name: 'ckeditor',

created() {
const { CKEDITOR_VERSION } = window;

// Starting from v34.0.0, CKEditor 5 introduces a lock mechanism enabling/disabling the read-only mode.
// As it is a breaking change between major releases of the integration, the component requires using
// CKEditor 5 in version 34 or higher.
if ( CKEDITOR_VERSION ) {
const [ major ] = CKEDITOR_VERSION.split( '.' ).map( Number );

if ( major < 34 ) {
console.warn( 'The <CKEditor> component requires using CKEditor 5 in version 34 or higher.' );
}
} else {
console.warn( 'Cannot find the "CKEDITOR_VERSION" in the "window" scope.' );
}
},

render() {
return h( this.tagName );
},
Expand Down Expand Up @@ -73,7 +91,9 @@ export default {
this.instance = markRaw( editor );

// Set initial disabled state.
editor.isReadOnly = this.disabled;
if ( this.disabled ) {
editor.enableReadOnlyMode( SAMPLE_READ_ONLY_LOCK_ID );
}

this.setUpEditorEvents();

Expand All @@ -100,12 +120,12 @@ export default {
modelValue( newValue, oldValue ) {
// Synchronize changes of #modelValue. There are two sources of changes:
//
// External modelValue change ------\
// -----> +-----------+
// | Component |
// -----> +-----------+
// Internal data change ------/
// (typing, commands, collaboration)
// External modelValue change ──────╮
// ╰─────> ┏━━━━━━━━━━━┓
// Component
// ╭─────> ┗━━━━━━━━━━━┛
// Internal data change ──────╯
// (typing, commands, collaboration)
//
// Case 1: If the change was external (via props), the editor data must be synced with
// the component using instance#setData() and it is OK to destroy the selection.
Expand All @@ -126,8 +146,12 @@ export default {
},

// Synchronize changes of #disabled.
disabled( val ) {
this.instance.isReadOnly = val;
disabled( readOnlyMode ) {
if ( readOnlyMode ) {
this.instance.enableReadOnlyMode( SAMPLE_READ_ONLY_LOCK_ID );
} else {
this.instance.disableReadOnlyMode( SAMPLE_READ_ONLY_LOCK_ID );
}
}
},

Expand Down
2 changes: 2 additions & 0 deletions src/plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
* For licensing, see LICENSE.md.
*/

/* eslint-env browser */

import Vue, { version as vueVersion } from 'vue';
import CKEditorComponent from './ckeditor.js';

Expand Down
10 changes: 10 additions & 0 deletions tests/_utils/mockeditor.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ export class MockEditor {
document: new ViewDocument()
}
};

this._readOnlyLocks = new Set();
}

static create( el, config ) {
Expand All @@ -47,5 +49,13 @@ export class MockEditor {
getData() {
return this.data;
}

enableReadOnlyMode( key ) {
this._readOnlyLocks.add( key );
}

disableReadOnlyMode( key ) {
this._readOnlyLocks.delete( key );
}
}

85 changes: 75 additions & 10 deletions tests/ckeditor.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* For licensing, see LICENSE.md.
*/

/* global console, setTimeout */
/* global window, console, setTimeout */

import { nextTick } from 'vue';
import { mount } from '@vue/test-utils';
Expand All @@ -15,20 +15,68 @@ import {
} from './_utils/mockeditor';

describe( 'CKEditor Component', () => {
let sandbox;
let sandbox, CKEDITOR_VERSION;

beforeEach( () => {
CKEDITOR_VERSION = window.CKEDITOR_VERSION;

window.CKEDITOR_VERSION = '34.0.0';
sandbox = sinon.createSandbox();
} );

afterEach( () => {
window.CKEDITOR_VERSION = CKEDITOR_VERSION;
sandbox.restore();
} );

it( 'should have a name', () => {
expect( CKEditorComponent.name ).to.equal( 'ckeditor' );
} );

it( 'should print a warning if the "window.CKEDITOR_VERSION" variable is not available', async () => {
const warnStub = sandbox.stub( console, 'warn' );

delete window.CKEDITOR_VERSION;

sandbox.stub( MockEditor, 'create' ).resolves( new MockEditor() );
const { wrapper } = mountComponent();

await nextTick();
wrapper.unmount();

expect( warnStub.callCount ).to.equal( 1 );
expect( warnStub.firstCall.args[ 0 ] ).to.equal( 'Cannot find the "CKEDITOR_VERSION" in the "window" scope.' );
} );

it( 'should print a warning if using CKEditor 5 in version lower than 34', async () => {
const warnStub = sandbox.stub( console, 'warn' );

window.CKEDITOR_VERSION = '30.0.0';

sandbox.stub( MockEditor, 'create' ).resolves( new MockEditor() );
const { wrapper } = mountComponent();

await nextTick();
wrapper.unmount();

expect( warnStub.callCount ).to.equal( 1 );
expect( warnStub.firstCall.args[ 0 ] ).to.equal( 'The <CKEditor> component requires using CKEditor 5 in version 34 or higher.' );
} );

it( 'should not print any warninig if using CKEditor 5 in version 34 or higher', async () => {
const warnStub = sandbox.stub( console, 'warn' );

window.CKEDITOR_VERSION = '34.0.0';

sandbox.stub( MockEditor, 'create' ).resolves( new MockEditor() );
const { wrapper } = mountComponent();

await nextTick();
wrapper.unmount();

expect( warnStub.callCount ).to.equal( 0 );
} );

it( 'should call editor#create when initializing', async () => {
const stub = sandbox.stub( MockEditor, 'create' ).resolves( new MockEditor() );
const { wrapper } = mountComponent();
Expand Down Expand Up @@ -132,25 +180,36 @@ describe( 'CKEditor Component', () => {
} );
} );

describe( '#disabled', () => {
it( 'should be defined', async () => {
describe( '_readOnlyLocks', () => {
it( 'should be an instance of set', async () => {
const { wrapper, vm } = mountComponent();

await nextTick();

expect( vm.disabled ).to.be.false;
expect( vm.instance._readOnlyLocks ).to.be.instanceOf( Set );

wrapper.unmount();
} );

it( 'should set the initial editor#isReadOnly', async () => {
it( 'should be empty when editor is not set to read only mode', async () => {
const { wrapper, vm } = mountComponent();

await nextTick();

expect( vm.instance._readOnlyLocks.size ).to.equal( 0 );

wrapper.unmount();
} );

it( 'should contain one lock when editor is set to read only mode', async () => {
const { wrapper, vm } = mountComponent( {
disabled: true
} );

await nextTick();

expect( vm.instance.isReadOnly ).to.be.true;
expect( vm.instance._readOnlyLocks.size ).to.equal( 1 );

wrapper.unmount();
} );
} );
Expand Down Expand Up @@ -235,20 +294,26 @@ describe( 'CKEditor Component', () => {
} );

describe( 'bindings', () => {
it( '#disabled should control editor#isReadOnly', async () => {
it( '#disabled should control read only mode of the editor', async () => {
const { wrapper, vm } = mountComponent( {
disabled: true
} );

await nextTick();

expect( vm.instance.isReadOnly ).to.be.true;
expect( vm.instance._readOnlyLocks.size ).to.equal( 1 );

wrapper.setProps( { disabled: false } );

await nextTick();

expect( vm.instance.isReadOnly ).to.be.false;
expect( vm.instance._readOnlyLocks.size ).to.equal( 0 );

wrapper.setProps( { disabled: true } );

await nextTick();

expect( vm.instance._readOnlyLocks.size ).to.equal( 1 );

wrapper.unmount();
} );
Expand Down