diff --git a/.travis.yml b/.travis.yml index 4e86352..a1b9372 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,7 +22,6 @@ matrix: env: WP_VERSION=latest WP_MULTISITE=1 before_script: - - bash bin/install-wp-tests.sh wordpress_test root '' localhost $WP_VERSION - composer install script: diff --git a/README.md b/README.md index de9a808..cd14cf1 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,73 @@ -# search -Basic extensions for searching in WordPress. +# Search +[![Build Status](https://travis-ci.org/trendwerk/search.svg?branch=master)](https://travis-ci.org/trendwerk/search) [![codecov](https://codecov.io/gh/trendwerk/search/branch/master/graph/badge.svg)](https://codecov.io/gh/trendwerk/search) + +Basic extensions for searching in WordPress. Currently only supports searching in `postmeta`. + +Quick links: [Install](#install) | [Usage](#usage) | [Dimensions](#dimensions) | [Example](#example) + +_Note: This basic extension is not very scalable and meant for smaller databases. This package could get slow for complex meta searches. In that case, [Elasticsearch](https://www.elastic.co/) would be a better solution._ + +## Install +```sh +composer require trendwerk/search +``` + +## Usage + +1. [Initialize package](#initialize) +2. [Add search dimension(s)](#dimensions) + +### Initialize + +```php +$search = new \Trendwerk\Search\Search(); +$search->init(); +``` + +This code should be run when bootstrapping your theme. + +### Dimensions +Currently this package only supports metadata as a search dimension. Dimensions can be added by using `addDimension`: + +```php +$search->addDimension($dimension); +``` + +| Parameter | Default | Required | Description | +| :--- | :--- | :--- | :--- | +| `$dimension` | `null` | Yes | Should be an instance of a class that implements [`Dimension\Dimension`](https://github.com/trendwerk/search/blob/master/src/Dimension/Dimension.php). + +### Meta +```php +$metaDimension = new \Trendwerk\Search\Dimension\Meta([ + 'key' => 'firstName', +]); + +$search->addDimension($metaDimension); +``` + +Available options for constructing an instance of `Meta`: + +| Parameter | Default | Required | Description | +| :--- | :--- | :--- | :--- | +| `key` | `null` | Yes | The `meta_key` to search for +| `compare` | `=` | No | The database comparison that should be made for the meta key. Currently supports `LIKE` and `=`. When using `LIKE`, make sure to include a percent symbol (`%`) in your `key` parameter as a wildcard. See [Example](#example) + +## Example + +```php +use Trendwerk\Search\Dimension\Meta; +use Trendwerk\Search\Search; + +$search = Search(); +$search->init(); + +$search->addDimension(new Meta($wpdb, [ + 'compare' => 'LIKE', + 'key' => 'lastNames%', +])); + +$search->addDimension(new Meta($wpdb, [ + 'key' => 'firstName', +])); +``` diff --git a/bin/install-wp-tests.sh b/bin/install-wp-tests.sh deleted file mode 100644 index 73bb4c7..0000000 --- a/bin/install-wp-tests.sh +++ /dev/null @@ -1,127 +0,0 @@ -#!/usr/bin/env bash - -if [ $# -lt 3 ]; then - echo "usage: $0 [db-host] [wp-version] [skip-database-creation]" - exit 1 -fi - -DB_NAME=$1 -DB_USER=$2 -DB_PASS=$3 -DB_HOST=${4-localhost} -WP_VERSION=${5-latest} -SKIP_DB_CREATE=${6-false} - -WP_TESTS_DIR=${WP_TESTS_DIR-/tmp/wordpress-tests-lib} -WP_CORE_DIR=${WP_CORE_DIR-/tmp/wordpress/} - -download() { - if [ `which curl` ]; then - curl -s "$1" > "$2"; - elif [ `which wget` ]; then - wget -nv -O "$2" "$1" - fi -} - -if [[ $WP_VERSION =~ [0-9]+\.[0-9]+(\.[0-9]+)? ]]; then - WP_TESTS_TAG="tags/$WP_VERSION" -elif [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then - WP_TESTS_TAG="trunk" -else - # http serves a single offer, whereas https serves multiple. we only want one - download http://api.wordpress.org/core/version-check/1.7/ /tmp/wp-latest.json - grep '[0-9]+\.[0-9]+(\.[0-9]+)?' /tmp/wp-latest.json - LATEST_VERSION=$(grep -o '"version":"[^"]*' /tmp/wp-latest.json | sed 's/"version":"//') - if [[ -z "$LATEST_VERSION" ]]; then - echo "Latest WordPress version could not be found" - exit 1 - fi - WP_TESTS_TAG="tags/$LATEST_VERSION" -fi - -set -ex - -install_wp() { - - if [ -d $WP_CORE_DIR ]; then - return; - fi - - mkdir -p $WP_CORE_DIR - - if [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then - mkdir -p /tmp/wordpress-nightly - download https://wordpress.org/nightly-builds/wordpress-latest.zip /tmp/wordpress-nightly/wordpress-nightly.zip - unzip -q /tmp/wordpress-nightly/wordpress-nightly.zip -d /tmp/wordpress-nightly/ - mv /tmp/wordpress-nightly/wordpress/* $WP_CORE_DIR - else - if [ $WP_VERSION == 'latest' ]; then - local ARCHIVE_NAME='latest' - else - local ARCHIVE_NAME="wordpress-$WP_VERSION" - fi - download https://wordpress.org/${ARCHIVE_NAME}.tar.gz /tmp/wordpress.tar.gz - tar --strip-components=1 -zxmf /tmp/wordpress.tar.gz -C $WP_CORE_DIR - fi - - download https://raw.github.com/markoheijnen/wp-mysqli/master/db.php $WP_CORE_DIR/wp-content/db.php -} - -install_test_suite() { - # portable in-place argument for both GNU sed and Mac OSX sed - if [[ $(uname -s) == 'Darwin' ]]; then - local ioption='-i .bak' - else - local ioption='-i' - fi - - # set up testing suite if it doesn't yet exist - if [ ! -d $WP_TESTS_DIR ]; then - # set up testing suite - mkdir -p $WP_TESTS_DIR - svn co --quiet https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/includes/ $WP_TESTS_DIR/includes - svn co --quiet https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/data/ $WP_TESTS_DIR/data - fi - - if [ ! -f wp-tests-config.php ]; then - download https://develop.svn.wordpress.org/${WP_TESTS_TAG}/wp-tests-config-sample.php "$WP_TESTS_DIR"/wp-tests-config.php - # remove all forward slashes in the end - WP_CORE_DIR=$(echo $WP_CORE_DIR | sed "s:/\+$::") - sed $ioption "s:dirname( __FILE__ ) . '/src/':'$WP_CORE_DIR/':" "$WP_TESTS_DIR"/wp-tests-config.php - sed $ioption "s/youremptytestdbnamehere/$DB_NAME/" "$WP_TESTS_DIR"/wp-tests-config.php - sed $ioption "s/yourusernamehere/$DB_USER/" "$WP_TESTS_DIR"/wp-tests-config.php - sed $ioption "s/yourpasswordhere/$DB_PASS/" "$WP_TESTS_DIR"/wp-tests-config.php - sed $ioption "s|localhost|${DB_HOST}|" "$WP_TESTS_DIR"/wp-tests-config.php - fi - -} - -install_db() { - - if [ ${SKIP_DB_CREATE} = "true" ]; then - return 0 - fi - - # parse DB_HOST for port or socket references - local PARTS=(${DB_HOST//\:/ }) - local DB_HOSTNAME=${PARTS[0]}; - local DB_SOCK_OR_PORT=${PARTS[1]}; - local EXTRA="" - - if ! [ -z $DB_HOSTNAME ] ; then - if [ $(echo $DB_SOCK_OR_PORT | grep -e '^[0-9]\{1,\}$') ]; then - EXTRA=" --host=$DB_HOSTNAME --port=$DB_SOCK_OR_PORT --protocol=tcp" - elif ! [ -z $DB_SOCK_OR_PORT ] ; then - EXTRA=" --socket=$DB_SOCK_OR_PORT" - elif ! [ -z $DB_HOSTNAME ] ; then - EXTRA=" --host=$DB_HOSTNAME --protocol=tcp" - fi - fi - - # create database - mysqladmin create $DB_NAME --user="$DB_USER" --password="$DB_PASS"$EXTRA -} - -install_wp -install_test_suite -install_db diff --git a/composer.json b/composer.json index a558707..1fe75d6 100644 --- a/composer.json +++ b/composer.json @@ -24,6 +24,7 @@ }, "require-dev": { "phpunit/phpunit": "5.7.*", - "squizlabs/php_codesniffer": "2.*" + "squizlabs/php_codesniffer": "2.*", + "10up/wp_mock": "dev-master" } } diff --git a/composer.lock b/composer.lock index e913f7a..789be3e 100644 --- a/composer.lock +++ b/composer.lock @@ -4,10 +4,93 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "2acfe3d253f465fa7a2431863b8343fc", - "content-hash": "ba217e22e6436ef5179464b66a86aa41", + "hash": "2f3e41d7dba81aff7c7608ed15c53137", + "content-hash": "dd261d147fe5fa4a34597e7617e29cf0", "packages": [], "packages-dev": [ + { + "name": "10up/wp_mock", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/10up/wp_mock.git", + "reference": "994e0ad5b15a4b539ca77d377c198b1c43b1b7f5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/10up/wp_mock/zipball/994e0ad5b15a4b539ca77d377c198b1c43b1b7f5", + "reference": "994e0ad5b15a4b539ca77d377c198b1c43b1b7f5", + "shasum": "" + }, + "require": { + "antecedent/patchwork": "~1.2", + "mockery/mockery": "~0.8", + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "~3.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-dev": "1.0.x-dev" + } + }, + "autoload": { + "psr-0": { + "WP_Mock\\": "./" + }, + "classmap": [ + "WP_Mock.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0+" + ], + "description": "A mocking library to take the pain out of unit testing for WordPress", + "time": "2016-09-20 15:24:58" + }, + { + "name": "antecedent/patchwork", + "version": "1.4.3", + "source": { + "type": "git", + "url": "https://github.com/antecedent/patchwork.git", + "reference": "6e1a0a0c1282c9690d38fb4831cbdfcd04d02171" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/antecedent/patchwork/zipball/6e1a0a0c1282c9690d38fb4831cbdfcd04d02171", + "reference": "6e1a0a0c1282c9690d38fb4831cbdfcd04d02171", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ignas Rudaitis", + "email": "ignas.rudaitis@gmail.com" + } + ], + "description": "Method redefinition (monkey-patching) functionality for PHP.", + "keywords": [ + "aop", + "aspect", + "interception", + "monkeypatching", + "redefinition", + "runkit", + "testing" + ], + "time": "2016-07-02 04:25:33" + }, { "name": "doctrine/instantiator", "version": "1.0.5", @@ -62,6 +145,116 @@ ], "time": "2015-06-14 21:17:01" }, + { + "name": "hamcrest/hamcrest-php", + "version": "v1.2.2", + "source": { + "type": "git", + "url": "https://github.com/hamcrest/hamcrest-php.git", + "reference": "b37020aa976fa52d3de9aa904aa2522dc518f79c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/b37020aa976fa52d3de9aa904aa2522dc518f79c", + "reference": "b37020aa976fa52d3de9aa904aa2522dc518f79c", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "replace": { + "cordoval/hamcrest-php": "*", + "davedevelopment/hamcrest-php": "*", + "kodova/hamcrest-php": "*" + }, + "require-dev": { + "phpunit/php-file-iterator": "1.3.3", + "satooshi/php-coveralls": "dev-master" + }, + "type": "library", + "autoload": { + "classmap": [ + "hamcrest" + ], + "files": [ + "hamcrest/Hamcrest.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD" + ], + "description": "This is the PHP port of Hamcrest Matchers", + "keywords": [ + "test" + ], + "time": "2015-05-11 14:41:42" + }, + { + "name": "mockery/mockery", + "version": "0.9.8", + "source": { + "type": "git", + "url": "https://github.com/padraic/mockery.git", + "reference": "1e5e2ffdc4d71d7358ed58a6fdd30a4a0c506855" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/padraic/mockery/zipball/1e5e2ffdc4d71d7358ed58a6fdd30a4a0c506855", + "reference": "1e5e2ffdc4d71d7358ed58a6fdd30a4a0c506855", + "shasum": "" + }, + "require": { + "hamcrest/hamcrest-php": "~1.1", + "lib-pcre": ">=7.0", + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.9.x-dev" + } + }, + "autoload": { + "psr-0": { + "Mockery": "library/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Pádraic Brady", + "email": "padraic.brady@gmail.com", + "homepage": "http://blog.astrumfutura.com" + }, + { + "name": "Dave Marshall", + "email": "dave.marshall@atstsolutions.co.uk", + "homepage": "http://davedevelopment.co.uk" + } + ], + "description": "Mockery is a simple yet flexible PHP mock object framework for use in unit testing with PHPUnit, PHPSpec or any other testing framework. Its core goal is to offer a test double framework with a succinct API capable of clearly defining all possible object operations and interactions using a human readable Domain Specific Language (DSL). Designed as a drop in alternative to PHPUnit's phpunit-mock-objects library, Mockery is easy to integrate with PHPUnit and can operate alongside phpunit-mock-objects without the World ending.", + "homepage": "http://github.com/padraic/mockery", + "keywords": [ + "BDD", + "TDD", + "library", + "mock", + "mock objects", + "mockery", + "stub", + "test", + "test double", + "testing" + ], + "time": "2017-02-09 13:29:38" + }, { "name": "myclabs/deep-copy", "version": "1.6.0", @@ -1397,7 +1590,9 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": { + "10up/wp_mock": 20 + }, "prefer-stable": false, "prefer-lowest": false, "platform": { diff --git a/src/Dimension/Dimension.php b/src/Dimension/Dimension.php new file mode 100644 index 0000000..19fa020 --- /dev/null +++ b/src/Dimension/Dimension.php @@ -0,0 +1,11 @@ +dimensions[] = $dimension; + } + + public function get() + { + return $this->dimensions; + } +} diff --git a/src/Dimension/Meta.php b/src/Dimension/Meta.php new file mode 100644 index 0000000..cd66b9d --- /dev/null +++ b/src/Dimension/Meta.php @@ -0,0 +1,46 @@ +options = wp_parse_args($options, [ + 'compare' => '=', + ]); + $this->wpdb = $wpdb; + } + + public function join($aliasCount = 0) + { + $tableAlias = $this->tableAlias . $aliasCount; + + $sql = "INNER JOIN {$this->wpdb->postmeta} AS {$tableAlias} "; + $sql .= "ON ({$this->wpdb->posts}.ID = {$tableAlias}.post_id)"; + + return $sql; + } + + public function search($searchWord, $aliasCount = 0) + { + $tableAlias = $this->tableAlias . $aliasCount; + $searchWord = $this->wpdb->esc_like($searchWord); + + $searchSql = "({$tableAlias}.meta_key {$this->options['compare']} %s"; + $searchSql .= " AND "; + $searchSql .= "{$tableAlias}.meta_value LIKE %s)"; + + return $this->wpdb->prepare($searchSql, $this->options['key'], '%' . $searchWord . '%'); + } +} diff --git a/src/Hook/Posts.php b/src/Hook/Posts.php new file mode 100644 index 0000000..45c3a81 --- /dev/null +++ b/src/Hook/Posts.php @@ -0,0 +1,98 @@ +dimensions = $dimensions; + } + + public function init() + { + $acceptedArgs = 2; + $defaultPriority = 10; + + add_filter('posts_distinct', [$this, 'distinct'], $defaultPriority, $acceptedArgs); + add_filter('posts_join', [$this, 'join'], $defaultPriority, $acceptedArgs); + add_filter('posts_search', [$this, 'search'], $defaultPriority, $acceptedArgs); + } + + public function distinct($sql, WP_Query $query) + { + if (! $this->isSearch($query)) { + return $sql; + } + + return "DISTINCT"; + } + + public function join($sql, WP_Query $query) + { + if (! $this->isSearch($query)) { + return $sql; + } + + $searchWords = $query->get('search_terms'); + + $joins = []; + + foreach ($searchWords as $wordCount => $searchWord) { + foreach ($this->dimensions->get() as $dimension) { + $dimensionType = get_class($dimension); + $joins[$dimensionType . $wordCount] = $dimension->join($wordCount); + } + } + + return $sql . ' ' . implode(' ', $joins); + } + + public function search($sql, WP_Query $query) + { + if (! $this->isSearch($query)) { + return $sql; + } + + $and = " AND "; + $or = " OR "; + + $searchWords = $query->get('search_terms'); + $andClauses = array_values(array_filter(explode($and, $sql))); + + foreach ($andClauses as $index => &$clause) { + $searchWord = $searchWords[$index]; + $searches = []; + + foreach ($this->dimensions->get() as $dimension) { + $searches[] = $dimension->search($searchWord, $index); + } + + $search = '(' . implode($or, $searches) . ')' . $or; + + $clause = preg_replace('/' . $or . '/', $or . $search, $clause, 1); + } + + return $and . implode($and, $andClauses); + } + + private function isSearch(WP_Query $query) + { + if (! $query->is_search) { + return false; + } + + $searchWords = $query->get('search_terms'); + + if (! $searchWords) { + return false; + } + + return true; + } +} diff --git a/src/Search.php b/src/Search.php new file mode 100644 index 0000000..6dd2a78 --- /dev/null +++ b/src/Search.php @@ -0,0 +1,29 @@ +dimensions = new Dimensions(); + $this->postsHook = new Posts($this->dimensions); + } + + public function init() + { + $this->postsHook->init(); + } + + public function addDimension(Dimension $dimension) + { + $this->dimensions->add($dimension); + } +} diff --git a/tests/Search/Dimension/DimensionsTest.php b/tests/Search/Dimension/DimensionsTest.php new file mode 100644 index 0000000..921c713 --- /dev/null +++ b/tests/Search/Dimension/DimensionsTest.php @@ -0,0 +1,41 @@ +dimensions = new Dimensions(); + } + + public function testGetSet() + { + $wpdb = Mockery::mock('wpdb'); + + WP_Mock::wpPassthruFunction('wp_parse_args', ['times' => 2]); + + $metaSearches = [ + new Meta($wpdb, [ + 'key' => 'firstName', + ]), + new Meta($wpdb, [ + 'key' => 'lastName', + ]), + ]; + + foreach ($metaSearches as $metaSearch) { + $this->assertNull($this->dimensions->add($metaSearch)); + } + + $this->assertEquals($this->dimensions->get(), $metaSearches); + } +} diff --git a/tests/Search/Dimension/MetaTest.php b/tests/Search/Dimension/MetaTest.php new file mode 100644 index 0000000..8618e60 --- /dev/null +++ b/tests/Search/Dimension/MetaTest.php @@ -0,0 +1,94 @@ +wpdb = Mockery::mock('wpdb'); + } + + public function testKeyRequired() + { + $this->expectException(BadMethodCallException::class); + $meta = new Meta($this->wpdb, []); + } + + public function testJoin() + { + $tableAliasCount = 2; + $tableAlias = $this->tableAlias . $tableAliasCount; + + $this->wpdb->posts = 'wp_posts'; + $this->wpdb->postmeta = 'wp_postmeta'; + + $expectation = "INNER JOIN {$this->wpdb->postmeta} AS {$tableAlias} "; + $expectation .= "ON ({$this->wpdb->posts}.ID = {$tableAlias}.post_id)"; + + $meta = $this->create('firstName', '='); + + $result = $meta->join($tableAliasCount); + + $this->assertEquals($expectation, $result); + } + + public function testSearchEquals() + { + $this->search('Testman', 'firstName', '='); + } + + public function testSearchLike() + { + $this->search('McTest', 'lastName', 'LIKE'); + } + + public function testSearchAliasCount() + { + $this->search('Testman', 'firstName', '=', 2); + } + + private function search($searchWord, $metaKey, $compare, $tableAliasCount = 0) + { + $tableAlias = $this->tableAlias . $tableAliasCount; + $expectation = "({$tableAlias}.meta_key {$compare} %s AND {$tableAlias}.meta_value LIKE %s)"; + + $meta = $this->create($metaKey, $compare); + + $this->wpdb->shouldReceive('esc_like') + ->andReturnUsing(function ($searchWord) { + return $searchWord; + }); + + $this->wpdb->shouldReceive('prepare') + ->once() + ->andReturnUsing(function () { + return func_get_args(); + }); + + $result = $meta->search($searchWord, $tableAliasCount); + + $this->assertEquals($result, [$expectation, $metaKey, '%' . $searchWord . '%']); + } + + private function create($metaKey, $compare) + { + WP_Mock::wpPassthruFunction('wp_parse_args', ['times' => 1]); + + $meta = new Meta($this->wpdb, [ + 'compare' => $compare, + 'key' => $metaKey, + ]); + + return $meta; + } +} diff --git a/tests/Search/Hook/PostsTest.php b/tests/Search/Hook/PostsTest.php new file mode 100644 index 0000000..44ef22c --- /dev/null +++ b/tests/Search/Hook/PostsTest.php @@ -0,0 +1,163 @@ +wpdb = Mockery::mock('wpdb'); + $this->wpdb->postmeta = 'wp_postmeta'; + $this->wpdb->posts = 'wp_posts'; + + $dimensions = new Dimensions(); + $dimensions->add(new Meta($this->wpdb, [ + 'compare' => '=', + 'key' => $this->metaKey, + ])); + + $this->posts = new Posts($dimensions); + } + + public function testInit() + { + $argsCount = 2; + $priority = 10; + + WP_Mock::expectFilterAdded('posts_distinct', [$this->posts, 'distinct'], $priority, $argsCount); + WP_Mock::expectFilterAdded('posts_join', [$this->posts, 'join'], $priority, $argsCount); + WP_Mock::expectFilterAdded('posts_search', [$this->posts, 'search'], $priority, $argsCount); + + $this->posts->init(); + + WP_Mock::assertHooksAdded(); + } + + public function testDistinct() + { + $expectation = "DISTINCT"; + $result = $this->posts->distinct('', $this->getQuery()); + + $this->assertEquals($expectation, $result); + } + + public function testDistinctNotSearch() + { + $expectation = ''; + $result = $this->posts->distinct('', $this->getQuery(false)); + + $this->assertEquals($expectation, $result); + } + + public function testDistinctNoWords() + { + $expectation = ''; + $result = $this->posts->distinct('', $this->getQuery(true, [])); + + $this->assertEquals($expectation, $result); + } + + public function testJoin() + { + $baseSql = 'INNER JOIN alreadyAvailableSql'; + + $searchTerms = ['Testman', 'theTester']; + $expectation = []; + + foreach ($searchTerms as $index => $searchTerm) { + $tableAlias = 'searchMeta' . $index; + + $wordExpectation = "INNER JOIN {$this->wpdb->postmeta} AS {$tableAlias} "; + $wordExpectation .= "ON ({$this->wpdb->posts}.ID = {$tableAlias}.post_id)"; + + $expectation[] = $wordExpectation; + } + + $expectation = $baseSql . ' ' . implode(' ', $expectation); + $result = $this->posts->join($baseSql, $this->getQuery(true, $searchTerms)); + + $this->assertEquals($expectation, $result); + } + + public function testJoinWithoutSearch() + { + $baseSql = 'INNER JOIN alreadyAvailable'; + $expectation = $baseSql; + $result = $this->posts->join($baseSql, $this->getQuery(false)); + + $this->assertEquals($expectation, $result); + } + + public function testSearch() + { + $and = " AND "; + $or = " OR "; + + $searchTerms = ['Testman', 'theTester']; + $baseSql = $and . "("; + + foreach ($searchTerms as $searchTerm) { + $baseSql .= "("; + $baseSql .= "({$this->wpdb->posts}.post_title LIKE '%{$searchTerm}%')"; + $baseSql .= $or; + $baseSql .= "({$this->wpdb->posts}.post_content LIKE '%{$searchTerm}%')"; + $baseSql .= ")" . $and; + } + + $baseSql = mb_substr($baseSql, 0, mb_strlen($baseSql) - mb_strlen($or)); + $baseSql .= ")"; + + $expectations = []; + + foreach ($searchTerms as $index => $searchTerm) { + $expectations[] = "searchMeta{$index}.meta_key %s AND searchMeta{$index}.meta_value LIKE %s"; + } + + $this->wpdb->shouldReceive('esc_like') + ->andReturnUsing(function ($searchWord) { + return $searchWord; + }); + + $this->wpdb->shouldReceive('prepare') + ->times(count($searchTerms)) + ->andReturnUsing(function ($sql) { + return $sql; + }); + + $result = $this->posts->search($baseSql, $this->getQuery(true, $searchTerms)); + + foreach ($expectations as $expectation) { + $this->assertContains($expectation, $result); + } + } + + public function testSearchWithoutSearch() + { + $expectation = ''; + $result = $this->posts->search('', $this->getQuery(false)); + + $this->assertEquals($expectation, $result); + } + + private function getQuery($isSearch = true, $terms = ['Testman', 'mcTest']) + { + $wpQuery = Mockery::mock('WP_Query'); + $wpQuery->is_search = $isSearch; + $wpQuery->shouldReceive('get') + ->with('search_terms') + ->andReturn($terms); + + return $wpQuery; + } +} diff --git a/tests/Search/SampleTest.php b/tests/Search/SampleTest.php deleted file mode 100644 index 58c85d7..0000000 --- a/tests/Search/SampleTest.php +++ /dev/null @@ -1,12 +0,0 @@ -assertTrue(true); - } -} diff --git a/tests/Search/SearchTest.php b/tests/Search/SearchTest.php new file mode 100644 index 0000000..e919cb6 --- /dev/null +++ b/tests/Search/SearchTest.php @@ -0,0 +1,34 @@ +search = new Search(); + } + + public function testInit() + { + $this->assertNull($this->search->init()); + } + + public function testAddDimension() + { + $wpdb = Mockery::mock('wpdb'); + + $this->assertNull($this->search->addDimension(new Meta($wpdb, [ + 'key' => 'firstName', + ]))); + } +} diff --git a/tests/Search/TestCase.php b/tests/Search/TestCase.php new file mode 100644 index 0000000..c20b3e4 --- /dev/null +++ b/tests/Search/TestCase.php @@ -0,0 +1,18 @@ +