includesListTable.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462
  1. <?php
  2. /**
  3. * @group admin
  4. */
  5. class Tests_Admin_includesListTable extends WP_UnitTestCase {
  6. protected static $top = array();
  7. protected static $children = array();
  8. protected static $grandchildren = array();
  9. protected static $post_ids = array();
  10. /**
  11. * @var WP_Posts_List_Table
  12. */
  13. protected $table;
  14. function setUp() {
  15. parent::setUp();
  16. $this->table = _get_list_table( 'WP_Posts_List_Table', array( 'screen' => 'edit-page' ) );
  17. }
  18. public static function wpSetUpBeforeClass( WP_UnitTest_Factory $factory ) {
  19. // Note that our top/children/grandchildren arrays are 1-indexed.
  20. // Create top-level pages.
  21. $num_posts = 5;
  22. foreach ( range( 1, $num_posts ) as $i ) {
  23. $p = $factory->post->create_and_get(
  24. array(
  25. 'post_type' => 'page',
  26. 'post_title' => sprintf( 'Top Level Page %d', $i ),
  27. )
  28. );
  29. self::$top[ $i ] = $p;
  30. self::$post_ids[] = $p->ID;
  31. }
  32. // Create child pages.
  33. $num_children = 3;
  34. foreach ( self::$top as $top => $top_page ) {
  35. foreach ( range( 1, $num_children ) as $i ) {
  36. $p = $factory->post->create_and_get(
  37. array(
  38. 'post_type' => 'page',
  39. 'post_parent' => $top_page->ID,
  40. 'post_title' => sprintf( 'Child %d', $i ),
  41. )
  42. );
  43. self::$children[ $top ][ $i ] = $p;
  44. self::$post_ids[] = $p->ID;
  45. }
  46. }
  47. // Create grand-child pages for the third and fourth top-level pages.
  48. $num_grandchildren = 3;
  49. foreach ( range( 3, 4 ) as $top ) {
  50. foreach ( self::$children[ $top ] as $child => $child_page ) {
  51. foreach ( range( 1, $num_grandchildren ) as $i ) {
  52. $p = $factory->post->create_and_get(
  53. array(
  54. 'post_type' => 'page',
  55. 'post_parent' => $child_page->ID,
  56. 'post_title' => sprintf( 'Grandchild %d', $i ),
  57. )
  58. );
  59. self::$grandchildren[ $top ][ $child ][ $i ] = $p;
  60. self::$post_ids[] = $p->ID;
  61. }
  62. }
  63. }
  64. }
  65. /**
  66. * @ticket 15459
  67. */
  68. function test_list_hierarchical_pages_first_page() {
  69. $this->_test_list_hierarchical_page(
  70. array(
  71. 'paged' => 1,
  72. 'posts_per_page' => 2,
  73. ),
  74. array(
  75. self::$top[1]->ID,
  76. self::$children[1][1]->ID,
  77. )
  78. );
  79. }
  80. /**
  81. * @ticket 15459
  82. */
  83. function test_list_hierarchical_pages_second_page() {
  84. $this->_test_list_hierarchical_page(
  85. array(
  86. 'paged' => 2,
  87. 'posts_per_page' => 2,
  88. ),
  89. array(
  90. self::$top[1]->ID,
  91. self::$children[1][2]->ID,
  92. self::$children[1][3]->ID,
  93. )
  94. );
  95. }
  96. /**
  97. * @ticket 15459
  98. */
  99. function test_search_hierarchical_pages_first_page() {
  100. $this->_test_list_hierarchical_page(
  101. array(
  102. 'paged' => 1,
  103. 'posts_per_page' => 2,
  104. 's' => 'Child',
  105. ),
  106. array(
  107. self::$children[1][1]->ID,
  108. self::$children[1][2]->ID,
  109. )
  110. );
  111. }
  112. /**
  113. * @ticket 15459
  114. */
  115. function test_search_hierarchical_pages_second_page() {
  116. $this->_test_list_hierarchical_page(
  117. array(
  118. 'paged' => 2,
  119. 'posts_per_page' => 2,
  120. 's' => 'Top',
  121. ),
  122. array(
  123. self::$top[3]->ID,
  124. self::$top[4]->ID,
  125. )
  126. );
  127. }
  128. /**
  129. * @ticket 15459
  130. */
  131. function test_grandchildren_hierarchical_pages_first_page() {
  132. // Page 6 is the first page with grandchildren.
  133. $this->_test_list_hierarchical_page(
  134. array(
  135. 'paged' => 6,
  136. 'posts_per_page' => 2,
  137. ),
  138. array(
  139. self::$top[3]->ID,
  140. self::$children[3][1]->ID,
  141. self::$grandchildren[3][1][1]->ID,
  142. self::$grandchildren[3][1][2]->ID,
  143. )
  144. );
  145. }
  146. /**
  147. * @ticket 15459
  148. */
  149. function test_grandchildren_hierarchical_pages_second_page() {
  150. // Page 7 is the second page with grandchildren.
  151. $this->_test_list_hierarchical_page(
  152. array(
  153. 'paged' => 7,
  154. 'posts_per_page' => 2,
  155. ),
  156. array(
  157. self::$top[3]->ID,
  158. self::$children[3][1]->ID,
  159. self::$grandchildren[3][1][3]->ID,
  160. self::$children[3][2]->ID,
  161. )
  162. );
  163. }
  164. /**
  165. * Helper function to test the output of a page which uses `WP_Posts_List_Table`.
  166. *
  167. * @param array $args Query args for the list of pages.
  168. * @param array $expected_ids Expected IDs of pages returned.
  169. */
  170. protected function _test_list_hierarchical_page( array $args, array $expected_ids ) {
  171. $matches = array();
  172. $_REQUEST['paged'] = $args['paged'];
  173. $GLOBALS['per_page'] = $args['posts_per_page'];
  174. $args = array_merge(
  175. array(
  176. 'post_type' => 'page',
  177. ),
  178. $args
  179. );
  180. // Mimic the behaviour of `wp_edit_posts_query()`:
  181. if ( ! isset( $args['orderby'] ) ) {
  182. $args['orderby'] = 'menu_order title';
  183. $args['order'] = 'asc';
  184. $args['posts_per_page'] = -1;
  185. $args['posts_per_archive_page'] = -1;
  186. }
  187. $pages = new WP_Query( $args );
  188. ob_start();
  189. $this->table->set_hierarchical_display( true );
  190. $this->table->display_rows( $pages->posts );
  191. $output = ob_get_clean();
  192. // Clean up.
  193. unset( $_REQUEST['paged'] );
  194. unset( $GLOBALS['per_page'] );
  195. preg_match_all( '|<tr[^>]*>|', $output, $matches );
  196. $this->assertCount( count( $expected_ids ), array_keys( $matches[0] ) );
  197. foreach ( $expected_ids as $id ) {
  198. $this->assertContains( sprintf( 'id="post-%d"', $id ), $output );
  199. }
  200. }
  201. /**
  202. * @ticket 37407
  203. */
  204. function test_filter_button_should_not_be_shown_if_there_are_no_posts() {
  205. // Set post type to a non-existent one.
  206. $this->table->screen->post_type = 'foo';
  207. ob_start();
  208. $this->table->extra_tablenav( 'top' );
  209. $output = ob_get_clean();
  210. $this->assertNotContains( 'id="post-query-submit"', $output );
  211. }
  212. /**
  213. * @ticket 37407
  214. */
  215. function test_months_dropdown_should_not_be_shown_if_there_are_no_posts() {
  216. // Set post type to a non-existent one.
  217. $this->table->screen->post_type = 'foo';
  218. ob_start();
  219. $this->table->extra_tablenav( 'top' );
  220. $output = ob_get_clean();
  221. $this->assertNotContains( 'id="filter-by-date"', $output );
  222. }
  223. /**
  224. * @ticket 37407
  225. */
  226. function test_category_dropdown_should_not_be_shown_if_there_are_no_posts() {
  227. // Set post type to a non-existent one.
  228. $this->table->screen->post_type = 'foo';
  229. ob_start();
  230. $this->table->extra_tablenav( 'top' );
  231. $output = ob_get_clean();
  232. $this->assertNotContains( 'id="cat"', $output );
  233. }
  234. /**
  235. * @ticket 38341
  236. */
  237. public function test_empty_trash_button_should_not_be_shown_if_there_are_no_posts() {
  238. // Set post type to a non-existent one.
  239. $this->table->screen->post_type = 'foo';
  240. ob_start();
  241. $this->table->extra_tablenav( 'top' );
  242. $output = ob_get_clean();
  243. $this->assertNotContains( 'id="delete_all"', $output );
  244. }
  245. /**
  246. * @ticket 40188
  247. */
  248. public function test_filter_button_should_not_be_shown_if_there_are_no_comments() {
  249. $table = _get_list_table( 'WP_Comments_List_Table', array( 'screen' => 'edit-comments' ) );
  250. ob_start();
  251. $table->extra_tablenav( 'top' );
  252. $output = ob_get_clean();
  253. $this->assertNotContains( 'id="post-query-submit"', $output );
  254. }
  255. /**
  256. * @ticket 40188
  257. */
  258. public function test_filter_button_should_be_shown_if_there_are_comments() {
  259. $post_id = self::factory()->post->create();
  260. $comment_id = self::factory()->comment->create(
  261. array(
  262. 'comment_post_ID' => $post_id,
  263. 'comment_approved' => '1',
  264. )
  265. );
  266. $table = _get_list_table( 'WP_Comments_List_Table', array( 'screen' => 'edit-comments' ) );
  267. $table->prepare_items();
  268. ob_start();
  269. $table->extra_tablenav( 'top' );
  270. $output = ob_get_clean();
  271. $this->assertContains( 'id="post-query-submit"', $output );
  272. }
  273. /**
  274. * @ticket 40188
  275. */
  276. public function test_filter_comment_type_dropdown_should_be_shown_if_there_are_comments() {
  277. $post_id = self::factory()->post->create();
  278. $comment_id = self::factory()->comment->create(
  279. array(
  280. 'comment_post_ID' => $post_id,
  281. 'comment_approved' => '1',
  282. )
  283. );
  284. $table = _get_list_table( 'WP_Comments_List_Table', array( 'screen' => 'edit-comments' ) );
  285. $table->prepare_items();
  286. ob_start();
  287. $table->extra_tablenav( 'top' );
  288. $output = ob_get_clean();
  289. $this->assertContains( 'id="filter-by-comment-type"', $output );
  290. $this->assertContains( "<option value='comment'>", $output );
  291. }
  292. /**
  293. * @ticket 38341
  294. */
  295. public function test_empty_trash_button_should_not_be_shown_if_there_are_no_comments() {
  296. $table = _get_list_table( 'WP_Comments_List_Table', array( 'screen' => 'edit-comments' ) );
  297. ob_start();
  298. $table->extra_tablenav( 'top' );
  299. $output = ob_get_clean();
  300. $this->assertNotContains( 'id="delete_all"', $output );
  301. }
  302. /**
  303. * @ticket 19278
  304. */
  305. public function test_bulk_action_menu_supports_options_and_optgroups() {
  306. $table = _get_list_table( 'WP_Comments_List_Table', array( 'screen' => 'edit-comments' ) );
  307. add_filter(
  308. 'bulk_actions-edit-comments',
  309. function() {
  310. return array(
  311. 'delete' => 'Delete',
  312. 'Change State' => array(
  313. 'feature' => 'Featured',
  314. 'sale' => 'On Sale',
  315. ),
  316. );
  317. }
  318. );
  319. ob_start();
  320. $table->bulk_actions();
  321. $output = ob_get_clean();
  322. $expected = <<<'OPTIONS'
  323. <option value="delete">Delete</option>
  324. <optgroup label="Change State">
  325. <option value="feature">Featured</option>
  326. <option value="sale">On Sale</option>
  327. </optgroup>
  328. OPTIONS;
  329. $expected = str_replace( "\r\n", "\n", $expected );
  330. $this->assertContains( $expected, $output );
  331. }
  332. /**
  333. * @ticket 45089
  334. */
  335. public function test_sortable_columns() {
  336. require_once ABSPATH . 'wp-admin/includes/class-wp-comments-list-table.php';
  337. $override_sortable_columns = array(
  338. 'author' => array( 'comment_author', true ),
  339. 'response' => 'comment_post_ID',
  340. 'date' => array( 'comment_date', 'dEsC' ), // The ordering support should be case-insensitive.
  341. );
  342. // Stub the get_sortable_columns() method.
  343. $object = $this->getMockBuilder( 'WP_Comments_List_Table' )
  344. ->setConstructorArgs( array( array( 'screen' => 'edit-comments' ) ) )
  345. ->setMethods( array( 'get_sortable_columns' ) )
  346. ->getMock();
  347. // Change the null return value of the stubbed get_sortable_columns() method.
  348. $object->method( 'get_sortable_columns' )
  349. ->willReturn( $override_sortable_columns );
  350. $output = get_echo( array( $object, 'print_column_headers' ) );
  351. $this->assertContains( '?orderby=comment_author&#038;order=desc', $output, 'Mismatch of the default link ordering for comment author column. Should be desc.' );
  352. $this->assertContains( 'column-author sortable asc', $output, 'Mismatch of CSS classes for the comment author column.' );
  353. $this->assertContains( '?orderby=comment_post_ID&#038;order=asc', $output, 'Mismatch of the default link ordering for comment response column. Should be asc.' );
  354. $this->assertContains( 'column-response sortable desc', $output, 'Mismatch of CSS classes for the comment post ID column.' );
  355. $this->assertContains( '?orderby=comment_date&#038;order=desc', $output, 'Mismatch of the default link ordering for comment date column. Should be asc.' );
  356. $this->assertContains( 'column-date sortable asc', $output, 'Mismatch of CSS classes for the comment date column.' );
  357. }
  358. /**
  359. * @ticket 45089
  360. */
  361. public function test_sortable_columns_with_current_ordering() {
  362. require_once ABSPATH . 'wp-admin/includes/class-wp-comments-list-table.php';
  363. $override_sortable_columns = array(
  364. 'author' => array( 'comment_author', false ),
  365. 'response' => 'comment_post_ID',
  366. 'date' => array( 'comment_date', 'asc' ), // We will override this with current ordering.
  367. );
  368. // Current ordering.
  369. $_GET['orderby'] = 'comment_date';
  370. $_GET['order'] = 'desc';
  371. // Stub the get_sortable_columns() method.
  372. $object = $this->getMockBuilder( 'WP_Comments_List_Table' )
  373. ->setConstructorArgs( array( array( 'screen' => 'edit-comments' ) ) )
  374. ->setMethods( array( 'get_sortable_columns' ) )
  375. ->getMock();
  376. // Change the null return value of the stubbed get_sortable_columns() method.
  377. $object->method( 'get_sortable_columns' )
  378. ->willReturn( $override_sortable_columns );
  379. $output = get_echo( array( $object, 'print_column_headers' ) );
  380. $this->assertContains( '?orderby=comment_author&#038;order=asc', $output, 'Mismatch of the default link ordering for comment author column. Should be asc.' );
  381. $this->assertContains( 'column-author sortable desc', $output, 'Mismatch of CSS classes for the comment author column.' );
  382. $this->assertContains( '?orderby=comment_post_ID&#038;order=asc', $output, 'Mismatch of the default link ordering for comment response column. Should be asc.' );
  383. $this->assertContains( 'column-response sortable desc', $output, 'Mismatch of CSS classes for the comment post ID column.' );
  384. $this->assertContains( '?orderby=comment_date&#038;order=asc', $output, 'Mismatch of the current link ordering for comment date column. Should be asc.' );
  385. $this->assertContains( 'column-date sorted desc', $output, 'Mismatch of CSS classes for the comment date column.' );
  386. }
  387. }