diff --git a/package_manager/src/ProjectInfo.php b/package_manager/src/ProjectInfo.php index a12369f23a86eb9098152858bf9126cd2b469237..2a98428180105d5a01cfcdd714db3be6edb70171 100644 --- a/package_manager/src/ProjectInfo.php +++ b/package_manager/src/ProjectInfo.php @@ -199,4 +199,15 @@ final class ProjectInfo { return FALSE; } + /** + * Gets the supported branches of the project. + * + * @return string[] + * The supported branches. + */ + public function getSupportedBranches(): array { + $available_updates = $this->getAvailableProjects()[$this->name]; + return isset($available_updates['supported_branches']) ? explode(',', $available_updates['supported_branches']) : []; + } + } diff --git a/package_manager/tests/fixtures/release-history/drupal.10.0.0.xml b/package_manager/tests/fixtures/release-history/drupal.10.0.0.xml new file mode 100644 index 0000000000000000000000000000000000000000..47b00822f1d8652e1f25600185ce1187e1049f7a --- /dev/null +++ b/package_manager/tests/fixtures/release-history/drupal.10.0.0.xml @@ -0,0 +1,210 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +Contains metadata about the following (fake) releases of Drupal core, all of which are secure, in order: +* 10.0.0 +* 9.7.2 +* 9.7.1 +* 9.7.0 +* 9.6.1 +* 9.6.0 +* 9.5.1 +* 9.5.0 +* 9.4.0, which is in an unsupported branch +* 9.7.x-dev +--> +<project xmlns:dc="http://purl.org/dc/elements/1.1/"> + <title>Drupal</title> + <short_name>drupal</short_name> + <dc:creator>Drupal</dc:creator> + <supported_branches>9.5.,9.6.,9.7.,10.0.</supported_branches> + <project_status>published</project_status> + <link>http://example.com/project/drupal</link> + <terms> + <term> + <name>Projects</name> + <value>Drupal project</value> + </term> + </terms> + <releases> + <release> + <name>Drupal 10.0.0</name> + <version>10.0.0</version> + <status>published</status> + <release_link>http://example.com/drupal-10-0-0-release</release_link> + <download_link>http://example.com/drupal-10-0-0.tar.gz</download_link> + <date>1250425521</date> + <terms> + <term> + <name>Release type</name> + <value>New features</value> + </term> + <term> + <name>Release type</name> + <value>Bug fixes</value> + </term> + </terms> + </release> + <release> + <name>Drupal 9.7.2</name> + <version>9.7.2</version> + <status>published</status> + <release_link>http://example.com/drupal-9-7-2-release</release_link> + <download_link>http://example.com/drupal-9-7-2.tar.gz</download_link> + <date>1250425521</date> + <terms> + <term> + <name>Release type</name> + <value>New features</value> + </term> + <term> + <name>Release type</name> + <value>Bug fixes</value> + </term> + </terms> + </release> + <release> + <name>Drupal 9.7.1</name> + <version>9.7.1</version> + <status>published</status> + <release_link>http://example.com/drupal-9-7-1-release</release_link> + <download_link>http://example.com/drupal-9-7-1.tar.gz</download_link> + <date>1250425521</date> + <terms> + <term> + <name>Release type</name> + <value>New features</value> + </term> + <term> + <name>Release type</name> + <value>Bug fixes</value> + </term> + </terms> + </release> + <release> + <name>Drupal 9.7.0</name> + <version>9.7.0</version> + <status>published</status> + <release_link>http://example.com/drupal-9-7-0-release</release_link> + <download_link>http://example.com/drupal-9-7-0.tar.gz</download_link> + <date>1250424521</date> + <terms> + <term> + <name>Release type</name> + <value>New features</value> + </term> + <term> + <name>Release type</name> + <value>Bug fixes</value> + </term> + </terms> + </release> + <release> + <name>Drupal 9.6.1</name> + <version>9.6.1</version> + <status>published</status> + <release_link>http://example.com/drupal-9-6-1-release</release_link> + <download_link>http://example.com/drupal-9-6-1.tar.gz</download_link> + <date>1250425521</date> + <terms> + <term> + <name>Release type</name> + <value>New features</value> + </term> + <term> + <name>Release type</name> + <value>Bug fixes</value> + </term> + </terms> + </release> + <release> + <name>Drupal 9.6.0</name> + <version>9.6.0</version> + <status>published</status> + <release_link>http://example.com/drupal-9-6-0-release</release_link> + <download_link>http://example.com/drupal-9-6-0.tar.gz</download_link> + <date>1250424521</date> + <terms> + <term> + <name>Release type</name> + <value>New features</value> + </term> + <term> + <name>Release type</name> + <value>Bug fixes</value> + </term> + </terms> + </release> + <release> + <name>Drupal 9.5.1</name> + <version>9.5.1</version> + <status>published</status> + <release_link>http://example.com/drupal-9-5-1-release</release_link> + <download_link>http://example.com/drupal-9-5-1.tar.gz</download_link> + <date>1250425521</date> + <terms> + <term> + <name>Release type</name> + <value>New features</value> + </term> + <term> + <name>Release type</name> + <value>Bug fixes</value> + </term> + </terms> + </release> + <release> + <name>Drupal 9.5.0</name> + <version>9.5.0</version> + <status>published</status> + <release_link>http://example.com/drupal-9-5-0-release</release_link> + <download_link>http://example.com/drupal-9-5-0.tar.gz</download_link> + <date>1250424521</date> + <terms> + <term> + <name>Release type</name> + <value>New features</value> + </term> + <term> + <name>Release type</name> + <value>Bug fixes</value> + </term> + </terms> + </release> + <release> + <name>Drupal 9.4.0</name> + <version>9.4.0</version> + <status>published</status> + <release_link>http://example.com/drupal-9-4-0-release</release_link> + <download_link>http://example.com/drupal-9-4-0.tar.gz</download_link> + <date>1240424421</date> + <terms> + <term> + <name>Release type</name> + <value>New features</value> + </term> + <term> + <name>Release type</name> + <value>Bug fixes</value> + </term> + </terms> + </release> + <release> + <name>Drupal 9.7.x-dev</name> + <version>9.7.x-dev</version> + <status>published</status> + <release_link>http://example.com/drupal-9-7-x-dex-release</release_link> + <download_link>http://example.com/drupal-9-7-x-dex.tar.gz</download_link> + <date>1250424521</date> + <terms> + <term> + <name>Release type</name> + <value>New features</value> + </term> + <term> + <name>Release type</name> + <value>Bug fixes</value> + </term> + </terms> + </release> + </releases> +</project> diff --git a/package_manager/tests/fixtures/release-history/drupal.9.8.1-empty_supported_branches.xml b/package_manager/tests/fixtures/release-history/drupal.9.8.1-empty_supported_branches.xml new file mode 100644 index 0000000000000000000000000000000000000000..bd0f458177f3094b8ef5e0d47e1de217bdd70b87 --- /dev/null +++ b/package_manager/tests/fixtures/release-history/drupal.9.8.1-empty_supported_branches.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +Contains metadata about releases of Drupal core with no supported branches: +--> +<project xmlns:dc="http://purl.org/dc/elements/1.1/"> + <title>Drupal</title> + <short_name>drupal</short_name> + <dc:creator>Drupal</dc:creator> + <supported_branches/> + <project_status>published</project_status> + <link>http://example.com/project/drupal</link> + <terms> + <term> + <name>Projects</name> + <value>Drupal project</value> + </term> + </terms> +</project> diff --git a/package_manager/tests/fixtures/release-history/drupal.9.8.1-supported_branches_not_set.xml b/package_manager/tests/fixtures/release-history/drupal.9.8.1-supported_branches_not_set.xml new file mode 100644 index 0000000000000000000000000000000000000000..15545c7131e42f7bba6d50bb9a0fa76cfd260125 --- /dev/null +++ b/package_manager/tests/fixtures/release-history/drupal.9.8.1-supported_branches_not_set.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +Contains metadata about releases of Drupal core with <supported_branches></supported_branches> not set: +--> +<project xmlns:dc="http://purl.org/dc/elements/1.1/"> + <title>Drupal</title> + <short_name>drupal</short_name> + <dc:creator>Drupal</dc:creator> + <project_status>published</project_status> + <link>http://example.com/project/drupal</link> + <terms> + <term> + <name>Projects</name> + <value>Drupal project</value> + </term> + </terms> +</project> diff --git a/package_manager/tests/src/Kernel/ProjectInfoTest.php b/package_manager/tests/src/Kernel/ProjectInfoTest.php index 97dfe1b0ca47b6261f2c304ec1744df283811f59..23568af765ca5c8fbc629e3476d079c28de1eb53 100644 --- a/package_manager/tests/src/Kernel/ProjectInfoTest.php +++ b/package_manager/tests/src/Kernel/ProjectInfoTest.php @@ -242,4 +242,52 @@ class ProjectInfoTest extends PackageManagerKernelTestBase { $this->assertSame($expected_to_be_safe, $project_info->isInstalledVersionSafe()); } + /** + * Data provider for testGetSupportedBranches(). + * + * @return mixed[][] + * The test cases. + */ + public function providerGetSupportedBranches(): array { + $dir = __DIR__ . '/../../fixtures/release-history/'; + + return [ + 'xml with supported branches' => [ + $dir . 'drupal.10.0.0.xml', + [ + '9.5.', + '9.6.', + '9.7.', + '10.0.', + ], + ], + 'xml with supported branches not set' => [ + $dir . 'drupal.9.8.1-supported_branches_not_set.xml', + [], + ], + 'xml with empty supported branches' => [ + $dir . 'drupal.9.8.1-empty_supported_branches.xml', + [ + '', + ], + ], + ]; + } + + /** + * @covers ::getSupportedBranches() + * + * @param string $release_xml + * The path of the release metadata. + * @param string[] $expected_supported_branches + * The expected supported branches. + * + * @dataProvider providerGetSupportedBranches + */ + public function testGetSupportedBranches(string $release_xml, array $expected_supported_branches): void { + $this->setReleaseMetadata(['drupal' => $release_xml]); + $project_info = new ProjectInfo('drupal'); + $this->assertSame($expected_supported_branches, $project_info->getSupportedBranches()); + } + } diff --git a/src/Form/UpdaterForm.php b/src/Form/UpdaterForm.php index 7c8ace411e780459ee9a12eaba1d718801193b68..ba571317315c823f6c558a381d98b93e152c6ec6 100644 --- a/src/Form/UpdaterForm.php +++ b/src/Form/UpdaterForm.php @@ -174,13 +174,19 @@ final class UpdaterForm extends UpdateFormBase { ]; $project_info = new ProjectInfo('drupal'); + $installed_version = ExtensionVersion::createFromVersionString($project_info->getInstalledVersion()); try { - // @todo Until https://www.drupal.org/i/3264849 is fixed, we can only show - // one release on the form. First, try to show the latest release in the - // currently installed minor. Failing that, try to show the latest - // release in the next minor. - $installed_minor_release = $this->releaseChooser->getLatestInInstalledMinor($this->updater); - $next_minor_release = $this->releaseChooser->getLatestInNextMinor($this->updater); + $support_branches = $project_info->getSupportedBranches(); + $releases = []; + foreach ($support_branches as $support_branch) { + $support_branch_extension_version = ExtensionVersion::createFromSupportBranch($support_branch); + if ($support_branch_extension_version->getMajorVersion() === $installed_version->getMajorVersion() && $support_branch_extension_version->getMinorVersion() >= $installed_version->getMinorVersion()) { + $recent_release_in_minor = $this->releaseChooser->getMostRecentReleaseInMinor($this->updater, $support_branch . '0'); + if ($recent_release_in_minor) { + $releases[$support_branch] = $recent_release_in_minor; + } + } + } } catch (\RuntimeException $e) { $form['message'] = [ @@ -197,7 +203,7 @@ final class UpdaterForm extends UpdateFormBase { } $this->displayResults($results, $this->renderer); $project = $project_info->getProjectInfo(); - if ($installed_minor_release === NULL && $next_minor_release === NULL) { + if (empty($releases)) { if ($project['status'] === UpdateManagerInterface::CURRENT) { $this->messenger()->addMessage($this->t('No update available')); } @@ -249,72 +255,82 @@ final class UpdaterForm extends UpdateFormBase { $type = 'update-recommended'; } $create_update_buttons = !$stage_exists && ValidationResult::getOverallSeverity($results) !== SystemManager::REQUIREMENT_ERROR; - if ($installed_minor_release) { - $installed_version = ExtensionVersion::createFromVersionString($project_info->getInstalledVersion()); - $form['installed_minor'] = $this->createReleaseTable( - $installed_minor_release, - $release_status, - $this->t('Latest version of Drupal @major.@minor (currently installed):', [ - '@major' => $installed_version->getMajorVersion(), - '@minor' => $installed_version->getMinorVersion(), - ]), - $type, - $create_update_buttons, - // Any update in the current minor should be the primary update. - TRUE, - ); - } - if ($next_minor_release) { - // If there is no update in the current minor make the button for the next - // minor primary unless the project status is 'CURRENT' or 'NOT_CURRENT'. - // 'NOT_CURRENT' does not denote that installed version is not a valid - // only that there is newer version available. - $is_primary = !$installed_minor_release && !($project['status'] === UpdateManagerInterface::CURRENT || $project['status'] === UpdateManagerInterface::NOT_CURRENT); - $next_minor_version = ExtensionVersion::createFromVersionString($next_minor_release->getVersion()); - - // Since updating to another minor version of Drupal is more disruptive - // than updating within the currently installed minor version, ensure we - // display a link to the release notes for the first (x.y.0) release of - // the next minor version, which will inform site owners of any potential - // pitfalls or major changes. We should always be able to get release info - // for it; if we can't, that's an error condition. - $first_release_version = $next_minor_version->getMajorVersion() . '.' . $next_minor_version->getMinorVersion() . '.0'; - $available_updates = update_get_available(TRUE); - if (isset($available_updates['drupal']['releases'][$first_release_version])) { - $next_minor_first_release = ProjectRelease::createFromArray($available_updates['drupal']['releases'][$first_release_version]); + + $installed_minor_release = FALSE; + $next_minor_release_count = 0; + foreach ($releases as $release) { + $release_version = ExtensionVersion::createFromVersionString($release->getVersion()); + if ($release_version->getMinorVersion() === $installed_version->getMinorVersion()) { + $installed_minor_release = TRUE; + $installed_version = ExtensionVersion::createFromVersionString($project_info->getInstalledVersion()); + $form['installed_minor'] = $this->createReleaseTable( + $release, + $release_status, + $this->t('Latest version of Drupal @major.@minor (currently installed):', [ + '@major' => $installed_version->getMajorVersion(), + '@minor' => $installed_version->getMinorVersion(), + ]), + $type, + $create_update_buttons, + // Any update in the current minor should be the primary update. + TRUE, + ); } else { - throw new \LogicException("Release information for Drupal $first_release_version is not available."); - } + $next_minor_release_count++; + if ($next_minor_release_count === 1) { + if ($this->moduleHandler->moduleExists('help')) { + $url = Url::fromRoute('help.page') + ->setRouteParameter('name', 'automatic_updates') + ->setOption('fragment', 'minor-update'); + + $form['minor_update_help'] = [ + '#markup' => $this->t('The following updates are in newer minor version of Drupal. <a href=":url">Learn more about updating to another minor version.</a>', [ + ':url' => $url->toString(), + ]), + '#prefix' => '<p>', + '#suffix' => '</p>', + ]; + } + } + // If there is no update in the current minor make the button for the + // next minor primary unless the project status is 'CURRENT' or + // 'NOT_CURRENT'. 'NOT_CURRENT' does not denote that installed version + // is not a valid only that there is newer version available. + if (!isset($is_primary)) { + $is_primary = !$installed_minor_release && !($project['status'] === UpdateManagerInterface::CURRENT || $project['status'] === UpdateManagerInterface::NOT_CURRENT); + } + else { + $is_primary = FALSE; + } - if ($this->moduleHandler->moduleExists('help')) { - $url = Url::fromRoute('help.page') - ->setRouteParameter('name', 'automatic_updates') - ->setOption('fragment', 'minor-update'); + // Since updating to another minor version of Drupal is more + // disruptive than updating within the currently installed minor + // version, ensure we display a link to the release notes for the + // first (x.y.0) release of the next minor version, which will inform + // site owners of any potential pitfalls or major changes. We should + // always be able to get release info for it; if we can't, that's an + // error condition. + $first_release_version = $release_version->getMajorVersion() . '.' . $release_version->getMinorVersion() . '.0'; + $available_updates = update_get_available(TRUE); + + // @todo In https://www.drupal.org/i/3310666 handle if the .0 release is + // not available, and only pre-releases are available. + $next_minor_first_release = ProjectRelease::createFromArray($available_updates['drupal']['releases'][$first_release_version]); - // @todo Updating this wording in https://www.drupal.org/i/3280403 to - // reflect that multiple minor branches may be visible. - $form['minor_update_help'] = [ - '#markup' => $this->t('The following updates are in the next minor version of Drupal. <a href=":url">Learn more about updating to another minor version.</a>', [ - ':url' => $url->toString(), + $form["next_minor_$next_minor_release_count"] = $this->createReleaseTable( + $release, + $installed_minor_release ? $this->t('Minor update') : $release_status, + $this->t('Latest version of Drupal @major.@minor (next minor) (<a href=":url">Release notes</a>):', [ + '@major' => $release_version->getMajorVersion(), + '@minor' => $release_version->getMinorVersion(), + ':url' => $next_minor_first_release->getReleaseUrl(), ]), - '#prefix' => '<p>', - '#suffix' => '</p>', - ]; + $installed_minor_release ? 'update-optional' : $type, + $create_update_buttons, + $is_primary + ); } - - $form['next_minor'] = $this->createReleaseTable( - $next_minor_release, - $installed_minor_release ? $this->t('Minor update') : $release_status, - $this->t('Latest version of Drupal @major.@minor (next minor) (<a href=":url">Release notes</a>):', [ - '@major' => $next_minor_version->getMajorVersion(), - '@minor' => $next_minor_version->getMinorVersion(), - ':url' => $next_minor_first_release->getReleaseUrl(), - ]), - $installed_minor_release ? 'update-optional' : $type, - $create_update_buttons, - $is_primary - ); } $form['backup'] = [ diff --git a/src/ReleaseChooser.php b/src/ReleaseChooser.php index 5e819ce06416f539d39849927003fe9dff14b28c..6438820232fb5c487976b137ef20c6521908da36 100644 --- a/src/ReleaseChooser.php +++ b/src/ReleaseChooser.php @@ -75,7 +75,7 @@ final class ReleaseChooser { * @throws \InvalidArgumentException * If the given semantic version number does not contain a patch version. */ - protected function getMostRecentReleaseInMinor(Updater $updater, string $version): ?ProjectRelease { + public function getMostRecentReleaseInMinor(Updater $updater, string $version): ?ProjectRelease { if (static::getPatchVersion($version) === NULL) { throw new \InvalidArgumentException("The version number $version does not contain a patch version"); } diff --git a/tests/src/Functional/UpdaterFormTest.php b/tests/src/Functional/UpdaterFormTest.php index 10683dc12c5ec090714c6636f6da78874559b6d2..03942b99e7770eb65f84a5df2634b056c7c09402 100644 --- a/tests/src/Functional/UpdaterFormTest.php +++ b/tests/src/Functional/UpdaterFormTest.php @@ -130,11 +130,11 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase { $assert_session = $this->assertSession(); $assert_minor_update_help = function () use ($assert_session): void { - $assert_session->pageTextContains('The following updates are in the next minor version of Drupal. Learn more about updating to another minor version.'); + $assert_session->pageTextContainsOnce('The following updates are in newer minor version of Drupal. Learn more about updating to another minor version.'); $assert_session->linkExists('Learn more about updating to another minor version.'); }; $assert_no_minor_update_help = function () use ($assert_session): void { - $assert_session->pageTextNotContains('The following updates are in the next minor version of Drupal. Learn more about updating to another minor version.'); + $assert_session->pageTextNotContains('The following updates are in newer minor version of Drupal. Learn more about updating to another minor version.'); }; $page = $this->getSession()->getPage(); @@ -158,7 +158,7 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase { // Check the form when there is an update in the installed minor only. $assert_session->pageTextContainsOnce('Currently installed: 9.8.0 (Security update required!)'); $this->checkReleaseTable('#edit-installed-minor', '.update-update-security', '9.8.1', TRUE, 'Latest version of Drupal 9.8 (currently installed):'); - $assert_session->elementNotExists('css', '#edit-next-minor'); + $assert_session->elementNotExists('css', '#edit-next-minor-1'); $assert_no_minor_update_help(); // Check the form when there is an update in the next minor only. @@ -166,9 +166,9 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase { $this->setCoreVersion('9.7.0'); $page->clickLink('Check manually'); $this->checkForMetaRefresh(); - $this->checkReleaseTable('#edit-next-minor', '.update-update-recommended', '9.8.1', TRUE, 'Latest version of Drupal 9.8 (next minor) (Release notes):'); + $this->checkReleaseTable('#edit-next-minor-1', '.update-update-recommended', '9.8.1', TRUE, 'Latest version of Drupal 9.8 (next minor) (Release notes):'); $assert_minor_update_help(); - $this->assertReleaseNotesLink(9, 8); + $this->assertReleaseNotesLink(9, 8, '#edit-next-minor-1'); $assert_session->pageTextContainsOnce('Currently installed: 9.7.0 (Not supported!)'); $assert_session->elementNotExists('css', '#edit-installed-minor'); @@ -180,7 +180,7 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase { $this->checkForMetaRefresh(); $assert_session->pageTextContainsOnce('Currently installed: 9.7.0 (Update available)'); $this->checkReleaseTable('#edit-installed-minor', '.update-update-recommended', '9.7.1', TRUE, 'Latest version of Drupal 9.7 (currently installed):'); - $assert_session->elementNotExists('css', '#edit-next-minor'); + $assert_session->elementNotExists('css', '#edit-next-minor-1'); $assert_no_minor_update_help(); // Check that if minor updates are enabled the update in the next minor will @@ -188,8 +188,8 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase { $this->config('automatic_updates.settings')->set('allow_core_minor_updates', TRUE)->save(); $this->getSession()->reload(); $this->checkReleaseTable('#edit-installed-minor', '.update-update-recommended', '9.7.1', TRUE, 'Latest version of Drupal 9.7 (currently installed):'); - $this->checkReleaseTable('#edit-next-minor', '.update-update-optional', '9.8.2', FALSE, 'Latest version of Drupal 9.8 (next minor) (Release notes):'); - $this->assertReleaseNotesLink(9, 8); + $this->checkReleaseTable('#edit-next-minor-1', '.update-update-optional', '9.8.2', FALSE, 'Latest version of Drupal 9.8 (next minor) (Release notes):'); + $this->assertReleaseNotesLink(9, 8, '#edit-next-minor-1'); $assert_minor_update_help(); $this->setCoreVersion('9.7.1'); @@ -197,8 +197,39 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase { $this->checkForMetaRefresh(); $assert_session->pageTextContainsOnce('Currently installed: 9.7.1 (Update available)'); $assert_session->elementNotExists('css', '#edit-installed-minor'); - $this->checkReleaseTable('#edit-next-minor', '.update-update-recommended', '9.8.2', FALSE, 'Latest version of Drupal 9.8 (next minor) (Release notes):'); - $this->assertReleaseNotesLink(9, 8); + $this->checkReleaseTable('#edit-next-minor-1', '.update-update-recommended', '9.8.2', FALSE, 'Latest version of Drupal 9.8 (next minor) (Release notes):'); + $this->assertReleaseNotesLink(9, 8, '#edit-next-minor-1'); + $assert_minor_update_help(); + + // Check that if minor updates are enabled then updates in the next minors + // are visible. + $this->config('automatic_updates.settings')->set('allow_core_minor_updates', TRUE)->save(); + $this->setReleaseMetadata(__DIR__ . '/../../../package_manager/tests/fixtures/release-history/drupal.10.0.0.xml'); + $this->setCoreVersion('9.5.0'); + $page->clickLink('Check manually'); + $this->checkForMetaRefresh(); + $assert_session->pageTextNotContains('10.0.0'); + $assert_session->pageTextContainsOnce('Currently installed: 9.5.0 (Update available)'); + $this->checkReleaseTable('#edit-installed-minor', '.update-update-recommended', '9.5.1', TRUE, 'Latest version of Drupal 9.5 (currently installed):'); + $this->checkReleaseTable('#edit-next-minor-1', '.update-update-optional', '9.6.1', FALSE, 'Latest version of Drupal 9.6 (next minor) (Release notes):'); + $this->assertReleaseNotesLink(9, 6, '#edit-next-minor-1'); + $this->checkReleaseTable('#edit-next-minor-2', '.update-update-optional', '9.7.2', FALSE, 'Latest version of Drupal 9.7 (next minor) (Release notes):'); + $this->assertReleaseNotesLink(9, 7, '#edit-next-minor-2'); + $assert_minor_update_help(); + + // Check that if installed version is unsupported and minor updates are + // enabled then updates in the next minors are visible. + $this->setCoreVersion('9.4.0'); + $page->clickLink('Check manually'); + $this->checkForMetaRefresh(); + $assert_session->pageTextNotContains('10.0.0'); + $assert_session->pageTextContainsOnce('Currently installed: 9.4.0 (Not supported!)'); + $this->checkReleaseTable('#edit-next-minor-1', '.update-update-recommended', '9.5.1', TRUE, 'Latest version of Drupal 9.5 (next minor) (Release notes):'); + $this->assertReleaseNotesLink(9, 5, '#edit-next-minor-1'); + $this->checkReleaseTable('#edit-next-minor-2', '.update-update-recommended', '9.6.1', FALSE, 'Latest version of Drupal 9.6 (next minor) (Release notes):'); + $this->assertReleaseNotesLink(9, 6, '#edit-next-minor-2'); + $this->checkReleaseTable('#edit-next-minor-3', '.update-update-recommended', '9.7.2', FALSE, 'Latest version of Drupal 9.7 (next minor) (Release notes):'); + $this->assertReleaseNotesLink(9, 7, '#edit-next-minor-3'); $assert_minor_update_help(); $this->assertUpdateStagedTimes(0); @@ -898,10 +929,12 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase { * Major version of next minor release. * @param int $minor * Minor version of next minor release. + * @param string $selector + * The selector. */ - private function assertReleaseNotesLink(int $major, int $minor): void { + private function assertReleaseNotesLink(int $major, int $minor, string $selector): void { $assert_session = $this->assertSession(); - $row = $assert_session->elementExists('css', '#edit-next-minor'); + $row = $assert_session->elementExists('css', $selector); $link_href = $assert_session->elementExists('named', ['link', 'Release notes'], $row)->getAttribute('href'); $this->assertSame('http://example.com/drupal-' . $major . '-' . $minor . '-0-release', $link_href); } diff --git a/tests/src/Kernel/ReleaseChooserTest.php b/tests/src/Kernel/ReleaseChooserTest.php index 7303121228d245df6a5ba80403954e1ded1f0730..79d60367d2b3f3386c176839f83f5aca85e2d88f 100644 --- a/tests/src/Kernel/ReleaseChooserTest.php +++ b/tests/src/Kernel/ReleaseChooserTest.php @@ -2,6 +2,7 @@ namespace Drupal\Tests\automatic_updates\Kernel; +use Drupal\Core\Extension\ExtensionVersion; use Drupal\update\ProjectRelease; /** @@ -140,6 +141,7 @@ class ReleaseChooserTest extends AutomaticUpdatesKernelTestBase { * * @covers ::getLatestInInstalledMinor * @covers ::getLatestInNextMinor + * @covers ::getMostRecentReleaseInMinor */ public function testReleases(string $updater_service, bool $minor_support, string $installed_version, ?string $current_minor, ?string $next_minor): void { $this->setCoreVersion($installed_version); @@ -150,6 +152,14 @@ class ReleaseChooserTest extends AutomaticUpdatesKernelTestBase { $updater = $this->container->get($updater_service); $this->assertReleaseVersion($current_minor, $chooser->getLatestInInstalledMinor($updater)); $this->assertReleaseVersion($next_minor, $chooser->getLatestInNextMinor($updater)); + + $this->assertReleaseVersion($current_minor, $chooser->getMostRecentReleaseInMinor($updater, $this->getRelativeVersion($installed_version, 0))); + $next_minor_version = $this->getRelativeVersion($installed_version, 1); + $this->assertReleaseVersion($next_minor, $chooser->getMostRecentReleaseInMinor($updater, $next_minor_version)); + $previous_minor_version = $this->getRelativeVersion($installed_version, -1); + // The chooser should never return a release for a minor before the + // currently installed version. + $this->assertReleaseVersion(NULL, $chooser->getMostRecentReleaseInMinor($updater, $previous_minor_version)); } /** @@ -170,4 +180,20 @@ class ReleaseChooserTest extends AutomaticUpdatesKernelTestBase { } } + /** + * Gets a version number in a minor version relative to another version. + * + * @param string $version + * The version string. + * @param int $minor_offset + * The minor offset. + * + * @return string + * The first patch release in a minor relative to the version string. + */ + private function getRelativeVersion(string $version, int $minor_offset): string { + $installed_version_object = ExtensionVersion::createFromVersionString($version); + return $installed_version_object->getMajorVersion() . '.' . (((int) $installed_version_object->getMinorVersion()) + $minor_offset) . '.0'; + } + }