From b702cb3b246b2bf1652ed93ac09b343557f361b3 Mon Sep 17 00:00:00 2001 From: Chad Horohoe Date: Wed, 26 Nov 2014 10:29:15 -0800 Subject: [PATCH] Support offsets in prefix searching Fixes T75522 Change-Id: I7a27a64e295a1efcb1d9728d95cf254bb8bfbe92 --- RELEASE-NOTES-1.25 | 5 +++ docs/hooks.txt | 1 + includes/PrefixSearch.php | 49 ++++++++++++++------- includes/api/ApiQueryPrefixSearch.php | 16 ++++--- includes/api/i18n/en.json | 1 + includes/api/i18n/qqq.json | 1 + includes/specialpage/SpecialPage.php | 28 ++++++++++-- includes/specials/SpecialEditWatchlist.php | 23 ++++------ includes/specials/SpecialJavaScriptTest.php | 14 ++---- includes/specials/SpecialListusers.php | 11 ++--- includes/specials/SpecialLog.php | 10 ++--- includes/specials/SpecialPagesWithProp.php | 49 ++++++++++++++------- includes/specials/SpecialRedirect.php | 22 ++++----- includes/specials/SpecialWatchlist.php | 22 ++++----- tests/phpunit/includes/PrefixSearchTest.php | 36 +++++++++++++++ 15 files changed, 181 insertions(+), 107 deletions(-) diff --git a/RELEASE-NOTES-1.25 b/RELEASE-NOTES-1.25 index e848472cde..8f6aaca4b6 100644 --- a/RELEASE-NOTES-1.25 +++ b/RELEASE-NOTES-1.25 @@ -59,6 +59,10 @@ production. * Added a hook, "ApiOpenSearchSuggest", to allow extensions to provide extracts and images for ApiOpenSearch output. The semantics are identical to the "OpenSearchXml" hook provided by the OpenSearchXml extension. +* PrefixSearchBackend hook now has an $offset parameter. Combined with $limit, + this allows for pagination of prefix results. Extensions using this hook + should implement supporting behavior. Not doing so can result in undefined + behavior from API clients trying to continue through prefix results. === Bug fixes in 1.25 === * (T73003) No additional code will be generated to try to load CSS-embedded @@ -118,6 +122,7 @@ production. in JSON format. * (T76051) list=tags will now continue correctly. * (T76052) list=tags can now indicate whether a tag is defined. +* (T75522) list=prefixsearch now supports continuation === Action API internal changes in 1.25 === * ApiHelp has been rewritten to support i18n and paginated HTML output. diff --git a/docs/hooks.txt b/docs/hooks.txt index 0146b86c51..a4e02c68fe 100644 --- a/docs/hooks.txt +++ b/docs/hooks.txt @@ -2166,6 +2166,7 @@ $ns : array of int namespace keys to search in $search : search term (not guaranteed to be conveniently normalized) $limit : maximum number of results to return &$results : out param: array of page names (strings) +$offset : number of results to offset from the beginning 'PrefixSearchExtractNamespace': Called if core was not able to extract a namespace from the search string so that extensions can attempt it. diff --git a/includes/PrefixSearch.php b/includes/PrefixSearch.php index 955313b3eb..a4e64ee63d 100644 --- a/includes/PrefixSearch.php +++ b/includes/PrefixSearch.php @@ -34,11 +34,12 @@ abstract class PrefixSearch { * @param string $search * @param int $limit * @param array $namespaces Used if query is not explicitly prefixed + * @param int $offset How many results to offset from the beginning * @return array Array of strings */ - public static function titleSearch( $search, $limit, $namespaces = array() ) { + public static function titleSearch( $search, $limit, $namespaces = array(), $offset = 0 ) { $prefixSearch = new StringPrefixSearch; - return $prefixSearch->search( $search, $limit, $namespaces ); + return $prefixSearch->search( $search, $limit, $namespaces, $offset ); } /** @@ -47,9 +48,10 @@ abstract class PrefixSearch { * @param string $search * @param int $limit * @param array $namespaces Used if query is not explicitly prefixed + * @param int $offset How many results to offset from the beginning * @return array Array of strings or Title objects */ - public function search( $search, $limit, $namespaces = array() ) { + public function search( $search, $limit, $namespaces = array(), $offset = 0 ) { $search = trim( $search ); if ( $search == '' ) { return array(); // Return empty result @@ -65,7 +67,7 @@ abstract class PrefixSearch { $ns = $namespaces; // no explicit prefix, use default namespaces wfRunHooks( 'PrefixSearchExtractNamespace', array( &$ns, &$search ) ); } - return $this->searchBackend( $ns, $search, $limit ); + return $this->searchBackend( $ns, $search, $limit, $offset ); } // Is this a namespace prefix? @@ -80,7 +82,7 @@ abstract class PrefixSearch { wfRunHooks( 'PrefixSearchExtractNamespace', array( &$namespaces, &$search ) ); } - return $this->searchBackend( $namespaces, $search, $limit ); + return $this->searchBackend( $namespaces, $search, $limit, $offset ); } /** @@ -88,12 +90,13 @@ abstract class PrefixSearch { * @param string $search * @param int $limit * @param array $namespaces + * @param int $offset How many results to offset from the beginning * * @return array */ - public function searchWithVariants( $search, $limit, array $namespaces ) { + public function searchWithVariants( $search, $limit, array $namespaces, $offset = 0 ) { wfProfileIn( __METHOD__ ); - $searches = $this->search( $search, $limit, $namespaces ); + $searches = $this->search( $search, $limit, $namespaces, $offset ); // if the content language has variants, try to retrieve fallback results $fallbackLimit = $limit - count( $searches ); @@ -141,20 +144,21 @@ abstract class PrefixSearch { * @param array $namespaces * @param string $search * @param int $limit + * @param int $offset How many results to offset from the beginning * @return array Array of strings */ - protected function searchBackend( $namespaces, $search, $limit ) { + protected function searchBackend( $namespaces, $search, $limit, $offset ) { if ( count( $namespaces ) == 1 ) { $ns = $namespaces[0]; if ( $ns == NS_MEDIA ) { $namespaces = array( NS_FILE ); } elseif ( $ns == NS_SPECIAL ) { - return $this->titles( $this->specialSearch( $search, $limit ) ); + return $this->titles( $this->specialSearch( $search, $limit, $offset ) ); } } $srchres = array(); - if ( wfRunHooks( 'PrefixSearchBackend', array( $namespaces, $search, $limit, &$srchres ) ) ) { - return $this->titles( $this->defaultSearchBackend( $namespaces, $search, $limit ) ); + if ( wfRunHooks( 'PrefixSearchBackend', array( $namespaces, $search, $limit, &$srchres, $offset ) ) ) { + return $this->titles( $this->defaultSearchBackend( $namespaces, $search, $limit, $offset ) ); } return $this->strings( $this->handleResultFromHook( $srchres, $namespaces, $search, $limit ) ); } @@ -189,8 +193,8 @@ abstract class PrefixSearch { // returned match to the front. This might look odd but the alternative // is to put the redirect in front and drop the match. The name of the // found match is often more descriptive/better formed than the name of - // the redirec AND by definition they share a prefix. Hopefully this - // choice is less confusing and more helpful. But it might now be. But + // the redirect AND by definition they share a prefix. Hopefully this + // choice is less confusing and more helpful. But it might not be. But // it is the choice we're going with for now. return $this->pullFront( $key, $srchres ); } @@ -266,9 +270,10 @@ abstract class PrefixSearch { * * @param string $search Term * @param int $limit Max number of items to return + * @param int $offset Number of items to offset * @return array */ - protected function specialSearch( $search, $limit ) { + protected function specialSearch( $search, $limit, $offset ) { global $wgContLang; $searchParts = explode( '/', $search, 2 ); @@ -284,7 +289,7 @@ abstract class PrefixSearch { } $special = SpecialPageFactory::getPage( $specialTitle->getText() ); if ( $special ) { - $subpages = $special->prefixSearchSubpages( $subpageSearch, $limit ); + $subpages = $special->prefixSearchSubpages( $subpageSearch, $limit, $offset ); return array_map( function ( $sub ) use ( $specialTitle ) { return $specialTitle->getSubpage( $sub ); }, $subpages ); @@ -316,12 +321,17 @@ abstract class PrefixSearch { ksort( $keys ); $srchres = array(); + $skipped = 0; foreach ( $keys as $pageKey => $page ) { if ( $searchKey === '' || strpos( $pageKey, $searchKey ) === 0 ) { // bug 27671: Don't use SpecialPage::getTitleFor() here because it // localizes its input leading to searches for e.g. Special:All // returning Spezial:MediaWiki-Systemnachrichten and returning // Spezial:Alle_Seiten twice when $wgLanguageCode == 'de' + if ( $offset > 0 && $skipped < $offset ) { + $skipped++; + continue; + } $srchres[] = Title::makeTitleSafe( NS_SPECIAL, $page ); } @@ -342,9 +352,10 @@ abstract class PrefixSearch { * @param array $namespaces Namespaces to search in * @param string $search Term * @param int $limit Max number of items to return + * @param int $offset Number of items to skip * @return array Array of Title objects */ - protected function defaultSearchBackend( $namespaces, $search, $limit ) { + protected function defaultSearchBackend( $namespaces, $search, $limit, $offset ) { $ns = array_shift( $namespaces ); // support only one namespace if ( in_array( NS_MAIN, $namespaces ) ) { $ns = NS_MAIN; // if searching on many always default to main @@ -360,7 +371,11 @@ abstract class PrefixSearch { 'page_title ' . $dbr->buildLike( $prefix, $dbr->anyString() ) ), __METHOD__, - array( 'LIMIT' => $limit, 'ORDER BY' => 'page_title' ) + array( + 'LIMIT' => $limit, + 'ORDER BY' => 'page_title', + 'OFFSET' => $offset + ) ); $srchres = array(); foreach ( $res as $row ) { diff --git a/includes/api/ApiQueryPrefixSearch.php b/includes/api/ApiQueryPrefixSearch.php index 95f84839a3..069e30bc5f 100644 --- a/includes/api/ApiQueryPrefixSearch.php +++ b/includes/api/ApiQueryPrefixSearch.php @@ -43,20 +43,21 @@ class ApiQueryPrefixSearch extends ApiQueryGeneratorBase { $search = $params['search']; $limit = $params['limit']; $namespaces = $params['namespace']; + $offset = $params['offset']; $searcher = new TitlePrefixSearch; - $titles = $searcher->searchWithVariants( $search, $limit, $namespaces ); + $titles = $searcher->searchWithVariants( $search, $limit + 1, $namespaces, $offset ); if ( $resultPageSet ) { $resultPageSet->populateFromTitles( $titles ); - /** @todo If this module gets an 'offset' parameter, use it here */ - $offset = 1; foreach ( $titles as $index => $title ) { - $resultPageSet->setGeneratorData( $title, array( 'index' => $index + $offset ) ); + $resultPageSet->setGeneratorData( $title, array( 'index' => $index + $offset + 1 ) ); } } else { $result = $this->getResult(); + $count = 0; foreach ( $titles as $title ) { - if ( !$limit-- ) { + if ( ++$count > $limit ) { + $this->setContinueEnumParameter( 'offset', $offset + $params['limit'] ); break; } $vals = array( @@ -70,6 +71,7 @@ class ApiQueryPrefixSearch extends ApiQueryGeneratorBase { } $fit = $result->addValue( array( 'query', $this->getModuleName() ), null, $vals ); if ( !$fit ) { + $this->setContinueEnumParameter( 'offset', $offset + $count - 1 ); break; } } @@ -102,6 +104,10 @@ class ApiQueryPrefixSearch extends ApiQueryGeneratorBase { ApiBase::PARAM_MAX => 100, ApiBase::PARAM_MAX2 => 200, ), + 'offset' => array( + ApiBase::PARAM_DFLT => 0, + ApiBase::PARAM_TYPE => 'integer', + ), ); } diff --git a/includes/api/i18n/en.json b/includes/api/i18n/en.json index a15b9cf1f6..f7e3a57ad2 100644 --- a/includes/api/i18n/en.json +++ b/includes/api/i18n/en.json @@ -743,6 +743,7 @@ "apihelp-query+prefixsearch-param-search": "Search string.", "apihelp-query+prefixsearch-param-namespace": "Namespaces to search.", "apihelp-query+prefixsearch-param-limit": "Maximum number of results to return.", + "apihelp-query+prefixsearch-param-offset": "Number of results to skip.", "apihelp-query+prefixsearch-example-simple": "Search for page titles beginning with \"meaning\"", "apihelp-query+protectedtitles-description": "List all titles protected from creation.", diff --git a/includes/api/i18n/qqq.json b/includes/api/i18n/qqq.json index 31d88711fa..9a226593df 100644 --- a/includes/api/i18n/qqq.json +++ b/includes/api/i18n/qqq.json @@ -676,6 +676,7 @@ "apihelp-query+prefixsearch-param-search": "{{doc-apihelp-param|query+prefixsearch|search}}", "apihelp-query+prefixsearch-param-namespace": "{{doc-apihelp-param|query+prefixsearch|namespace}}", "apihelp-query+prefixsearch-param-limit": "{{doc-apihelp-param|query+prefixsearch|limit}}", + "apihelp-query+prefixsearch-param-offset": "{{doc-apihelp-param|query+prefixsearch|offset}}", "apihelp-query+prefixsearch-example-simple": "{{doc-apihelp-example|query+prefixsearch}}", "apihelp-query+protectedtitles-description": "{{doc-apihelp-description|query+protectedtitles}}", "apihelp-query+protectedtitles-param-namespace": "{{doc-apihelp-param|query+protectedtitles|namespace}}", diff --git a/includes/specialpage/SpecialPage.php b/includes/specialpage/SpecialPage.php index c0a94af1a0..072f87f383 100644 --- a/includes/specialpage/SpecialPage.php +++ b/includes/specialpage/SpecialPage.php @@ -303,10 +303,28 @@ class SpecialPage { * - `prefixSearchSubpages( "" )` should return `array( foo", "bar", "baz" )` * * @param string $search Prefix to search for - * @param int $limit Maximum number of results to return + * @param int $limit Maximum number of results to return (usually 10) + * @param int $offset Number of results to skip (usually 0) * @return string[] Matching subpages */ - public function prefixSearchSubpages( $search, $limit = 10 ) { + public function prefixSearchSubpages( $search, $limit, $offset ) { + $subpages = $this->getSubpagesForPrefixSearch(); + if ( !$subpages ) { + return array(); + } + + return self::prefixSearchArray( $search, $limit, $subpages, $offset ); + } + + /** + * Return an array of subpages that this special page will accept for prefix + * searches. If this method requires a query you might instead want to implement + * prefixSearchSubpages() directly so you can support $limit and $offset. This + * method is better for static-ish lists of things. + * + * @return string[] subpages to search from + */ + protected function getSubpagesForPrefixSearch() { return array(); } @@ -318,11 +336,13 @@ class SpecialPage { * @param string $search * @param int $limit * @param array $subpages + * @param int $offset * @return string[] */ - protected static function prefixSearchArray( $search, $limit, array $subpages ) { + protected static function prefixSearchArray( $search, $limit, array $subpages, $offset ) { $escaped = preg_quote( $search, '/' ); - return array_slice( preg_grep( "/^$escaped/i", $subpages ), 0, $limit ); + return array_slice( preg_grep( "/^$escaped/i", + array_slice( $subpages, $offset ) ), 0, $limit ); } /** diff --git a/includes/specials/SpecialEditWatchlist.php b/includes/specials/SpecialEditWatchlist.php index 900760334f..cb97420aa8 100644 --- a/includes/specials/SpecialEditWatchlist.php +++ b/includes/specials/SpecialEditWatchlist.php @@ -134,22 +134,17 @@ class SpecialEditWatchlist extends UnlistedSpecialPage { } /** - * Return an array of subpages beginning with $search that this special page will accept. + * Return an array of subpages that this special page will accept. * - * @param string $search Prefix to search for - * @param int $limit Maximum number of results to return - * @return string[] Matching subpages + * @see also SpecialWatchlist::getSubpagesForPrefixSearch + * @return string[] subpages */ - public function prefixSearchSubpages( $search, $limit = 10 ) { - return self::prefixSearchArray( - $search, - $limit, - // SpecialWatchlist uses SpecialEditWatchlist::getMode, so new types should be added - // here and there - no 'edit' here, because that the default for this page - array( - 'clear', - 'raw', - ) + public function getSubpagesForPrefixSearch() { + // SpecialWatchlist uses SpecialEditWatchlist::getMode, so new types should be added + // here and there - no 'edit' here, because that the default for this page + return array( + 'clear', + 'raw', ); } diff --git a/includes/specials/SpecialJavaScriptTest.php b/includes/specials/SpecialJavaScriptTest.php index 0efebb3eef..a61a673c5d 100644 --- a/includes/specials/SpecialJavaScriptTest.php +++ b/includes/specials/SpecialJavaScriptTest.php @@ -173,18 +173,12 @@ HTML; } /** - * Return an array of subpages beginning with $search that this special page will accept. + * Return an array of subpages that this special page will accept. * - * @param string $search Prefix to search for - * @param int $limit Maximum number of results to return - * @return string[] Matching subpages + * @return string[] subpages */ - public function prefixSearchSubpages( $search, $limit = 10 ) { - return self::prefixSearchArray( - $search, - $limit, - array_keys( self::$frameworks ) - ); + public function getSubpagesForPrefixSearch() { + return array_keys( self::$frameworks ); } protected function getGroupName() { diff --git a/includes/specials/SpecialListusers.php b/includes/specials/SpecialListusers.php index dad9074dd3..fa20994177 100644 --- a/includes/specials/SpecialListusers.php +++ b/includes/specials/SpecialListusers.php @@ -397,15 +397,12 @@ class SpecialListUsers extends IncludableSpecialPage { } /** - * Return an array of subpages beginning with $search that this special page will accept. + * Return an array of subpages that this special page will accept. * - * @param string $search Prefix to search for - * @param int $limit Maximum number of results to return - * @return string[] Matching subpages + * @return string[] subpages */ - public function prefixSearchSubpages( $search, $limit = 10 ) { - $subpages = User::getAllGroups(); - return self::prefixSearchArray( $search, $limit, $subpages ); + public function getSubpagesForPrefixSearch() { + return User::getAllGroups(); } protected function getGroupName() { diff --git a/includes/specials/SpecialLog.php b/includes/specials/SpecialLog.php index 99704a9c73..04c2fe965a 100644 --- a/includes/specials/SpecialLog.php +++ b/includes/specials/SpecialLog.php @@ -119,17 +119,15 @@ class SpecialLog extends SpecialPage { } /** - * Return an array of subpages beginning with $search that this special page will accept. + * Return an array of subpages that this special page will accept. * - * @param string $search Prefix to search for - * @param int $limit Maximum number of results to return - * @return string[] Matching subpages + * @return string[] subpages */ - public function prefixSearchSubpages( $search, $limit = 10 ) { + public function getSubpagesForPrefixSearch() { $subpages = $this->getConfig()->get( 'LogTypes' ); $subpages[] = 'all'; sort( $subpages ); - return self::prefixSearchArray( $search, $limit, $subpages ); + return $subpages; } private function parseParams( FormOptions $opts, $par ) { diff --git a/includes/specials/SpecialPagesWithProp.php b/includes/specials/SpecialPagesWithProp.php index f5b19cc6c6..670a3973b9 100644 --- a/includes/specials/SpecialPagesWithProp.php +++ b/includes/specials/SpecialPagesWithProp.php @@ -83,11 +83,13 @@ class SpecialPagesWithProp extends QueryPage { * * @param string $search Prefix to search for * @param int $limit Maximum number of results to return + * @param int $offset Number of pages to skip * @return string[] Matching subpages */ - public function prefixSearchSubpages( $search, $limit = 10 ) { - $subpages = array_keys( $this->getExistingPropNames() ); - return self::prefixSearchArray( $search, $limit, $subpages ); + public function prefixSearchSubpages( $search, $limit, $offset ) { + $subpages = array_keys( $this->queryExistingProps( $limit, $offset ) ); + // We've already limited and offsetted, set to N and 0 respectively. + return self::prefixSearchArray( $search, count( $subpages ), $subpages, 0 ); } /** @@ -154,23 +156,38 @@ class SpecialPagesWithProp extends QueryPage { public function getExistingPropNames() { if ( $this->existingPropNames === null ) { - $dbr = wfGetDB( DB_SLAVE ); - $res = $dbr->select( - 'page_props', - 'pp_propname', - '', - __METHOD__, - array( 'DISTINCT', 'ORDER BY' => 'pp_propname' ) - ); - $propnames = array(); - foreach ( $res as $row ) { - $propnames[$row->pp_propname] = $row->pp_propname; - } - $this->existingPropNames = $propnames; + $this->existingPropNames = $this->queryExistingProps(); } return $this->existingPropNames; } + protected function queryExistingProps( $limit = null, $offset = 0 ) { + $opts = array( + 'DISTINCT', 'ORDER BY' => 'pp_propname' + ); + if ( $limit ) { + $opts['LIMIT'] = $limit; + } + if ( $offset ) { + $opts['OFFSET'] = $offset; + } + + $res = wfGetDB( DB_SLAVE )->select( + 'page_props', + 'pp_propname', + '', + __METHOD__, + $opts + ); + + $propnames = array(); + foreach ( $res as $row ) { + $propnames[$row->pp_propname] = $row->pp_propname; + } + + return $propnames; + } + protected function getGroupName() { return 'pages'; } diff --git a/includes/specials/SpecialRedirect.php b/includes/specials/SpecialRedirect.php index a2683e5363..72d21ebedb 100644 --- a/includes/specials/SpecialRedirect.php +++ b/includes/specials/SpecialRedirect.php @@ -263,22 +263,16 @@ class SpecialRedirect extends FormSpecialPage { } /** - * Return an array of subpages beginning with $search that this special page will accept. + * Return an array of subpages that this special page will accept. * - * @param string $search Prefix to search for - * @param int $limit Maximum number of results to return - * @return string[] Matching subpages + * @return string[] subpages */ - public function prefixSearchSubpages( $search, $limit = 10 ) { - return self::prefixSearchArray( - $search, - $limit, - array( - "file", - "page", - "revision", - "user", - ) + protected function getSubpagesForPrefixSearch() { + return array( + "file", + "page", + "revision", + "user", ); } diff --git a/includes/specials/SpecialWatchlist.php b/includes/specials/SpecialWatchlist.php index 421840f05b..a06955de76 100644 --- a/includes/specials/SpecialWatchlist.php +++ b/includes/specials/SpecialWatchlist.php @@ -79,22 +79,16 @@ class SpecialWatchlist extends ChangesListSpecialPage { } /** - * Return an array of subpages beginning with $search that this special page will accept. + * Return an array of subpages that this special page will accept. * - * @param string $search Prefix to search for - * @param int $limit Maximum number of results to return - * @return string[] Matching subpages + * @see also SpecialEditWatchlist::getSubpagesForPrefixSearch + * @return string[] subpages */ - public function prefixSearchSubpages( $search, $limit = 10 ) { - // See also SpecialEditWatchlist::prefixSearchSubpages - return self::prefixSearchArray( - $search, - $limit, - array( - 'clear', - 'edit', - 'raw', - ) + public function getSubpagesForPrefixSearch() { + return array( + 'clear', + 'edit', + 'raw', ); } diff --git a/tests/phpunit/includes/PrefixSearchTest.php b/tests/phpunit/includes/PrefixSearchTest.php index 411d40618d..d0f6208034 100644 --- a/tests/phpunit/includes/PrefixSearchTest.php +++ b/tests/phpunit/includes/PrefixSearchTest.php @@ -70,6 +70,10 @@ class PrefixSearchTest extends MediaWikiLangTestCase { 'Example/Baz', 'Example Bar', ), + // Third result when testing offset + 'offsetresult' => array( + 'Example Foo', + ), ) ), array( array( 'Talk namespace prefix', @@ -94,6 +98,10 @@ class PrefixSearchTest extends MediaWikiLangTestCase { 'Special:AllMessages', 'Special:AllMyFiles', ), + // Third result when testing offset + 'offsetresult' => array( + 'Special:AllMyUploads', + ), ) ), array( array( 'Special namespace with prefix', @@ -103,6 +111,10 @@ class PrefixSearchTest extends MediaWikiLangTestCase { 'Special:UncategorizedCategories', 'Special:UncategorizedFiles', ), + // Third result when testing offset + 'offsetresult' => array( + 'Special:UncategorizedImages', + ), ) ), array( array( 'Special page name', @@ -145,6 +157,30 @@ class PrefixSearchTest extends MediaWikiLangTestCase { ); } + /** + * @dataProvider provideSearch + * @covers PrefixSearch::search + * @covers PrefixSearch::searchBackend + */ + public function testSearchWithOffset( Array $case ) { + $this->searchProvision( null ); + $searcher = new StringPrefixSearch; + $results = $searcher->search( $case['query'], 3, array(), 1 ); + + // We don't expect the first result when offsetting + array_shift( $case['results'] ); + // And sometimes we expect a different last result + $expected = isset( $case['offsetresult'] ) ? + array_merge( $case['results'], $case['offsetresult'] ): + $case['results']; + + $this->assertEquals( + $expected, + $results, + $case[0] + ); + } + public static function provideSearchBackend() { return array( array( array( -- 2.20.1