ViewsModerationStateFilterTest.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  1. <?php
  2. namespace Drupal\Tests\content_moderation\Functional;
  3. use Drupal\node\Entity\NodeType;
  4. use Drupal\Tests\content_moderation\Traits\ContentModerationTestTrait;
  5. use Drupal\Tests\views\Functional\ViewTestBase;
  6. use Drupal\views\ViewExecutable;
  7. use Drupal\views\Views;
  8. use Drupal\workflows\Entity\Workflow;
  9. /**
  10. * Tests the views 'moderation_state_filter' filter plugin.
  11. *
  12. * @coversDefaultClass \Drupal\content_moderation\Plugin\views\filter\ModerationStateFilter
  13. *
  14. * @group content_moderation
  15. */
  16. class ViewsModerationStateFilterTest extends ViewTestBase {
  17. use ContentModerationTestTrait;
  18. /**
  19. * {@inheritdoc}
  20. */
  21. public static $modules = [
  22. 'content_moderation_test_views',
  23. 'node',
  24. 'content_moderation',
  25. 'workflows',
  26. 'workflow_type_test',
  27. 'entity_test',
  28. 'language',
  29. 'content_translation',
  30. 'views_ui',
  31. ];
  32. /**
  33. * {@inheritdoc}
  34. */
  35. protected function setUp($import_test_views = TRUE) {
  36. parent::setUp(FALSE);
  37. NodeType::create([
  38. 'type' => 'example_a',
  39. ])->save();
  40. NodeType::create([
  41. 'type' => 'example_b',
  42. ])->save();
  43. $this->createEditorialWorkflow();
  44. $new_workflow = Workflow::create([
  45. 'type' => 'content_moderation',
  46. 'id' => 'new_workflow',
  47. 'label' => 'New workflow',
  48. ]);
  49. $new_workflow->getTypePlugin()->addState('bar', 'Bar');
  50. $new_workflow->save();
  51. $this->drupalLogin($this->drupalCreateUser(['administer workflows', 'administer views']));
  52. }
  53. /**
  54. * Tests the dependency handling of the moderation state filter.
  55. *
  56. * @covers ::calculateDependencies
  57. * @covers ::onDependencyRemoval
  58. */
  59. public function testModerationStateFilterDependencyHandling() {
  60. // First, check that the view doesn't have any config dependency when there
  61. // are no states configured in the filter.
  62. $view_id = 'test_content_moderation_state_filter_base_table';
  63. $view = Views::getView($view_id);
  64. $this->assertWorkflowDependencies([], $view);
  65. $this->assertTrue($view->storage->status());
  66. // Configure the Editorial workflow for a node bundle, set the filter value
  67. // to use one of its states and check that the workflow is now a dependency
  68. // of the view.
  69. $this->drupalPostForm('admin/config/workflow/workflows/manage/editorial/type/node', [
  70. 'bundles[example_a]' => TRUE,
  71. ], 'Save');
  72. $edit['options[value][]'] = ['editorial-published'];
  73. $this->drupalPostForm("admin/structure/views/nojs/handler/$view_id/default/filter/moderation_state", $edit, 'Apply');
  74. $this->drupalPostForm("admin/structure/views/view/$view_id", [], 'Save');
  75. $view = Views::getView($view_id);
  76. $this->assertWorkflowDependencies(['editorial'], $view);
  77. $this->assertTrue($view->storage->status());
  78. // Create another workflow and repeat the checks above.
  79. $this->drupalPostForm('admin/config/workflow/workflows/add', [
  80. 'label' => 'Translation',
  81. 'id' => 'translation',
  82. 'workflow_type' => 'content_moderation',
  83. ], 'Save');
  84. $this->drupalPostForm('admin/config/workflow/workflows/manage/translation/add_state', [
  85. 'label' => 'Needs Review',
  86. 'id' => 'needs_review',
  87. ], 'Save');
  88. $this->drupalPostForm('admin/config/workflow/workflows/manage/translation/type/node', [
  89. 'bundles[example_b]' => TRUE,
  90. ], 'Save');
  91. $edit['options[value][]'] = ['editorial-published', 'translation-needs_review'];
  92. $this->drupalPostForm("admin/structure/views/nojs/handler/$view_id/default/filter/moderation_state", $edit, 'Apply');
  93. $this->drupalPostForm("admin/structure/views/view/$view_id", [], 'Save');
  94. $view = Views::getView($view_id);
  95. $this->assertWorkflowDependencies(['editorial', 'translation'], $view);
  96. $this->assertTrue(isset($view->storage->getDisplay('default')['display_options']['filters']['moderation_state']));
  97. $this->assertTrue($view->storage->status());
  98. // Remove the 'Translation' workflow.
  99. $this->drupalPostForm('admin/config/workflow/workflows/manage/translation/delete', [], 'Delete');
  100. // Check that the view has been disabled, the filter has been deleted, the
  101. // view can be saved and there are no more config dependencies.
  102. $view = Views::getView($view_id);
  103. $this->assertFalse($view->storage->status());
  104. $this->assertFalse(isset($view->storage->getDisplay('default')['display_options']['filters']['moderation_state']));
  105. $this->drupalPostForm("admin/structure/views/view/$view_id", [], 'Save');
  106. $this->assertWorkflowDependencies([], $view);
  107. }
  108. /**
  109. * Tests the moderation state filter when the configured workflow is changed.
  110. *
  111. * @dataProvider providerTestWorkflowChanges
  112. */
  113. public function testWorkflowChanges($view_id, $filter_name) {
  114. // Update the view and make the default filter not exposed anymore,
  115. // otherwise all results will be shown when there are no more moderated
  116. // bundles left.
  117. $this->drupalPostForm("admin/structure/views/nojs/handler/$view_id/default/filter/moderation_state", [], 'Hide filter');
  118. $this->drupalPostForm("admin/structure/views/view/$view_id", [], 'Save');
  119. // First, apply the Editorial workflow to both of our content types.
  120. $this->drupalPostForm('admin/config/workflow/workflows/manage/editorial/type/node', [
  121. 'bundles[example_a]' => TRUE,
  122. 'bundles[example_b]' => TRUE,
  123. ], 'Save');
  124. \Drupal::service('entity_type.bundle.info')->clearCachedBundles();
  125. // Add a few nodes in various moderation states.
  126. $this->createNode(['type' => 'example_a', 'moderation_state' => 'published']);
  127. $this->createNode(['type' => 'example_b', 'moderation_state' => 'published']);
  128. $archived_node_a = $this->createNode(['type' => 'example_a', 'moderation_state' => 'archived']);
  129. $archived_node_b = $this->createNode(['type' => 'example_b', 'moderation_state' => 'archived']);
  130. // Configure the view to only show nodes in the 'archived' moderation state.
  131. $edit['options[value][]'] = ['editorial-archived'];
  132. $this->drupalPostForm("admin/structure/views/nojs/handler/$view_id/default/filter/moderation_state", $edit, 'Apply');
  133. $this->drupalPostForm("admin/structure/views/view/$view_id", [], 'Save');
  134. // Check that only the archived nodes from both bundles are displayed by the
  135. // view.
  136. $view = Views::getView($view_id);
  137. $this->executeView($view);
  138. $this->assertIdenticalResultset($view, [['nid' => $archived_node_a->id()], ['nid' => $archived_node_b->id()]], ['nid' => 'nid']);
  139. // Remove the Editorial workflow from one of the bundles.
  140. $this->drupalPostForm('admin/config/workflow/workflows/manage/editorial/type/node', [
  141. 'bundles[example_a]' => TRUE,
  142. 'bundles[example_b]' => FALSE,
  143. ], 'Save');
  144. \Drupal::service('entity_type.bundle.info')->clearCachedBundles();
  145. $view = Views::getView($view_id);
  146. $this->executeView($view);
  147. $this->assertIdenticalResultset($view, [['nid' => $archived_node_a->id()]], ['nid' => 'nid']);
  148. // Check that the view can still be edited and saved without any
  149. // intervention.
  150. $this->drupalPostForm("admin/structure/views/view/$view_id", [], 'Save');
  151. // Remove the Editorial workflow from both bundles.
  152. $this->drupalPostForm('admin/config/workflow/workflows/manage/editorial/type/node', [
  153. 'bundles[example_a]' => FALSE,
  154. 'bundles[example_b]' => FALSE,
  155. ], 'Save');
  156. \Drupal::service('entity_type.bundle.info')->clearCachedBundles();
  157. $view = Views::getView($view_id);
  158. $this->executeView($view);
  159. // Check that the view doesn't return any result.
  160. $this->assertEmpty($view->result);
  161. // Check that the view can not be edited without any intervention anymore
  162. // because the user needs to fix the filter.
  163. $this->drupalPostForm("admin/structure/views/view/$view_id", [], 'Save');
  164. $this->assertSession()->pageTextContains("No valid values found on filter: $filter_name.");
  165. }
  166. /**
  167. * Data provider for testWorkflowChanges.
  168. *
  169. * @return string[]
  170. * An array of view IDs.
  171. */
  172. public function providerTestWorkflowChanges() {
  173. return [
  174. 'view on base table, filter on base table' => [
  175. 'test_content_moderation_state_filter_base_table',
  176. 'Content: Moderation state',
  177. ],
  178. 'view on base table, filter on revision table' => [
  179. 'test_content_moderation_state_filter_base_table_filter_on_revision',
  180. 'Content revision: Moderation state',
  181. ],
  182. ];
  183. }
  184. /**
  185. * Tests the content moderation state filter caching is correct.
  186. */
  187. public function testFilterRenderCache() {
  188. // Initially all states of the workflow are displayed.
  189. $this->drupalPostForm('admin/config/workflow/workflows/manage/editorial/type/node', [
  190. 'bundles[example_a]' => TRUE,
  191. ], 'Save');
  192. $this->assertFilterStates(['All', 'editorial-draft', 'editorial-published', 'editorial-archived']);
  193. // Adding a new state to the editorial workflow will display that state in
  194. // the list of filters.
  195. $this->drupalPostForm('admin/config/workflow/workflows/manage/editorial/add_state', [
  196. 'label' => 'Foo',
  197. 'id' => 'foo',
  198. ], 'Save');
  199. $this->assertFilterStates(['All', 'editorial-draft', 'editorial-published', 'editorial-archived', 'editorial-foo']);
  200. // Adding a second workflow to nodes will also show new states.
  201. $this->drupalPostForm('admin/config/workflow/workflows/manage/new_workflow/type/node', [
  202. 'bundles[example_b]' => TRUE,
  203. ], 'Save');
  204. $this->assertFilterStates(['All', 'editorial-draft', 'editorial-published', 'editorial-archived', 'editorial-foo', 'new_workflow-draft', 'new_workflow-published', 'new_workflow-bar']);
  205. // Add a few more states and change the exposed filter to allow multiple
  206. // selections so we can check that the size of the select element does not
  207. // exceed 8 options.
  208. $this->drupalPostForm('admin/config/workflow/workflows/manage/editorial/add_state', [
  209. 'label' => 'Foo 2',
  210. 'id' => 'foo2',
  211. ], 'Save');
  212. $this->drupalPostForm('admin/config/workflow/workflows/manage/editorial/add_state', [
  213. 'label' => 'Foo 3',
  214. 'id' => 'foo3',
  215. ], 'Save');
  216. $view_id = 'test_content_moderation_state_filter_base_table';
  217. $edit['options[expose][multiple]'] = TRUE;
  218. $this->drupalPostForm("admin/structure/views/nojs/handler/$view_id/default/filter/moderation_state", $edit, 'Apply');
  219. $this->drupalPostForm("admin/structure/views/view/$view_id", [], 'Save');
  220. $this->assertFilterStates(['editorial-draft', 'editorial-published', 'editorial-archived', 'editorial-foo', 'editorial-foo2', 'editorial-foo3', 'new_workflow-draft', 'new_workflow-published', 'new_workflow-bar'], TRUE);
  221. }
  222. /**
  223. * Assert the states which appear in the filter.
  224. *
  225. * @param array $states
  226. * The states which should appear in the filter.
  227. * @param bool $check_size
  228. * (optional) Whether to check that size of the select element is not
  229. * greater than 8. Defaults to FALSE.
  230. */
  231. protected function assertFilterStates($states, $check_size = FALSE) {
  232. $this->drupalGet('/filter-test-path');
  233. $assert_session = $this->assertSession();
  234. // Check that the select contains the correct number of options.
  235. $assert_session->elementsCount('css', '#edit-default-revision-state option', count($states));
  236. // Check that the size of the select element does not exceed 8 options.
  237. if ($check_size) {
  238. $this->assertGreaterThan(8, count($states));
  239. $assert_session->elementAttributeContains('css', '#edit-default-revision-state', 'size', 8);
  240. }
  241. // Check that an option exists for each of the expected states.
  242. foreach ($states as $state) {
  243. $assert_session->optionExists('Default Revision State', $state);
  244. }
  245. }
  246. /**
  247. * Asserts the views dependencies on workflow config entities.
  248. *
  249. * @param string[] $workflow_ids
  250. * An array of workflow IDs to check.
  251. * @param \Drupal\views\ViewExecutable $view
  252. * An executable View object.
  253. */
  254. protected function assertWorkflowDependencies(array $workflow_ids, ViewExecutable $view) {
  255. $dependencies = $view->getDependencies();
  256. $expected = [];
  257. foreach (Workflow::loadMultiple($workflow_ids) as $workflow) {
  258. $expected[] = $workflow->getConfigDependencyName();
  259. }
  260. if ($expected) {
  261. $this->assertSame($expected, $dependencies['config']);
  262. }
  263. else {
  264. $this->assertTrue(!isset($dependencies['config']));
  265. }
  266. }
  267. }