diff --git a/tests/features/community_content/add_community_content.feature b/tests/features/community_content/add_community_content.feature index 383a7af439..a2c4c46904 100644 --- a/tests/features/community_content/add_community_content.feature +++ b/tests/features/community_content/add_community_content.feature @@ -66,14 +66,11 @@ Feature: Add community content Scenario Outline: Publishing a content for the first time updates the creation time Given users: - | Username | E-mail | First name | Family name | - | Publisher | publisher-example@test.com | Publihser | Georgakopoulos | + | Username | E-mail | First name | Family name | Roles | + | Publisher | publisher-example@test.com | Publihser | Georgakopoulos | moderator | And the following collection: | title | The afternoon shift | | state | validated | - And the following collection user membership: - | collection | user | role | - | The afternoon shift | Publisher | facilitator | And discussion content: | title | content | author | state | collection | created | | Sample discussion | Sample content. | Publisher | draft | The afternoon shift | 01-01-2010 | @@ -90,8 +87,17 @@ Feature: Add community content And I click "Edit" in the "Entity actions" region And I press "Publish" Then I should see the heading "Sample " - # The created date has been updated. - And I should not see the text "01/01/2010" + And the latest version of the "Sample " should have a different created date than the last unpublished version + + When I click "Revisions" in the "Entity actions" region + And I click the last "Revert" link + And I press "Revert" + And I go to the "Sample " + + When I click "Edit" in the "Entity actions" region + And I press "Publish" + Then I should see the heading "Sample " + Then the latest version of the "Sample " should have the same created date as the last published version # The document is not tested as the creation date is not shown in the page. For documents, the document publication # date is the one shown and this field is exposed to the user. diff --git a/tests/src/Traits/NodeTrait.php b/tests/src/Traits/NodeTrait.php index 65407e5da9..633e4ee92a 100644 --- a/tests/src/Traits/NodeTrait.php +++ b/tests/src/Traits/NodeTrait.php @@ -1,7 +1,11 @@ condition('title', $title) ->range(0, 1); @@ -47,4 +51,37 @@ public static function getNodeByTitle($title, $bundle = NULL) { return \Drupal::entityTypeManager()->getStorage('node')->loadUnchanged($id); } + /** + * Returns a list of revision IDs. + * + * @param string $title + * The title of the node. + * @param string $bundle + * The type of the node. + * @param bool $published + * Whether to request the last published or last unpublished verion. + * + * @return array + * A list of revision IDs. + */ + public function getNodeRevisionIdsList(string $title, string $bundle, bool $published = NULL): array { + $current_revision = $this->getNodeByTitle($title, $bundle); + // We gather all revisions and then filter out the one we want as filtering + // by vid will lead in false results. + // @see: https://www.drupal.org/project/drupal/issues/2766135 + $query = \Drupal::entityQuery('node') + ->allRevisions() + ->condition('type', $bundle); + if ($published !== NULL) { + $published = (int) $published; + $query->condition('status', $published); + } + + $revisions = $query->condition('nid', $current_revision->id()) + ->sort('vid', 'DESC') + ->execute(); + + return empty($revisions) ? [] : array_keys($revisions); + } + } diff --git a/web/modules/custom/joinup_community_content/joinup_community_content.behat.inc b/web/modules/custom/joinup_community_content/joinup_community_content.behat.inc index bce5c04f9b..383b3842d2 100644 --- a/web/modules/custom/joinup_community_content/joinup_community_content.behat.inc +++ b/web/modules/custom/joinup_community_content/joinup_community_content.behat.inc @@ -2,12 +2,16 @@ /** * @file - * Contains \SolutionSubContext. + * Contains \JoinupCommunityContentSubContext. */ +declare(strict_types = 1); + use Drupal\DrupalExtension\Context\DrupalSubContextBase; use Drupal\DrupalExtension\Context\DrupalSubContextInterface; +use Drupal\joinup\Traits\NodeTrait; use Drupal\joinup\Traits\TraversingTrait; +use PHPUnit\Framework\Assert; /** * Behat step definitions to test common community content functionality. @@ -15,6 +19,7 @@ use Drupal\joinup\Traits\TraversingTrait; class JoinupCommunityContentSubContext extends DrupalSubContextBase implements DrupalSubContextInterface { use TraversingTrait; + use NodeTrait; /** * Asserts that a tile is not marked as shared from another collection. @@ -27,7 +32,7 @@ class JoinupCommunityContentSubContext extends DrupalSubContextBase implements D * * @Then the :heading tile should not be marked as shared */ - public function assertTileNotMarkedAsShared($heading) { + public function assertTileNotMarkedAsShared(string $heading): void { $element = $this->getTileByHeading($heading); if ($element->find('css', '.icon--shared')) { @@ -49,7 +54,7 @@ class JoinupCommunityContentSubContext extends DrupalSubContextBase implements D * * @Then the :heading tile should be marked as shared from :collection */ - public function assertTileMarkedAsShared($heading, $collection) { + public function assertTileMarkedAsShared(string $heading, string $collection): void { $element = $this->getTileByHeading($heading); $share = $element->find('css', '.icon--shared'); @@ -69,4 +74,84 @@ class JoinupCommunityContentSubContext extends DrupalSubContextBase implements D } } + /** + * Asserts that the created time differs between published and unpublished. + * + * @param string $title + * The title of the content. + * @param string $type + * The type of the content. + * @param string $published + * Whether to request the published or unpublished version. Allowed values + * are 'published' and 'unpublished'. + * + * @throws \InvalidArgumentException + * Thrown if $published has an invalid value. + * @throws \Exception + * Thrown if the expected and the actual values match. + * + * @Given the latest version of the :title :type should have a different created date than the last :published version + */ + public function assertDifferentCreatedTimeWithUnpublishedVersion(string $title, string $type, string $published): void { + if (!in_array($published, ['published', 'unpublished'])) { + throw new Exception('Only "published" and "unpublished" values are allowed for variable $published.'); + } + $published = $published === 'published'; + $revision_list = $this->getNodeRevisionIdsList($title, $type); + $revision_id = reset($revision_list); + $latest_revision = \Drupal::entityTypeManager()->getStorage('node')->loadRevision($revision_id); + + $previous_revision_list = $this->getNodeRevisionIdsList($title, $type, $published); + // If both requested versions are of the same status, the pre-last revision + // is the correct one. + $index = array_search($revision_id, $previous_revision_list); + if ($index !== FALSE) { + unset($previous_revision_list[$index]); + } + $previous_id = reset($previous_revision_list); + $previous_revision = \Drupal::entityTypeManager()->getStorage('node')->loadRevision($previous_id); + + Assert::assertNotEquals($latest_revision->getCreatedTime(), $previous_revision->getCreatedTime()); + } + + /** + * Asserts that the created time is the same with the last published version. + * + * @param string $title + * The title of the content. + * @param string $type + * The type of the content. + * @param string $published + * Whether to request the published or unpublished version. Allowed values + * are 'published' and 'unpublished'. + * + * @throws \InvalidArgumentException + * Thrown if $published has an invalid value. + * @throws \Exception + * Thrown if the expected and the actual values do not match. + * + * @Given the latest version of the :title :type should have the same created date as the last :published version + */ + public function assertSameCreatedTimeWithLastVersion(string $title, string $type, string $published): void { + if (!in_array($published, ['published', 'unpublished'])) { + throw new InvalidArgumentException('Only "published" and "unpublished" values are allowed for variable $published.'); + } + $published = $published === 'published'; + $revision_list = $this->getNodeRevisionIdsList($title, $type); + $revision_id = reset($revision_list); + $latest_revision = \Drupal::entityTypeManager()->getStorage('node')->loadRevision($revision_id); + + $previous_revision_list = $this->getNodeRevisionIdsList($title, $type, $published); + // If both requested versions are of the same status, the pre-last revision + // is the correct one. + $index = array_search($revision_id, $previous_revision_list); + if ($index !== FALSE) { + unset($previous_revision_list[$index]); + } + $previous_id = reset($previous_revision_list); + $previous_revision = \Drupal::entityTypeManager()->getStorage('node')->loadRevision($previous_id); + + Assert::assertEquals($latest_revision->getCreatedTime(), $previous_revision->getCreatedTime()); + } + } diff --git a/web/modules/custom/joinup_core/joinup_core.behat.inc b/web/modules/custom/joinup_core/joinup_core.behat.inc index ac4486cde8..c87174fc35 100644 --- a/web/modules/custom/joinup_core/joinup_core.behat.inc +++ b/web/modules/custom/joinup_core/joinup_core.behat.inc @@ -2,9 +2,11 @@ /** * @file - * Contains \JoinupSubContext. + * Contains \JoinupCoreSubContext. */ +declare(strict_types = 1); + use Behat\Gherkin\Node\TableNode; use Drupal\DrupalExtension\Context\DrupalSubContextBase; use Drupal\joinup\Traits\TraversingTrait; @@ -37,7 +39,7 @@ class JoinupCoreSubContext extends DrupalSubContextBase { * * @Then the page should show( only) the chips: */ - public function assertChipElements(TableNode $table) { + public function assertChipElements(TableNode $table): void { $chips = $this->getSession()->getPage()->findAll('css', '.mdl-chip__text'); $found = array_map(function ($element) { /** @var \Behat\Mink\Element\NodeElement $element */ @@ -59,7 +61,7 @@ class JoinupCoreSubContext extends DrupalSubContextBase { * * @Then I press the remove button on the chip :text */ - public function clickRemoveChipButton($text) { + public function clickRemoveChipButton(string $text): void { // Find the element that contains the user name. $xpath = "//span[contains(concat(' ', normalize-space(@class), ' '), ' mdl-chip__text ')][contains(text(), '$text')]"; $chip = $this->getSession()->getPage()->find('xpath', $xpath); @@ -85,7 +87,7 @@ class JoinupCoreSubContext extends DrupalSubContextBase { * * @Given I go to the :name (taxonomy )term( page) */ - public function visitTaxonomyTermPage(string $name) { + public function visitTaxonomyTermPage(string $name): void { $term = static::getEntityByLabel('taxonomy_term', $name); $this->visitPath($term->toUrl()->toString()); } @@ -150,7 +152,7 @@ class JoinupCoreSubContext extends DrupalSubContextBase { * * @Then the content type of the response should be :content_type */ - public function assertResponseContentType($content_type) { + public function assertResponseContentType(string $content_type): void { $this->assertSession()->responseHeaderEquals('Content-Type', $content_type); } @@ -173,7 +175,7 @@ class JoinupCoreSubContext extends DrupalSubContextBase { * @Then the :group_label :group_type should have a custom page titled :group_title * @Then the :group_label :group_type should have a community content titled :group_title */ - public function assertNodeOgMembership($group_label, $group_type, $content_title) { + public function assertNodeOgMembership(string $group_label, string $group_type, string $content_title): void { $group = $this->getRdfEntityByLabel($group_label, $group_type); $node = $this->getNodeByTitle($content_title); if ($node->get(OgGroupAudienceHelperInterface::DEFAULT_FIELD)->target_id !== $group->id()) { @@ -186,7 +188,7 @@ class JoinupCoreSubContext extends DrupalSubContextBase { * * @Then I should see the following group menu items in the specified order: */ - public function assertRepeatedElementContainsText(TableNode $table) { + public function assertRepeatedElementContainsText(TableNode $table): void { $parent = $this->getSession()->getPage()->findAll('css', '.block-group-menu-blocknavigation li.sidebar-menu__item'); $i = 0; foreach ($table->getHash() as $repeatedElement) { @@ -197,4 +199,22 @@ class JoinupCoreSubContext extends DrupalSubContextBase { } } + /** + * Searches for links matching the criteria and clicks on the last of them. + * + * Since the results are sequential, the last link in the results is also the + * last instance of the link in the page matching the given criteria. + * + * @param string $link + * The link locator. + * + * @Given I click the last :link link + */ + public function assertClickLastLink(string $link): void { + $locator = ['link', $link]; + $links = $this->getSession()->getPage()->findAll('named', $locator); + $link = end($links); + $link->click(); + } + } diff --git a/web/modules/custom/joinup_core/joinup_core.module b/web/modules/custom/joinup_core/joinup_core.module index 6f2e2aab9a..fedae007f7 100644 --- a/web/modules/custom/joinup_core/joinup_core.module +++ b/web/modules/custom/joinup_core/joinup_core.module @@ -845,16 +845,30 @@ function joinup_core_node_presave(EntityInterface $entity) { // Set the update time as the created time only if this is the first time this // entity is published. $published_revisions = \Drupal::entityQuery('node') + ->allRevisions() ->condition('type', $entity->bundle()) ->condition('status', 1) ->condition('nid', $entity->id()) - ->count() + ->sort('vid', 'ASC') + ->range(0, 1) ->execute(); - if ($published_revisions != 0) { - return; + + if (count($published_revisions) === 0) { + $entity->set('created', \Drupal::time()->getRequestTime()); + } + else { + // If the node is reverted into a previously created draft version, the + // created time is a date different than the first publication date. For + // that case, always set the created time from the first published revision + // every time a community content is saved. + $revision_ids = array_keys($published_revisions); + $revision_id = reset($revision_ids); + $revision = \Drupal::entityTypeManager()->getStorage('node')->loadRevision($revision_id); + if (!empty($revision)) { + $entity->set('created', $revision->getCreatedTime()); + } } - $entity->set('created', \Drupal::time()->getRequestTime()); } /**