From 5d5dde43571e1c3e5673fcaa53ac1e8ab8e9590f Mon Sep 17 00:00:00 2001
From: Dave Long <dave@longwaveconsulting.com>
Date: Wed, 13 Mar 2024 15:52:49 +0000
Subject: [PATCH] Issue #3396742 by ReINFaTe, smustgrave, Wim Leers, nod_:
 CKEditor 5 doesn't save updated value if form submitted right after the
 change

---
 core/modules/ckeditor5/js/ckeditor5.js        | 19 +----
 .../FunctionalJavascript/CKEditor5Test.php    | 78 +++++++++++++++++++
 2 files changed, 81 insertions(+), 16 deletions(-)

diff --git a/core/modules/ckeditor5/js/ckeditor5.js b/core/modules/ckeditor5/js/ckeditor5.js
index 1d81ad30bfba..4d6f4d185517 100644
--- a/core/modules/ckeditor5/js/ckeditor5.js
+++ b/core/modules/ckeditor5/js/ckeditor5.js
@@ -443,22 +443,9 @@
           editor.model.document.on('change:data', () => {
             const callback = callbacks.get(id);
             if (callback) {
-              if (editor.plugins.has('SourceEditing')) {
-                // If the change:data is being called while in source editing
-                // mode, it means that the form is being submitted. To avoid
-                // race conditions, in this case the callback gets called
-                // without decorating the callback with debounce.
-                // @see https://www.drupal.org/i/3229174
-                // @see Drupal.editorDetach
-                if (editor.plugins.get('SourceEditing').isSourceEditingMode) {
-                  callback();
-                  return;
-                }
-              }
-
               // Marks the field as changed.
               // @see Drupal.editorAttach
-              debounce(callback, 400)();
+              callback();
             }
           });
 
@@ -530,7 +517,7 @@
      *   Callback called with the value of the editor.
      */
     onChange(element, callback) {
-      callbacks.set(getElementId(element), callback);
+      callbacks.set(getElementId(element), debounce(callback, 400, true));
     },
 
     /**
@@ -570,7 +557,7 @@
             const callback = callbacks.get(id);
             if (callback) {
               // Allow modules to update EditorModel by providing the current data.
-              debounce(callback, 400)(editor.getData());
+              callback(editor.getData());
             }
           });
         })
diff --git a/core/modules/ckeditor5/tests/src/FunctionalJavascript/CKEditor5Test.php b/core/modules/ckeditor5/tests/src/FunctionalJavascript/CKEditor5Test.php
index 3e17c31e00fa..90111da112a0 100644
--- a/core/modules/ckeditor5/tests/src/FunctionalJavascript/CKEditor5Test.php
+++ b/core/modules/ckeditor5/tests/src/FunctionalJavascript/CKEditor5Test.php
@@ -797,4 +797,82 @@ function (ConstraintViolation $v) {
     $assert_session->responseContains('<!-- Hamsters, alpacas, llamas, and kittens are cute! --><p>This is a <em>test!</em></p>');
   }
 
+  /**
+   * Ensures that changes are saved in CKEditor 5.
+   */
+  public function testSave(): void {
+    // To replicate the bug from https://www.drupal.org/i/3396742
+    // We need 2 or more text formats and node edit page.
+    FilterFormat::create([
+      'format' => 'ckeditor5',
+      'name' => 'CKEditor 5 HTML',
+      'roles' => [RoleInterface::AUTHENTICATED_ID],
+    ])->save();
+    Editor::create([
+      'format' => 'ckeditor5',
+      'editor' => 'ckeditor5',
+      'settings' => [
+        'toolbar' => [
+          'items' => [
+            'sourceEditing',
+          ],
+        ],
+        'plugins' => [
+          'ckeditor5_sourceEditing' => [
+            'allowed_tags' => [],
+          ],
+        ],
+      ],
+    ])->save();
+    $this->assertSame([], array_map(
+      function (ConstraintViolation $v) {
+        return (string) $v->getMessage();
+      },
+      iterator_to_array(CKEditor5::validatePair(
+        Editor::load('ckeditor5'),
+        FilterFormat::load('ckeditor5')
+      ))
+    ));
+    FilterFormat::create([
+      'format' => 'ckeditor5_2',
+      'name' => 'CKEditor 5 HTML 2',
+      'roles' => [RoleInterface::AUTHENTICATED_ID],
+    ])->save();
+    Editor::create([
+      'format' => 'ckeditor5_2',
+      'editor' => 'ckeditor5',
+    ])->save();
+    $this->assertSame([], array_map(
+      function (ConstraintViolation $v) {
+        return (string) $v->getMessage();
+      },
+      iterator_to_array(CKEditor5::validatePair(
+        Editor::load('ckeditor5_2'),
+        FilterFormat::load('ckeditor5_2')
+      ))
+    ));
+    $this->drupalCreateNode([
+      'title' => 'My test content',
+    ]);
+
+    // Test that entered text is saved.
+    $this->drupalGet('node/1/edit');
+    $page = $this->getSession()->getPage();
+    $this->waitForEditor();
+    $editor = $page->find('css', '.ck-content');
+    $editor->setValue('Very important information');
+    $page->pressButton('Save');
+    $this->assertSession()->responseContains('Very important information');
+
+    // Test that changes only in source are saved.
+    $this->drupalGet('node/1/edit');
+    $page = $this->getSession()->getPage();
+    $this->waitForEditor();
+    $this->pressEditorButton('Source');
+    $editor = $page->find('css', '.ck-source-editing-area textarea');
+    $editor->setValue('Text hidden in the source');
+    $page->pressButton('Save');
+    $this->assertSession()->responseContains('Text hidden in the source');
+  }
+
 }
-- 
GitLab