diff --git a/scripts/utils/getkarmaconfig.js b/scripts/utils/getkarmaconfig.js index 2a77871..e8f7385 100644 --- a/scripts/utils/getkarmaconfig.js +++ b/scripts/utils/getkarmaconfig.js @@ -70,11 +70,11 @@ module.exports = function getKarmaConfig() { customLaunchers: { CHROME_TRAVIS_CI: { base: 'Chrome', - flags: [ '--no-sandbox', '--disable-background-timer-throttling' ] + flags: [ '--disable-background-timer-throttling', '--js-flags="--expose-gc"' ] }, CHROME_LOCAL: { base: 'Chrome', - flags: [ '--disable-background-timer-throttling' ] + flags: [ '--disable-background-timer-throttling', '--js-flags="--expose-gc"' ] } }, @@ -123,7 +123,10 @@ module.exports = function getKarmaConfig() { webpackConfig.module.rules.push( { test: /\.jsx?$/, loader: 'istanbul-instrumenter-loader', - include: /src/, + include: [ + /src/, + /_utils-tests/ + ], exclude: [ /node_modules/ ], diff --git a/src/ckeditor.jsx b/src/ckeditor.jsx index fb31fa9..c0a9f5b 100644 --- a/src/ckeditor.jsx +++ b/src/ckeditor.jsx @@ -21,13 +21,15 @@ export default class CKEditor extends React.Component { /** * An instance of EditorWatchdog or an instance of EditorWatchdog-like adapter for ContextWatchdog. * - * @type {EditorWatchdog|EditorWatchdogAdapter} + * @type {module:watchdog/watchdog~Watchdog|EditorWatchdogAdapter} */ this.watchdog = null; } /** * An editor instance. + * + * @type {module:core/editor/editor~Editor|null} */ get editor() { if ( !this.watchdog ) { @@ -37,17 +39,20 @@ export default class CKEditor extends React.Component { return this.watchdog.editor; } - // The CKEditor component should not be updated by React itself. - // However, if the component identifier changes, the whole structure should be created once again. + /** + * The CKEditor component should not be updated by React itself. + * However, if the component identifier changes, the whole structure should be created once again. + * + * @param {Object} nextProps + * @return {Boolean} + */ shouldComponentUpdate( nextProps ) { if ( !this.editor ) { return false; } - // Only when the editor component changes the whole structure should be restarted. + // Only when the component identifier changes the whole structure should be re-created once again. if ( nextProps.id !== this.props.id ) { - this._destroyEditor(); - return true; } @@ -62,22 +67,33 @@ export default class CKEditor extends React.Component { return false; } - // Initialize the editor when the component is mounted. + /** + * Initialize the editor when the component is mounted. + */ componentDidMount() { this._initializeEditor(); } - // Initialize the editor when the component is updated as it should be destroyed before the update. + /** + * Re-render the entire component once again. The old editor will be destroyed and the new one will be created. + */ componentDidUpdate() { + this._destroyEditor(); this._initializeEditor(); } - // Destroy the editor before unmouting the component. + /** + * Destroy the editor before unmounting the component. + */ componentWillUnmount() { this._destroyEditor(); } - // Render a
element which will be replaced by CKEditor. + /** + * Render a
element which will be replaced by CKEditor. + * + * @return {JSX.Element} + */ render() { return (
@@ -86,12 +102,14 @@ export default class CKEditor extends React.Component { /** * Initializes the editor by creating a proper watchdog and initializing it with the editor's configuration. + * + * @private */ _initializeEditor() { if ( this.context instanceof ContextWatchdog ) { this.watchdog = new EditorWatchdogAdapter( this.context ); } else { - this.watchdog = new EditorWatchdog( this.editor ); + this.watchdog = new CKEditor._EditorWatchdog( this.props.editor ); } this.watchdog.setCreator( ( el, config ) => this._createEditor( el, config ) ); @@ -107,6 +125,7 @@ export default class CKEditor extends React.Component { /** * Creates an editor from the element and configuration. * + * @private * @param {HTMLElement} element The source element. * @param {Object} config CKEditor 5 editor configuration. * @returns {Promise} @@ -157,6 +176,8 @@ export default class CKEditor extends React.Component { /** * Destroys the editor by destroying the watchdog. + * + * @private */ _destroyEditor() { // It may happen during the tests that the watchdog instance is not assigned before destroying itself. See: #197. @@ -172,7 +193,8 @@ export default class CKEditor extends React.Component { /** * Returns true when the editor should be updated. * - * @param {*} nextProps React's properties. + * @private + * @param {Object} nextProps React's properties. * @returns {Boolean} */ _shouldUpdateEditor( nextProps ) { @@ -192,6 +214,12 @@ export default class CKEditor extends React.Component { return true; } + /** + * Returns the editor configuration. + * + * @private + * @return {Object} + */ _getConfig() { if ( this.props.data && this.props.config.initialData ) { console.warn( @@ -277,12 +305,7 @@ class EditorWatchdogAdapter { * An editor instance. */ get editor() { - // TODO - try/catch should not be necessary as `getItem` could return `null` instead of throwing errors. - try { - return this._contextWatchdog.getItem( this._id ); - } catch ( err ) { - return null; - } + return this._contextWatchdog.getItem( this._id ); } } @@ -313,3 +336,7 @@ CKEditor.defaultProps = { config: {}, onError: ( error, details ) => console.error( error, details ) }; + +// Store the API in the static property to easily overwrite it in tests. +// Too bad dependency injection does not work in Webpack + ES 6 (const) + Babel. +CKEditor._EditorWatchdog = EditorWatchdog; diff --git a/tests/_utils-tests/context.js b/tests/_utils-tests/context.js new file mode 100644 index 0000000..75ff4fd --- /dev/null +++ b/tests/_utils-tests/context.js @@ -0,0 +1,40 @@ +/** + * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +import Context from '../_utils/context'; + +describe( 'Context', () => { + describe( 'constructor()', () => { + it( 'saves the config', () => { + const config = { foo: 'bar' }; + const context = new Context( config ); + + expect( context.config ).to.equal( config ); + } ); + } ); + + describe( 'destroy()', () => { + it( 'should return a promise that resolves properly', () => { + return Context.create() + .then( context => { + const promise = context.destroy(); + + expect( promise ).to.be.an.instanceof( Promise ); + + return promise; + } ); + } ); + } ); + + describe( 'create()', () => { + it( 'should return a promise that resolves properly', () => { + const promise = Context.create(); + + expect( promise ).to.be.an.instanceof( Promise ); + + return promise; + } ); + } ); +} ); diff --git a/tests/_utils-tests/turnoffdefaulterrorcatching.js b/tests/_utils-tests/turnoffdefaulterrorcatching.js index 7d1c9bd..16d5210 100644 --- a/tests/_utils-tests/turnoffdefaulterrorcatching.js +++ b/tests/_utils-tests/turnoffdefaulterrorcatching.js @@ -1,19 +1,22 @@ /** * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved. - * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + * For licensing, see LICENSE.md. */ /* global window */ -/** - * Turns off the default error catching - * so Mocha won't complain about errors caused by the called function. - */ -export default async function turnOffDefaultErrorCatching( fn ) { - const originalOnError = window.onerror; - window.onerror = null; +import turnOffDefaultErrorCatching from '../_utils/turnoffdefaulterrorcatching'; + +describe( 'turnOffDefaultErrorCatching()', () => { + it( 'should catch the error', () => { + const onErrorStub = sinon.stub( window, 'onerror' ); + + turnOffDefaultErrorCatching( () => { + window.onerror( 'Foo', null, 0 ); + } ); - await fn(); + onErrorStub.restore(); - window.onerror = originalOnError; -} + expect( onErrorStub.called ).to.equal( false ); + } ); +} ); diff --git a/tests/_utils/context.js b/tests/_utils/context.js index f312724..23cf822 100644 --- a/tests/_utils/context.js +++ b/tests/_utils/context.js @@ -1,13 +1,22 @@ - /** * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.md. */ +/** + * Mock of class that representing the Context feature. + * + * @see: https://ckeditor.com/docs/ckeditor5/latest/api/module_core_context-Context.html + */ export default class ContextMock { + constructor( config ) { + this.config = config; + } + static create( config ) { return Promise.resolve( new ContextMock( config ) ); } + static destroy() { return Promise.resolve(); } diff --git a/tests/_utils/turnoffdefaulterrorcatching.js b/tests/_utils/turnoffdefaulterrorcatching.js new file mode 100644 index 0000000..fd2e555 --- /dev/null +++ b/tests/_utils/turnoffdefaulterrorcatching.js @@ -0,0 +1,19 @@ +/** + * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +/* global window */ + +/** + * Turns off the default error catching + * so Mocha won't complain about errors caused by the called function. + */ +export default async function turnOffDefaultErrorCatching( fn ) { + const originalOnError = window.onerror; + window.onerror = () => {}; + + await fn(); + + window.onerror = originalOnError; +} diff --git a/tests/ckeditor-classiceditor.jsx b/tests/ckeditor-classiceditor.jsx deleted file mode 100644 index be9c458..0000000 --- a/tests/ckeditor-classiceditor.jsx +++ /dev/null @@ -1,36 +0,0 @@ -/** - * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved. - * For licensing, see LICENSE.md. - */ - -/* global ClassicEditor */ - -import React from 'react'; -import 'react-dom'; -import { configure, mount } from 'enzyme'; -import Adapter from 'enzyme-adapter-react-16'; - -import { CKEditor } from '../dist/ckeditor'; - -configure( { adapter: new Adapter() } ); - -describe( 'CKEditor Component + ClassicEditor Build', () => { - let wrapper; - - afterEach( () => { - if ( wrapper ) { - wrapper.unmount(); - } - } ); - - it( 'should initialize the ClassicEditor properly', async () => { - await new Promise( res => { - wrapper = mount( ); - } ); - - const component = wrapper.instance(); - - expect( component.editor ).to.not.be.null; - expect( component.editor.element ).to.not.be.null; - } ); -} ); diff --git a/tests/ckeditor.jsx b/tests/ckeditor.jsx index b5c6615..882ac4b 100644 --- a/tests/ckeditor.jsx +++ b/tests/ckeditor.jsx @@ -11,11 +11,11 @@ import Adapter from 'enzyme-adapter-react-16'; import Editor from './_utils/editor'; import CKEditor from '../src/ckeditor.jsx'; import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror'; -import turnOffDefaultErrorCatching from './_utils-tests/turnoffdefaulterrorcatching'; +import turnOffDefaultErrorCatching from './_utils/turnoffdefaulterrorcatching'; configure( { adapter: new Adapter() } ); -describe( 'CKEditor Component', () => { +describe( ' Component', () => { let wrapper; beforeEach( () => { @@ -87,61 +87,53 @@ describe( 'CKEditor Component', () => { ); } ); - it( 'sets initial data if was specified (using the "config" property with the `initialData` key)', done => { + it( 'sets initial data if was specified (using the "config" property with the `initialData` key)', async () => { sinon.stub( Editor, 'create' ).resolves( new Editor() ); - wrapper = mount( Hello CKEditor 5!

' - } } /> ); - - setTimeout( () => { - expect( Editor.create.firstCall.args[ 1 ].initialData ).to.equal( - '

Hello CKEditor 5!

' - ); - - done(); + await new Promise( res => { + wrapper = mount( Hello CKEditor 5!

' } } onReady={ res } /> ); } ); + + expect( Editor.create.firstCall.args[ 1 ].initialData ).to.equal( + '

Hello CKEditor 5!

' + ); } ); - it( 'shows a warning if used "data" and "config.initialData" at the same time', done => { + it( 'shows a warning if used "data" and "config.initialData" at the same time', async () => { const consoleWarnStub = sinon.stub( console, 'warn' ); - wrapper = mount( Bar

' - } } /> ); + await new Promise( res => { + const data = '

Foo

'; - setTimeout( () => { - // We must restore "console.warn" before assertions in order to see warnings if they were logged. - consoleWarnStub.restore(); + wrapper = mount( ); + } ); - expect( consoleWarnStub.calledOnce ).to.be.true; - expect( consoleWarnStub.firstCall.args[ 0 ] ).to.equal( - 'Editor data should be provided either using `config.initialData` or `data` properties. ' + - 'The config property is over the data value and the first one will be used when specified both.' - ); + // We must restore "console.warn" before assertions in order to see warnings if they were logged. + consoleWarnStub.restore(); - done(); - } ); + expect( consoleWarnStub.calledOnce ).to.be.true; + expect( consoleWarnStub.firstCall.args[ 0 ] ).to.equal( + 'Editor data should be provided either using `config.initialData` or `data` properties. ' + + 'The config property is over the data value and the first one will be used when specified both.' + ); } ); - it( 'uses "config.initialData" over "data" when specified both', done => { + it( 'uses "config.initialData" over "data" when specified both', async () => { const consoleWarnStub = sinon.stub( console, 'warn' ); sinon.stub( Editor, 'create' ).resolves( new Editor() ); - wrapper = mount( Bar

' - } } /> ); - - setTimeout( () => { - // We must restore "console.warn" before assertions in order to see warnings if they were logged. - consoleWarnStub.restore(); + await new Promise( res => { + wrapper = mount( Bar

' + } } onReady={ res } /> ); + } ); - expect( Editor.create.firstCall.args[ 1 ].initialData ).to.equal( - '

Bar

' - ); + // We must restore "console.warn" before assertions in order to see warnings if they were logged. + consoleWarnStub.restore(); - done(); - } ); + expect( Editor.create.firstCall.args[ 1 ].initialData ).to.equal( + '

Bar

' + ); } ); it( 'when setting initial data, it must not use "Editor.setData()"', async () => { @@ -191,6 +183,29 @@ describe( 'CKEditor Component', () => { expect( consoleErrorStub.firstCall.args[ 1 ].phase ).to.equal( 'initialization' ); expect( consoleErrorStub.firstCall.args[ 1 ].willEditorRestart ).to.equal( false ); } ); + + it( 'passes the specified editor class to the watchdog feature', async () => { + const EditorWatchdog = CKEditor._EditorWatchdog; + const constructorSpy = sinon.spy(); + + class CustomEditorWatchdog extends EditorWatchdog { + constructor( ...args ) { + super( ...args ); + constructorSpy( ...args ); + } + } + + CKEditor._EditorWatchdog = CustomEditorWatchdog; + + await new Promise( res => { + wrapper = mount( ); + } ); + + expect( constructorSpy.called ).to.equal( true ); + expect( constructorSpy.firstCall.args[ 0 ] ).to.equal( Editor ); + + CKEditor._EditorWatchdog = EditorWatchdog; + } ); } ); describe( 'properties', () => { @@ -279,140 +294,128 @@ describe( 'CKEditor Component', () => { } ); describe( '#onFocus', () => { - it( 'listens to the "viewDocument#focus" event in order to call "onFocus" callback', done => { + it( 'listens to the "viewDocument#focus" event in order to call "onFocus" callback', async () => { const editorInstance = new Editor(); const viewDocument = Editor._editing.view.document; sinon.stub( Editor, 'create' ).resolves( editorInstance ); sinon.stub( editorInstance, 'getData' ).returns( '

Foo.

' ); - wrapper = mount( ); - - setTimeout( () => { - // More events are being attached to `viewDocument`. - expect( viewDocument.on.calledTwice ).to.be.true; - expect( viewDocument.on.firstCall.args[ 0 ] ).to.equal( 'focus' ); - expect( viewDocument.on.firstCall.args[ 1 ] ).to.be.a( 'function' ); - - done(); + await new Promise( res => { + wrapper = mount( ); } ); + + // More events are being attached to `viewDocument`. + expect( viewDocument.on.calledTwice ).to.be.true; + expect( viewDocument.on.firstCall.args[ 0 ] ).to.equal( 'focus' ); + expect( viewDocument.on.firstCall.args[ 1 ] ).to.be.a( 'function' ); } ); - it( 'executes "onFocus" callback if it was specified and the editor was focused', done => { + it( 'executes "onFocus" callback if it was specified and the editor was focused', async () => { const onFocus = sinon.spy(); const editorInstance = new Editor(); const viewDocument = Editor._editing.view.document; sinon.stub( Editor, 'create' ).resolves( editorInstance ); - wrapper = mount( ); - - setTimeout( () => { - const fireChanges = viewDocument.on.firstCall.args[ 1 ]; - const event = { name: 'focus' }; + await new Promise( res => { + wrapper = mount( ); + } ); - fireChanges( event ); + const fireChanges = viewDocument.on.firstCall.args[ 1 ]; + const event = { name: 'focus' }; - expect( onFocus.calledOnce ).to.equal( true ); - expect( onFocus.firstCall.args[ 0 ] ).to.equal( event ); - expect( onFocus.firstCall.args[ 1 ] ).to.equal( editorInstance ); + fireChanges( event ); - done(); - } ); + expect( onFocus.calledOnce ).to.equal( true ); + expect( onFocus.firstCall.args[ 0 ] ).to.equal( event ); + expect( onFocus.firstCall.args[ 1 ] ).to.equal( editorInstance ); } ); - it( 'executes "onFocus" callback if it is available in runtime when the editor was focused', done => { + it( 'executes "onFocus" callback if it is available in runtime when the editor was focused', async () => { const onFocus = sinon.spy(); const editorInstance = new Editor(); const viewDocument = Editor._editing.view.document; sinon.stub( Editor, 'create' ).resolves( editorInstance ); - wrapper = mount( ); - - setTimeout( () => { - wrapper.setProps( { onFocus } ); + await new Promise( res => { + wrapper = mount( ); + } ); - const fireChanges = viewDocument.on.firstCall.args[ 1 ]; - const event = { name: 'focus' }; + wrapper.setProps( { onFocus } ); - fireChanges( event ); + const fireChanges = viewDocument.on.firstCall.args[ 1 ]; + const event = { name: 'focus' }; - expect( onFocus.calledOnce ).to.equal( true ); - expect( onFocus.firstCall.args[ 0 ] ).to.equal( event ); - expect( onFocus.firstCall.args[ 1 ] ).to.equal( editorInstance ); + fireChanges( event ); - done(); - } ); + expect( onFocus.calledOnce ).to.equal( true ); + expect( onFocus.firstCall.args[ 0 ] ).to.equal( event ); + expect( onFocus.firstCall.args[ 1 ] ).to.equal( editorInstance ); } ); } ); describe( '#onBlur', () => { - it( 'listens to the "viewDocument#blur" event in order to call "onBlur" callback', done => { + it( 'listens to the "viewDocument#blur" event in order to call "onBlur" callback', async () => { const editorInstance = new Editor(); const viewDocument = Editor._editing.view.document; sinon.stub( Editor, 'create' ).resolves( editorInstance ); sinon.stub( editorInstance, 'getData' ).returns( '

Foo.

' ); - wrapper = mount( ); - - setTimeout( () => { - // More events are being attached to `viewDocument`. - expect( viewDocument.on.calledTwice ).to.be.true; - expect( viewDocument.on.secondCall.args[ 0 ] ).to.equal( 'blur' ); - expect( viewDocument.on.secondCall.args[ 1 ] ).to.be.a( 'function' ); - - done(); + await new Promise( res => { + wrapper = mount( ); } ); + + // More events are being attached to `viewDocument`. + expect( viewDocument.on.calledTwice ).to.be.true; + expect( viewDocument.on.secondCall.args[ 0 ] ).to.equal( 'blur' ); + expect( viewDocument.on.secondCall.args[ 1 ] ).to.be.a( 'function' ); } ); - it( 'executes "onBlur" callback if it was specified and the editor was blurred', done => { + it( 'executes "onBlur" callback if it was specified and the editor was blurred', async () => { const onBlur = sinon.spy(); const editorInstance = new Editor(); const viewDocument = Editor._editing.view.document; sinon.stub( Editor, 'create' ).resolves( editorInstance ); - wrapper = mount( ); - - setTimeout( () => { - const fireChanges = viewDocument.on.secondCall.args[ 1 ]; - const event = { name: 'blur' }; + await new Promise( res => { + wrapper = mount( ); + } ); - fireChanges( event ); + const fireChanges = viewDocument.on.secondCall.args[ 1 ]; + const event = { name: 'blur' }; - expect( onBlur.calledOnce ).to.equal( true ); - expect( onBlur.firstCall.args[ 0 ] ).to.equal( event ); - expect( onBlur.firstCall.args[ 1 ] ).to.equal( editorInstance ); + fireChanges( event ); - done(); - } ); + expect( onBlur.calledOnce ).to.equal( true ); + expect( onBlur.firstCall.args[ 0 ] ).to.equal( event ); + expect( onBlur.firstCall.args[ 1 ] ).to.equal( editorInstance ); } ); - it( 'executes "onBlur" callback if it is available in runtime when the editor was blurred', done => { + it( 'executes "onBlur" callback if it is available in runtime when the editor was blurred', async () => { const onBlur = sinon.spy(); const editorInstance = new Editor(); const viewDocument = Editor._editing.view.document; sinon.stub( Editor, 'create' ).resolves( editorInstance ); - wrapper = mount( ); - - setTimeout( () => { - wrapper.setProps( { onBlur } ); + await new Promise( res => { + wrapper = mount( ); + } ); - const fireChanges = viewDocument.on.secondCall.args[ 1 ]; - const event = { name: 'blur' }; + wrapper.setProps( { onBlur } ); - fireChanges( event ); + const fireChanges = viewDocument.on.secondCall.args[ 1 ]; + const event = { name: 'blur' }; - expect( onBlur.calledOnce ).to.equal( true ); - expect( onBlur.firstCall.args[ 0 ] ).to.equal( event ); - expect( onBlur.firstCall.args[ 1 ] ).to.equal( editorInstance ); + fireChanges( event ); - done(); - } ); + expect( onBlur.calledOnce ).to.equal( true ); + expect( onBlur.firstCall.args[ 0 ] ).to.equal( event ); + expect( onBlur.firstCall.args[ 1 ] ).to.equal( editorInstance ); } ); } ); @@ -488,7 +491,7 @@ describe( 'CKEditor Component', () => { } ); describe( '#id', () => { - it( 'should make the editor restart when its value changes', async () => { + it( 'should re-mount the editor if the attribute has changed', async () => { sinon.stub( Editor, 'create' ).callsFake( async () => new Editor() ); const editor = await new Promise( ( res, rej ) => { @@ -497,7 +500,8 @@ describe( 'CKEditor Component', () => { onReady={ res } onError={ rej } config={ { initialData: '

foo

' } } - id="1" /> ); + id="1" + /> ); } ); sinon.assert.calledOnce( Editor.create ); @@ -514,14 +518,15 @@ describe( 'CKEditor Component', () => { expect( editor ).to.not.equal( editor2 ); } ); - it( 'should not make the editor restart when its value does not change', async () => { + it( 'should not re-mount the editor if the attribute has not changed', async () => { await new Promise( ( res, rej ) => { wrapper = mount( foo

' } } - id="1" /> ); + id="1" + /> ); } ); sinon.stub( Editor, 'create' ).callsFake( async () => new Editor() ); @@ -532,6 +537,47 @@ describe( 'CKEditor Component', () => { sinon.assert.notCalled( Editor.create ); } ); + + it( 'should destroy the old watchdog instance while re-mounting the editor', async () => { + await new Promise( ( res, rej ) => { + wrapper = mount( foo

' } } + id="1" + /> ); + } ); + + const { watchdog: firstWatchdog } = wrapper.instance(); + + await new Promise( res => { + wrapper.setProps( { onReady: res, id: '2', config: { initialData: '

bar

' } } ); + } ); + + const { watchdog: secondWatchdog } = wrapper.instance(); + + expect( firstWatchdog ).to.not.equal( secondWatchdog ); + expect( firstWatchdog.state ).to.equal( 'destroyed' ); + expect( secondWatchdog.state ).to.equal( 'ready' ); + } ); + } ); + + describe( '#onInit', () => { + it( 'should throw an error when using the unsupported property', async () => { + const consoleErrorStub = sinon.stub( console, 'error' ); + const onInit = sinon.spy(); + + wrapper = mount( ); + + consoleErrorStub.restore(); + + expect( onInit.called ).to.equal( false ); + expect( consoleErrorStub.calledOnce ).to.equal( true ); + expect( consoleErrorStub.firstCall.args[ 0 ] ).to.match( + /The "onInit" property is not supported anymore by the CKEditor component\. Use the "onReady" property instead./ + ); + } ); } ); } ); diff --git a/tests/ckeditorcontext.jsx b/tests/ckeditorcontext.jsx index e63ddff..76cc0b9 100644 --- a/tests/ckeditorcontext.jsx +++ b/tests/ckeditorcontext.jsx @@ -11,12 +11,12 @@ import CKEditor from '../src/ckeditor.jsx'; import EditorMock from './_utils/editor.js'; import ContextWatchdog from '@ckeditor/ckeditor5-watchdog/src/contextwatchdog'; import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror'; -import turnOffDefaultErrorCatching from './_utils-tests/turnoffdefaulterrorcatching.js'; +import turnOffDefaultErrorCatching from './_utils/turnoffdefaulterrorcatching.js'; import ContextMock from './_utils/context.js'; configure( { adapter: new Adapter() } ); -describe( 'CKEditor Context Component', () => { +describe( ' Component', () => { let wrapper; afterEach( () => { @@ -39,6 +39,14 @@ describe( 'CKEditor Context Component', () => { expect( component.contextWatchdog ).to.be.instanceOf( ContextWatchdog ); } ); + it( 'should not create anything if the layout is not ready', async () => { + wrapper = mount( ); + + const component = wrapper.instance(); + + expect( component.contextWatchdog ).to.equal( null ); + } ); + it( 'should render its children', async () => { wrapper = mount( @@ -174,7 +182,7 @@ describe( 'CKEditor Context Component', () => { } ); } ); - describe( 'onReady', () => { + describe( '#onReady', () => { it( 'should be called when all editors are ready', async () => { const editorReadySpy = sinon.spy(); @@ -195,8 +203,8 @@ describe( 'CKEditor Context Component', () => { } ); } ); - describe( 'Restarting CKEditor Context with editor', () => { - it( 'should restart the Context and all editors if the Context id changes', async () => { + describe( 'restarting CKEditorContext with nested CKEditor components', () => { + it( 'should restart the Context and all editors if the Context#id has changed', async () => { const oldContext = await new Promise( res => { wrapper = mount( @@ -215,5 +223,124 @@ describe( 'CKEditor Context Component', () => { expect( newContext ).to.not.equal( oldContext ); expect( newContext ).to.be.an.instanceOf( ContextMock ); } ); + + it( 'should re-render the entire component when the layout is ready', async () => { + wrapper = mount( + + + + ); + + const context = await new Promise( res => { + wrapper.setProps( { + onReady: res, + isLayoutReady: true + } ); + } ); + + expect( context ).to.be.an.instanceOf( ContextMock ); + expect( wrapper.instance().contextWatchdog ).to.not.equal( null ); + } ); + + it( 'should not re-render the component if layout is not ready after initialization', async () => { + const oldContext = await new Promise( res => { + wrapper = mount( + + + + ); + } ); + + wrapper.setProps( { + isLayoutReady: false + } ); + + const componentInstance = wrapper.instance(); // + + expect( componentInstance.contextWatchdog.context ).to.equal( oldContext ); + } ); + + it( 'should restart the Context and all editors if children has changed', async () => { + const oldContext = await new Promise( res => { + wrapper = mount( + + + + ); + } ); + + const newContext = await new Promise( res => { + wrapper.setProps( { + onReady: res, + children: [ + // The `key` property is required when defining children this way. + // See: https://reactjs.org/docs/lists-and-keys.html#keys. + , + + ] + } ); + } ); + + expect( newContext ).to.not.equal( oldContext ); + expect( newContext ).to.be.an.instanceOf( ContextMock ); + } ); + } ); +} ); + +describe( 'EditorWatchdogAdapter', () => { + let wrapper; + + afterEach( () => { + sinon.restore(); + + if ( wrapper ) { + wrapper.unmount(); + } + } ); + + describe( '#on', () => { + const error = new Error( 'Example error.' ); + + it( 'should execute the onError callback if an error was reported by the CKEditorContext component', async () => { + const errorSpy = sinon.spy(); + + await new Promise( res => { + wrapper = mount( + + + + ); + } ); + + const { watchdog } = wrapper.childAt( 0 ).instance(); + + watchdog._contextWatchdog._fire( 'itemError', { error, itemId: watchdog._id } ); + + expect( errorSpy.calledOnce ).to.equal( true ); + expect( errorSpy.firstCall.args[ 0 ] ).to.equal( error ); + } ); + + it( 'should execute the onError callback for proper editor', async () => { + const firstEditorErrorSpy = sinon.spy(); + const secondEditorErrorSpy = sinon.spy(); + + await new Promise( res => { + wrapper = mount( + + + + + ); + } ); + + // Report an error for the second editor. + const { watchdog } = wrapper.childAt( 1 ).instance(); + + watchdog._contextWatchdog._fire( 'itemError', { error, itemId: watchdog._id } ); + + expect( firstEditorErrorSpy.called ).to.equal( false ); + expect( secondEditorErrorSpy.calledOnce ).to.equal( true ); + expect( secondEditorErrorSpy.firstCall.args[ 0 ] ).to.equal( error ); + } ); } ); } ); diff --git a/tests/index.jsx b/tests/index.jsx index ae21673..2fd9f4c 100644 --- a/tests/index.jsx +++ b/tests/index.jsx @@ -13,42 +13,44 @@ import { CKEditor, CKEditorContext } from '../dist/ckeditor'; configure( { adapter: new Adapter() } ); -describe( 'index.js - CKEditor', () => { - let wrapper; +describe( 'index.js', () => { + describe( 'the component', () => { + let wrapper; + + afterEach( () => { + if ( wrapper ) { + wrapper.unmount(); + } + } ); - afterEach( () => { - if ( wrapper ) { - wrapper.unmount(); - } - } ); + it( 'should be the CKEditor Component', async () => { + expect( CKEditor ).to.be.a( 'function' ); - it( 'should be a CKEditor Component', async () => { - expect( CKEditor ).to.be.a( 'function' ); + const editor = await new Promise( res => { + wrapper = mount( ); + } ); - const editor = await new Promise( res => { - wrapper = mount( ); + expect( editor ).to.be.instanceOf( Editor ); } ); - - expect( editor ).to.be.instanceOf( Editor ); } ); -} ); -describe( 'index.js - CKEditorContext', () => { - let wrapper; + describe( 'the component', () => { + let wrapper; - afterEach( () => { - if ( wrapper ) { - wrapper.unmount(); - } - } ); + afterEach( () => { + if ( wrapper ) { + wrapper.unmount(); + } + } ); - it( 'should be a CKEditorContext Component', async () => { - expect( CKEditorContext ).to.be.a( 'function' ); + it( 'should be the CKEditorContext Component', async () => { + expect( CKEditorContext ).to.be.a( 'function' ); - const context = await new Promise( res => { - wrapper = mount( ); - } ); + const context = await new Promise( res => { + wrapper = mount( ); + } ); - expect( context ).to.be.instanceOf( ContextMock ); + expect( context ).to.be.instanceOf( ContextMock ); + } ); } ); } ); diff --git a/tests/integrations/ckeditor-classiceditor.jsx b/tests/integrations/ckeditor-classiceditor.jsx new file mode 100644 index 0000000..e77d32e --- /dev/null +++ b/tests/integrations/ckeditor-classiceditor.jsx @@ -0,0 +1,160 @@ +/** + * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +/* global ClassicEditor, window, document */ + +import React from 'react'; +import ReactDOM from 'react-dom'; +import { configure, mount } from 'enzyme'; +import Adapter from 'enzyme-adapter-react-16'; + +import { CKEditor } from '../..'; + +configure( { adapter: new Adapter() } ); + +describe( 'CKEditor Component + ClassicEditor Build', () => { + let wrapper; + + afterEach( () => { + if ( wrapper ) { + wrapper.unmount(); + } + } ); + + it( 'should initialize the ClassicEditor properly', async () => { + await new Promise( res => { + wrapper = mount( ); + } ); + + const component = wrapper.instance(); + + expect( component.editor ).to.not.be.null; + expect( component.editor.element ).to.not.be.null; + } ); +} ); + +// The memory test based on: https://github.com/ckeditor/ckeditor5/blob/master/packages/ckeditor5-core/tests/_utils/memory.js. +// It's the simplified, adjusted version that allows checking whether the component destroys all references. +const TEST_RETRIES = 2; +const TEST_TIMEOUT = 5000; +const GARBAGE_COLLECTOR_TIMEOUT = 500; + +describe( ' memory usage', () => { + const config = { + initialData: '

Editor 1

\n' + + '

This is an editor instance. And there\'s some link.

' + }; + + let div; + + // Will skip test suite if tests are run inside incompatible environment: + // - No window.gc (only Google Chrome). + // - Chrome on Windows (tests heavily break). + // + // Currently on Google Chrome supports this method and must be run with proper flags: + // + // google-chrome -js-flags="--expose-gc" + // + before( function() { + if ( !window.gc || isWindows() ) { + this.skip(); + } + } ); + + // Single test case for memory usage test. Handles the memory leak test procedure. + // + // 1. Mount and unmount the component to pre-fill the memory with some cacheable data. + // 2. Record the heap size. + // 3. Mount and unmount the component 5 times. + // 4. Record the heap size and compare with the previous result. + // 5. Fail when exceeded a 1MB treshold (see code comments for why 1MB). + it( 'should not grow on multiple component creations', function() { + this.timeout( TEST_TIMEOUT ); + + // Unfortunately the tests fails from time to time so retry a failed tests. + this.retries( TEST_RETRIES ); + + function createEditor() { + div = document.createElement( 'div' ); + document.body.appendChild( div ); + + return new Promise( res => { + ReactDOM.render( , div ); + } ); + } + + function destroyEditor() { + return new Promise( res => { + ReactDOM.unmountComponentAtNode( div ); + div.remove(); + + res(); + } ); + } + + return runTest( createEditor, destroyEditor ); + } ); + + // Runs a single test case. + function runTest( createEditor, destroyEditor ) { + let memoryAfterFirstStart; + + return Promise + .resolve() + // Initialize the first editor before measuring the heap size. + // A cold start may allocate a bit of memory on the module-level. + .then( createAndDestroy ) + .then( () => { + return collectMemoryStats().then( mem => { + memoryAfterFirstStart = mem; + } ); + } ) + // Run create&destroy multiple times. Helps scaling up the issue. + .then( createAndDestroy ) // #1 + .then( createAndDestroy ) // #2 + .then( createAndDestroy ) // #3 + .then( createAndDestroy ) // #4 + .then( createAndDestroy ) // #5 + .then( collectMemoryStats ) + .then( memory => { + const memoryDifference = memory.usedJSHeapSize - memoryAfterFirstStart.usedJSHeapSize; + // While theoretically we should get 0KB when there's no memory leak, in reality, + // the results we get (when there are no leaks) vary from -500KB to 500KB (depending on which tests are executed). + // However, when we had memory leaks, memoryDifference was reaching 20MB, + // so, in order to detect significant memory leaks we can expect that the heap won't grow more than 1MB. + expect( memoryDifference, 'used heap size should not grow' ).to.be.at.most( 1e6 ); + } ); + + function createAndDestroy() { + return Promise.resolve() + .then( createEditor ) + .then( destroyEditor ); + } + } + + function collectMemoryStats() { + return new Promise( resolve => { + // Enforce garbage collection before recording memory stats. + window.gc(); + + setTimeout( () => { + const memeInfo = window.performance.memory; + + resolve( { + totalJSHeapSize: memeInfo.totalJSHeapSize, + usedJSHeapSize: memeInfo.usedJSHeapSize, + jsHeapSizeLimit: memeInfo.jsHeapSizeLimit + } ); + }, GARBAGE_COLLECTOR_TIMEOUT ); + } ); + } + + // The windows environment does not cooperate with this tests. + function isWindows() { + const userAgent = window.navigator.userAgent.toLowerCase(); + + return userAgent.indexOf( 'windows' ) > -1; + } +} ); diff --git a/tests/ckeditor-integration.jsx b/tests/integrations/ckeditor-editor-data.jsx similarity index 98% rename from tests/ckeditor-integration.jsx rename to tests/integrations/ckeditor-editor-data.jsx index 5d9a1b8..840a0e3 100644 --- a/tests/ckeditor-integration.jsx +++ b/tests/integrations/ckeditor-editor-data.jsx @@ -11,7 +11,7 @@ import ReactDOM from 'react-dom'; import { configure } from 'enzyme'; import Adapter from 'enzyme-adapter-react-16'; -import CKEditor from '../src/ckeditor.jsx'; +import CKEditor from '../../src/ckeditor.jsx'; configure( { adapter: new Adapter() } ); diff --git a/tests/integrations/39-frozen browser.jsx b/tests/issues/39-frozen browser.jsx similarity index 91% rename from tests/integrations/39-frozen browser.jsx rename to tests/issues/39-frozen browser.jsx index e418629..fbd850c 100644 --- a/tests/integrations/39-frozen browser.jsx +++ b/tests/issues/39-frozen browser.jsx @@ -44,7 +44,7 @@ class App extends React.Component { } } -describe( '#37 - bug: a browser is being froze', () => { +describe( 'issue #37: the browser is being frozen', () => { let div, component; beforeEach( () => { @@ -60,7 +60,7 @@ describe( '#37 - bug: a browser is being froze', () => { div.remove(); } ); - it( 'if "data" property is not specified, a browser should not be freeze', () => { + it( 'if the "#data" property is not specified, the browser should not freeze', () => { const editor = component.editor; editor.model.change( writer => { diff --git a/yarn.lock b/yarn.lock index dace7ab..47de7cd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9,7 +9,7 @@ dependencies: "@babel/highlight" "^7.10.4" -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13": +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13": version "7.12.13" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.13.tgz#dcfc826beef65e75c50e21d3837d7d95798dd658" integrity sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g== @@ -234,21 +234,6 @@ "@babel/plugin-transform-react-jsx-development" "^7.12.12" "@babel/plugin-transform-react-pure-annotations" "^7.12.1" -"@babel/runtime-corejs3@^7.10.2": - version "7.13.9" - resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.13.9.tgz#b2fa9a6e5690ef8d4c4f2d30cac3ec1a8bb633ce" - integrity sha512-p6WSr71+5u/VBf1KDS/Y4dK3ZwbV+DD6wQO3X2EbUVluEOiyXUk09DzcwSaUH4WomYXrEPC+i2rqzuthhZhOJw== - dependencies: - core-js-pure "^3.0.0" - regenerator-runtime "^0.13.4" - -"@babel/runtime@^7.10.2", "@babel/runtime@^7.12.5": - version "7.13.9" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.13.9.tgz#97dbe2116e2630c489f22e0656decd60aaa1fcee" - integrity sha512-aY2kU+xgJ3dJ1eU6FMB9EH8dIe8dmusF1xEku52joLvw6eAFN0AI+WxCLDnpev2LEejWBAy2sBvBOBAjI3zmvA== - dependencies: - regenerator-runtime "^0.13.4" - "@babel/template@^7.12.13": version "7.12.13" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.12.13.tgz#530265be8a2589dbb37523844c5bcb55947fb327" @@ -366,17 +351,6 @@ resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== -"@jest/types@^26.6.2": - version "26.6.2" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.6.2.tgz#bef5a532030e1d88a2f5a6d933f84e97226ed48e" - integrity sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ== - dependencies: - "@types/istanbul-lib-coverage" "^2.0.0" - "@types/istanbul-reports" "^3.0.0" - "@types/node" "*" - "@types/yargs" "^15.0.0" - chalk "^4.0.0" - "@mrmlnc/readdir-enhanced@^2.2.1": version "2.2.1" resolved "https://registry.yarnpkg.com/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz#524af240d1a360527b730475ecfa1344aa540dde" @@ -563,33 +537,6 @@ resolved "https://registry.yarnpkg.com/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz#8da5c6530915653f3a1f38fd5f101d8c3f8079c5" integrity sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ== -"@testing-library/dom@^7.28.1": - version "7.30.0" - resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-7.30.0.tgz#53697851f7708a1448cc30b74a2ea056dd709cd6" - integrity sha512-v4GzWtltaiDE0yRikLlcLAfEiiK8+ptu6OuuIebm9GdC2XlZTNDPGEfM2UkEtnH7hr9TRq2sivT5EA9P1Oy7bw== - dependencies: - "@babel/code-frame" "^7.10.4" - "@babel/runtime" "^7.12.5" - "@types/aria-query" "^4.2.0" - aria-query "^4.2.2" - chalk "^4.1.0" - dom-accessibility-api "^0.5.4" - lz-string "^1.4.4" - pretty-format "^26.6.2" - -"@testing-library/react@^11.2.5": - version "11.2.5" - resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-11.2.5.tgz#ae1c36a66c7790ddb6662c416c27863d87818eb9" - integrity sha512-yEx7oIa/UWLe2F2dqK0FtMF9sJWNXD+2PPtp39BvE0Kh9MJ9Kl0HrZAgEuhUJR+Lx8Di6Xz+rKwSdEPY2UV8ZQ== - dependencies: - "@babel/runtime" "^7.12.5" - "@testing-library/dom" "^7.28.1" - -"@types/aria-query@^4.2.0": - version "4.2.1" - resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-4.2.1.tgz#78b5433344e2f92e8b306c06a5622c50c245bf6b" - integrity sha512-S6oPal772qJZHoRZLFc/XoZW2gFvwXusYUmXPXkgxJLuEk2vOt7jc4Yo6z/vtI0EBkbPBVrJJ0B+prLIKiWqHg== - "@types/glob@^7.1.1": version "7.1.3" resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.3.tgz#e6ba80f36b7daad2c685acd9266382e68985c183" @@ -598,25 +545,6 @@ "@types/minimatch" "*" "@types/node" "*" -"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz#4ba8ddb720221f432e443bd5f9117fd22cfd4762" - integrity sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw== - -"@types/istanbul-lib-report@*": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#c14c24f18ea8190c118ee7562b7ff99a36552686" - integrity sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg== - dependencies: - "@types/istanbul-lib-coverage" "*" - -"@types/istanbul-reports@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz#508b13aa344fa4976234e75dddcc34925737d821" - integrity sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA== - dependencies: - "@types/istanbul-lib-report" "*" - "@types/json-schema@^7.0.5": version "7.0.7" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.7.tgz#98a993516c859eb0d5c4c8f098317a9ea68db9ad" @@ -652,18 +580,6 @@ resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.4.tgz#15925414e0ad2cd765bfef58842f7e26a7accb24" integrity sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug== -"@types/yargs-parser@*": - version "20.2.0" - resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-20.2.0.tgz#dd3e6699ba3237f0348cd085e4698780204842f9" - integrity sha512-37RSHht+gzzgYeobbG+KWryeAW8J33Nhr69cjTqSYymXVZEN9NbRYWoYlRtDhHKPVT1FyNKwaTPC1NynKZpzRA== - -"@types/yargs@^15.0.0": - version "15.0.13" - resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.13.tgz#34f7fec8b389d7f3c1fd08026a5763e072d3c6dc" - integrity sha512-kQ5JNTrbDv3Rp5X2n/iUu37IJBDU2gsZ5R/g1/KHOOEc5IKfUFjXT6DENPGduh08I/pamwtEq4oul7gUqKTQDQ== - dependencies: - "@types/yargs-parser" "*" - "@ungap/promise-all-settled@1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz#aa58042711d6e3275dd37dc597e5d31e8c290a44" @@ -1029,14 +945,6 @@ argparse@^2.0.1: resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== -aria-query@^4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-4.2.2.tgz#0d2ca6c9aceb56b8977e9fed6aed7e15bbd2f83b" - integrity sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA== - dependencies: - "@babel/runtime" "^7.10.2" - "@babel/runtime-corejs3" "^7.10.2" - arr-diff@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" @@ -2204,11 +2112,6 @@ copy-descriptor@^0.1.0: resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= -core-js-pure@^3.0.0: - version "3.9.1" - resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.9.1.tgz#677b322267172bd490e4464696f790cbc355bec5" - integrity sha512-laz3Zx0avrw9a4QEIdmIblnVuJz8W51leY9iLThatCsFawWxC3sE4guASC78JbCin+DkwMpCdp1AVAuzL/GN7A== - core-js@^2.4.0: version "2.6.12" resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec" @@ -2705,11 +2608,6 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" -dom-accessibility-api@^0.5.4: - version "0.5.4" - resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.4.tgz#b06d059cdd4a4ad9a79275f9d414a5c126241166" - integrity sha512-TvrjBckDy2c6v6RLxPv5QXOnU+SmF9nBII5621Ve5fu6Z/BDrENurBEvlC1f44lKEUVqOpK4w9E5Idc5/EgkLQ== - dom-serialize@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/dom-serialize/-/dom-serialize-2.2.1.tgz#562ae8999f44be5ea3076f5419dcd59eb43ac95b" @@ -5231,11 +5129,6 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" -lz-string@^1.4.4: - version "1.4.4" - resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.4.4.tgz#c0d8eaf36059f705796e1e344811cf4c498d3a26" - integrity sha1-wNjq82BZ9wV5bh40SBHPTEmNOiY= - macos-release@^2.2.0: version "2.4.1" resolved "https://registry.yarnpkg.com/macos-release/-/macos-release-2.4.1.tgz#64033d0ec6a5e6375155a74b1a1eba8e509820ac" @@ -6564,16 +6457,6 @@ prelude-ls@~1.1.2: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= -pretty-format@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.6.2.tgz#e35c2705f14cb7fe2fe94fa078345b444120fc93" - integrity sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg== - dependencies: - "@jest/types" "^26.6.2" - ansi-regex "^5.0.0" - ansi-styles "^4.0.0" - react-is "^17.0.1" - process-nextick-args@^2.0.0, process-nextick-args@~2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" @@ -6784,11 +6667,6 @@ react-is@^16.13.1, react-is@^16.8.1, react-is@^16.8.6: resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== -react-is@^17.0.1: - version "17.0.1" - resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.1.tgz#5b3531bd76a645a4c9fb6e693ed36419e3301339" - integrity sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA== - react-test-renderer@^16.0.0-0: version "16.14.0" resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.14.0.tgz#e98360087348e260c56d4fe2315e970480c228ae" @@ -6897,11 +6775,6 @@ regenerator-runtime@^0.11.0: resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg== -regenerator-runtime@^0.13.4: - version "0.13.7" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55" - integrity sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew== - regex-not@^1.0.0, regex-not@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c"