Unit Testing AJAX calls in WordPress
-
-
Save davilera/f2e0a9c54cc9c13e21a4599a938b4a4b to your computer and use it in GitHub Desktop.
Unit Testing AJAX
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* The plugin bootstrap file. | |
* | |
* Plugin Name: Nelio Post Searcher | |
* Description: Example. | |
* Version: 1.0.0 | |
* | |
* Author: Nelio Software | |
* Author URI: https://neliosoftware.com | |
* License: GPL-2.0+ | |
* License URI: http://www.gnu.org/licenses/gpl-2.0.txt | |
* | |
* @package Nelio_Post_Searcher | |
* @subpackage Root | |
* @author David Aguilera <david.aguilera@neliosoftware.com> | |
* @since 1.0.0 | |
*/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* Adds the search form at the end of a post. | |
* | |
* @param string $where The content of the current post. | |
* | |
* @return string the post with the new form. | |
* | |
* @since 1.0.0 | |
*/ | |
function neliops_add_form( $content ) { | |
$input = '<input id="neliops_search" type="text" placeholder="Search…" />'; | |
$results = '<div id="neliops_results"></div>'; | |
return $content . $input . $results; | |
}//end neliops_add_form | |
add_filter( 'the_content', 'neliops_add_form' ); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* Enqueues searcher scripts. | |
* | |
* @since 1.0.0 | |
*/ | |
function neliops_enqueue_scripts() { | |
$url = untrailingslashit( plugin_dir_url( __FILE__ ) ); | |
wp_enqueue_script( | |
'neliops-searcher', | |
$url . '/searcher.js', | |
array( 'jquery', 'underscore' ), | |
'1.0.0', | |
false | |
); | |
wp_enqueue_script( | |
'neliops-functions', | |
$url . '/functions.js', | |
array( 'neliops-searcher' ), | |
'1.0.0', | |
false | |
); | |
}//end neliops_enqueue_scripts(); | |
add_action( 'wp_enqueue_scripts', 'neliops_enqueue_scripts' ); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* This function returns the posts with a title that contains the query string. | |
* | |
* @param string $query the search string. | |
* | |
* @return array List of posts. Each element in the array is an associative | |
* array with the ID, title, and permalink of the matching posts. | |
* | |
* @since 1.0.0 | |
*/ | |
function neliops_search_posts( $query ) { | |
$args = array( | |
'post_title__like' => $query, | |
'paged' => 1, | |
'posts_per_page' => 10, | |
'orderby' => 'date', | |
'order' => 'desc', | |
'post_type' => array( 'post' ), | |
); | |
add_filter( 'posts_where', 'neliops_add_title_filter_to_wp_query', 10, 2 ); | |
$query = new WP_Query( $args ); | |
remove_filter( 'posts_where', 'neliops_add_title_filter_to_wp_query', 10, 2 ); | |
$result = array(); | |
while ( $query->have_posts() ) { | |
$query->the_post(); | |
array_push( $result, array( | |
'ID' => get_the_ID(), | |
'title' => get_the_title(), | |
'permalink' => get_the_permalink(), | |
) ); | |
}//end while | |
wp_reset_postdata(); | |
return $result; | |
}//end neliops_search_posts() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* A filter to search posts based on their title. | |
* | |
* This function modifies the posts query so that we can search posts based on | |
* a term that should appear in their titles. | |
* | |
* @param string $where The where clause, as it's originally defined. | |
* @param WP_Query $wp_query The $wp_query object that contains the params used | |
* to build the where clause. | |
* | |
* @return string wpdb's where statement. | |
* | |
* @since 1.0.0 | |
*/ | |
function neliops_add_title_filter_to_wp_query( $where, &$wp_query ) { | |
global $wpdb; | |
if ( $search_term = $wp_query->get( 'post_title__like' ) ) { | |
$search_term = $wpdb->esc_like( $search_term ); | |
$search_term = ' \'%' . $search_term . '%\''; | |
$where .= ' AND ' . $wpdb->posts . '.post_title LIKE ' . $search_term; | |
}//end if | |
return $where; | |
}//end neliops_add_title_filter_to_wp_query() | |
add_filter( 'posts_where', 'neliops_add_title_filter_to_wp_query', 10, 2 ); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* AJAX callback for searching posts. | |
* | |
* Expected parameters in the request: | |
* * q: the query string. | |
* | |
* @since 1.0.0 | |
*/ | |
function neliops_search_posts_ajax_callback() { | |
$query = false; | |
if ( isset( $_REQUEST['q'] ) ) { // Input var okay. | |
$query = sanitize_text_field( wp_unslash( $_REQUEST['q'] ) ); // Input var okay. | |
}//end if | |
if ( false === $query ) { | |
wp_send_json_error( 'Search string can\'t be empty.' ); | |
}//end if | |
wp_send_json_success( neliops_search_posts( $query ) ); | |
}//end neliops_search_posts_ajax_callback() | |
add_action( 'wp_ajax_neliops_search_posts', 'neliops_search_posts_ajax_callback' ); | |
add_action( 'wp_ajax_nopriv_neliops_search_posts', 'neliops_search_posts_ajax_callback' ); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Our searcher class. | |
*/ | |
function Searcher( $input, $results ) { | |
this.$input = $input; | |
this.$results = $results; | |
this.ajax = false; | |
this.query = ''; | |
this.searchPostsDebounced = _.debounce( this.searchPosts, 200 ); | |
this.$input.on( 'keyup change', _.bind( this.onQueryChanged, this ) ); | |
} | |
/** | |
* Callback on input text change. | |
*/ | |
Searcher.prototype.onQueryChanged = function() { | |
var value = this.$input.val(); | |
if ( this.query === value && this.query.length ) { | |
return; | |
}//end if | |
if ( this.ajax ) { | |
this.ajax.abort(); | |
}//end if | |
if ( "" === value ) { | |
this.clear(); | |
} else { | |
this.searching(); | |
}//end if | |
this.searchPostsDebounced( value ); | |
}; | |
/** | |
* This function searches the posts. | |
*/ | |
Searcher.prototype.searchPosts = function( query ) { | |
if ( this.ajax ) { | |
this.ajax.abort(); | |
}//end if | |
this.query = query; | |
this.ajax = jQuery.ajax({ | |
url: '/wp-admin/admin-ajax.php', | |
data: { | |
action: 'neliops_search_posts', | |
q: query | |
}, | |
success: _.bind( this.processResult, this ) | |
}); | |
}; | |
/** | |
* This function processes the AJAX result and updates the view. | |
*/ | |
Searcher.prototype.processResult = function( result ) { | |
if ( ! result.success ) { | |
this.clear(); | |
}//end if | |
this.draw( result.data ); | |
}; | |
/** | |
* This function prints the list of posts in the result area. | |
*/ | |
Searcher.prototype.draw = function( posts ) { | |
if ( 0 === posts.length ) { | |
this.$results.html( 'No posts match the search criteria.' ); | |
return; | |
}//end if | |
var list = '<ul>'; | |
for ( var i = 0; i < posts.length; ++i ) { | |
var post = posts[ i ]; | |
list += '<li><a href="' + post.permalink + '">' + post.title + '</a></li>'; | |
}//end for | |
list += '</ul>'; | |
this.$results.html( list ); | |
}; | |
/** | |
* This function modifies the results area to notify the user we're currently searching. | |
*/ | |
Searcher.prototype.searching = function() { | |
this.$results.html( 'Searching…' ); | |
}; | |
/** | |
* This function clears the result area. | |
*/ | |
Searcher.prototype.clear = function() { | |
this.$results.empty(); | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(function( $ ) { | |
var searcher = new Searcher( $( '#neliops_search' ), $( '#neliops_results' ) ); | |
})( jQuery ); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* Class Search_Form_Test | |
* | |
* @package Post_Searcher | |
*/ | |
class Search_Form_Test extends WP_UnitTestCase { | |
function test_form_should_appear_at_the_end_of_the_content() { | |
$pid = $this->factory->post->create( array( | |
'post_title' => 'A Title', | |
'post_content' => '<p>Some content.</p>' | |
) ); | |
$post = get_post( $pid ); | |
$content = apply_filters( 'the_content', $post->post_content ); | |
$this->assertContains( 'neliops_search', $content ); | |
$this->assertContains( 'neliops_results', $content ); | |
}//end test_form_should_appear_at_the_end_of_the_content() | |
}//end class |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* Class Search_Posts_Function_Test | |
* | |
* @package Post_Searcher | |
*/ | |
class Search_Posts_Function_Test extends WP_UnitTestCase { | |
function test_if_there_are_no_posts_the_function_should_always_return_an_empty_array() { | |
$result = neliops_search_posts( '' ); | |
$this->assertInternalType( 'array', $result ); | |
$this->assertEquals( 0, count( $result ) ); | |
$result = neliops_search_posts( 'some title' ); | |
$this->assertInternalType( 'array', $result ); | |
$this->assertEquals( 0, count( $result ) ); | |
}//end test_if_there_are_no_posts_the_function_should_always_return_an_empty_array() | |
function test_if_there_is_one_post_whose_title_contains_the_query_string_the_function_should_return_this_post_in_an_array() { | |
$p = $this->factory->post->create( array( | |
'post_title' => 'Some title here', | |
) ); | |
$result = neliops_search_posts( 'Some title here' ); | |
$this->assertInternalType( 'array', $result ); | |
$this->assertEquals( 1, count( $result ) ); | |
$result = neliops_search_posts( 'title' ); | |
$this->assertInternalType( 'array', $result ); | |
$this->assertEquals( 1, count( $result ) ); | |
}//end test_if_there_are_no_posts_the_function_should_always_return_an_empty_array() | |
function test_if_there_are_multiple_posts_whose_titles_match_the_search_criteria_the_function_shoould_include_them_all() { | |
$this->factory->post->create( array( | |
'post_title' => 'First title here', | |
) ); | |
$this->factory->post->create( array( | |
'post_title' => 'Second title here', | |
) ); | |
$this->factory->post->create( array( | |
'post_title' => 'Third title here', | |
) ); | |
$result = neliops_search_posts( 'title here' ); | |
$this->assertEquals( 3, count( $result ) ); | |
}//end test_if_there_are_multiple_posts_whose_titles_match_the_search_criteria_the_function_shoould_include_them_all() | |
function test_the_function_should_not_return_posts_that_dont_match_the_search_criteria() { | |
$match = $this->factory->post->create( array( | |
'post_title' => 'Valid title', | |
) ); | |
$miss = $this->factory->post->create( array( | |
'post_title' => 'Something completely different', | |
) ); | |
$result = neliops_search_posts( 'Valid title' ); | |
$this->assertEquals( 1, count( $result ) ); | |
$found_post = $result[0]; | |
$this->assertEquals( $match, $found_post['ID'] ); | |
$this->assertNotEquals( $miss, $found_post['ID'] ); | |
}//end test_the_function_should_not_return_posts_that_dont_match_the_search_criteria() | |
}//end class |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* Class Ajax_Test | |
* | |
* @package Post_Searcher | |
*/ | |
class Ajax_Test extends WP_Ajax_UnitTestCase { | |
function test_if_there_is_no_query_string_the_callback_should_return_a_non_success_json() { | |
try { | |
$this->_handleAjax( 'neliops_search_posts' ); | |
$this->fail( 'Expected exception: WPAjaxDieContinueException' ); | |
} catch ( WPAjaxDieContinueException $e ) { | |
// We expected this, do nothing. | |
}//end try | |
$response = json_decode( $this->_last_response, true ); | |
$this->assertFalse( $response['success'] ); | |
}//end test_if_there_is_no_query_string_the_callback_should_return_a_non_success_json() | |
function test_if_there_is_a_query_string_the_callback_should_return_a_success_json_with_an_array() { | |
try { | |
$_POST['q'] = 'Search string'; | |
$this->_handleAjax( 'neliops_search_posts' ); | |
$this->fail( 'Expected exception: WPAjaxDieContinueException' ); | |
} catch ( WPAjaxDieContinueException $e ) { | |
// We expected this, do nothing. | |
}//end try | |
$response = json_decode( $this->_last_response, true ); | |
$this->assertTrue( $response['success'] ); | |
$this->assertInternalType( 'array', $response['data'] ); | |
}//end test_if_there_is_a_query_string_the_callback_should_return_a_success_json_with_an_array() | |
}//end class |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
QUnit.module( 'Searcher class', {}, function() { | |
QUnit.module( 'Method "draw"', {}, function() { | |
QUnit.module( 'when the results are empty', {}, function() { | |
QUnit.test( 'should tell the user no posts match the criteria.', function( assert ) { | |
var $input = jQuery( '<input type="text" />' ); | |
var $result = jQuery( '<div></div>' ); | |
var searcher = new Searcher( $input, $result ); | |
searcher.draw( [] ); | |
assert.equal( 'No posts match the search criteria.', $result.text() ); | |
}); | |
}); | |
QUnit.module( 'when the results are not empty', {}, function() { | |
QUnit.test( 'should show all returned posts in a list.', function( assert ) { | |
var $input = jQuery( '<input type="text" />' ); | |
var $result = jQuery( '<div></div>' ); | |
var searcher = new Searcher( $input, $result ); | |
var posts = [ | |
{ ID: 1, title: 'Title 1', permalink: '#' }, | |
{ ID: 2, title: 'Title 2', permalink: '#' }, | |
{ ID: 3, title: 'Title 3', permalink: '#' } | |
]; | |
searcher.draw( posts ); | |
assert.equal( $result.find( 'ul li' ).length, posts.length ); | |
}); | |
}); | |
}); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment