From 2a529f99fc72e888f1ce0966e712f81b7b415cfb Mon Sep 17 00:00:00 2001 From: Aleksander Nowodzinski Date: Wed, 3 Nov 2021 14:51:42 +0100 Subject: [PATCH 1/3] Destroyed FocusTracker and KeystrokeHandler instances in UI Views. --- .../src/ui/findandreplaceformview.js | 7 +++++++ packages/ckeditor5-font/src/ui/colortableview.js | 7 +++++++ .../src/imageinsert/ui/imageinsertpanelview.js | 7 +++++++ .../src/imagetextalternative/ui/textalternativeformview.js | 7 +++++++ packages/ckeditor5-link/src/ui/linkactionsview.js | 7 +++++++ packages/ckeditor5-link/src/ui/linkformview.js | 7 +++++++ packages/ckeditor5-media-embed/src/ui/mediaformview.js | 7 +++++++ .../src/tablecellproperties/ui/tablecellpropertiesview.js | 7 +++++++ .../src/tableproperties/ui/tablepropertiesview.js | 7 +++++++ packages/ckeditor5-ui/src/colorgrid/colorgridview.js | 7 +++++++ .../ckeditor5-ui/src/dropdown/button/splitbuttonview.js | 7 +++++++ packages/ckeditor5-ui/src/inputtext/inputtextview.js | 6 ++++++ packages/ckeditor5-ui/src/list/listview.js | 7 +++++++ .../ckeditor5-ui/src/panel/balloon/contextualballoon.js | 6 ++++++ packages/ckeditor5-ui/src/toolbar/toolbarview.js | 2 ++ 15 files changed, 98 insertions(+) diff --git a/packages/ckeditor5-find-and-replace/src/ui/findandreplaceformview.js b/packages/ckeditor5-find-and-replace/src/ui/findandreplaceformview.js index 8487df35908..b7725b5ae0a 100644 --- a/packages/ckeditor5-find-and-replace/src/ui/findandreplaceformview.js +++ b/packages/ckeditor5-find-and-replace/src/ui/findandreplaceformview.js @@ -353,6 +353,13 @@ export default class FindAndReplaceFormView extends View { this._initKeystrokeHandling(); } + destroy() { + super.destroy(); + + this._focusTracker.destroy(); + this._keystrokes.destroy(); + } + /** * Focuses the fist {@link #_focusables} in the form. */ diff --git a/packages/ckeditor5-font/src/ui/colortableview.js b/packages/ckeditor5-font/src/ui/colortableview.js index 177e69cf4f4..5eed001c6ac 100644 --- a/packages/ckeditor5-font/src/ui/colortableview.js +++ b/packages/ckeditor5-font/src/ui/colortableview.js @@ -235,6 +235,13 @@ export default class ColorTableView extends View { this.keystrokes.listenTo( this.element ); } + destroy() { + super.destroy(); + + this.focusTracker.destroy(); + this.keystrokes.destroy(); + } + /** * Appends {@link #staticColorsGrid} and {@link #documentColorsGrid} views. */ diff --git a/packages/ckeditor5-image/src/imageinsert/ui/imageinsertpanelview.js b/packages/ckeditor5-image/src/imageinsert/ui/imageinsertpanelview.js index 850fa469292..66d4e1124ef 100644 --- a/packages/ckeditor5-image/src/imageinsert/ui/imageinsertpanelview.js +++ b/packages/ckeditor5-image/src/imageinsert/ui/imageinsertpanelview.js @@ -203,6 +203,13 @@ export default class ImageInsertPanelView extends View { }, { priority: 'high' } ); } + destroy() { + super.destroy(); + + this.focusTracker.destroy(); + this.keystrokes.destroy(); + } + /** * Returns a view of the integration. * diff --git a/packages/ckeditor5-image/src/imagetextalternative/ui/textalternativeformview.js b/packages/ckeditor5-image/src/imagetextalternative/ui/textalternativeformview.js index d0713d65881..b40a184d20b 100644 --- a/packages/ckeditor5-image/src/imagetextalternative/ui/textalternativeformview.js +++ b/packages/ckeditor5-image/src/imagetextalternative/ui/textalternativeformview.js @@ -151,6 +151,13 @@ export default class TextAlternativeFormView extends View { } ); } + destroy() { + super.destroy(); + + this.focusTracker.destroy(); + this.keystrokes.destroy(); + } + /** * Creates the button view. * diff --git a/packages/ckeditor5-link/src/ui/linkactionsview.js b/packages/ckeditor5-link/src/ui/linkactionsview.js index f6b01fe0dd4..0b73d2bec48 100644 --- a/packages/ckeditor5-link/src/ui/linkactionsview.js +++ b/packages/ckeditor5-link/src/ui/linkactionsview.js @@ -155,6 +155,13 @@ export default class LinkActionsView extends View { this.keystrokes.listenTo( this.element ); } + destroy() { + super.destroy(); + + this.focusTracker.destroy(); + this.keystrokes.destroy(); + } + /** * Focuses the fist {@link #_focusables} in the actions. */ diff --git a/packages/ckeditor5-link/src/ui/linkformview.js b/packages/ckeditor5-link/src/ui/linkformview.js index df24b7bc578..71ebef56fa6 100644 --- a/packages/ckeditor5-link/src/ui/linkformview.js +++ b/packages/ckeditor5-link/src/ui/linkformview.js @@ -200,6 +200,13 @@ export default class LinkFormView extends View { this.keystrokes.listenTo( this.element ); } + destroy() { + super.destroy(); + + this.focusTracker.destroy(); + this.keystrokes.destroy(); + } + /** * Focuses the fist {@link #_focusables} in the form. */ diff --git a/packages/ckeditor5-media-embed/src/ui/mediaformview.js b/packages/ckeditor5-media-embed/src/ui/mediaformview.js index 56313a492db..206f67bbb24 100644 --- a/packages/ckeditor5-media-embed/src/ui/mediaformview.js +++ b/packages/ckeditor5-media-embed/src/ui/mediaformview.js @@ -210,6 +210,13 @@ export default class MediaFormView extends View { }, { priority: 'high' } ); } + destroy() { + super.destroy(); + + this.focusTracker.destroy(); + this.keystrokes.destroy(); + } + /** * Focuses the fist {@link #_focusables} in the form. */ diff --git a/packages/ckeditor5-table/src/tablecellproperties/ui/tablecellpropertiesview.js b/packages/ckeditor5-table/src/tablecellproperties/ui/tablecellpropertiesview.js index 8b33d3dfdef..dff190cb13d 100644 --- a/packages/ckeditor5-table/src/tablecellproperties/ui/tablecellpropertiesview.js +++ b/packages/ckeditor5-table/src/tablecellproperties/ui/tablecellpropertiesview.js @@ -430,6 +430,13 @@ export default class TableCellPropertiesView extends View { this.keystrokes.listenTo( this.element ); } + destroy() { + super.destroy(); + + this.focusTracker.destroy(); + this.keystrokes.destroy(); + } + /** * Focuses the fist focusable field in the form. */ diff --git a/packages/ckeditor5-table/src/tableproperties/ui/tablepropertiesview.js b/packages/ckeditor5-table/src/tableproperties/ui/tablepropertiesview.js index bfd28e493d2..4cf423bb1b6 100644 --- a/packages/ckeditor5-table/src/tableproperties/ui/tablepropertiesview.js +++ b/packages/ckeditor5-table/src/tableproperties/ui/tablepropertiesview.js @@ -379,6 +379,13 @@ export default class TablePropertiesView extends View { this.keystrokes.listenTo( this.element ); } + destroy() { + super.destroy(); + + this.focusTracker.destroy(); + this.keystrokes.destroy(); + } + /** * Focuses the fist focusable field in the form. */ diff --git a/packages/ckeditor5-ui/src/colorgrid/colorgridview.js b/packages/ckeditor5-ui/src/colorgrid/colorgridview.js index 609e4880a1b..b829d13df0e 100644 --- a/packages/ckeditor5-ui/src/colorgrid/colorgridview.js +++ b/packages/ckeditor5-ui/src/colorgrid/colorgridview.js @@ -176,6 +176,13 @@ export default class ColorGridView extends View { this.keystrokes.listenTo( this.element ); } + destroy() { + super.destroy(); + + this.focusTracker.destroy(); + this.keystrokes.destroy(); + } + /** * Fired when the `ColorTileView` for the picked item is executed. * diff --git a/packages/ckeditor5-ui/src/dropdown/button/splitbuttonview.js b/packages/ckeditor5-ui/src/dropdown/button/splitbuttonview.js index 5d9f7f82f0d..10b14f4b628 100644 --- a/packages/ckeditor5-ui/src/dropdown/button/splitbuttonview.js +++ b/packages/ckeditor5-ui/src/dropdown/button/splitbuttonview.js @@ -155,6 +155,13 @@ export default class SplitButtonView extends View { } ); } + destroy() { + super.destroy(); + + this.focusTracker.destroy(); + this.keystrokes.destroy(); + } + /** * Focuses the {@link #actionView#element} of the action part of split button. */ diff --git a/packages/ckeditor5-ui/src/inputtext/inputtextview.js b/packages/ckeditor5-ui/src/inputtext/inputtextview.js index 94ad312c9b3..0a607b0bbcd 100644 --- a/packages/ckeditor5-ui/src/inputtext/inputtextview.js +++ b/packages/ckeditor5-ui/src/inputtext/inputtextview.js @@ -160,6 +160,12 @@ export default class InputTextView extends View { } ); } + destroy() { + super.destroy(); + + this.focusTracker.destroy(); + } + /** * Moves the focus to the input and selects the value. */ diff --git a/packages/ckeditor5-ui/src/list/listview.js b/packages/ckeditor5-ui/src/list/listview.js index a0fb4b9d85d..79ea475aefa 100644 --- a/packages/ckeditor5-ui/src/list/listview.js +++ b/packages/ckeditor5-ui/src/list/listview.js @@ -109,6 +109,13 @@ export default class ListView extends View { this.keystrokes.listenTo( this.element ); } + destroy() { + super.destroy(); + + this.focusTracker.destroy(); + this.keystrokes.destroy(); + } + /** * Focuses the first focusable in {@link #items}. */ diff --git a/packages/ckeditor5-ui/src/panel/balloon/contextualballoon.js b/packages/ckeditor5-ui/src/panel/balloon/contextualballoon.js index f30bc59fbdb..45874a246ec 100644 --- a/packages/ckeditor5-ui/src/panel/balloon/contextualballoon.js +++ b/packages/ckeditor5-ui/src/panel/balloon/contextualballoon.js @@ -628,6 +628,12 @@ class RotatorView extends View { this.focusTracker.add( this.element ); } + destroy() { + super.destroy(); + + this.focusTracker.destroy(); + } + /** * Shows a given view. * diff --git a/packages/ckeditor5-ui/src/toolbar/toolbarview.js b/packages/ckeditor5-ui/src/toolbar/toolbarview.js index 17671792ddf..0326ab2432f 100644 --- a/packages/ckeditor5-ui/src/toolbar/toolbarview.js +++ b/packages/ckeditor5-ui/src/toolbar/toolbarview.js @@ -259,6 +259,8 @@ export default class ToolbarView extends View { */ destroy() { this._behavior.destroy(); + this.focusTracker.destroy(); + this.keystrokes.destroy(); return super.destroy(); } From 546d4535292e4b75fba51773fd4763ce156aa200 Mon Sep 17 00:00:00 2001 From: Aleksander Nowodzinski Date: Thu, 4 Nov 2021 13:18:10 +0100 Subject: [PATCH 2/3] Tests: Added a manual test for semi-automated memory leak analysis. --- .../manual/memory/memory-semi-automated.html | 264 ++++++++++++++++++ tests/manual/memory/memory-semi-automated.js | 223 +++++++++++++++ tests/manual/memory/memory-semi-automated.md | 26 ++ 3 files changed, 513 insertions(+) create mode 100644 tests/manual/memory/memory-semi-automated.html create mode 100644 tests/manual/memory/memory-semi-automated.js create mode 100644 tests/manual/memory/memory-semi-automated.md diff --git a/tests/manual/memory/memory-semi-automated.html b/tests/manual/memory/memory-semi-automated.html new file mode 100644 index 00000000000..7ab86bd2af4 --- /dev/null +++ b/tests/manual/memory/memory-semi-automated.html @@ -0,0 +1,264 @@ + + + + +

+ +

+ +
+

Lists in the table

+
+ + + + + + + + + + + + + +
Bulleted listNumbered listTo do list
+
    +
  • UL List item 1
  • +
  • UL List item 2
  • +
+
+
    +
  1. OL List item 1
  2. +
  3. OL List item 2
  4. +
+
+
    +
  • + +
  • +
  • + +
  • +
+
+
+ +

Basic features overview

+

Lorem ipsum dolor sit amet, consectetur adipisicing elit.1

+

Basic styles

+

Ad alias, architecto culpa cumque dignissimos dolor eos incidunt ipsa itaque laboriosam magnam molestias nihil numquam odit quam, suscipit veritatis voluptate voluptatum.2

+

Image

+
+ bar +
Caption
+
+

Blockquote

+
+

Quote

+
    +
  • Quoted UL List item 1
  • +
  • Quoted UL List item 2
  • +
+

Quote

+
+ +
 
+ +

Media with previews in the table

+
+ + + + + + + + + + + + + + + + + + +
ServicePreviewURL
YouTube +
+ +
+
+ https://www.youtube.com/watch?v=ZVv7UMQPEWk +
Vimeo +
+ +
+
+ https://vimeo.com/1084537 +
+
+ +

Code blocks in the table

+
+ + + + + + + + + + + + + + + + + + + + + +
LanguageCode snippetLanguageCode snippet
CSS +
body {
+	color: red;
+}
+
+p {
+	font-size: 10px;
+}
PHP
<?php
+
+function dump( array ...$args ) {
+	foreach ( $args as $item ) {
+		var_dump( $item );
+	}
+
+	die;
+}
JavaScript +
function foo() {
+	console.log( 'indented using 1 tab' );
+}
+
+function bar() {
+    console.log( 'indented using spaces' );
+}
Plaintext
Plain text
+
+ +
+ +

Link images + Link decorators

+ + + + + + + + + + + + +
Linked textLinked image (figure > a > img)Linked image (a > img)
+

+ https://cksource.com +

+
+
+ + bar + +
Caption
+
+
+ + bar + +
+ +
 
+ +

HTML embed

+ +

<details> and <summary> tags as HTML snippet

+
+
+ Details + Something small enough to escape casual notice. +
+
+ +

<video> tag as HTML snippet

+
+ +
+ +

<iframe> tag as HTML snippet

+
+ +
+ +

Paste from Office

+

Paste here:

+

+ +

Text part language

+

+ + Un lenguaje (del provenzal lenguatge y este del latín lingua) es un sistema de comunicación + estructurado para el que existe un contexto de uso y ciertos principios combinatorios formales. Existen + contextos tanto naturales como artificiales. + +

+

+ + اللغة نسق من الإشارات والرموز، يشكل أداة من أدوات المعرفة، وتعتبر اللغة أهم وسائل التفاهم + والاحتكاك بين أفراد امجتمع في جميع ميادين الحياة. وبدون اللغة يتعذر نشاط الناس المعرفي. + +

+ +

HTML comments

+ +

Paragraph

+ + + + + + + +
Table cell #1Table cell #2Table cell #3
+
    +
  1. Item #1
  2. +
  3. Item #2
  4. +
  5. Item #3 +
      +
    • Nested item #3.1
    • +
    • Nested item #3.2
    • +
    • Nested item #3.3
    • +
    +
  6. +
+
+ + diff --git a/tests/manual/memory/memory-semi-automated.js b/tests/manual/memory/memory-semi-automated.js new file mode 100644 index 00000000000..d4632bfde2f --- /dev/null +++ b/tests/manual/memory/memory-semi-automated.js @@ -0,0 +1,223 @@ +/** + * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +/* globals console:false, document, window, setTimeout, gc */ + +import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor'; +import Alignment from '@ckeditor/ckeditor5-alignment/src/alignment'; +import ArticlePluginSet from '@ckeditor/ckeditor5-core/tests/_utils/articlepluginset'; +import AutoImage from '@ckeditor/ckeditor5-image/src/autoimage'; +import AutoLink from '@ckeditor/ckeditor5-link/src/autolink'; +import Code from '@ckeditor/ckeditor5-basic-styles/src/code'; +import CodeBlock from '@ckeditor/ckeditor5-code-block/src/codeblock'; +import EasyImage from '@ckeditor/ckeditor5-easy-image/src/easyimage'; +import FindAndReplace from '@ckeditor/ckeditor5-find-and-replace/src/findandreplace'; +import FontBackgroundColor from '@ckeditor/ckeditor5-font/src/fontbackgroundcolor'; +import FontColor from '@ckeditor/ckeditor5-font/src/fontcolor'; +import FontFamily from '@ckeditor/ckeditor5-font/src/fontfamily'; +import FontSize from '@ckeditor/ckeditor5-font/src/fontsize'; +import Highlight from '@ckeditor/ckeditor5-highlight/src/highlight'; +import HorizontalLine from '@ckeditor/ckeditor5-horizontal-line/src/horizontalline'; +import HtmlEmbed from '@ckeditor/ckeditor5-html-embed/src/htmlembed'; +import HtmlComment from '@ckeditor/ckeditor5-html-support/src/htmlcomment'; +import ImageResize from '@ckeditor/ckeditor5-image/src/imageresize'; +import IndentBlock from '@ckeditor/ckeditor5-indent/src/indentblock'; +import LinkImage from '@ckeditor/ckeditor5-link/src/linkimage'; +import ListStyle from '@ckeditor/ckeditor5-list/src/liststyle'; +import Mention from '@ckeditor/ckeditor5-mention/src/mention'; +import PageBreak from '@ckeditor/ckeditor5-page-break/src/pagebreak'; +import PasteFromOffice from '@ckeditor/ckeditor5-paste-from-office/src/pastefromoffice'; +import RemoveFormat from '@ckeditor/ckeditor5-remove-format/src/removeformat'; +import SourceEditing from '@ckeditor/ckeditor5-source-editing/src/sourceediting'; +import SpecialCharacters from '@ckeditor/ckeditor5-special-characters/src/specialcharacters'; +import SpecialCharactersEssentials from '@ckeditor/ckeditor5-special-characters/src/specialcharactersessentials'; +import Strikethrough from '@ckeditor/ckeditor5-basic-styles/src/strikethrough'; +import Subscript from '@ckeditor/ckeditor5-basic-styles/src/subscript'; +import Superscript from '@ckeditor/ckeditor5-basic-styles/src/superscript'; +import TableCellProperties from '@ckeditor/ckeditor5-table/src/tablecellproperties'; +import TableProperties from '@ckeditor/ckeditor5-table/src/tableproperties'; +import TableCaption from '@ckeditor/ckeditor5-table/src/tablecaption'; +import TextTransformation from '@ckeditor/ckeditor5-typing/src/texttransformation'; +import TextPartLanguage from '@ckeditor/ckeditor5-language/src/textpartlanguage'; +import TodoList from '@ckeditor/ckeditor5-list/src/todolist'; +import Underline from '@ckeditor/ckeditor5-basic-styles/src/underline'; +import WordCount from '@ckeditor/ckeditor5-word-count/src/wordcount'; +import CloudServices from '@ckeditor/ckeditor5-cloud-services/src/cloudservices'; +import ImageUpload from '@ckeditor/ckeditor5-image/src/imageupload'; +// import MathType from '@wiris/mathtype-ckeditor5'; + +import { CS_CONFIG } from '@ckeditor/ckeditor5-cloud-services/tests/_utils/cloud-services-config'; + +function initEditor() { + return ClassicEditor + .create( document.querySelector( '#editor' ), { + plugins: [ + ArticlePluginSet, Underline, Strikethrough, Superscript, Subscript, Code, RemoveFormat, + FindAndReplace, FontColor, FontBackgroundColor, FontFamily, FontSize, Highlight, + CodeBlock, TodoList, ListStyle, TableProperties, TableCellProperties, TableCaption, + EasyImage, ImageResize, LinkImage, AutoImage, HtmlEmbed, HtmlComment, + AutoLink, Mention, TextTransformation, + Alignment, IndentBlock, + PasteFromOffice, PageBreak, HorizontalLine, + SpecialCharacters, SpecialCharactersEssentials, WordCount, + ImageUpload, CloudServices, TextPartLanguage, SourceEditing + // MathType + ], + toolbar: [ + // 'MathType', 'ChemType', + // '|', + 'heading', + '|', + 'removeFormat', 'bold', 'italic', 'strikethrough', 'underline', 'code', 'subscript', 'superscript', 'link', + '|', + 'highlight', 'fontSize', 'fontFamily', 'fontColor', 'fontBackgroundColor', + '|', + 'bulletedList', 'numberedList', 'todoList', + '|', + 'blockQuote', 'uploadImage', 'insertTable', 'mediaEmbed', 'codeBlock', + '|', + 'htmlEmbed', + '|', + 'alignment', 'outdent', 'indent', + '|', + 'pageBreak', 'horizontalLine', 'specialCharacters', + '|', + 'textPartLanguage', + '|', + 'sourceEditing', + '|', + 'undo', 'redo', 'findAndReplace' + ], + cloudServices: CS_CONFIG, + table: { + contentToolbar: [ + 'tableColumn', 'tableRow', 'mergeTableCells', 'tableProperties', 'tableCellProperties', 'toggleTableCaption' + ] + }, + image: { + styles: [ + 'alignCenter', + 'alignLeft', + 'alignRight' + ], + resizeOptions: [ + { + name: 'resizeImage:original', + label: 'Original size', + value: null + }, + { + name: 'resizeImage:50', + label: '50%', + value: '50' + }, + { + name: 'resizeImage:75', + label: '75%', + value: '75' + } + ], + toolbar: [ + 'imageTextAlternative', 'toggleImageCaption', '|', + 'imageStyle:inline', 'imageStyle:wrapText', 'imageStyle:breakText', 'imageStyle:side', '|', + 'resizeImage' + ], + insert: { + integrations: [ + 'insertImageViaUrl' + ] + } + }, + placeholder: 'Type the content here!', + mention: { + feeds: [ + { + marker: '@', + feed: [ + '@apple', '@bears', '@brownie', '@cake', '@cake', '@candy', '@canes', '@chocolate', '@cookie', '@cotton', + '@cream', '@cupcake', '@danish', '@donut', '@dragée', '@fruitcake', '@gingerbread', '@gummi', '@ice', + '@jelly-o', '@liquorice', '@macaroon', '@marzipan', '@oat', '@pie', '@plum', '@pudding', '@sesame', '@snaps', + '@soufflé', '@sugar', '@sweet', '@topping', '@wafer' + ], + minimumCharacters: 1 + } + ] + }, + link: { + decorators: { + isExternal: { + mode: 'manual', + label: 'Open in a new tab', + attributes: { + target: '_blank', + rel: 'noopener noreferrer' + } + }, + isDownloadable: { + mode: 'manual', + label: 'Downloadable', + attributes: { + download: 'download' + } + }, + isGallery: { + mode: 'manual', + label: 'Gallery link', + classes: 'gallery' + } + } + }, + htmlEmbed: { + showPreviews: true, + sanitizeHtml: html => ( { html, hasChange: false } ) + } + } ) + .then( editor => { + window.memoryTestEditor = editor; + + console.log( 'Editor was created.' ); + } ) + .catch( err => { + console.error( err.stack ); + } ); +} + +function destroyEditor() { + return window.memoryTestEditor.destroy().then( () => { + window.memoryTestEditor = null; + console.log( 'Editor was destroyed.' ); + } ); +} + +let i = 1; +function runAnotherCycleOfInitsAndDestroys() { + if ( i > 10 ) { + i = 0; + return; + } + + console.group( '#' + i ); + console.log( 'Starting init/destroy cycle #' + i ); + + initEditor().then( () => { + setTimeout( () => { + destroyEditor().then( () => { + console.log( 'Forcing the garbage collector.' ); + + gc(); + i++; + + console.log( 'Finished the cycle.' ); + console.groupEnd(); + + setTimeout( () => { + runAnotherCycleOfInitsAndDestroys(); + }, 500 ); + } ); + }, 500 ); + } ); +} + +document.getElementById( 'start' ).addEventListener( 'click', runAnotherCycleOfInitsAndDestroys ); diff --git a/tests/manual/memory/memory-semi-automated.md b/tests/manual/memory/memory-semi-automated.md new file mode 100644 index 00000000000..99c046e679b --- /dev/null +++ b/tests/manual/memory/memory-semi-automated.md @@ -0,0 +1,26 @@ +## A semi-automated tests to analyze memory leaks + +**The list of features loaded by the editor in this test could be outdated. Make sure as many features as possible are loaded, the `all-features` test is the right place to copy from.** + +1. Open DevTools on Chrome. +2. Go to Memory tab. +3. Start the test by clicking the button (if you see the "gc is not defined" error, see Notes below). +4. Take a snapshot (you don't need to GC manually). +5. Run another cycle. +6. Take another snapshot. +7. Repeat the process a couple of times. +8. Analyze the memory consumption by comparing the snapshots. It should not grow dramatically, that is more than ~.2MB per 10 initializations/destructions (1 cycle). + +## Notes: + +Browser extensions might attach event listeners to the DOM, so the safest way to run this test is by running Chrome in guest mode. + +This manual test also requires the browser to expose the `gc()` function. + +To prepare the browser, **close all Chrome windows first** (the whole app), then (on Mac): + +``` +/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --guest --js-flags="--expose-gc" +``` + +If you don't close all Chrome windows first, the `--expose-gc` flag will not work. From e8b0d2d2ad38a81c07196a27274cbd77a9f7905b Mon Sep 17 00:00:00 2001 From: Aleksander Nowodzinski Date: Thu, 4 Nov 2021 13:45:05 +0100 Subject: [PATCH 3/3] Tests: Ensured FocusTracker and KeystrokeHandler instances are destroyed. --- .../src/ui/findandreplaceformview.js | 3 ++ .../tests/ui/findandreplaceformview.js | 18 ++++++++++ .../ckeditor5-font/src/ui/colortableview.js | 3 ++ .../ckeditor5-font/tests/ui/colortableview.js | 18 ++++++++++ .../imageinsert/ui/imageinsertpanelview.js | 3 ++ .../ui/textalternativeformview.js | 3 ++ .../imageinsert/ui/imageinsertpanelview.js | 18 ++++++++++ .../ui/textalternativeformview.js | 18 ++++++++++ .../ckeditor5-link/src/ui/linkactionsview.js | 3 ++ .../ckeditor5-link/src/ui/linkformview.js | 3 ++ .../tests/ui/linkactionsview.js | 18 ++++++++++ .../ckeditor5-link/tests/ui/linkformview.js | 18 ++++++++++ .../src/ui/mediaformview.js | 3 ++ .../tests/ui/mediaformview.js | 18 ++++++++++ .../ui/tablecellpropertiesview.js | 3 ++ .../tableproperties/ui/tablepropertiesview.js | 3 ++ .../ui/tablecellpropertiesview.js | 18 ++++++++++ .../tableproperties/ui/tablepropertiesview.js | 18 ++++++++++ .../src/colorgrid/colorgridview.js | 3 ++ .../src/dropdown/button/splitbuttonview.js | 3 ++ .../src/inputtext/inputtextview.js | 3 ++ packages/ckeditor5-ui/src/list/listview.js | 3 ++ .../src/panel/balloon/contextualballoon.js | 14 ++++++++ .../tests/colorgrid/colorgridview.js | 18 ++++++++++ .../tests/dropdown/button/splitbuttonview.js | 18 ++++++++++ .../tests/inputtext/inputtextview.js | 10 ++++++ packages/ckeditor5-ui/tests/list/listview.js | 18 ++++++++++ .../tests/panel/balloon/contextualballoon.js | 34 +++++++++++++++++++ .../ckeditor5-ui/tests/toolbar/toolbarview.js | 16 +++++++++ 29 files changed, 329 insertions(+) diff --git a/packages/ckeditor5-find-and-replace/src/ui/findandreplaceformview.js b/packages/ckeditor5-find-and-replace/src/ui/findandreplaceformview.js index b7725b5ae0a..87d10777730 100644 --- a/packages/ckeditor5-find-and-replace/src/ui/findandreplaceformview.js +++ b/packages/ckeditor5-find-and-replace/src/ui/findandreplaceformview.js @@ -353,6 +353,9 @@ export default class FindAndReplaceFormView extends View { this._initKeystrokeHandling(); } + /** + * @inheritDoc + */ destroy() { super.destroy(); diff --git a/packages/ckeditor5-find-and-replace/tests/ui/findandreplaceformview.js b/packages/ckeditor5-find-and-replace/tests/ui/findandreplaceformview.js index e075d47c64d..d3f7a088359 100644 --- a/packages/ckeditor5-find-and-replace/tests/ui/findandreplaceformview.js +++ b/packages/ckeditor5-find-and-replace/tests/ui/findandreplaceformview.js @@ -673,6 +673,24 @@ describe( 'FindAndReplaceFormView', () => { } ); } ); + describe( 'destroy()', () => { + it( 'should destroy the FocusTracker instance', () => { + const destroySpy = sinon.spy( view._focusTracker, 'destroy' ); + + view.destroy(); + + sinon.assert.calledOnce( destroySpy ); + } ); + + it( 'should destroy the KeystrokeHandler instance', () => { + const destroySpy = sinon.spy( view._keystrokes, 'destroy' ); + + view.destroy(); + + sinon.assert.calledOnce( destroySpy ); + } ); + } ); + describe( 'focus()', () => { it( 'should focus the #findInputView', () => { const spy = sinon.spy( view._findInputView, 'focus' ); diff --git a/packages/ckeditor5-font/src/ui/colortableview.js b/packages/ckeditor5-font/src/ui/colortableview.js index 5eed001c6ac..2649fdff56c 100644 --- a/packages/ckeditor5-font/src/ui/colortableview.js +++ b/packages/ckeditor5-font/src/ui/colortableview.js @@ -235,6 +235,9 @@ export default class ColorTableView extends View { this.keystrokes.listenTo( this.element ); } + /** + * @inheritDoc + */ destroy() { super.destroy(); diff --git a/packages/ckeditor5-font/tests/ui/colortableview.js b/packages/ckeditor5-font/tests/ui/colortableview.js index ca23a72c698..e3c2e7367e6 100644 --- a/packages/ckeditor5-font/tests/ui/colortableview.js +++ b/packages/ckeditor5-font/tests/ui/colortableview.js @@ -145,6 +145,24 @@ describe( 'ColorTableView', () => { } ); } ); + describe( 'destroy()', () => { + it( 'should destroy the FocusTracker instance', () => { + const destroySpy = sinon.spy( colorTableView.focusTracker, 'destroy' ); + + colorTableView.destroy(); + + sinon.assert.calledOnce( destroySpy ); + } ); + + it( 'should destroy the KeystrokeHandler instance', () => { + const destroySpy = sinon.spy( colorTableView.keystrokes, 'destroy' ); + + colorTableView.destroy(); + + sinon.assert.calledOnce( destroySpy ); + } ); + } ); + describe( 'focus tracker', () => { it( 'should focus first child of colorTableView in DOM', () => { const spy = sinon.spy( colorTableView._focusCycler, 'focusFirst' ); diff --git a/packages/ckeditor5-image/src/imageinsert/ui/imageinsertpanelview.js b/packages/ckeditor5-image/src/imageinsert/ui/imageinsertpanelview.js index 66d4e1124ef..d0aa826928c 100644 --- a/packages/ckeditor5-image/src/imageinsert/ui/imageinsertpanelview.js +++ b/packages/ckeditor5-image/src/imageinsert/ui/imageinsertpanelview.js @@ -203,6 +203,9 @@ export default class ImageInsertPanelView extends View { }, { priority: 'high' } ); } + /** + * @inheritDoc + */ destroy() { super.destroy(); diff --git a/packages/ckeditor5-image/src/imagetextalternative/ui/textalternativeformview.js b/packages/ckeditor5-image/src/imagetextalternative/ui/textalternativeformview.js index b40a184d20b..0fa07b6061b 100644 --- a/packages/ckeditor5-image/src/imagetextalternative/ui/textalternativeformview.js +++ b/packages/ckeditor5-image/src/imagetextalternative/ui/textalternativeformview.js @@ -151,6 +151,9 @@ export default class TextAlternativeFormView extends View { } ); } + /** + * @inheritDoc + */ destroy() { super.destroy(); diff --git a/packages/ckeditor5-image/tests/imageinsert/ui/imageinsertpanelview.js b/packages/ckeditor5-image/tests/imageinsert/ui/imageinsertpanelview.js index c02a4d6a556..9d0b3836041 100644 --- a/packages/ckeditor5-image/tests/imageinsert/ui/imageinsertpanelview.js +++ b/packages/ckeditor5-image/tests/imageinsert/ui/imageinsertpanelview.js @@ -271,6 +271,24 @@ describe( 'ImageUploadPanelView', () => { } ); } ); + describe( 'destroy()', () => { + it( 'should destroy the FocusTracker instance', () => { + const destroySpy = sinon.spy( view.focusTracker, 'destroy' ); + + view.destroy(); + + sinon.assert.calledOnce( destroySpy ); + } ); + + it( 'should destroy the KeystrokeHandler instance', () => { + const destroySpy = sinon.spy( view.keystrokes, 'destroy' ); + + view.destroy(); + + sinon.assert.calledOnce( destroySpy ); + } ); + } ); + describe( 'focus()', () => { it( 'should focus on the first integration', () => { const spy = sinon.spy( view.getIntegration( 'insertImageViaUrl' ), 'focus' ); diff --git a/packages/ckeditor5-image/tests/imagetextalternative/ui/textalternativeformview.js b/packages/ckeditor5-image/tests/imagetextalternative/ui/textalternativeformview.js index 5950bb1a721..b91c4dffe01 100644 --- a/packages/ckeditor5-image/tests/imagetextalternative/ui/textalternativeformview.js +++ b/packages/ckeditor5-image/tests/imagetextalternative/ui/textalternativeformview.js @@ -150,6 +150,24 @@ describe( 'TextAlternativeFormView', () => { } ); } ); + describe( 'destroy()', () => { + it( 'should destroy the FocusTracker instance', () => { + const destroySpy = sinon.spy( view.focusTracker, 'destroy' ); + + view.destroy(); + + sinon.assert.calledOnce( destroySpy ); + } ); + + it( 'should destroy the KeystrokeHandler instance', () => { + const destroySpy = sinon.spy( view.keystrokes, 'destroy' ); + + view.destroy(); + + sinon.assert.calledOnce( destroySpy ); + } ); + } ); + describe( 'DOM bindings', () => { describe( 'submit event', () => { it( 'should trigger submit event', () => { diff --git a/packages/ckeditor5-link/src/ui/linkactionsview.js b/packages/ckeditor5-link/src/ui/linkactionsview.js index 0b73d2bec48..1da3357bee6 100644 --- a/packages/ckeditor5-link/src/ui/linkactionsview.js +++ b/packages/ckeditor5-link/src/ui/linkactionsview.js @@ -155,6 +155,9 @@ export default class LinkActionsView extends View { this.keystrokes.listenTo( this.element ); } + /** + * @inheritDoc + */ destroy() { super.destroy(); diff --git a/packages/ckeditor5-link/src/ui/linkformview.js b/packages/ckeditor5-link/src/ui/linkformview.js index 71ebef56fa6..45decef8ed0 100644 --- a/packages/ckeditor5-link/src/ui/linkformview.js +++ b/packages/ckeditor5-link/src/ui/linkformview.js @@ -200,6 +200,9 @@ export default class LinkFormView extends View { this.keystrokes.listenTo( this.element ); } + /** + * @inheritDoc + */ destroy() { super.destroy(); diff --git a/packages/ckeditor5-link/tests/ui/linkactionsview.js b/packages/ckeditor5-link/tests/ui/linkactionsview.js index f2d5387ebfc..054fb652d01 100644 --- a/packages/ckeditor5-link/tests/ui/linkactionsview.js +++ b/packages/ckeditor5-link/tests/ui/linkactionsview.js @@ -199,6 +199,24 @@ describe( 'LinkActionsView', () => { } ); } ); + describe( 'destroy()', () => { + it( 'should destroy the FocusTracker instance', () => { + const destroySpy = sinon.spy( view.focusTracker, 'destroy' ); + + view.destroy(); + + sinon.assert.calledOnce( destroySpy ); + } ); + + it( 'should destroy the KeystrokeHandler instance', () => { + const destroySpy = sinon.spy( view.keystrokes, 'destroy' ); + + view.destroy(); + + sinon.assert.calledOnce( destroySpy ); + } ); + } ); + describe( 'focus()', () => { it( 'focuses the #previewButtonView', () => { const spy = sinon.spy( view.previewButtonView, 'focus' ); diff --git a/packages/ckeditor5-link/tests/ui/linkformview.js b/packages/ckeditor5-link/tests/ui/linkformview.js index d12f10d43b2..8429d5ca776 100644 --- a/packages/ckeditor5-link/tests/ui/linkformview.js +++ b/packages/ckeditor5-link/tests/ui/linkformview.js @@ -171,6 +171,24 @@ describe( 'LinkFormView', () => { } ); } ); + describe( 'destroy()', () => { + it( 'should destroy the FocusTracker instance', () => { + const destroySpy = sinon.spy( view.focusTracker, 'destroy' ); + + view.destroy(); + + sinon.assert.calledOnce( destroySpy ); + } ); + + it( 'should destroy the KeystrokeHandler instance', () => { + const destroySpy = sinon.spy( view.keystrokes, 'destroy' ); + + view.destroy(); + + sinon.assert.calledOnce( destroySpy ); + } ); + } ); + describe( 'DOM bindings', () => { describe( 'submit event', () => { it( 'should trigger submit event', () => { diff --git a/packages/ckeditor5-media-embed/src/ui/mediaformview.js b/packages/ckeditor5-media-embed/src/ui/mediaformview.js index 206f67bbb24..7860e9126db 100644 --- a/packages/ckeditor5-media-embed/src/ui/mediaformview.js +++ b/packages/ckeditor5-media-embed/src/ui/mediaformview.js @@ -210,6 +210,9 @@ export default class MediaFormView extends View { }, { priority: 'high' } ); } + /** + * @inheritDoc + */ destroy() { super.destroy(); diff --git a/packages/ckeditor5-media-embed/tests/ui/mediaformview.js b/packages/ckeditor5-media-embed/tests/ui/mediaformview.js index 7d5bc62dddb..70e0ab9868d 100644 --- a/packages/ckeditor5-media-embed/tests/ui/mediaformview.js +++ b/packages/ckeditor5-media-embed/tests/ui/mediaformview.js @@ -220,6 +220,24 @@ describe( 'MediaFormView', () => { } ); } ); + describe( 'destroy()', () => { + it( 'should destroy the FocusTracker instance', () => { + const destroySpy = sinon.spy( view.focusTracker, 'destroy' ); + + view.destroy(); + + sinon.assert.calledOnce( destroySpy ); + } ); + + it( 'should destroy the KeystrokeHandler instance', () => { + const destroySpy = sinon.spy( view.keystrokes, 'destroy' ); + + view.destroy(); + + sinon.assert.calledOnce( destroySpy ); + } ); + } ); + describe( 'DOM bindings', () => { describe( 'submit event', () => { it( 'should trigger submit event', () => { diff --git a/packages/ckeditor5-table/src/tablecellproperties/ui/tablecellpropertiesview.js b/packages/ckeditor5-table/src/tablecellproperties/ui/tablecellpropertiesview.js index dff190cb13d..2c4882d0af5 100644 --- a/packages/ckeditor5-table/src/tablecellproperties/ui/tablecellpropertiesview.js +++ b/packages/ckeditor5-table/src/tablecellproperties/ui/tablecellpropertiesview.js @@ -430,6 +430,9 @@ export default class TableCellPropertiesView extends View { this.keystrokes.listenTo( this.element ); } + /** + * @inheritDoc + */ destroy() { super.destroy(); diff --git a/packages/ckeditor5-table/src/tableproperties/ui/tablepropertiesview.js b/packages/ckeditor5-table/src/tableproperties/ui/tablepropertiesview.js index 4cf423bb1b6..f8fb3bc445d 100644 --- a/packages/ckeditor5-table/src/tableproperties/ui/tablepropertiesview.js +++ b/packages/ckeditor5-table/src/tableproperties/ui/tablepropertiesview.js @@ -379,6 +379,9 @@ export default class TablePropertiesView extends View { this.keystrokes.listenTo( this.element ); } + /** + * @inheritDoc + */ destroy() { super.destroy(); diff --git a/packages/ckeditor5-table/tests/tablecellproperties/ui/tablecellpropertiesview.js b/packages/ckeditor5-table/tests/tablecellproperties/ui/tablecellpropertiesview.js index 1fa114ca2ce..56a69d75bdd 100644 --- a/packages/ckeditor5-table/tests/tablecellproperties/ui/tablecellpropertiesview.js +++ b/packages/ckeditor5-table/tests/tablecellproperties/ui/tablecellpropertiesview.js @@ -760,6 +760,24 @@ describe( 'table cell properties', () => { } ); } ); + describe( 'destroy()', () => { + it( 'should destroy the FocusTracker instance', () => { + const destroySpy = sinon.spy( view.focusTracker, 'destroy' ); + + view.destroy(); + + sinon.assert.calledOnce( destroySpy ); + } ); + + it( 'should destroy the KeystrokeHandler instance', () => { + const destroySpy = sinon.spy( view.keystrokes, 'destroy' ); + + view.destroy(); + + sinon.assert.calledOnce( destroySpy ); + } ); + } ); + describe( 'DOM bindings', () => { describe( 'submit event', () => { it( 'should trigger submit event', () => { diff --git a/packages/ckeditor5-table/tests/tableproperties/ui/tablepropertiesview.js b/packages/ckeditor5-table/tests/tableproperties/ui/tablepropertiesview.js index 01be9ee018f..2510ee92c0f 100644 --- a/packages/ckeditor5-table/tests/tableproperties/ui/tablepropertiesview.js +++ b/packages/ckeditor5-table/tests/tableproperties/ui/tablepropertiesview.js @@ -693,6 +693,24 @@ describe( 'table properties', () => { } ); } ); + describe( 'destroy()', () => { + it( 'should destroy the FocusTracker instance', () => { + const destroySpy = sinon.spy( view.focusTracker, 'destroy' ); + + view.destroy(); + + sinon.assert.calledOnce( destroySpy ); + } ); + + it( 'should destroy the KeystrokeHandler instance', () => { + const destroySpy = sinon.spy( view.keystrokes, 'destroy' ); + + view.destroy(); + + sinon.assert.calledOnce( destroySpy ); + } ); + } ); + describe( 'DOM bindings', () => { describe( 'submit event', () => { it( 'should trigger submit event', () => { diff --git a/packages/ckeditor5-ui/src/colorgrid/colorgridview.js b/packages/ckeditor5-ui/src/colorgrid/colorgridview.js index b829d13df0e..ae9a911a09a 100644 --- a/packages/ckeditor5-ui/src/colorgrid/colorgridview.js +++ b/packages/ckeditor5-ui/src/colorgrid/colorgridview.js @@ -176,6 +176,9 @@ export default class ColorGridView extends View { this.keystrokes.listenTo( this.element ); } + /** + * @inheritDoc + */ destroy() { super.destroy(); diff --git a/packages/ckeditor5-ui/src/dropdown/button/splitbuttonview.js b/packages/ckeditor5-ui/src/dropdown/button/splitbuttonview.js index 10b14f4b628..58d67118b3b 100644 --- a/packages/ckeditor5-ui/src/dropdown/button/splitbuttonview.js +++ b/packages/ckeditor5-ui/src/dropdown/button/splitbuttonview.js @@ -155,6 +155,9 @@ export default class SplitButtonView extends View { } ); } + /** + * @inheritDoc + */ destroy() { super.destroy(); diff --git a/packages/ckeditor5-ui/src/inputtext/inputtextview.js b/packages/ckeditor5-ui/src/inputtext/inputtextview.js index 0a607b0bbcd..2e440d6f5c1 100644 --- a/packages/ckeditor5-ui/src/inputtext/inputtextview.js +++ b/packages/ckeditor5-ui/src/inputtext/inputtextview.js @@ -160,6 +160,9 @@ export default class InputTextView extends View { } ); } + /** + * @inheritDoc + */ destroy() { super.destroy(); diff --git a/packages/ckeditor5-ui/src/list/listview.js b/packages/ckeditor5-ui/src/list/listview.js index 79ea475aefa..56d92da22c8 100644 --- a/packages/ckeditor5-ui/src/list/listview.js +++ b/packages/ckeditor5-ui/src/list/listview.js @@ -109,6 +109,9 @@ export default class ListView extends View { this.keystrokes.listenTo( this.element ); } + /** + * @inheritDoc + */ destroy() { super.destroy(); diff --git a/packages/ckeditor5-ui/src/panel/balloon/contextualballoon.js b/packages/ckeditor5-ui/src/panel/balloon/contextualballoon.js index 45874a246ec..4318b2edb84 100644 --- a/packages/ckeditor5-ui/src/panel/balloon/contextualballoon.js +++ b/packages/ckeditor5-ui/src/panel/balloon/contextualballoon.js @@ -170,6 +170,17 @@ export default class ContextualBalloon extends Plugin { this._fakePanelsView = this._createFakePanelsView(); } + /** + * @inheritDoc + */ + destroy() { + super.destroy(); + + this.view.destroy(); + this._rotatorView.destroy(); + this._fakePanelsView.destroy(); + } + /** * Returns `true` when the given view is in one of the stacks. Otherwise returns `false`. * @@ -628,6 +639,9 @@ class RotatorView extends View { this.focusTracker.add( this.element ); } + /** + * @inheritDoc + */ destroy() { super.destroy(); diff --git a/packages/ckeditor5-ui/tests/colorgrid/colorgridview.js b/packages/ckeditor5-ui/tests/colorgrid/colorgridview.js index c6f519ff597..4c23bb70100 100644 --- a/packages/ckeditor5-ui/tests/colorgrid/colorgridview.js +++ b/packages/ckeditor5-ui/tests/colorgrid/colorgridview.js @@ -141,6 +141,24 @@ describe( 'ColorGridView', () => { } ); } ); + describe( 'destroy()', () => { + it( 'should destroy the FocusTracker instance', () => { + const destroySpy = sinon.spy( view.focusTracker, 'destroy' ); + + view.destroy(); + + sinon.assert.calledOnce( destroySpy ); + } ); + + it( 'should destroy the KeystrokeHandler instance', () => { + const destroySpy = sinon.spy( view.keystrokes, 'destroy' ); + + view.destroy(); + + sinon.assert.calledOnce( destroySpy ); + } ); + } ); + describe( 'execute()', () => { it( 'fires event for rendered tiles', () => { const spy = sinon.spy(); diff --git a/packages/ckeditor5-ui/tests/dropdown/button/splitbuttonview.js b/packages/ckeditor5-ui/tests/dropdown/button/splitbuttonview.js index 63302a5c96d..f11692c1d47 100644 --- a/packages/ckeditor5-ui/tests/dropdown/button/splitbuttonview.js +++ b/packages/ckeditor5-ui/tests/dropdown/button/splitbuttonview.js @@ -153,6 +153,24 @@ describe( 'SplitButtonView', () => { } ); } ); + describe( 'destroy()', () => { + it( 'should destroy the FocusTracker instance', () => { + const destroySpy = sinon.spy( view.focusTracker, 'destroy' ); + + view.destroy(); + + sinon.assert.calledOnce( destroySpy ); + } ); + + it( 'should destroy the KeystrokeHandler instance', () => { + const destroySpy = sinon.spy( view.keystrokes, 'destroy' ); + + view.destroy(); + + sinon.assert.calledOnce( destroySpy ); + } ); + } ); + describe( 'bindings', () => { it( 'delegates actionView#execute to view#execute', () => { const spy = sinon.spy(); diff --git a/packages/ckeditor5-ui/tests/inputtext/inputtextview.js b/packages/ckeditor5-ui/tests/inputtext/inputtextview.js index 200fc7af8ba..92a17934f5d 100644 --- a/packages/ckeditor5-ui/tests/inputtext/inputtextview.js +++ b/packages/ckeditor5-ui/tests/inputtext/inputtextview.js @@ -208,6 +208,16 @@ describe( 'InputTextView', () => { } ); } ); + describe( 'destroy()', () => { + it( 'should destroy the FocusTracker instance', () => { + const destroySpy = sinon.spy( view.focusTracker, 'destroy' ); + + view.destroy(); + + sinon.assert.calledOnce( destroySpy ); + } ); + } ); + describe( 'select()', () => { it( 'should select input value', () => { const selectSpy = sinon.spy( view.element, 'select' ); diff --git a/packages/ckeditor5-ui/tests/list/listview.js b/packages/ckeditor5-ui/tests/list/listview.js index 5c8836045d9..1266db35f25 100644 --- a/packages/ckeditor5-ui/tests/list/listview.js +++ b/packages/ckeditor5-ui/tests/list/listview.js @@ -156,6 +156,24 @@ describe( 'ListView', () => { } ); } ); + describe( 'destroy()', () => { + it( 'should destroy the FocusTracker instance', () => { + const destroySpy = sinon.spy( view.focusTracker, 'destroy' ); + + view.destroy(); + + sinon.assert.calledOnce( destroySpy ); + } ); + + it( 'should destroy the KeystrokeHandler instance', () => { + const destroySpy = sinon.spy( view.keystrokes, 'destroy' ); + + view.destroy(); + + sinon.assert.calledOnce( destroySpy ); + } ); + } ); + describe( 'focus()', () => { it( 'focuses the first focusable item in DOM', () => { // No children to focus. diff --git a/packages/ckeditor5-ui/tests/panel/balloon/contextualballoon.js b/packages/ckeditor5-ui/tests/panel/balloon/contextualballoon.js index 75b57fa8b70..91ddabd7663 100644 --- a/packages/ckeditor5-ui/tests/panel/balloon/contextualballoon.js +++ b/packages/ckeditor5-ui/tests/panel/balloon/contextualballoon.js @@ -775,6 +775,30 @@ describe( 'ContextualBalloon', () => { expect( editor.ui.view.body.getIndex( balloon.view ) ).to.not.equal( -1 ); } ); + + it( 'should destroy the #view', () => { + const destroySpy = sinon.spy( balloon.view, 'destroy' ); + + balloon.destroy(); + + sinon.assert.called( destroySpy ); + } ); + + it( 'should destroy the #_rotatorView', () => { + const destroySpy = sinon.spy( balloon._rotatorView, 'destroy' ); + + balloon.destroy(); + + sinon.assert.called( destroySpy ); + } ); + + it( 'should destroy the #_fakePanelsView', () => { + const destroySpy = sinon.spy( balloon._rotatorView, 'destroy' ); + + balloon.destroy(); + + sinon.assert.called( destroySpy ); + } ); } ); describe( 'rotator view', () => { @@ -1083,6 +1107,16 @@ describe( 'ContextualBalloon', () => { } ); } ); + describe( 'destroy()', () => { + it( 'should destroy the FocusTracker instance', () => { + const destroySpy = sinon.spy( rotatorView.focusTracker, 'destroy' ); + + rotatorView.destroy(); + + sinon.assert.calledOnce( destroySpy ); + } ); + } ); + describe( 'singleViewMode', () => { it( 'should not display navigation when there is more than one stack', () => { const navigationElement = rotatorView.element.querySelector( '.ck-balloon-rotator__navigation' ); diff --git a/packages/ckeditor5-ui/tests/toolbar/toolbarview.js b/packages/ckeditor5-ui/tests/toolbar/toolbarview.js index 81000a48182..0e3d23f971d 100644 --- a/packages/ckeditor5-ui/tests/toolbar/toolbarview.js +++ b/packages/ckeditor5-ui/tests/toolbar/toolbarview.js @@ -430,6 +430,22 @@ describe( 'ToolbarView', () => { view.destroy(); sinon.assert.calledOnce( view._behavior.destroy ); } ); + + it( 'should destroy the FocusTracker instance', () => { + const destroySpy = sinon.spy( view.focusTracker, 'destroy' ); + + view.destroy(); + + sinon.assert.calledOnce( destroySpy ); + } ); + + it( 'should destroy the KeystrokeHandler instance', () => { + const destroySpy = sinon.spy( view.keystrokes, 'destroy' ); + + view.destroy(); + + sinon.assert.calledOnce( destroySpy ); + } ); } ); describe( 'focus()', () => {