Developing a plugin using DI and TDD 12

Hiding the comments.

More feedback, less feedback

The last version of the plugin is able to send and receive clicks on the “I’d like this” button but, beside the comments table, there is no quick and easy way for a post author to look up the feedback at a glance.
I will add two pieces of code to the plugin to:

  • hide the plugin generated comments from the comments list
  • show the post author a meta box to see which idea liked among the ones proposed on the post

Hiding the comments

Thanks to the WordPress filter mechanism filtering the comments table displayed on the back-end admin screen is easy.
I’ve set up a service provider to take care of it and registered it in the bootstrap.php file of the plugin

class idlikethis_ServiceProviders_CommentsTable extends tad_DI52_ServiceProvider
{

    /**
     * Binds and sets up implementations.
     */
    public function register()
    {
        $this->container->bind('idlikethis_Comments_TableContextInterface', 'idlikethis_Comments_TableContext');
        $this->container->bind('idlikethis_Comments_TableFilterInterface', 'idlikethis_Comments_TableFilter');

        add_filter('pre_get_comments', array($this->container->resolve('idlikethis_Comments_TableFilterInterface'), 'on_pre_get_comments'), 10, 1);

    }

    /**
     * Binds and sets up implementations at boot time.
     */
    public function boot()
    {
        // TODO: Implement boot() method.
    }
}

To filter the view the idlikethis_Comments_TableFilter class will modify the type__not_in query variable using the pre_get_comments filter.
To make sure such an intervention will be made on the comments edit screen only I will provide the class a context: a concrete implementation of the idlikethis_Comments_TableContextInterface interface.
This class is a mere adapter that I will not cover, as such, with tests; the function of an object modeling the context is to decouple the idlikethis_Comments_TableFilter class from its surroundings and make future iterations easier.
The adapter class code itself is hence trivial

class idlikethis_Comments_TableContext implements idlikethis_Comments_TableContextInterface
{

    /**
     * Whether the current context is an admin comments edit screen or not.
     *
     * @return bool
     */
    public function is_comments_edit_screen()
    {
        $current_screen = get_current_screen();
        return is_admin() && $current_screen && $current_screen->base == 'edit-comments';
    }
}

A quick test scaffold for the filtering class

wpcept generate:wpunit wpunit "idlikethis\Comments\TableFilter"

and the code to fill it

namespace idlikethis\Comments;

use idlikethis_Comments_TableFilter as TableFilter;

class TableFilterTest extends \Codeception\TestCase\WPTestCase
{
    /**
     * @var idlikethis_Comments_TableContextInterface
     */
    protected $context;
    protected $types;

    public function setUp()
    {
        // before
        parent::setUp();

        // your set up methods here
        $this->types = 'some-commment-type';
        $this->context = $this->prophesize('idlikethis_Comments_TableContextInterface');
    }

    public function tearDown()
    {
        // your tear down methods here

        // then
        parent::tearDown();
    }

    /**
     * @test
     * it should be instantiatable
     */
    public function it_should_be_instantiatable()
    {
        $sut = $this->make_instance();

        $this->assertInstanceOf('idlikethis_Comments_TableFilter', $sut);
    }

    /**
     * @test
     * it should not add the type__not_in query var if not the right context
     */
    public function it_should_not_add_the_types_not_in_query_var_if_not_the_right_context()
    {
        $this->context->is_comments_edit_screen()->willReturn(false);

        $sut = $this->make_instance();

        $query = $this->getMockBuilder('WP_Comment_Query')->disableOriginalConstructor()->getMock();
        $query->query_vars = [];
        $query->query_vars['type__not_in'] = [];

        $sut->on_pre_get_comments($query);

        $this->assertEmpty($query->query_vars['type__not_in']);
    }

    /**
     * @test
     * it should not add the type__not_in query var if types empty
     */
    public function it_should_not_add_the_types_not_in_query_var_if_types_empty()
    {
        $this->types = [];
        $this->context->is_comments_edit_screen()->willReturn(true);

        $sut = $this->make_instance();

        $query = $this->getMockBuilder('WP_Comment_Query')->disableOriginalConstructor()->getMock();
        $query->query_vars = [];
        $query->query_vars['type__not_in'] = [];

        $sut->on_pre_get_comments($query);

        $this->assertEmpty($query->query_vars['type__not_in']);
    }

    /**
     * @test
     * it should add the type__not_in query var
     */
    public function it_should_add_the_type_not_in_query_var()
    {
        $this->context->is_comments_edit_screen()->willReturn(true);

        $sut = $this->make_instance();

        $query = $this->getMockBuilder('WP_Comment_Query')->disableOriginalConstructor()->getMock();
        $query->query_vars = [];
        $query->query_vars['type__not_in'] = [];

        $sut->on_pre_get_comments($query);

        $this->assertEquals((array)$this->types, $query->query_vars['type__not_in']);
    }

    /**
     * @test
     * it should preserve existing type exclusions
     */
    public function it_should_preserve_existing_type_exclusions()
    {
        $this->context->is_comments_edit_screen()->willReturn(true);

        $sut = $this->make_instance();

        $query = $this->getMockBuilder('WP_Comment_Query')->disableOriginalConstructor()->getMock();
        $query->query_vars = [];
        $query->query_vars['type__not_in'] = ['some-other-type'];

        $sut->on_pre_get_comments($query);

        $this->assertEquals(array_merge(['some-other-type'], (array)$this->types), $query->query_vars['type__not_in']);
    }

    private function make_instance()
    {
        return new TableFilter($this->types, $this->context->reveal());
    }

}

The final TableFilter class code is as follows

class idlikethis_Comments_TableFilter implements idlikethis_Comments_TableFilterInterface
{
    /**
     * @var string
     */
    protected $comment_type;
    /**
     * @var idlikethis_Comments_TableContextInterface
     */
    private $context;

    /**
     * idlikethis_Comments_TableFilter constructor.
     * @param string $comment_type
     * @param idlikethis_Comments_TableContextInterface $context
     */
    public function __construct($comment_type = 'idlikethis', idlikethis_Comments_TableContextInterface $context)
    {
        $this->comment_type = $comment_type;
        $this->context = $context;
    }

    /**
     * Sets some query var parameters on the comments query before comments are fetched.
     *
     * @param WP_Comment_Query $query
     * @return void
     */
    public function on_pre_get_comments(WP_Comment_Query $query)
    {
        if (!$this->context->is_comments_edit_screen()) {
            return;
        }
        $old = isset($query->query_vars['type__not_in']) ? (array)$query->query_vars['type__not_in'] : array();
        $query->query_vars['type__not_in'] = array_merge($old, (array)$this->comment_type);
    }
}

On GitHub

The code current to this post is on GitHub.

Next

The meta box next!

I appreciate your input