From a103dcc755aa335f8270ed7ae783ccbd3ca4ee4e Mon Sep 17 00:00:00 2001
From: "lee.5151" <lee.5151@osu.edu>
Date: Mon, 8 Jul 2024 11:21:19 -0400
Subject: [PATCH] Upgrading drupal/scheduler (2.0.3 => 2.0.4)

---
 composer.json                                 |   2 +-
 composer.lock                                 |  16 +--
 vendor/composer/installed.json                |  16 +--
 vendor/composer/installed.php                 |  10 +-
 web/modules/scheduler/.eslintignore           |   7 +-
 web/modules/scheduler/.eslintrc.json          |  13 --
 web/modules/scheduler/.gitlab-ci.yml          |  38 ++---
 .../scheduler/js/scheduler_default_time.js    |  46 +++---
 .../scheduler/js/scheduler_default_time_8x.js |  44 +++---
 .../scheduler/js/scheduler_vertical_tabs.js   |  61 ++++----
 .../scheduler/phpstan-baseline-to-fix.neon    |   9 --
 web/modules/scheduler/scheduler.info.yml      |   6 +-
 .../scheduler_rules_integration.info.yml      |   6 +-
 .../TimestampDatetimeNoDefaultWidget.php      |   4 +-
 .../scheduler/src/SchedulerManager.php        |   5 +-
 .../scheduler/src/SchedulerPluginManager.php  |   4 +-
 .../scheduler_access_test.info.yml            |   6 +-
 .../scheduler_api_legacy_test.info.yml        |   6 +-
 .../scheduler_api_test.info.yml               |   6 +-
 .../scheduler_extras.info.yml                 |   6 +-
 .../Functional/SchedulerTokenReplaceTest.php  |  10 +-
 .../Functional/SchedulerViewsAccessTest.php   |  14 ++
 .../SchedulerJavascriptDefaultTimeTest.php    |   4 +-
 .../SchedulerJavascriptTestBase.php           |  35 ++++-
 .../SchedulerJavascriptVerticalTabsTest.php   | 132 ++++++++++++++++++
 25 files changed, 351 insertions(+), 155 deletions(-)
 delete mode 100644 web/modules/scheduler/.eslintrc.json
 create mode 100644 web/modules/scheduler/tests/src/FunctionalJavascript/SchedulerJavascriptVerticalTabsTest.php

diff --git a/composer.json b/composer.json
index 207f844929..ab5058a338 100644
--- a/composer.json
+++ b/composer.json
@@ -145,7 +145,7 @@
         "drupal/recaptcha_v3": "^2.0",
         "drupal/redirect": "^1.8",
         "drupal/roleassign": "2.0.2",
-        "drupal/scheduler": "2.0.3",
+        "drupal/scheduler": "2.0.4",
         "drupal/simple_gmap": "3.1.0",
         "drupal/simple_sitemap": "4.1.9",
         "drupal/smtp": "1.2",
diff --git a/composer.lock b/composer.lock
index 489f06f8ad..3d96c833a2 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "7d237beef02700d6a2f5d3ab090f6792",
+    "content-hash": "99dc24e60634b6d9442ce25eb8d4c8f1",
     "packages": [
         {
             "name": "algolia/places",
@@ -5436,17 +5436,17 @@
         },
         {
             "name": "drupal/scheduler",
-            "version": "2.0.3",
+            "version": "2.0.4",
             "source": {
                 "type": "git",
                 "url": "https://git.drupalcode.org/project/scheduler.git",
-                "reference": "2.0.3"
+                "reference": "2.0.4"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://ftp.drupal.org/files/projects/scheduler-2.0.3.zip",
-                "reference": "2.0.3",
-                "shasum": "1ef7a92afcb8e138cf697dc35f15cbc2353801b4"
+                "url": "https://ftp.drupal.org/files/projects/scheduler-2.0.4.zip",
+                "reference": "2.0.4",
+                "shasum": "50ee56a90243363453dcdeeaf96dbd6cbec9b7c8"
             },
             "require": {
                 "drupal/core": "^8 || ^9 || ^10"
@@ -5462,8 +5462,8 @@
             "type": "drupal-module",
             "extra": {
                 "drupal": {
-                    "version": "2.0.3",
-                    "datestamp": "1714312557",
+                    "version": "2.0.4",
+                    "datestamp": "1719580022",
                     "security-coverage": {
                         "status": "covered",
                         "message": "Covered by Drupal's security advisory policy"
diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json
index 9322661462..c47b110063 100644
--- a/vendor/composer/installed.json
+++ b/vendor/composer/installed.json
@@ -5703,18 +5703,18 @@
         },
         {
             "name": "drupal/scheduler",
-            "version": "2.0.3",
-            "version_normalized": "2.0.3.0",
+            "version": "2.0.4",
+            "version_normalized": "2.0.4.0",
             "source": {
                 "type": "git",
                 "url": "https://git.drupalcode.org/project/scheduler.git",
-                "reference": "2.0.3"
+                "reference": "2.0.4"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://ftp.drupal.org/files/projects/scheduler-2.0.3.zip",
-                "reference": "2.0.3",
-                "shasum": "1ef7a92afcb8e138cf697dc35f15cbc2353801b4"
+                "url": "https://ftp.drupal.org/files/projects/scheduler-2.0.4.zip",
+                "reference": "2.0.4",
+                "shasum": "50ee56a90243363453dcdeeaf96dbd6cbec9b7c8"
             },
             "require": {
                 "drupal/core": "^8 || ^9 || ^10"
@@ -5730,8 +5730,8 @@
             "type": "drupal-module",
             "extra": {
                 "drupal": {
-                    "version": "2.0.3",
-                    "datestamp": "1714312557",
+                    "version": "2.0.4",
+                    "datestamp": "1719580022",
                     "security-coverage": {
                         "status": "covered",
                         "message": "Covered by Drupal's security advisory policy"
diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php
index 37d2303980..7e5fda48c0 100644
--- a/vendor/composer/installed.php
+++ b/vendor/composer/installed.php
@@ -3,7 +3,7 @@
         'name' => 'osu-asc-webservices/d8-upstream',
         'pretty_version' => 'dev-main',
         'version' => 'dev-main',
-        'reference' => 'd2f29e21f9b833a6bd43863efaf810f305055609',
+        'reference' => '6877cfc0e77a628513ba677a59cf65fa63a22872',
         'type' => 'project',
         'install_path' => __DIR__ . '/../../',
         'aliases' => array(),
@@ -980,9 +980,9 @@
             'dev_requirement' => false,
         ),
         'drupal/scheduler' => array(
-            'pretty_version' => '2.0.3',
-            'version' => '2.0.3.0',
-            'reference' => '2.0.3',
+            'pretty_version' => '2.0.4',
+            'version' => '2.0.4.0',
+            'reference' => '2.0.4',
             'type' => 'drupal-module',
             'install_path' => __DIR__ . '/../../web/modules/scheduler',
             'aliases' => array(),
@@ -1519,7 +1519,7 @@
         'osu-asc-webservices/d8-upstream' => array(
             'pretty_version' => 'dev-main',
             'version' => 'dev-main',
-            'reference' => 'd2f29e21f9b833a6bd43863efaf810f305055609',
+            'reference' => '6877cfc0e77a628513ba677a59cf65fa63a22872',
             'type' => 'project',
             'install_path' => __DIR__ . '/../../',
             'aliases' => array(),
diff --git a/web/modules/scheduler/.eslintignore b/web/modules/scheduler/.eslintignore
index 14313bdae6..0da6c5f6f7 100644
--- a/web/modules/scheduler/.eslintignore
+++ b/web/modules/scheduler/.eslintignore
@@ -1,6 +1,7 @@
-# These files fail eslint standards. If possible they may be fixed in due course
-# then removed from this ignore file.
-# See https://www.drupal.org/project/scheduler/issues/3314451
+# These files have been fixed on the Gitlab Templates ESLint job, which uses a better config.
+# However, this config is not used in DrupalCI so the files still need to be ignored.
+# This .eslintignore file is deleted in the pipeline before ESLint is run, so that the files are not ignored there.
+# See https://www.drupal.org/project/scheduler/issues/3432976
 
 js/scheduler_default_time.js
 js/scheduler_default_time_8x.js
diff --git a/web/modules/scheduler/.eslintrc.json b/web/modules/scheduler/.eslintrc.json
deleted file mode 100644
index 5cdd8fcf9f..0000000000
--- a/web/modules/scheduler/.eslintrc.json
+++ /dev/null
@@ -1,13 +0,0 @@
-// This file does not strictly need to exist in contrib, because eslint config
-// files placed futher up in the directory structure will be merged and
-// inherited. The top-level .eslintrc.json extends ./core/.eslintrc.json.
-//
-// However, this file is added for two reasons:
-// (a) Drupalci eslint does not have a 'sniff all files' argument, but making a
-//     change to this file will cause all js and yml files to be checked.
-//     See https://www.drupal.org/project/scheduler/issues/3314451
-// (b) If a patch has changes to .yml files but no changes to .js files then
-//     eslint is not executed allowing a .yml fault to be missed. Making a
-//     temporary change to this file will cause the modified .yml to be checked.
-//     See https://www.drupal.org/project/drupalci_testbot/issues/3333051
-{}
diff --git a/web/modules/scheduler/.gitlab-ci.yml b/web/modules/scheduler/.gitlab-ci.yml
index 28f84cc9b0..8b4a33f653 100644
--- a/web/modules/scheduler/.gitlab-ci.yml
+++ b/web/modules/scheduler/.gitlab-ci.yml
@@ -16,11 +16,11 @@ include:
 ################
 variables:
   OPT_IN_TEST_CURRENT: 1
+  OPT_IN_TEST_MAX_PHP: 1
   OPT_IN_TEST_NEXT_MINOR: 1
   OPT_IN_TEST_NEXT_MAJOR: 1
   OPT_IN_TEST_PREVIOUS_MINOR: 1
   OPT_IN_TEST_PREVIOUS_MAJOR: 1
-  OPT_IN_TEST_MAX_PHP: 1
 
   _SHOW_ENVIRONMENT_VARIABLES: 1
   _PHPUNIT_CONCURRENT: 1
@@ -36,6 +36,7 @@ variables:
     printf "CI_DEFAULT_BRANCH           = %s\n" $CI_DEFAULT_BRANCH &&
     printf "CI_COMMIT_BRANCH            = %s\n" $CI_COMMIT_BRANCH &&
     printf "CI_COMMIT_TAG               = %s\n" $CI_COMMIT_TAG &&
+    printf "CI_COMMIT_TITLE             = %s\n" "$CI_COMMIT_TITLE" &&
     printf "CI_MERGE_REQUEST_IID        = %s\n" $CI_MERGE_REQUEST_IID &&
     printf "CI_MERGE_REQUEST_TITLE      = %s\n" "$CI_MERGE_REQUEST_TITLE" &&
     printf "CI_PROJECT_ROOT_NAMESPACE   = %s\n" $CI_PROJECT_ROOT_NAMESPACE &&
@@ -43,23 +44,29 @@ variables:
     printf "CI_PROJECT_NAME             = %s\n" $CI_PROJECT_NAME &&
     printf "CI_JOB_NAME                 = %s\n" "$CI_JOB_NAME" &&
     printf "CI_JOB_NAME chopped         = %s\n" "${CI_JOB_NAME%:*}" &&
+    printf "DRUPAL_CORE                 = %s\n" $DRUPAL_CORE &&
+    printf "PHP_VERSION                 = %s\n" $PHP_VERSION &&
     printf "MODULE_NAME                 = %s\n" $MODULE_NAME
 
 # -------------------------------- BUILD ---------------------------------------
 
 .composer-base:
   after_script:
+    # Delete the .eslintignore file, it is only required for DrupalCI
+    # @todo remove the file and this delete when no longer running DrupalCI
+    - rm $CI_PROJECT_DIR/$_WEB_ROOT/modules/custom/$CI_PROJECT_NAME/.eslintignore || true
     # Show the last two commits. Current directory /builds/project/scheduler ($CI_PROJECT_DIR) is correct.
     - git show -2 --stat --oneline
+    - *show-variables
 
 composer (max PHP version):
+  # Using when: manual needs 'allow failure: true' otherwise the overall pipeline status shows 'blocked'
   rules:
     - !reference [ .opt-in-max-php-rule ]
     - when: manual
   allow_failure: true
 
 composer (previous minor):
-  # Using when: manual needs 'allow failure: true' otherwise the overall pipeline status shows 'blocked'
   rules:
     - !reference [ .opt-in-previous-minor-rule ]
     - when: manual
@@ -83,6 +90,10 @@ composer (next major):
 
 eslint:
   allow_failure: false
+  after_script:
+    - cd $CI_PROJECT_DIR/$_WEB_ROOT/modules/custom/$CI_PROJECT_NAME
+    - test -f .eslintignore && echo "===  This is .eslintignore  ===" && cat .eslintignore || true
+    - test -f .prettierignore && echo "=== This is .prettierignore ===" && cat .prettierignore || true
 
 stylelint:
   allow_failure: false
@@ -95,19 +106,13 @@ phpstan:
     - test -f phpstan.neon && echo "=== This is phpstan.neon ===" && cat phpstan.neon
     - php $CI_PROJECT_DIR/scripts/phpstan-baseline-summary.php phpstan-baseline-to-fix.neon
 
-# Do not automatically run, but allow the jobs to be started manually.
+# Do not automatically run 'next minor', but allow it to be started manually.
 phpstan (next minor):
   rules:
     - !reference [ .opt-in-next-minor-rule ]
     - !reference [ .skip-phpstan-rule ]
     - when: manual
 
-phpstan (next major):
-  rules:
-    - !reference [ .opt-in-next-major-rule ]
-    - !reference [ .skip-phpstan-rule ]
-    - when: manual
-
 # -------------------------------- TEST ----------------------------------------
 
 phpunit:
@@ -144,23 +149,22 @@ phpunit:
       fi
     - echo "End of before_script _PHPUNIT_CONCURRENT=$_PHPUNIT_CONCURRENT _PHPUNIT_EXTRA=$_PHPUNIT_EXTRA"
 
+# Do not automatically run 'next minor' PHPUnit but allow it to be started
+# manually. To do this the .phpunit-tests-exist-rule has to be removed.
+# The other variants all have 'manual' on the Composer job instead.
 phpunit (next minor):
   allow_failure: true
   rules:
     - !reference [ .opt-in-next-minor-rule ]
     - !reference [ .skip-phpunit-rule ]
-    # Do not automatically run, but allow the job to be started manually.
-    # To do this the .phpunit-tests-exist-rule has to be removed.
     - when: manual
   variables:
-    # Use core ignoreFile to show deprecations.
+    # Use core ignoreFile to show deprecations. This is only used when $_PHPUNIT_CONCURRENT=1 so make sure that is also set.
+    _PHPUNIT_CONCURRENT: 1
     SYMFONY_DEPRECATIONS_HELPER: "ignoreFile=$CI_PROJECT_DIR/$_WEB_ROOT/core/.deprecation-ignore.txt"
 
 phpunit (next major):
-  rules:
-    - !reference [ .opt-in-next-major-rule ]
-    - !reference [ .skip-phpunit-rule ]
-    - when: manual
   variables:
-    # Use core ignoreFile to show deprecations.
+    # Use core ignoreFile to show deprecations. This is only used when $_PHPUNIT_CONCURRENT=1 so make sure that is also set.
+    _PHPUNIT_CONCURRENT: 1
     SYMFONY_DEPRECATIONS_HELPER: "ignoreFile=$CI_PROJECT_DIR/$_WEB_ROOT/core/.deprecation-ignore.txt"
diff --git a/web/modules/scheduler/js/scheduler_default_time.js b/web/modules/scheduler/js/scheduler_default_time.js
index c1b90e3a7b..6510938ed4 100644
--- a/web/modules/scheduler/js/scheduler_default_time.js
+++ b/web/modules/scheduler/js/scheduler_default_time.js
@@ -4,9 +4,6 @@
  */
 
 (function ($, drupalSettings, once) {
-
-  'use strict';
-
   /**
    * Provide default time if schedulerDefaultTime is set.
    *
@@ -17,19 +14,25 @@
    * @see https://www.drupal.org/project/scheduler/issues/2913829
    */
   Drupal.behaviors.setSchedulerDefaultTime = {
-    attach: function (context) {
-
+    attach(context) {
       // Drupal.behaviors are called many times per page. Using .once() adds the
       // class onto the matched DOM element and uses this to prevent it running
       // on subsequent calls.
-      const $default_time = once('default-time-done', '#edit-scheduler-settings', context);
+      const $defaultTime = once(
+        'default-time-done',
+        '#edit-scheduler-settings',
+        context,
+      );
 
-      if ($default_time.length && typeof drupalSettings.schedulerDefaultTime !== "undefined") {
-        var operations = ["publish", "unpublish"];
+      if (
+        $defaultTime.length &&
+        typeof drupalSettings.schedulerDefaultTime !== 'undefined'
+      ) {
+        const operations = ['publish', 'unpublish'];
         operations.forEach(function (value) {
-          var element = $("input#edit-" + value + "-on-0-value-time", context);
+          const element = $(`input#edit-${value}-on-0-value-time`, context);
           // Only set the time when there is no value and the field is required.
-          if (element.val() === "" && element.prop("required")) {
+          if (element.val() === '' && element.prop('required')) {
             element.val(drupalSettings.schedulerDefaultTime);
           }
         });
@@ -38,15 +41,22 @@
       // Also use this jQuery behaviors function to set any pre-existing time
       // values with seconds removed if those drupalSettings values exist. This
       // is required by some browsers to make the seconds hidden.
-      if (typeof drupalSettings.schedulerHideSecondsPublishOn !== "undefined") {
-        var element = $("input#edit-publish-on-0-value-time", context);
-        element.val(drupalSettings.schedulerHideSecondsPublishOn);
+      if (typeof drupalSettings.schedulerHideSecondsPublishOn !== 'undefined') {
+        const elementPublishOn = $(
+          'input#edit-publish-on-0-value-time',
+          context,
+        );
+        elementPublishOn.val(drupalSettings.schedulerHideSecondsPublishOn);
       }
-      if (typeof drupalSettings.schedulerHideSecondsUnpublishOn !== "undefined") {
-        var element = $("input#edit-unpublish-on-0-value-time", context);
-        element.val(drupalSettings.schedulerHideSecondsUnpublishOn);
+      if (
+        typeof drupalSettings.schedulerHideSecondsUnpublishOn !== 'undefined'
+      ) {
+        const elementUnpublishOn = $(
+          'input#edit-unpublish-on-0-value-time',
+          context,
+        );
+        elementUnpublishOn.val(drupalSettings.schedulerHideSecondsUnpublishOn);
       }
-
-    }
+    },
   };
 })(jQuery, drupalSettings, once);
diff --git a/web/modules/scheduler/js/scheduler_default_time_8x.js b/web/modules/scheduler/js/scheduler_default_time_8x.js
index 7e2363f1e2..3b00f0da1e 100644
--- a/web/modules/scheduler/js/scheduler_default_time_8x.js
+++ b/web/modules/scheduler/js/scheduler_default_time_8x.js
@@ -6,9 +6,6 @@
  */
 
 (function ($, drupalSettings) {
-
-  'use strict';
-
   /**
    * Provide default time if schedulerDefaultTime is set.
    *
@@ -19,19 +16,23 @@
    * @see https://www.drupal.org/project/scheduler/issues/2913829
    */
   Drupal.behaviors.setSchedulerDefaultTime = {
-    attach: function (context) {
-
+    attach(context) {
       // Drupal.behaviors are called many times per page. Using .once() adds the
       // class onto the matched DOM element and uses this to prevent it running
       // on subsequent calls.
-      const $default_time = $(context).find('#edit-scheduler-settings').once('default-time-done');
+      const $defaultTime = $(context)
+        .find('#edit-scheduler-settings')
+        .once('default-time-done');
 
-      if ($default_time.length && typeof drupalSettings.schedulerDefaultTime !== "undefined") {
-        var operations = ["publish", "unpublish"];
+      if (
+        $defaultTime.length &&
+        typeof drupalSettings.schedulerDefaultTime !== 'undefined'
+      ) {
+        const operations = ['publish', 'unpublish'];
         operations.forEach(function (value) {
-          var element = $("input#edit-" + value + "-on-0-value-time", context);
+          const element = $(`input#edit-${value}-on-0-value-time`, context);
           // Only set the time when there is no value and the field is required.
-          if (element.val() === "" && element.prop("required")) {
+          if (element.val() === '' && element.prop('required')) {
             element.val(drupalSettings.schedulerDefaultTime);
           }
         });
@@ -40,15 +41,22 @@
       // Also use this jQuery behaviors function to set any pre-existing time
       // values with seconds removed if those drupalSettings values exist. This
       // is required by some browsers to make the seconds hidden.
-      if (typeof drupalSettings.schedulerHideSecondsPublishOn !== "undefined") {
-        var element = $("input#edit-publish-on-0-value-time", context);
-        element.val(drupalSettings.schedulerHideSecondsPublishOn);
+      if (typeof drupalSettings.schedulerHideSecondsPublishOn !== 'undefined') {
+        const elementPublishOn = $(
+          'input#edit-publish-on-0-value-time',
+          context,
+        );
+        elementPublishOn.val(drupalSettings.schedulerHideSecondsPublishOn);
       }
-      if (typeof drupalSettings.schedulerHideSecondsUnpublishOn !== "undefined") {
-        var element = $("input#edit-unpublish-on-0-value-time", context);
-        element.val(drupalSettings.schedulerHideSecondsUnpublishOn);
+      if (
+        typeof drupalSettings.schedulerHideSecondsUnpublishOn !== 'undefined'
+      ) {
+        const elementUnpublishOn = $(
+          'input#edit-unpublish-on-0-value-time',
+          context,
+        );
+        elementUnpublishOn.val(drupalSettings.schedulerHideSecondsUnpublishOn);
       }
-
-    }
+    },
   };
 })(jQuery, drupalSettings);
diff --git a/web/modules/scheduler/js/scheduler_vertical_tabs.js b/web/modules/scheduler/js/scheduler_vertical_tabs.js
index 1d46bc0fbb..99d35be8c1 100644
--- a/web/modules/scheduler/js/scheduler_vertical_tabs.js
+++ b/web/modules/scheduler/js/scheduler_vertical_tabs.js
@@ -4,42 +4,53 @@
  */
 
 (function ($) {
-
-  'use strict';
-
   /**
    * Provide summary information for vertical tabs.
    */
   Drupal.behaviors.scheduler_settings = {
-    attach: function (context) {
+    attach(context) {
+      // Provide summary when editing an entity. This is only applicable to
+      // themes that provide vertical tabs or modal details blocks with a
+      // summary area, such as Bartik or Claro. It does nothing in Stark.
+      $('details#edit-scheduler-settings', context).drupalSetSummary(
+        function (context) {
+          const publishOn = document.querySelector(
+            '#edit-publish-on-0-value-date',
+          );
+          const unpublishOn = document.querySelector(
+            '#edit-unpublish-on-0-value-date',
+          );
+          const values = [];
+          if (publishOn.value) {
+            values.push(Drupal.t('Scheduled for publishing'));
+          }
+          if (unpublishOn.value) {
+            values.push(Drupal.t('Scheduled for unpublishing'));
+          }
+          if (!values.length) {
+            values.push(Drupal.t('Not scheduled'));
+          }
+          return values.join('<br/>');
+        },
+      );
 
-      // Provide summary when editing a node.
-      $('details#edit-scheduler-settings', context).drupalSetSummary(function (context) {
-        var values = [];
-        if ($('#edit-publish-on-0-value-date').val()) {
-          values.push(Drupal.t('Scheduled for publishing'));
-        }
-        if ($('#edit-unpublish-on-0-value-date').val()) {
-          values.push(Drupal.t('Scheduled for unpublishing'));
-        }
-        if (!values.length) {
-          values.push(Drupal.t('Not scheduled'));
-        }
-        return values.join('<br/>');
-      });
-
-      // Provide summary during content type configuration.
+      // Provide summary during entity type configuration.
       $('#edit-scheduler', context).drupalSetSummary(function (context) {
-        var values = [];
-        if ($('#edit-scheduler-publish-enable', context).is(':checked')) {
+        const publishingEnabled = document.querySelector(
+          '#edit-scheduler-publish-enable',
+        );
+        const unpublishingEnabled = document.querySelector(
+          '#edit-scheduler-unpublish-enable',
+        );
+        const values = [];
+        if (publishingEnabled.matches(':checked')) {
           values.push(Drupal.t('Publishing enabled'));
         }
-        if ($('#edit-scheduler-unpublish-enable', context).is(':checked')) {
+        if (unpublishingEnabled.matches(':checked')) {
           values.push(Drupal.t('Unpublishing enabled'));
         }
         return values.join('<br/>');
       });
-    }
+    },
   };
-
 })(jQuery);
diff --git a/web/modules/scheduler/phpstan-baseline-to-fix.neon b/web/modules/scheduler/phpstan-baseline-to-fix.neon
index 05e597383b..d4c262a8f3 100644
--- a/web/modules/scheduler/phpstan-baseline-to-fix.neon
+++ b/web/modules/scheduler/phpstan-baseline-to-fix.neon
@@ -55,15 +55,6 @@ parameters:
       count: 1
       path: src/Routing/SchedulerRouteSubscriber.php
 
-    -
-      message: """
-        #^Call to deprecated method loadRevision\\(\\) of class Drupal\\\\Core\\\\Entity\\\\EntityStorageInterface\\:
-        in drupal\\:10\\.1\\.0 and is removed from drupal\\:11\\.0\\.0\\. Use
-        \\\\Drupal\\\\Core\\\\Entity\\\\RevisionableStorageInterface\\:\\:loadRevision instead\\.$#
-      """
-      count: 1
-      path: src/SchedulerManager.php
-
     -
       message: "#^\\\\Drupal calls should be avoided in classes, use dependency injection instead$#"
       count: 17
diff --git a/web/modules/scheduler/scheduler.info.yml b/web/modules/scheduler/scheduler.info.yml
index 5fdeb2d309..674ef1f1a8 100644
--- a/web/modules/scheduler/scheduler.info.yml
+++ b/web/modules/scheduler/scheduler.info.yml
@@ -20,7 +20,7 @@ libraries:
   - vertical-tabs
   - default-time
 
-# Information added by Drupal.org packaging script on 2024-04-28
-version: '2.0.3'
+# Information added by Drupal.org packaging script on 2024-06-28
+version: '2.0.4'
 project: 'scheduler'
-datestamp: 1714312560
+datestamp: 1719579867
diff --git a/web/modules/scheduler/scheduler_rules_integration/scheduler_rules_integration.info.yml b/web/modules/scheduler/scheduler_rules_integration/scheduler_rules_integration.info.yml
index ae2230263d..2640f13cb2 100644
--- a/web/modules/scheduler/scheduler_rules_integration/scheduler_rules_integration.info.yml
+++ b/web/modules/scheduler/scheduler_rules_integration/scheduler_rules_integration.info.yml
@@ -6,7 +6,7 @@ dependencies:
   - rules:rules
   - scheduler:scheduler
 
-# Information added by Drupal.org packaging script on 2024-04-28
-version: '2.0.3'
+# Information added by Drupal.org packaging script on 2024-06-28
+version: '2.0.4'
 project: 'scheduler'
-datestamp: 1714312560
+datestamp: 1719579867
diff --git a/web/modules/scheduler/src/Plugin/Field/FieldWidget/TimestampDatetimeNoDefaultWidget.php b/web/modules/scheduler/src/Plugin/Field/FieldWidget/TimestampDatetimeNoDefaultWidget.php
index 2c62a1ed0d..31aa7dc5b6 100644
--- a/web/modules/scheduler/src/Plugin/Field/FieldWidget/TimestampDatetimeNoDefaultWidget.php
+++ b/web/modules/scheduler/src/Plugin/Field/FieldWidget/TimestampDatetimeNoDefaultWidget.php
@@ -40,7 +40,9 @@ public function __construct(
     FieldDefinitionInterface $field_definition,
     array $settings,
     array $third_party_settings,
-    $scheduler_settings,
+    // Trailing comma is incompatible with PHPUnit 9.6.19 in Drupal 9.5 PHP 7.4.
+    // phpcs:ignore Drupal.Functions.MultiLineFunctionDeclaration.MissingTrailingComma
+    $scheduler_settings
   ) {
     parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings);
     $this->schedulerSettings = $scheduler_settings;
diff --git a/web/modules/scheduler/src/SchedulerManager.php b/web/modules/scheduler/src/SchedulerManager.php
index 9efd36bd41..71ae13aa29 100644
--- a/web/modules/scheduler/src/SchedulerManager.php
+++ b/web/modules/scheduler/src/SchedulerManager.php
@@ -103,7 +103,9 @@ public function __construct(
     EventDispatcherInterface $eventDispatcher,
     TimeInterface $time,
     EntityFieldManagerInterface $entityFieldManager,
-    SchedulerPluginManager $pluginManager,
+    // Trailing comma is incompatible with PHPUnit 9.6.19 in Drupal 9.5 PHP 7.4.
+    // phpcs:ignore Drupal.Functions.MultiLineFunctionDeclaration.MissingTrailingComma
+    SchedulerPluginManager $pluginManager
   ) {
     $this->dateFormatter = $dateFormatter;
     $this->logger = $logger;
@@ -908,6 +910,7 @@ public function getThirdPartySetting(EntityInterface $entity, $setting, $default
    *   Array of loaded entity objects, keyed by id.
    */
   protected function loadEntities(array $ids, string $type) {
+    /** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
     $storage = $this->entityTypeManager->getStorage($type);
     $entities = [];
     foreach ($ids as $id) {
diff --git a/web/modules/scheduler/src/SchedulerPluginManager.php b/web/modules/scheduler/src/SchedulerPluginManager.php
index f138358e49..0823abe671 100644
--- a/web/modules/scheduler/src/SchedulerPluginManager.php
+++ b/web/modules/scheduler/src/SchedulerPluginManager.php
@@ -29,7 +29,9 @@ public function __construct(
     \Traversable $namespaces,
     CacheBackendInterface $cacheBackend,
     ModuleHandlerInterface $module_handler,
-    EntityTypeManagerInterface $entity_type_manager,
+    // Trailing comma is incompatible with PHPUnit 9.6.19 in Drupal 9.5 PHP 7.4.
+    // phpcs:ignore Drupal.Functions.MultiLineFunctionDeclaration.MissingTrailingComma
+    EntityTypeManagerInterface $entity_type_manager
   ) {
     $subdir = 'Plugin/Scheduler';
     $plugin_interface = SchedulerPluginInterface::class;
diff --git a/web/modules/scheduler/tests/modules/scheduler_access_test/scheduler_access_test.info.yml b/web/modules/scheduler/tests/modules/scheduler_access_test/scheduler_access_test.info.yml
index 2be40bda1d..753c4316a1 100644
--- a/web/modules/scheduler/tests/modules/scheduler_access_test/scheduler_access_test.info.yml
+++ b/web/modules/scheduler/tests/modules/scheduler_access_test/scheduler_access_test.info.yml
@@ -5,7 +5,7 @@ package: Testing
 dependencies:
   - scheduler:scheduler
 
-# Information added by Drupal.org packaging script on 2024-04-28
-version: '2.0.3'
+# Information added by Drupal.org packaging script on 2024-06-28
+version: '2.0.4'
 project: 'scheduler'
-datestamp: 1714312560
+datestamp: 1719579867
diff --git a/web/modules/scheduler/tests/modules/scheduler_api_test/scheduler_api_legacy_test/scheduler_api_legacy_test.info.yml b/web/modules/scheduler/tests/modules/scheduler_api_test/scheduler_api_legacy_test/scheduler_api_legacy_test.info.yml
index 74b94fe764..998ab7baf9 100644
--- a/web/modules/scheduler/tests/modules/scheduler_api_test/scheduler_api_legacy_test/scheduler_api_legacy_test.info.yml
+++ b/web/modules/scheduler/tests/modules/scheduler_api_test/scheduler_api_legacy_test/scheduler_api_legacy_test.info.yml
@@ -6,7 +6,7 @@ dependencies:
   - scheduler:scheduler
   - scheduler_api_test:scheduler_api_test
 
-# Information added by Drupal.org packaging script on 2024-04-28
-version: '2.0.3'
+# Information added by Drupal.org packaging script on 2024-06-28
+version: '2.0.4'
 project: 'scheduler'
-datestamp: 1714312560
+datestamp: 1719579867
diff --git a/web/modules/scheduler/tests/modules/scheduler_api_test/scheduler_api_test.info.yml b/web/modules/scheduler/tests/modules/scheduler_api_test/scheduler_api_test.info.yml
index 484ca81d5a..783788c263 100644
--- a/web/modules/scheduler/tests/modules/scheduler_api_test/scheduler_api_test.info.yml
+++ b/web/modules/scheduler/tests/modules/scheduler_api_test/scheduler_api_test.info.yml
@@ -6,7 +6,7 @@ dependencies:
   - scheduler:scheduler
   - drupal:media
 
-# Information added by Drupal.org packaging script on 2024-04-28
-version: '2.0.3'
+# Information added by Drupal.org packaging script on 2024-06-28
+version: '2.0.4'
 project: 'scheduler'
-datestamp: 1714312560
+datestamp: 1719579867
diff --git a/web/modules/scheduler/tests/modules/scheduler_extras/scheduler_extras.info.yml b/web/modules/scheduler/tests/modules/scheduler_extras/scheduler_extras.info.yml
index e54b693e8b..71f2bddae9 100644
--- a/web/modules/scheduler/tests/modules/scheduler_extras/scheduler_extras.info.yml
+++ b/web/modules/scheduler/tests/modules/scheduler_extras/scheduler_extras.info.yml
@@ -5,7 +5,7 @@ package: Testing
 dependencies:
   - scheduler:scheduler
 
-# Information added by Drupal.org packaging script on 2024-04-28
-version: '2.0.3'
+# Information added by Drupal.org packaging script on 2024-06-28
+version: '2.0.4'
 project: 'scheduler'
-datestamp: 1714312560
+datestamp: 1719579867
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerTokenReplaceTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerTokenReplaceTest.php
index bb666c641c..b6397224a8 100644
--- a/web/modules/scheduler/tests/src/Functional/SchedulerTokenReplaceTest.php
+++ b/web/modules/scheduler/tests/src/Functional/SchedulerTokenReplaceTest.php
@@ -80,13 +80,12 @@ public function testSchedulerTokenReplacement($entityTypeId, $bundle) {
    * @dataProvider dataSchedulerWithoutTokenModule()
    */
   public function testSchedulerWithoutTokenModule($entityTypeId, $bundle) {
-    // This test is not run for commerce products because that module requires
-    // the token module, so it has to be uninstalled too.
-    $this->container->get('module_installer')->uninstall(['commerce_product']);
-    $this->container->get('module_installer')->uninstall(['token']);
+    // Commerce product requires the token module, so that has to be uninstalled
+    // also. Using FALSE allows both to be uninstalled in the same call.
+    $this->container->get('module_installer')->uninstall(['commerce_product', 'token'], FALSE);
 
     $this->drupalLogin($this->schedulerUser);
-    // Check that the entity add page can be accessed successfully, so show that
+    // Check that the entity add page can be accessed successfully, to show that
     // the token.entity_mapper service is avoided when not available.
     $this->drupalGet($this->entityAddUrl($entityTypeId, $bundle));
     $this->assertSession()->statusCodeEquals(200);
@@ -115,6 +114,7 @@ public function dataSchedulerTokenReplacement() {
   public function dataSchedulerWithoutTokenModule() {
     $data = $this->dataStandardEntityTypes();
     unset($data['#commerce_product']);
+    unset($data['#taxonomy_term']);
     return $data;
   }
 
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerViewsAccessTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerViewsAccessTest.php
index a416b84102..03cb44e8b3 100644
--- a/web/modules/scheduler/tests/src/Functional/SchedulerViewsAccessTest.php
+++ b/web/modules/scheduler/tests/src/Functional/SchedulerViewsAccessTest.php
@@ -165,6 +165,20 @@ public function testViewScheduledContentUser($entityTypeId, $bundle) {
     $assert->pageTextNotContains("$entityTypeId created by Scheduler Editor for unpublishing");
     $assert->pageTextContains("$entityTypeId created by Scheduler Viewer for publishing");
     $assert->pageTextContains("$entityTypeId created by Scheduler Viewer for unpublishing");
+
+    // Access the scheduled tab for the admin user, and check for the 'empty'
+    // text. This verifies that the uid argument plugin is working. In 10.2 the
+    // user id shown, but from 10.3+ the user entity is returned so the view
+    // displays the user name instead.
+    $this->drupalGet("user/{$this->adminUser->id()}/$url_end");
+    $assert->statusCodeEquals(200);
+    if (version_compare(\Drupal::VERSION, '10.3', '>=')) {
+      $assert->pageTextMatches("/No scheduled (content|media) for user {$this->adminUser->getDisplayName()}/");
+    }
+    else {
+      $assert->pageTextMatches("/No scheduled (content|media) for user {$this->adminUser->id()}/");
+    }
+
   }
 
   /**
diff --git a/web/modules/scheduler/tests/src/FunctionalJavascript/SchedulerJavascriptDefaultTimeTest.php b/web/modules/scheduler/tests/src/FunctionalJavascript/SchedulerJavascriptDefaultTimeTest.php
index 2c0ae45cd4..40ae8cab51 100644
--- a/web/modules/scheduler/tests/src/FunctionalJavascript/SchedulerJavascriptDefaultTimeTest.php
+++ b/web/modules/scheduler/tests/src/FunctionalJavascript/SchedulerJavascriptDefaultTimeTest.php
@@ -28,11 +28,12 @@ protected function setUp(): void {
     // to local testing having a different locale to drupal.org testing.
     // @see https://www.drupal.org/project/scheduler/issues/2913829 from #18.
     $this->drupalLogin($this->schedulerUser);
+    $this->nodetype->setThirdPartySetting('scheduler', 'fields_display_mode', 'fieldset')
+      ->setThirdPartySetting('scheduler', 'expand_fieldset', 'always')->save();
     $this->drupalGet('node/add/' . $this->type);
     $page = $this->getSession()->getPage();
     $title = "Add a {$this->typeName} to determine the date-picker format";
     $page->fillField('edit-title-0-value', $title);
-    $page->clickLink('Scheduling options');
     // Set the date using a day and month which could be correctly interpreted
     // either way. Set the year to be next year to ensure a future date.
     // Use a time format which includes 'pm' as this may be necessary, and will
@@ -41,7 +42,6 @@ protected function setUp(): void {
     $page->fillField('edit-publish-on-0-value-time', '06:00:00pm');
     $page->pressButton('Save');
     $node = $this->drupalGetNodeByTitle($title);
-    $this->drupalGet('node/' . $node->id());
     // If the saved month is 2 then the format is d/m/Y, otherwise it is m/d/Y.
     $this->datepickerFormat = (date('n', $node->publish_on->value) == 2 ? 'd/m/Y' : 'm/d/Y');
   }
diff --git a/web/modules/scheduler/tests/src/FunctionalJavascript/SchedulerJavascriptTestBase.php b/web/modules/scheduler/tests/src/FunctionalJavascript/SchedulerJavascriptTestBase.php
index 9747b39c85..c164ee0784 100644
--- a/web/modules/scheduler/tests/src/FunctionalJavascript/SchedulerJavascriptTestBase.php
+++ b/web/modules/scheduler/tests/src/FunctionalJavascript/SchedulerJavascriptTestBase.php
@@ -3,6 +3,7 @@
 namespace Drupal\Tests\scheduler\FunctionalJavascript;
 
 use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
+use Drupal\Tests\DocumentElement;
 use Drupal\Tests\scheduler\Traits\SchedulerCommerceProductSetupTrait;
 use Drupal\Tests\scheduler\Traits\SchedulerMediaSetupTrait;
 use Drupal\Tests\scheduler\Traits\SchedulerSetupTrait;
@@ -40,9 +41,13 @@ abstract class SchedulerJavascriptTestBase extends WebDriverTestBase {
   protected $profile = 'testing';
 
   /**
-   * {@inheritdoc}
+   * The default theme.
+   *
+   * The vertical tabs test needs 'claro' theme not 'stark'.
+   *
+   * @var string
    */
-  protected $defaultTheme = 'stark';
+  protected $defaultTheme = 'claro';
 
   /**
    * {@inheritdoc}
@@ -73,4 +78,30 @@ protected function flushCache() {
     $module_handler->invokeAll('cache_flush');
   }
 
+  /**
+   * Looks for the specified text and returns TRUE when it is unavailable.
+   *
+   * Core JSWebAssert has a function waitForText() but there is no equivalent to
+   * wait until text is hidden, as there is for some other page elements.
+   * Therefore define that function here, based on waitForText() in
+   * core/tests/Drupal/FunctionalJavascriptTests/JSWebAssert.php.
+   *
+   * @param string $text
+   *   The text to wait for.
+   * @param int $timeout
+   *   (Optional) Timeout in milliseconds, defaults to 10000.
+   *
+   * @return bool
+   *   TRUE if not found, FALSE if found.
+   */
+  public function waitForNoText($text, $timeout = 10000) {
+    $page = $this->getSession()->getPage();
+    return (bool) $page->waitFor($timeout / 1000, function (DocumentElement $page) use ($text) {
+      $actual = preg_replace('/\\s+/u', ' ', $page->getText());
+      // Negative look-ahead on the text that should be hidden.
+      $regex = '/^((?!' . preg_quote($text, '/') . ').)*$/ui';
+      return (bool) preg_match($regex, $actual);
+    });
+  }
+
 }
diff --git a/web/modules/scheduler/tests/src/FunctionalJavascript/SchedulerJavascriptVerticalTabsTest.php b/web/modules/scheduler/tests/src/FunctionalJavascript/SchedulerJavascriptVerticalTabsTest.php
new file mode 100644
index 0000000000..47d4c32bd9
--- /dev/null
+++ b/web/modules/scheduler/tests/src/FunctionalJavascript/SchedulerJavascriptVerticalTabsTest.php
@@ -0,0 +1,132 @@
+<?php
+
+namespace Drupal\Tests\scheduler\FunctionalJavascript;
+
+/**
+ * Tests the JavaScript functionality of vertical tabs summary information.
+ *
+ * @group scheduler_js
+ */
+class SchedulerJavascriptVerticalTabsTest extends SchedulerJavascriptTestBase {
+
+  /**
+   * Test editing an entity.
+   *
+   * @dataProvider dataStandardEntityTypes()
+   */
+  public function testEditEntitySummary($entityTypeId, $bundle) {
+    $this->drupalLogin($this->schedulerUser);
+    /** @var \Drupal\Tests\WebAssert $assert */
+    $assert = $this->assertSession();
+
+    // Set the entity edit form to use a vertical tab for the Scheduler dates.
+    $this->entityTypeObject($entityTypeId)
+      ->setThirdPartySetting('scheduler', 'fields_display_mode', 'vertical_tab')
+      ->setThirdPartySetting('scheduler', 'expand_fieldset', 'always')->save();
+    $titleField = $this->titleField($entityTypeId);
+
+    // Create an entity with a scheduled publishing date.
+    $entity = $this->createEntity($entityTypeId, $bundle, [
+      'publish_on' => strtotime('+2 months'),
+      "$titleField" => "$entityTypeId to publish",
+    ]);
+    $this->drupalGet($entity->toUrl('edit-form'));
+    $assert->pageTextContains('Scheduled for publishing');
+    $assert->pageTextNotContains('Scheduled for unpublishing');
+    $assert->pageTextNotContains('Not scheduled');
+
+    // Create an entity with a scheduled unpublishing date.
+    $entity = $this->createEntity($entityTypeId, $bundle, [
+      'unpublish_on' => strtotime('+3 months'),
+      "$titleField" => "$entityTypeId to unpublish",
+    ]);
+    $this->drupalGet($entity->toUrl('edit-form'));
+    $assert->pageTextNotContains('Scheduled for publishing');
+    $assert->pageTextContains('Scheduled for unpublishing');
+    $assert->pageTextNotContains('Not scheduled');
+
+    // In Claro, Node and Product have a separate vertical "tab" block which is
+    // always open. Taxonomy Term does not have vertical tabs, only the separate
+    // fieldset, but this also shows the summary. Media has the old-style block
+    // with side tabs, so we need to click 'Scheduling options'.
+    // In Drupal 10.3 the form for editing Taxonomy Terms seemed to change, and
+    // vertical tabs are implemented in a different way to 10.2. We now need to
+    // click to bring focus on that tab, ready for filling the date fields.
+    $page = $this->getSession()->getPage();
+    if ($entityTypeId == 'media' || ($entityTypeId == 'taxonomy_term' && version_compare(\Drupal::VERSION, '10.3', '>='))) {
+      $page->clickLink('Scheduling options');
+    }
+
+    $page->fillField('edit-publish-on-0-value-date', '05/02/' . (date('Y') + 1));
+    $page->fillField('edit-publish-on-0-value-time', '06:00:00pm');
+    $assert->waitForText('Scheduled for publishing');
+    $assert->pageTextContains('Scheduled for publishing');
+
+    // Setting the date and time values to '' only actually removes the first
+    // component of each of the fields. But this is enough for drupal.behaviors
+    // to update the summary correctly.
+    $page->fillField('edit-publish-on-0-value-date', '');
+    $page->fillField('edit-publish-on-0-value-time', '');
+    $page->fillField('edit-unpublish-on-0-value-date', '');
+    $page->fillField('edit-unpublish-on-0-value-time', '');
+    $assert->waitForText('Not scheduled');
+    $assert->pageTextNotContains('Scheduled for publishing');
+    $assert->pageTextNotContains('Scheduled for unpublishing');
+    $assert->pageTextContains('Not scheduled');
+  }
+
+  /**
+   * Test configuring an entity type.
+   *
+   * @dataProvider dataStandardEntityTypes()
+   */
+  public function testConfigureEntityTypeSummary($entityTypeId, $bundle) {
+    /** @var \Drupal\Tests\WebAssert $assert */
+    $assert = $this->assertSession();
+
+    $this->drupalLogin($this->adminUser);
+    $this->drupalGet($this->entityTypeObject($entityTypeId)->toUrl('edit-form'));
+
+    $page = $this->getSession()->getPage();
+    if (in_array($entityTypeId, ['node', 'media'])) {
+      // For node and media bring focus to the Scheduler vertical tab.
+      $page->clickLink('Scheduler');
+    }
+    else {
+      // For taxonomy term and product, open the closed modal details block.
+      $page->pressButton('Scheduler');
+    }
+
+    // Both options are enabled by default.
+    $assert->pageTextContains('Publishing enabled');
+    $assert->pageTextContains('Advanced options');
+    $assert->pageTextContains('Unpublishing enabled');
+
+    // Turn off the unpublishing enabled checkbox.
+    $page->uncheckField('edit-scheduler-unpublish-enable');
+    $this->waitForNoText('Unpublishing enabled');
+    $assert->pageTextContains('Publishing enabled');
+    $assert->pageTextContains('Advanced options');
+    $assert->pageTextNotContains('Unpublishing enabled');
+
+    // Turn off the publishing enabled checkbox.
+    $page->uncheckField('edit-scheduler-publish-enable');
+    $this->waitForNoText('Publishing enabled');
+    $assert->pageTextNotContains('Publishing enabled');
+    $assert->pageTextNotContains('Advanced options');
+
+    // Turn on the publishing enabled checkbox.
+    $page->checkField('edit-scheduler-publish-enable');
+    $assert->waitForText('Publishing enabled');
+    $assert->pageTextContains('Publishing enabled');
+    $assert->pageTextNotContains('Unpublishing enabled');
+    $assert->pageTextContains('Advanced options');
+
+    // Turn on the unpublishing enabled checkbox.
+    $page->checkField('edit-scheduler-unpublish-enable');
+    $assert->waitForText('Unpublishing enabled');
+    $assert->pageTextContains('Unpublishing enabled');
+
+  }
+
+}
-- 
GitLab